本文主要把一类自托管媒体库(以 Octans 播放子系统为例)的播放能力决策写成可复用的 Planner 模型:输入不是「codec 能不能播」的布尔判断,而是客户端能力、媒体事实与策略的交叉求解;输出是 DirectPlay / DirectStream / Transcode(及无法成计划时的 Unsupported),并与交付方式(HttpFile / Hls 等)解耦。
「为什么同一文件 Web 要转码、TV 却能直放?」表面看像兼容表查漏,根因往往是决策维度不够。只看容器 + 视频编码 + 音频编码,甚至再补上动态范围,仍然解释不了网络、输出链路、字幕路线、服务端 Slot / GPU 和用户偏好带来的路线分叉。下面按「问题 → 输入模型 → 播放方式边界 → 内部分层 → 关键原则」整理一版通用 Planner 口径,方便对照排障与转码矩阵阅读。
为什么不能只看格式名
若播放决策简化为:
container + videoCodec + audioCodec + dynamicRange
→ 能不能 Direct Play
在真实多端场景里会很快失效。同一个媒体文件,在不同设备、网络、显示输出、音频输出和用户偏好下,最优路径可能完全不同:
| 场景 | 更可能的路线倾向 |
|---|---|
| 普通浏览器 / WAN | 保守:基线 codec + HLS,必要时 Transcode |
| 局域网 Android TV + 硬解齐全 | 更积极 DirectPlay / 音视频 passthrough |
| 手机硬解 4K HEVC | 能解 ≠ 适合:发热、掉帧、耗电可能逼进降档 |
| Desktop libmpv 发烧核 | 复杂字幕 / 高阶格式可端侧处理 |
| 有 1080p SDR 备选版本 | 选 SDR 版本可能优于 4K HDR 的 tone mapping 转码 |
因此,播放决策不是格式判断,而是多维约束求解。Direct Play 的含义也应从「这个文件能不能被这个平台打开」修正为:
这个文件在当前设备、输出链路、网络环境、用户偏好和服务端资源下,
能否正确、稳定、符合预期地播放?
核心模型:输入交叉 → PlaybackPlan
可以把 Planner 的输入先收成产品化的三轴,再在工程上展开成更细的事实块。
产品视角:三轴交叉
ClientCapabilities × MediaFacts × Policy
→ PlaybackPlan
(DirectPlay / DirectStream / Transcode / Unsupported)
| 轴 | 问的是什么 | 典型内容 |
|---|---|---|
| 客户端能力 | 端上声明能承接什么 | 平台 / 引擎、容器与 codec 白名单、MSE / native HLS、HDR 能力、字幕渲染路线、是否支持 HttpFile 直放 |
| 媒体事实 | 源片实际是什么 | 容器结构、视频 profile/level/bit depth/chroma、动态范围与色彩、音轨声道、字幕形态、关键帧间隔与索引 |
| 策略 | 允许与偏好什么,以及服务端现在能不能做 | 用户偏好(质量 / 省电 / 强制 SDR)、交付上下文(LAN/WAN)、runtime(FFmpeg / Slot / GPU / 磁盘)、保守配置兜底 |
三轴缺一不可:
- 只有客户端声明 → 不知道源是否真落在声明内(profile、10-bit、DV 无 fallback 等)。
- 只有媒体事实 → 不知道 Web 与 TV 该走不同保守度。
- 只有策略与 runtime → 不知道该 copy 还是必须重编、该 tone mapping 还是换版本。
工程视角:六类事实块
在架构与能力模型草稿里,Policy 侧通常再拆成更可实现的输入,便于 evaluator 独立演进:
MediaFacts
+ ClientCapabilities
+ OutputChain # 屏幕 / HDMI / AVR / 耳机等实际输出
+ DeliveryContext # 网络 / 协议 / 认证 / 带宽
+ RuntimeResources # FFmpeg / Slot / Cache / GPU / 磁盘
+ UserPolicy # 质量优先 / 省电 / 强制 SDR / 字幕与音轨偏好
→ PlaybackPlan
阅读时不必纠结「三轴还是六块」:三轴是产品叙事,六块是实现拆分。排查「为什么没走 DirectPlay」时,先定是哪一轴否决,再落到具体事实字段。
服务端决策,客户端执行
与播放架构一致的职责边界:
| 侧 | 负责 |
|---|---|
| 服务端 | 权限校验、媒体事实读取、能力与策略求解、生成播放计划与受控流地址、进度与会话回收 |
| 客户端 | 上报能力与选轨意图、按计划初始化播放器、交互控制、错误展示、进度与 heartbeat |
客户端不应本地重算服务端应给出的 PlayMethod / Delivery / 字幕控制面字段;服务端不应把未确认的兼容维度默认成 DirectPlay。
播放方式 ≠ 交付方式
Planner 输出至少要拆开两层,否则「走了 HLS」会被误读成「一定在转码」。
| 概念 | 问的是什么 | 常见取值 |
|---|---|---|
| 播放方式(PlayMethod) | 服务端是否改变媒体内容 | DirectPlay / DirectStream / Transcode / Unsupported |
| 交付方式(Delivery) | 客户端如何拿到流 | HttpFile / Hls(以及未来可能的其它交付) |
两者正交。常见组合:
DirectPlay + HttpFile → 原始文件受控 Range 下发,不启 FFmpeg
DirectStream + Hls → 视频 copy,必要时音频转码 / 转封装
Transcode + Hls → 视频(或完整主轨)重新编码后输出 HLS
HLS 可以承载 DirectStream,也可以承载 Transcode;HttpFile 通常只承载 DirectPlay。排障时先看 PlayMethod,再看 Delivery,不要用「出现了 m3u8」反推一定是 Transcode。
DirectPlay / DirectStream / Transcode / Unsupported
优先级保持从轻到重:
DirectPlay → DirectStream → Transcode → Unsupported
DirectPlay
服务端不改变媒体内容,只做受控 I/O(权限、鉴权、Range、进度)。建议仅在完整链路安全时返回,而不是「codec 名字在白名单里」就放行。示意条件:
containerSupported
&& videoCodec / profile / level / bitDepth / chroma 均支持
&& frameRate / resolution / bitrate 在端侧舒适区内
&& dynamicRange 与输出链路可正确显示
&& audio 与输出设备策略兼容
&& subtitle 路线可执行且不强制服务端烧录
&& delivery 路径(含 Range / 认证)支持
DirectStream
只应解决不改变画面的问题:
| 可以 | 不应 |
|---|---|
| 容器不兼容 → remux | 视频编码不兼容 |
| 交付方式需要 HLS | HDR → SDR |
| 音频转 AAC / downmix | deinterlace、降分辨率、降码率 |
| 文本字幕资源转换 / 映射交付 | 把字幕合成进视频画面 |
只要需要改变视频画面本身,应进入 Transcode。
Transcode
负责需要重编码或改变画面内容的路径,例如:
视频重编码到客户端基线
HDR / HLG / DV → SDR tone mapping
deinterlace / downscale / 限码率
(少数设备策略下)字幕烧录进画面
音频转码 / downmix
Transcode 必须同时考虑 runtime 代价:Slot、Cache、FFmpeg / 硬件加速能力、失败回退、输出质量与启动等待。矩阵读法见相关阅读中的支持矩阵文;tone mapping 选型见硬件链路文。
Unsupported
表示当前输入组合下构不出可执行计划,必须带明确原因,避免前端静默失败。宜与「权限 / 资源不可见」错误分开:
| 更像 Unsupported | 更像权限或资源错误 |
|---|---|
| 无可用 PlayMethod 路线 | 未登录、无库读取权限 |
| FFmpeg / 编码器 / tone mapping 能力不足且不可排队 | 媒体记录存在但文件不可访问 |
| 客户端既不能直放也不接目标 HLS 输出 | 会话 / token 失效 |
用户无权限、媒体不可见等应按受保护资源口径处理(常见为隐藏为 404),不要一律写成 Unsupported。
容易被漏掉、却会否决直放的维度
完整字段清单属于实现与探测专题;Planner 阅读时至少记住这些「格式名装不下」的维度:
| 维度 | 为什么重要 |
|---|---|
| profile / level / bit depth / chroma | H.264 High 10、4:2:2 等会让「名义支持」失效 |
| 动态范围与色彩管线 | 能解码 ≠ 能正确显示;HDR→SDR 必须 Transcode + tone mapping,不能靠 DirectStream copy |
| 音频输出链路 | Web 的 DTS/TrueHD 常转 AAC;TV+AVR 才可能 passthrough |
| 字幕形态 | SRT/VTT 与 ASS overlay、PGS bitmap 路线完全不同;烧录会推高到 Transcode |
| 容器内部结构 | moov 在尾、VFR、异常时间戳、过长关键帧间隔影响直放与 HLS seek |
| 交付上下文 | WAN/移动网更倾向 HLS / 降码率;流地址必须受控,不能绕过权限 |
| 设备稳定性 | 能硬解 4K ≠ 长时间 smooth;热与电可能逼进降档 |
| 运行时资源 | Slot 满、GPU 不可用、缓存盘不可写应变成明确计划或失败,而不是卡住 |
一句话:未知的关键维度,默认不能 DirectPlay;可以提供「冒险直放」类用户开关,但默认 Planner 应保守。
Planner 内部分层:别写成一个巨型 if
建议把求解拆成多个 evaluator,再由 resolver 合并,而不是单文件堆条件:
MediaStructureEvaluator
VideoCompatibilityEvaluator
AudioCompatibilityEvaluator
SubtitleCompatibilityEvaluator
DeliveryCompatibilityEvaluator
RuntimeResourceEvaluator
PlaybackPlanResolver
每个 evaluator 不要只返回 true/false,而应返回结构化结果,例如:
Supported
RequiresDirectStream
RequiresTranscode
RequiresUserChoice
Unsupported
Unknown
并附带 reasonCode、requiredTransform、qualityImpact、serverCost、clientRisk 一类解释字段。这样最终 PlaybackPlan 才能回答「为什么是这条路、代价是什么」,前端与排障文档才有稳定口径。
示意:
Video: RequiresTranscode / UnsupportedVideoProfile / VideoTranscodeToH264
Audio: RequiresTranscode / UnsupportedAudioCodec / AudioTranscodeToAac
Subtitle: RequiresMappedDelivery / AssOverlay / ExtractSubtitleResource
→ Resolver: PlayMethod=Transcode, Delivery=Hls, …
多版本媒体时,还可在方法选择之前插入版本与选轨:
VersionSelector → TrackSelector → PlaybackMethodSelector → OutputProfileResolver
例如客户端不支持 HDR,但同一作品有 1080p SDR 版本时,换版本可能优于对 4K HDR 做 tone mapping。
关键原则(写进规范比写进 if 更重要)
未知关键维度不默认 DirectPlay HDR/DV profile、10-bit 支持、复杂字幕渲染、显示 HDR 能力、硬解稳定性等未知时走保守路线。
可降级与不可降级分开 可降级:音频 AAC / downmix、HDR→SDR、字幕转 WebVTT 或独立资源、降分辨率 / 限码率。 不可降级:权限、DRM/加密、文件损坏或缺失、解码器完全读不懂源。
DirectStream 不处理画面变化 tone mapping、deinterlace、烧录字幕、改像素格式一律算 Transcode。
多平台不共享一张静态兼容表 静态平台表只作默认;真实探针覆盖声明;用户偏好覆盖默认策略;服务端保守配置兜底。
PlaybackPlan 要暴露代价与影响 除了 method / delivery,还应让控制面能解释:是否 tone mapping、是否 downmix、字幕路线、是否降档、是否等 Slot、是否使用缓存等,避免用户只看到「转码中」而无归因。
和排障、矩阵、Tone Mapping 怎么对齐
| 文档角色 | 回答的问题 | 与本文关系 |
|---|---|---|
| 本文(Planner 模型) | 决策输入是什么、边界如何切、原则是什么 | 总模型与名词对齐 |
| 转码支持矩阵读法 | runtime 已接线的输入×输出真相表怎么读 | 主要服务 Transcode / DirectStream 后半段 |
| 播放排障 | 会话、HLS window、token、字幕与残留如何定位 | 决策之后的运行时与控制面 |
| 硬转码与 Tone Mapping | 软件 / VAAPI / OpenCL 等如何选型与回退 | Transcode 内色彩与加速子决策 |
排查顺序建议:
1. Planner 给出的 method / delivery 是否符合预期?
2. 否决轴落在客户端能力、媒体事实还是策略 / runtime?
3. 若是 Transcode:矩阵与 tone mapping 链路是否支持该输入?
4. 若计划正确但播不了:再进会话 / window / token / 客户端执行排障
小结
- 播放能力规划的本质是 ClientCapabilities × MediaFacts × Policy → PlaybackPlan,而不是 codec 白名单查表。
- PlayMethod 与 Delivery 必须拆开;HLS ≠ 一定 Transcode。
- 优先级:DirectPlay → DirectStream → Transcode → Unsupported;DirectStream 不改变画面。
- 未知关键维度默认保守;可降级与不可降级错误要分码。
- Planner 宜分层 evaluator + 可解释结果,便于前端展示、矩阵阅读与运行时排障共用同一套语言。
相关阅读
同系列还可对照:
- 如何阅读转码支持矩阵:DirectPlay / DirectStream / Transcode 的决策轴:矩阵读法、解码输入 / 编码输出与三轴交叉在 runtime 上的落地。
- 媒体库播放排障:从会话创建到 HLS / DirectPlay / 字幕链路:会话、HLS window、字幕与硬解回退的分场景定位。
- 媒体库硬转码与 Tone Mapping:软件 / VAAPI / OpenCL 如何选型:Transcode 路径上动态范围分流与硬件 / 软件回退。
来源
本文综合改写自 Octans 项目文档中的播放架构说明(octans-docs arch 层)与播放能力决策模型研究草稿(research 层)。正文收敛为通用 Planner 模型与边界原则,不展开完整字段契约、内部 API 细节或未落地实现;具体已接线行为以目标环境当前 API 与实现文档为准。