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 各轨在容器中的整体组织关系

说了这么多,咱们用一张图把整体关系理清楚。下面是我画的一个容器内部结构图,展示了视频轨、音频轨、字幕轨和章节信息如何共存。

容器内部结构示意图 容器(Container) 视频轨(Video Track) 样本序列(帧) 时间戳 + 关键帧索引 编码参数(分辨率、帧率) stbl / Cluster 索引结构 例:H.264 + 1920x1080 + 30fps 音频轨(Audio Track) 音频样本(帧) 采样率 + 声道数 + 位深 时间戳与视频对齐 mdhd / CodecPrivate 参数 例:AAC + 48000Hz + 立体声 字幕轨(Subtitle Track) 文本字幕(SRT/ASS/VTT) 或图形字幕(PGS/VobSub) 时间码 + 显示区域 例:ASS 特效字幕 章节信息(Chapters) 时间点标记 章节名称 支持嵌套(MKV) 例:第1章 00:00:00 各轨通过时间戳同步,容器负责索引、元数据、封装格式的读写 转换时需保持各轨参数一致,否则会出现音画不同步、字幕丢失等问题

3.7 各轨之间的同步机制

同步是容器最核心的功能。每个样本都有一个 PTS(显示时间戳) 和 DTS(解码时间戳)。容器通过时间戳把视频帧、音频样本、字幕事件对齐到同一个时间轴上。

为什么会不同步?我遇到过三种情况:

  1. 时间基准不一致:视频轨用 90000 时间尺度,音频轨用 48000,转换时没做归一化
  2. 帧率误判:容器标记 30fps,实际视频是 29.97fps,累积误差越来越大
  3. 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 分钟,但能省下后面排查问题的几小时。

好了,关于视频轨、音频轨、字幕轨和章节信息在容器中的组织方式,就聊到这儿。记住一句话:容器是骨架,轨是血肉,时间戳是灵魂。搞懂了这三者的关系,封装格式转换就成功了一半。