本文主要记录 Android 上 Media3 ExoPlayer 播放 PGS / SUP 时字幕整体被竖直压扁的现象、根因与修复方向。问题出在 构图平面与视频显示宽高比不一致时的非等比布局,不是 surface 算错,也不是位图像素被竖向压缩。
以 remux 进库的 2.39:1 片源为例:视频约 3840×1608,内嵌 PGS 的 composition plane 仍是蓝光常见的 1920×1080。libmpv 同片正常;Media3 路径(内嵌与外置)在修复前字形都「扁」。
问题现象
| 项 | 内容 |
|---|---|
| 片源 | scope 宽画幅 + BD 系 PGS plane(约 16:9) |
| Exo + 内嵌 PGS(修复前) | 有字,整体竖直压扁 / 水平拉宽 |
Exo + 外置 .sup overlay(修复前) | 同样压扁 |
| libmpv 同片 | 横竖屏字形正常 |
| 视频 surface | contain 已与片源比例一致 |
需要先分清两条渲染栈:
| 路径 | 解码 | 绘制 |
|---|---|---|
| HTTPFILE 内嵌原生 PGS | Media3 PgsParser | 历史上走 SubtitleView / SubtitlePainter |
外置 / complete-file .sup | 应用层 windowed decoder | 自绘 OverlayView |
外置栈在修复前与 Media3 内嵌栈 几何模型同构(宽、高各自按视口归一化),因此两条都会扁。
排查过程
量化变形
对样例 object、视口贴视频 2.39:1 时,显示宽高比相对位图固有比约等于:
视口 AR / plane AR ≈ 2.39 / 1.78 ≈ 1.34
即相对正确字形约 宽 34%、高只剩约 74%,与观感一致。
为何有的播放器「横屏看起来还行」
部分开源播放器把字幕层铺满全屏,只在「屏比视频更宽」时加左右 margin。横屏手机 / 平板屏比接近 16:9 时,字幕视口碰巧接近 plane,变形轻;竖屏时视口与 plane 差极大,变形立刻离谱。
结论:那是 碰运气,没有修 plane ≠ video 的几何。
libmpv / Kodi 对照
- libmpv:参考系是视频 content 区域;HD PGS 强制 letterbox,plane 等比映射进视频框并居中。
- Kodi:plane 与视频比接近时对齐视频;不一致时走保宽高比的 screen AR 对齐,避免字幕变形。
Media3 几何模型
PgsParser 把 object 的宽高分别按 plane 宽、高归一化到「相对视口宽 / 相对视口高」;SubtitlePainter 再乘父布局宽高。plane 按 16:9 假定、视频 2.39 时,等价于 非等比 stretch。
官方 issue androidx/media#2849 在 demo 上复现同一类片源;维护者说明分属视口高/宽的 fractional 字段在 AR 不一致时会错,并表示 不计划 在现有字幕管线里专门处理 plane AR ≠ video AR。后续 RELEASENOTES 有 PGS 解码修复,无 本布局问题修复。因此 不能 指望「只升级 Media3」解决内嵌压扁。
原因分析
根因(确定)
- PGS composition plane 与视频显示宽高比不一致(典型 plane 16:9,视频 scope 2.39)。
- Media3(及修复前的应用 overlay)把 plane 坐标 分别 映射到视口宽与高。
- 内嵌路径与外置路径曾是 同一几何错误的两套实现。
非根因
- 某一台平板的硬件 / DV 输出本身
- 视频 surface contain 算错
- PGS 位图像素被竖向压缩(像素比正常;扁在布局)
- 必须 full-cache 抽轨才能正确显示
产品约束
- 禁止 把 HTTPFILE 内嵌默认改成 full-cache
track.sup再 overlay:会推迟首字,破坏「开播即显」。 - UI 上显示 native / Embedded 只表示 轨目录来源,不表示绘制仍走
SubtitleView。
解决方法
外置 / complete-file overlay
对 OverlayView 使用与 libmpv / Kodi 一致的 contain 几何:
scale = min(viewW / planeW, viewH / planeH)
offsetX/Y = 居中
全部 object 坐标 × 同一 scale
HTTPFILE 内嵌
策略要点:
内嵌轨仍 selected(in-band demux,保马上出字)
解码:继续 Media3 PgsParser(禁止双解 / 禁止 full-cache)
绘制:Cue 映射到已修好的 OverlayView;SubtitleView 不画 bitmap
已否决:仅清 bitmapHeight 的「轻改」;内嵌改走 full-cache windowed session。
验证要点
| 路径 | 期望 |
|---|---|
| Exo + 外置/HLS PGS | 等比正常;可有 windowed session 日志 |
| Exo + HTTPFILE 内嵌 | 等比正常;服务端 无 该会话 track.sup / subtitle-resource |
| libmpv 同片 | 对照正常 |
复验时应用日志与服务端活动原因交叉:内嵌修复应来自 本地绘制接管,不是服务端抽轨。
相关阅读
参考
- androidx/media#2849 — PGS subtitles are stretched to match video aspect ratio
- mpv
sd_lavc/ OSD 位图缩放与 Kodi overlay 对齐策略(开源对照)