本文主要把一类自托管媒体库(以 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视频编码不兼容
交付方式需要 HLSHDR → SDR
音频转 AAC / downmixdeinterlace、降分辨率、降码率
文本字幕资源转换 / 映射交付把字幕合成进视频画面

只要需要改变视频画面本身,应进入 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 / chromaH.264 High 104: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

并附带 reasonCoderequiredTransformqualityImpactserverCostclientRisk 一类解释字段。这样最终 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 更重要)

  1. 未知关键维度不默认 DirectPlay HDR/DV profile、10-bit 支持、复杂字幕渲染、显示 HDR 能力、硬解稳定性等未知时走保守路线。

  2. 可降级与不可降级分开 可降级:音频 AAC / downmix、HDR→SDR、字幕转 WebVTT 或独立资源、降分辨率 / 限码率。 不可降级:权限、DRM/加密、文件损坏或缺失、解码器完全读不懂源。

  3. DirectStream 不处理画面变化 tone mapping、deinterlace、烧录字幕、改像素格式一律算 Transcode。

  4. 多平台不共享一张静态兼容表 静态平台表只作默认;真实探针覆盖声明;用户偏好覆盖默认策略;服务端保守配置兜底。

  5. 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 白名单查表。
  • PlayMethodDelivery 必须拆开;HLS ≠ 一定 Transcode。
  • 优先级:DirectPlay → DirectStream → Transcode → Unsupported;DirectStream 不改变画面。
  • 未知关键维度默认保守;可降级与不可降级错误要分码。
  • Planner 宜分层 evaluator + 可解释结果,便于前端展示、矩阵阅读与运行时排障共用同一套语言。

相关阅读

同系列还可对照:

来源

本文综合改写自 Octans 项目文档中的播放架构说明(octans-docs arch 层)与播放能力决策模型研究草稿(research 层)。正文收敛为通用 Planner 模型与边界原则,不展开完整字段契约、内部 API 细节或未落地实现;具体已接线行为以目标环境当前 API 与实现文档为准。