字幕处理:SRT/ASS字幕解析、字幕渲染、硬字幕与软字幕嵌入
字幕处理,说白了就是给视频配上文字说明。这件事看起来简单,但坑不少。我最早做播放器的时候,觉得字幕不就是显示几行字嘛,结果被各种编码、格式、时间轴偏移折磨得够呛。今天咱们就把这块彻底讲透。
字幕格式:SRT 与 ASS 的区别
先聊聊最常见的两种字幕格式。SRT 是最简单的,ASS 则复杂得多。
| 特性 | SRT | ASS |
|---|---|---|
| 结构 | 纯文本 + 时间轴 | 脚本 + 样式 + 事件 |
| 样式支持 | 基本无(仅颜色) | 字体、阴影、边框、动画 |
| 解析难度 | 简单 | 中等 |
| 文件大小 | 小 | 较大 |
我个人习惯,如果只是做简单的翻译字幕,用 SRT 就够了。但要做特效字幕,比如卡拉OK、滚动弹幕,那就得上 ASS。
SRT 字幕解析
SRT 的格式非常直白:序号、时间轴、文本。我写解析器的时候,核心就是按行读取,用正则匹配时间轴。
// SRT 解析核心逻辑
std::vector<SubtitleEntry> parseSRT(const std::string& content) {
std::vector<SubtitleEntry> entries;
std::istringstream stream(content);
std::string line;
while (std::getline(stream, line)) {
// 跳过空行
if (line.empty()) continue;
// 解析序号
int index = std::stoi(line);
// 解析时间轴
std::getline(stream, line);
// 格式: 00:01:23,456 --> 00:01:25,678
auto [start, end] = parseTimeRange(line);
// 解析文本(可能有多行)
std::string text;
while (std::getline(stream, line) && !line.empty()) {
text += line + "\n";
}
entries.push_back({index, start, end, text});
}
return entries;
}
ASS 字幕解析
ASS 的解析就复杂多了。它分为几个区:Script Info、V4+ Styles、Events。我建议用现成的库,比如 libass,别自己从头写。
// ASS 样式定义示例
struct ASSStyle {
std::string name; // 样式名
std::string fontName; // 字体
int fontSize; // 字号
uint32_t primaryColor; // 主色
uint32_t secondaryColor; // 辅色
uint32_t outlineColor; // 边框色
uint32_t shadowColor; // 阴影色
bool bold; // 粗体
bool italic; // 斜体
int borderStyle; // 边框样式
int outlineWidth; // 边框宽度
int shadowDepth; // 阴影深度
};
ASS 的事件行格式是:Dialogue: 0,0:01:00.00,0:01:05.00,Default,,0,0,0,,Hello World。中间那些逗号分隔的字段,每个都有含义。我曾经在解析时漏了一个字段,结果字幕全乱了。
字幕渲染:从文本到像素
渲染字幕,本质上就是把文字画到画面上。这里有几个关键点:
- 字体加载:系统字体 vs 内嵌字体。ASS 字幕经常指定字体,找不到就回退。
- 抗锯齿:字幕边缘要平滑,不然看着像锯齿。
- 编码处理:UTF-8 是主流,但老字幕可能是 GBK、Shift-JIS。
我推荐用 FreeType 做字体渲染,配合 OpenGL 或 Direct2D 做合成。libass 内部就是这么干的。
// 使用 libass 渲染字幕
ASS_Library* ass_lib = ass_library_init();
ASS_Renderer* ass_renderer = ass_renderer_init(ass_lib);
// 加载字幕文件
ass_process_file(ass_renderer, "subtitle.ass");
// 渲染某一帧
ASS_Image* img = ass_render_frame(ass_renderer,
video_width, video_height,
frame_timestamp);
// 遍历图像链表,合成到视频帧
for (ASS_Image* p = img; p; p = p->next) {
// 将字幕图像混合到视频帧
blend_subtitle_to_frame(frame, p);
}
硬字幕与软字幕
这两个概念,我当年也搞混过。简单说:
- 硬字幕:直接烧录到视频画面里,无法移除。
- 软字幕:作为独立流或文件,播放时叠加。
硬字幕适合发布到平台(如 B站、YouTube),因为平台会转码,软字幕可能丢失。软字幕则适合本地播放,用户可以自由开关。
硬字幕嵌入实现
硬字幕嵌入,说白了就是把字幕渲染到每一帧上,然后重新编码。我用 FFmpeg 的滤镜来做:
// FFmpeg 硬字幕嵌入命令
// 使用 subtitles 滤镜
ffmpeg -i input.mp4 -vf "subtitles=subtitle.srt" output.mp4
// 或者用 libass 滤镜(支持 ASS 特效)
ffmpeg -i input.mp4 -vf "ass=subtitle.ass" output.mp4
代码层面,就是逐帧解码、渲染字幕、编码输出。性能瓶颈在编码,不是字幕渲染。
软字幕嵌入实现
软字幕是把字幕作为独立流封装到容器里。MP4 用 mov_text,MKV 用 SRT/ASS 原生流。
// FFmpeg 软字幕嵌入
ffmpeg -i input.mp4 -i subtitle.srt \
-c copy -c:s mov_text \
output.mp4
// 或者封装到 MKV
ffmpeg -i input.mp4 -i subtitle.ass \
-c copy -c:s ass \
output.mkv
知识体系总览
下面这张图,是我整理的字幕处理全流程。从解析到渲染,再到嵌入,每一步都有坑。
避坑指南
做字幕处理这些年,我踩过的坑不少。挑几个典型的说说:
- 编码问题:我曾经遇到一个 SRT 文件,用 UTF-8 打开全是乱码。后来发现是 GBK 编码。建议统一用 UTF-8,解析前做编码检测。
- 时间轴偏移:ASS 字幕的时间轴是毫秒级精度,但有些播放器会四舍五入。我建议在解析时保留浮点数,不要转整数。
- 字体缺失:ASS 字幕指定了某个字体,但系统没有。这时候要回退到默认字体,或者从字幕文件里提取内嵌字体。
- 性能问题:ASS 的逐帧动画很吃 CPU。如果视频是 60fps,字幕渲染跟不上,就会掉帧。我一般会缓存静态帧,只对动态部分重新渲染。
嗯,字幕这块就聊到这儿。下次你遇到字幕显示不对,先检查编码,再看时间轴,最后看字体。这三步走完,90% 的问题都能解决。