本文是 Octans Android 播放内核演进 的能力边界附录:在「默认 libmpv + Media3 ExoPlayer 本地实验」已定之后,把实验内核上三条有独立证据的收口写清楚——外置音轨不进产品主线、视频轨道允许超出声明能力尝试播放、对白增强只保留一条低延迟图。目标是给读者一张可对照的实验路径能力表,而不是再讲一遍双内核为什么打开。
演进主文已经说明:Media3 ExoPlayer 是显式、可拒绝的本地实验通道,不是默认内核,也不做静默 fallback。本文三条边界都发生在用户已选择 ExoPlayer 实验项之后的本地 gate 与本地音频处理上;它们不改服务端生产契约,也不自动外推到 libmpv。
阅读约定与主文一致:
- 只写有据结论;「能出声 / 能出画」不等于产品可用。
- 按路径声明能力;双内核不是功能并集取 max。
- gate 失败要显式;不要把实验失败伪装成「播起来了」。
边界一:原生外置音轨不进 Media3 产品主线
问题
媒体库里常见「主视频 + 独立音频 sidecar」(配音、评论轨、后处理音轨等)。libmpv 侧可以走接近 audio-add 的挂载语义;Media3 实验路径曾 spike:临时把 nativeExternalAudioTracks 打开,拿服务端外置音轨 descriptor,再用 MergingMediaSource(主视频, 外置音频) 拼起来。
真机上没有得到稳定成功场景:merged 音轨 group 有时能暴露,但选中、override 目标、extractor 识别与 reopen restore 行为都不稳;也出现过未识别输入格式、override 落到内嵌音轨 group 等情况。
事实边界(API 与源码)
Media3 官方「sideload」入口主要面向字幕,而不是音频:
MediaItem/LocalConfiguration侧有字幕配置(SubtitleConfiguration/setSubtitleConfigurations),没有对称的产品级ExternalAudioConfiguration。DefaultMediaSourceFactory创建 media source 时,会读字幕配置并与主内容合并;没有「再挂一个独立音频 URL」的自动产品路径。MergingMediaSource是底层多MediaSource合并原语:timeline period 数要对齐,合并后 track id 会带 source 前缀。它证明的是受控 source 组合能力,不等于任意 sidecar 音频文件的起播、切换、seek 同步、release 与 session recreate 产品契约。
历史 ExoPlayer 社区讨论也能佐证误用点:把「1 个 MP4 + 多个独立 AAC/MP3 + 字幕」硬拼 MergingMediaSource / SingleSampleMediaSource 时,很容易撞上 buffer、格式识别和选轨问题;HLS / DASH manifest 声明的多音轨是另一类、可走 track selection 的路径。
决策口径
| 项 | 口径 |
|---|---|
| 客户端原生挂载 | 暂停对 Media3 原生外置音轨开发;MergingMediaSource(main, externalAudio) 不进产品主线,不能再当 nativeExternalAudioTracks=true 的能力依据 |
| 默认内核 | 正式默认仍是 libmpv;外置音轨继续走 libmpv 既有挂载能力 |
| 用户显式选 Exo | 带独立外置音频 URL 的 descriptor 不由 Android ExoPlayer 客户端直接挂载;应由服务端 remux / package 成单容器、HLS audio rendition 或 DASH adaptation set 等标准形态 |
| 若未来有 Auto | 需要独立外置音轨且没有 Exo 可消费的标准输出时,默认选 libmpv,不把 Exo 送进必败 gate |
| 仍可讨论的路径 | 内嵌多音轨;HLS / DASH manifest 声明的多音轨;服务端已 remux / package 后的标准媒体输出 |
一句话:拒绝的是「主视频 URL + 再挂一个独立 sidecar 音频 URL」的客户端原生模式,不是拒绝所有多音轨。
工程约束(摘要)
- Media3 profile 上
nativeExternalAudioTracks应收回到false;raw 外置音轨用明确拒绝原因表达(例如 external-audio / subtitle 类 unsupported),不能把 spike 期间的true当作产品能力恢复。 - 本地 Exo gate 应拒绝独立外置音频 URL,除非服务端已返回 Exo 可自然消费的 remux / package 输出。
- 服务端若要支持 Exo 下的「外置音」场景,应在 planner / descriptor 中表达已 package 的输出,不要把原始 sidecar audio URL 下发给 Android ExoPlayer。
- 若重开客户端原生外置音研究,必须隔离为 research / benchmark:单 period、duration 已知、时间戳起点一致,且 seek / pause / resume / recreate / 音轨切换全部真机通过后,再谈是否升格。
边界二:视频 FORMAT_EXCEEDS_CAPABILITIES 允许 best-effort
问题
在部分设备上用 Media3 播 Dolby Vision Profile 7.x(例如 dvhe.07.06 Matroska)时,本地选轨 gate 会因为 Tracks.Group.isTrackSupported(index) 为 false 而硬拒绝,用户侧表现为「不支持当前 video 轨道」。
对照成熟客户端(同一设备、Media3 系栈)可以出现:Media3 仍报告 NO_EXCEEDS_CAPABILITIES / 系统 MediaCodec 记 NoSupport [codec.profileLevel, …],但解码器仍被创建并继续渲染,取证窗口内未必有 fatal / ANR。差异不在「对方没用 Media3」,而在播放策略:对方对「超出声明能力」做 best-effort,Octans 早期对 strict support 做 fail-close。
Media3 语义(必须分清)
| API / 标签 | 含义(产品化理解) |
|---|---|
FORMAT_EXCEEDS_CAPABILITIES | 组件支持同类 MIME type,但当前格式属性超出组件声明能力;可能失败,平台上报也可能偏保守 |
isTrackSupported(index) | 等价于 allowExceedsCapabilities=false:超出声明能力视为不支持 |
isTrackSupported(index, true) | 允许把「同类 MIME 支持但超出声明能力」的轨道视为可尝试 |
FORMAT_UNSUPPORTED_TYPE / SUBTYPE / DRM | 明确不可播或不该尝试;继续硬拒绝 |
决策口径
- 仅 video 轨道:当 strict 不支持、但
allowExceedsCapabilities=true时支持,且 support label 为FORMAT_EXCEEDS_CAPABILITIES时,允许 best-effort 尝试播放。 - audio / text 不跟随放宽;独立外置音轨、字幕 sidecar、音频 codec fallback 仍遵守既有 Media3 边界。
- 日志必须同时保留:strict support、allow-exceeds support、support label、MIME、codec string、分辨率等,便于区分「完整声明支持」与「超出声明能力但尝试」。
- 不声明已完整支持某 DV Profile,也不声明所有设备都能 native 正确输出 Dolby Vision;只决定:平台 decoder 可能实际可播、而 Media3 能力声明偏保守时,客户端 gate 不再提前阻断该视频轨道。
为何不选其它方案
| 方案 | 为何不取 |
|---|---|
| 保持全局严格拒绝 | 失败早、错误明确,但会阻断已有实证可连续渲染的路径,也与成熟客户端行为不一致 |
放开一切 isTrackSupported=false | 会把 unsupported type / subtype / DRM 等明显不该尝试的情况也丢给播放器,噪声与体验风险过大 |
| 只对单一 codec string 或设备别名硬编码 | 把策略绑死在样本上,同类保守上报还会反复补洞 |
「只对 video + FORMAT_EXCEEDS_CAPABILITIES 放宽」直接对应官方能力枚举,风险边界可解释。
工程约束(摘要)
- 放开后应在代表性真机上验证:打开、首帧、连续渲染、拖动 / 暂停恢复、退出重进。
- 若某设备 best-effort 出现
PlaybackException、MediaCodec fatal、持续黑屏、异常掉帧或解码器崩溃,应按设备维度收窄,而不是立刻回到全局严格拒绝。 - 对用户表达宜为「尝试使用系统解码器播放」,不要把 exceeds-capabilities 写成「完全支持」。
- audio / text 若要纳入同类策略,必须单独证据与单独决策,不能由本条自动外推。
边界三:对白增强在 Media3 上的唯一实现
问题与产品语义
触控客户端已有「音频滤镜」本地偏好,其中「对白增强」要解决:5.1 / 7.1 等多声道片源在手机、平板、电视盒子等双声道输出上对白过小的问题。
关键约束:
- 它是客户端 PCM 输出处理,不写入服务端音频输出能力,也不触发服务端 AAC 2.0 或其它重规划。
- 服务端已做 AAC 2.0,或源轨本身 ≤ 2 声道时,本机增强应旁路。
- Media3 仍是实验内核;实现可与 libmpv 不同,但产品开关语义一致。
- Android App 只消费独立 runtime 仓产出的 LGPL-only Media3 FFmpeg decoder 制品;
avfilter只开本功能需要的最小集合。
探索过的三条图(结论级)
| 方案 | 链形态(示意) | 结论 |
|---|---|---|
| full-loudnorm | pan + acompressor + loudnorm + aresample | 听感接近 libmpv / 服务端路径,但 loudnorm 实时窗口带来明显延迟风险,不适合 Media3 主线默认 |
| strong-compressor | 更强中置权重的 pan + acompressor + aresample | 低延迟、简单,但真机对白增强偏弱,只适合保守候选 |
| dynaudnorm-low-window | pan + 短窗口 dynaudnorm + aresample | 听感明显强于 strong-compressor,真机未观察到明显延迟;在听感 / 延迟 / 复杂度之间最均衡 |
决策口径
- 默认且唯一实现:
dynaudnorm-low-window。 - 产品侧不再暴露「Media3 对白增强 Profile」选择项,也不把 full-loudnorm / strong-compressor 留给用户切换。
- 开启「对白增强」且本地判断该模式有效时,固定走中置加权 stereo downmix + 短窗口动态归一化 + 48 kHz 重采样一类图(内部可记 mode 名便于诊断;面向用户只显示滤镜开关状态)。
- 它不是人声分离:quiet 背景 / 环境声也可能被拉起;部分片源可能有轻微动态压平感,需继续用样片观察。
验证与旁路
- 编译通过不够:至少要有 5.1 / 7.1 合成 PCM 的 graph smoke(输出 stereo / 48 kHz、非零立体声能量)。
- Media3 + 多声道 HTTPFILE 源开启增强时,听感应稳定提升。
- Media3 + HLS / 服务端 AAC 2.0 输出时,按旁路规则不重复处理。
- 切音轨、重开、seek、暂停 / 恢复时不应出现明显延迟、破音或音画漂移。
- 若未来重开 loudnorm 等 profile,必须以新的 research / decision 立项,而不是恢复隐藏开关。
实验路径能力表(附录汇总)
把三条边界叠到「用户已选 Media3 ExoPlayer」之后,可写成:
| 能力项 | Media3 实验路径口径 | 对照 libmpv(正式默认) |
|---|---|---|
| 内嵌多音轨 | 可走 Media3 track selection;以真机为准 | 既有 ff-index → aid 等映射 |
| 独立外置音频 URL | 产品能力:否;应 remux / 标准多音轨输出 | 可走挂载类能力 |
| HLS / DASH 声明多音轨 | 可作为标准流媒体多音轨讨论 | 视音频切换仍偏服务端 session 语义 |
视频 EXCEEDS_CAPABILITIES | 允许 best-effort 尝试(仅 video + 官方枚举条件) | 走自身 runtime / 选轨语义,不套用本条 API |
| audio / text exceeds | 不放宽 | 各自边界 |
| 对白增强 | 唯一图 dynaudnorm-low-window;客户端本地;可旁路 | 已有等价产品语义,实现可不同 |
| 默认内核 / Auto | 仍不默认;Auto 后置,且外置音等条件要参与选型 | 正式默认 |
再强调一次主文原则:双内核不是功能并集自动取 max,而是按路径声明、按 gate 拒绝、按证据升格。
对同类客户端的通用启示
- 底层 MediaSource 原语 ≠ 产品 API。能 merge 两个 source,不等于外置音轨热切换、seek 同步与 session 恢复都有官方承诺。
- 能力声明偏保守时,策略要可解释。
EXCEEDS_CAPABILITIES与 unsupported type 必须分轨处理;「学成熟客户端」也要学到枚举边界,而不是全局关掉 gate。 - 同产品开关、异实现图可以成立。对白增强在 libmpv 与 Media3 上允许不同 filter 链,但用户只应看到一个语义稳定的开关,并避免把实现细节写进状态浮窗。
- 实验内核的音频处理仍要守 LGPL 与制品边界。App 仓不维护 native 全量 filter 构建;whitelist 按功能收窄,而不是「开了 FFmpeg 就全开 avfilter」。
注意事项与局限
- 本文是演进主文的能力边界附录,不是发布说明,也不承诺设备白名单或版本时间表。
- 外置音轨决策针对的是 sidecar 独立 URL;内嵌轨与 manifest 多音轨另表。
FORMAT_EXCEEDS_CAPABILITIES放宽只覆盖 video;DV / HDR 显示质量仍依赖设备与系统解码器,不能写成「已完整支持某 Profile」。- 对白增强不是 AI 人声提取;旁路规则与短窗口增益副作用需要持续样片观察。
- Auto、服务端 remux 外置音交付、跨端并行能力 schema 属于后置设计,本文只固定约束方向,不展开未落地协议细节。
可以把三条边界压成三句:Media3 实验路径不把客户端原生外置音轨当产品能力;视频轨道在官方「超出声明能力」语义下允许 best-effort,且必须可观测;对白增强在 Media3 上只保留一条已验证的低延迟图,产品语义与 libmpv 对齐、实现允许不同。 默认内核、生产契约与能力广告,仍然跟证据走。
相关阅读
同系列建议先读主文再读本附录:
- Octans Android 播放内核演进:从 libmpv 单内核到双内核实验:默认 libmpv、本地 Exo 实验、显式 gate 与外置音轨总述。
- 媒体库播放排障:从会话创建到 HLS / DirectPlay / 字幕链路:服务端会话与交付控制面定位。
- 媒体库播放能力 Planner:客户端能力 × 媒体事实 × 策略如何落到 DirectPlay / DirectStream / Transcode:能力声明如何进入服务端规划,而不是客户端本地重算。
来源
本文改写自 Octans 项目决策层中关于 Android Media3 ExoPlayer 外置音轨支持边界、超出声明能力视频轨尝试播放、以及对白增强(本地 dialogue stereo)的三份决策说明。表述面向公开技术选型,不绑定内部仓库路径与未发布版本号。