字幕是播放器里最容易被低估的模块。常见字幕分三类,处理方式完全不同:
- 特效字幕(ASS/SSA):自带排版语言,定位、动画、字体、卡拉OK 逐字变色都是指令,必须由排版引擎还原,不能抽成一行纯文本;
- 图形字幕(PGS):蓝光原盘的字幕,本质是一张张带时间戳的小位图加调色板,没有文字、字体、样式的概念,只能按坐标叠图;
- 文本字幕(SRT/mov_text):纯文本,自己栅格化成位图。
三条路的产物,最后要汇到同一个地方上屏。

一条画布,一个胜者
引擎里三种字幕最终都渲染进同一张「字幕画布」:透明背景的 RGBA 位图,以 sourceOver 混合叠加到画面上。内部开着三个槽位——内封轨、外挂文件、AI 产物——后激活者胜,空窗不回落,任一时刻至多一路字幕。菜单里那句「由引擎完整渲染特效字幕,与外挂字幕互斥」,机制来源就是这个单胜者语义:它不是开关,是仲裁。
上屏路线有两条(引擎里叫车道),按输出方式分,不按解码方式分:处理链里走 Vulkan 合成;HDR 直通输出时,画布跟着切到平台原生渲染腿(DX11 / Metal / GLES),只做叠加,不动视频信号。四条后端都有画布实现,连 HDR 直通态的色彩域都做过专门处理:Vulkan 直通态画布工作在 16 位浮点线性域,1.0 定义 80 尼特;Metal 的 EDR 腿 1.0 定义 100 尼特。HDR 直通态的字幕已经实现;真机 HDR 屏的人眼验收还在路上。
这个设计更大的价值在于:它给平台渲染腿留下一份只会「混合」的契约。不管字幕来自 ASS 排版、PGS 位图还是 SRT 文本,出引擎时永远是一块 RGBA 画布;平台渲染腿只需要做一件事——把这块画布以 sourceOver 混到画面上。它不需要认识任何字幕格式,也不需要知道当前放的是哪一类字幕。直接的好处:接入一个新平台,要写的是一个画布层,而不是一套字幕渲染系统;四个后端就是同一份契约的四个实现,各自只处理自己的色彩域和混合方式。
画布尺寸有个工程取舍:超过 1080p 的片子,字幕一律按 1920×1080 渲染再由 GPU 拉伸——4K 画布的内存是每帧 33MB,收进 8.3MB,纹理上传同步减少,肉眼几乎不可辨。
样式归片源,能调的是全局
ASS 的样式(字体、颜色、位置)属于片源。引擎对「按单一参数改样式」的接口是静默忽略的——想改得从脚本层覆盖:字号走排版引擎的缩放(立刻重排,不用重载字幕),另有整体缩放、位移、不透明度三个全局变换。外挂 SRT 用内置字体渲染,字号随画面高度缩放。
界面上的两个数字:字幕延迟 ±60 秒、步进 100 毫秒;字号滑条映射到 0.55–1.45 倍,默认精确 1.0。两者都即改即生效、落盘,下次开播沿用。
还没做的部分:样式增强(颜色、底板、字体名)在排队;在线字幕搜索没上 UI。双语同屏已撤除,当年为它而生的第二条渲染腿也随之退役——现在字幕全链只有引擎一条通道。
三个真实事故
事故一:换轨就崩。 有一类片子,切到内封字幕再切回外挂,必崩;更狠的是第二次切换会整体卡死。用逐帧复刻应用调用序的测试宿主稳定复现后,拆出两条独立机理:其一,切轨时在锁外析构排版引擎的覆盖层,渲染线程和 IO 线程还在锁内解引用它——use-after-free;其二,画布层每次切换都「摘掉再挂」,触发整图重建,第二次重建 Vulkan 资源时卡死在显卡驱动内部(现场转储显示 COM 消息等待不归还)。修法两条:覆盖层状态改在锁内摘除、锁外析构,并把画布内容拷贝成稳定快照;画布层改「粘性」——挂上后播放期不再摘,没字幕时只清内容。修后反复切换六轮全过。
事故二:黄字变青。 4K 蓝光原盘的 PGS 字幕位置偏、尺寸小。真因有两层:一是 PGS 的组成坐标写在 1080p 的图形平面上,老实现按 4K 帧像素 1:1 贴上去,自然错位;二是调色板的通道序——它是 0xAARRGGBB,代码把低字节当红色读,黄色(0,255,255)渲染成了青色(255,255,0)。第二个 bug 潜伏很久,因为白字三个通道相等,看不出问题。修法:解码端按合成画布重采样(同尺寸零重采样,不同尺寸双线性),顺修通道序。验证:探针几何 A/B,修复前字幕落点 (691,863),修复后落在屏幕 (1382,1726);端到端取帧加离线矩阵 77 项全过。
事故三:整行方框。 iPhone 上中文字幕整行空心方框,同一帧里英文正常。先怀疑编码——方框数如果是解码问题会按字节数膨胀;实测 11 个框对应 11 个字,定到字体层。真因:排版引擎在 iOS 上走系统文本框架找字形,缺字形的回退链指向一个系统私有框架,沙箱里打不开,每个字都落成「没有字形」的占位。修法:在插件初始化前,把包内字体喂给它。真机验证:字体选择日志指向包内字体,字形缺失与字体打开失败两个计数双双归零。
编码:乱码自动修复的边界
SRT 乱码基本是编码问题:GBK 被当成 UTF-8,就出一串菱形问号。链路里的策略:先做严格 UTF-8 校验(含过长、截断、代理区检查)→ 交给 GBK/GB18030 转码 → 剥掉 UTF-8 BOM。UTF-16 的 SRT 由应用侧统一加了一层护栏:检出 BOM 就归一成无 BOM 的 UTF-8 再交给引擎。
不支持 .sub / VobSub 这类图形格式;编码自愈覆盖的是常见组合。
数字与支持面
- 文本光栅化约 0.06 毫秒每帧(1080p 稳态);ASS 链活跃帧的 CPU 增量目标小于 3 毫秒,静止段零开销;
- 队列上限:ASS 每轨 512 块、PGS 每轨 8 帧——字幕永远不会把播放拖住;
- 支持格式:ASS / SSA / SRT / mov_text / PGS;蓝光原盘的 PGS 与 UHD 蓝光都在列。
写在最后
字幕这条链的价值,不在某一个功能点,而在「三种完全不同的东西,最后收敛到同一条画布、同两条车道——平台渲染只管混合」——以及在那之后,把每一个只在真实片源里出现的坑都填掉。上面三个事故的修复都进了 avox 引擎。系列其他篇:产品介绍从开源引擎 avox 到成品:全平台播放器「泛音」。