3、音视频流基础:视频轨、音频轨、字幕轨、章节信息在容器中的组织方式
做封装格式转换这么多年,我见过太多人把容器和编码搞混。说白了,一个 MP4 文件就像个旅行箱,视频轨、音频轨、字幕轨就是箱子里分门别类装好的行李。今天咱们就打开这个箱子,看看里面到底怎么组织的。
3.1 容器是什么?轨又是什么?
容器(Container)是包装层,它不关心里面是 H.264 还是 AAC,只管怎么把数据打包、索引、同步。每个独立的媒体流就叫一条轨(Track)。
我习惯把容器想象成一本带目录的书:
- 视频轨:连续的画面帧,带时间戳
- 音频轨:连续的采样点,也带时间戳
- 字幕轨:文字或图片,在特定时间点显示
- 章节信息:时间点标记,类似书签
这些轨在容器里是平行存放的,播放器靠时间戳把它们对齐。嗯,这里要注意:不同容器的组织方式差别很大,选错了坑你没商量。
3.2 视频轨的组织方式
视频轨的核心是帧序列。但帧不是随便堆的,容器会用 样本(Sample) 或 数据包(Packet) 来管理。
关键概念: 视频轨里存的是编码后的比特流,不是原始像素。容器只负责切分、索引、加时间戳。
我记得有一次接手一个项目,对方说「视频卡顿」,结果一查是 MP4 的 moov box 放在了文件末尾,导致流式播放时没法快速寻址。这就是容器组织方式的问题。
常见视频轨组织方式对比:
| 容器格式 | 视频轨存储方式 | 关键特点 |
|---|---|---|
| MP4 | mdat box 中的样本序列,stbl box 做索引 | 支持随机访问,moov box 位置影响启动速度 |
| MKV | Cluster 块,每个 Cluster 包含一组帧 | Cluster 自带时间戳,容错性好 |
| TS | 188 字节的 TS 包,PES 封装 | 适合广播,包结构固定,抗丢包 |
| AVI | RIFF 块,索引在文件末尾 | 古老,索引损坏就全废 |
我的经验: 做封装转换时,视频轨的 关键帧间隔 一定要保留。如果转换工具重新编码了关键帧位置,会导致 seek 不准。我一般用 -g 60 或 -keyint 60 保持原样。
3.3 音频轨的组织方式
音频轨比视频简单,但坑也不少。音频样本是固定大小的(比如 1024 个采样点一帧),容器只需要按顺序存,加上采样率和位深信息就行。
你想想看,为什么有些视频文件音频和视频不同步?多半是容器里音频轨的时间戳基准和视频轨不一致。MP4 用 mdhd box 定义时间尺度,MKV 用 CodecPrivate 存参数,搞混了就出问题。
音频轨在容器中的常见参数:
- 采样率:44100 Hz、48000 Hz 最常见
- 声道数:立体声、5.1、7.1
- 位深:16 bit、24 bit、32 bit float
- 帧大小:AAC 通常 1024 采样点,MP3 是 1152
避坑指南: 我曾经把一个 48kHz 的音频轨硬塞进一个标记为 44.1kHz 的容器里。播放器按 44.1kHz 读,结果声音变慢变粗,像开了慢放。转换时一定要检查 ffprobe 输出的 sample_rate 字段。
3.4 字幕轨的组织方式
字幕轨分两种:文本字幕 和 图形字幕。它们在容器里的组织方式完全不同。
文本字幕(如 SRT、ASS、VTT)存的是文字和时间码,体积小,可编辑。图形字幕(如 PGS、DVD 字幕)存的是位图,体积大,但显示效果固定。
容器对字幕轨的支持:
| 容器 | 文本字幕 | 图形字幕 | 备注 |
|---|---|---|---|
| MP4 | 支持(tx3g、VTT) | 不支持原生 | 文本字幕有限制,ASS 支持差 |
| MKV | 支持(SRT、ASS、VTT) | 支持(PGS、VobSub) | 字幕支持最全,我首选 |
| TS | 不支持原生 | 支持(DVB 字幕) | 广播场景专用 |
个人习惯: 做封装转换时,如果源文件是 MKV 带 ASS 字幕,转 MP4 时 ASS 基本会丢失特效。我一般先提取字幕单独转成 SRT,再封装进 MP4。用 ffmpeg -i input.mkv -map 0:s:0 output.srt 提取。
3.5 章节信息的组织方式
章节信息就是时间点标记,告诉你「第 1 章从 00:00:00 开始,第 2 章从 00:05:30 开始」。它在容器里通常单独存为一个元数据轨或专用结构。
不同容器的章节存储方式:
- MP4:用 chap box 或 tmcd box,每个章节是一个时间点 + 名称
- MKV:用 ChapterAtom 元素,支持嵌套章节(如第 1 章下有子章节)
- OGM:用 chapter 标签,简单粗暴
我记得有一次做蓝光转 MKV,原盘有 20 多个章节,但转换工具默认只保留了前 3 个。后来发现是 ffmpeg 的 -map_chapters 参数没加。加上 -map_chapters 0 就全保留了。
小技巧: 用 ffprobe -v quiet -print_format json -show_chapters input.mkv 可以查看所有章节信息。转换前先检查,避免丢章节。
3.6 各轨在容器中的整体组织关系
说了这么多,咱们用一张图把整体关系理清楚。下面是我画的一个容器内部结构图,展示了视频轨、音频轨、字幕轨和章节信息如何共存。
3.7 各轨之间的同步机制
同步是容器最核心的功能。每个样本都有一个 PTS(显示时间戳) 和 DTS(解码时间戳)。容器通过时间戳把视频帧、音频样本、字幕事件对齐到同一个时间轴上。
为什么会不同步?我遇到过三种情况:
- 时间基准不一致:视频轨用 90000 时间尺度,音频轨用 48000,转换时没做归一化
- 帧率误判:容器标记 30fps,实际视频是 29.97fps,累积误差越来越大
- B 帧导致 DTS/PTS 错乱:某些转换工具没正确处理 B 帧的显示顺序
避坑指南: 我曾经把一个 29.97fps 的 NTSC 视频转成 30fps 的 MP4,没做帧率适配。结果每过 1 分钟音频就落后视频约 0.1 秒,到 30 分钟时差了 3 秒。后来用 -vsync vfr 参数保留原始时间戳才解决。
3.8 封装转换时的轨操作要点
做封装格式转换时,轨的操作是核心。我总结了几条铁律:
- 映射所有轨:用
-map 0保留所有轨,别漏了字幕和章节 - 检查轨参数:用
ffprobe查看每个轨的编码、时间基准、语言标签 - 保持时间戳:不要重新生成时间戳,除非源文件本身有问题
- 注意轨类型兼容性:MP4 不支持 ASS 字幕,MKV 不支持某些 MP4 的私有元数据
我的习惯: 转换前先用 ffprobe -show_streams input.mp4 导出所有轨的详细信息,保存为 JSON。转换后再对比一次,确保轨的数量、类型、参数没变。这步花不了 1 分钟,但能省下后面排查问题的几小时。
好了,关于视频轨、音频轨、字幕轨和章节信息在容器中的组织方式,就聊到这儿。记住一句话:容器是骨架,轨是血肉,时间戳是灵魂。搞懂了这三者的关系,封装格式转换就成功了一半。