VOD 深度剖析(四):容器格式 —— .mp4 并不是编解码器
容器与编解码器的区别、MP4 内部结构(Box 结构)、faststart 陷阱、分片 MP4、用于统一 HLS 与 DASH 的 CMAF、分片时长权衡,以及字幕格式。
这是 VOD 流媒体深度剖析系列的第 4 部分。
最常见的误解
几乎每个初学者都会犯这样一个错误:
❌ “视频编解码器是 MP4。”
✅ “视频容器是 MP4,编解码器是 H.264(或 H.265、AV1 等)。”
MP4 是一个容器 —— 一个盒子。这个盒子里可以装任何受支持的编解码码流。
┌──────────────────────────────────────┐
│ MP4 Container (a .mp4 file) │
│ ┌────────────────────────────────┐ │
│ │ Video stream: H.264 / H.265 / AV1│
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ Audio stream: AAC / Opus / AC-3 │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ Subtitle stream: TTML / WebVTT │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ Metadata: title, duration, index│ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘
可以把 MP4 想象成一个快递箱,H.264 是箱子里的商品。同一个箱子可以装手机(H.264)、衬衫(H.265)或水果(AV1)。
为什么需要容器?
难道不能直接存储原始编码码流吗?
不行。 原始码流缺少以下信息:
- 如何同步视频与音频的播放
- 如何对齐字幕
- 当用户跳转到 1:00 时应从哪里开始读取
- 存在多少条音轨
- 使用了什么编解码器、以及对应的参数是什么
容器把这些信息全部组织起来,让播放器能够沿着时间轴读取数据。
主流容器格式
| 容器 | 扩展名 | 创建者 | 典型内容 | 最适合场景 |
|---|---|---|---|---|
| MP4 / fMP4 | .mp4 .m4s .m4a | MPEG | H.264/H.265/AV1 + AAC | 最通用,流媒体默认选择 |
| MOV | .mov | Apple | 几乎什么都能装 | macOS 制作、母版素材传输 |
| MKV | .mkv | CoreCodec | 几乎什么都能装 | 高清下载、开源社区 |
| WebM | .webm | VP9/AV1 + Opus | Web(非 iOS) | |
| MPEG-TS | .ts | MPEG | H.264/HEVC + AAC/AC-3 | 广播电视、传统 HLS |
| FLV | .flv | Adobe | H.264 + AAC | 已退役(RTMP 时代遗留) |
对于 VOD,实际上你只需要两种:MP4/fMP4(流媒体)和 MOV(母版素材传输)。
MP4 / ISO BMFF 内部结构
MP4 的正式标准是 ISO 基础媒体文件格式(ISO Base Media File Format,ISO BMFF),即 ISO/IEC 14496-12。在内部,它由一系列被称为 Box(也叫 atom)的嵌套单元组成:
File start
│
├── [ftyp] File Type Box ← Tells the player "this is MP4"
│
├── [moov] Movie Box ← Metadata "table of contents":
│ ├── [mvhd] Movie Header (total duration, timescale)
│ ├── [trak] Track Box (one per audio/video/subtitle track)
│ │ ├── [tkhd] Track Header
│ │ └── [mdia] Media
│ │ ├── [mdhd] Media Header
│ │ ├── [hdlr] Handler (vide/soun/subt)
│ │ └── [minf]
│ │ └── [stbl] Sample Table ← byte offset of every frame
│ └── [trak] ... (more tracks)
│
└── [mdat] Media Data Box ← The actual audio/video binary data
└── (large binary blob)
两个关键概念:
- moov:元数据/目录。它告诉播放器“第 1 帧在字节 12345 处,第 2 帧在字节 13800 处……”
- mdat:真正的数据,纯粹的 H.264 + AAC 二进制内容。
faststart 陷阱
默认情况下,MP4 编码器会把 moov box 放在文件末尾(因为完整的时间戳索引在编码完成之前无法确定)。
Default MP4:
┌─────────┬─────────────────────────────────┬──────┐
│ ftyp │ mdat (99.9%) │ moov │
└─────────┴─────────────────────────────────┴──────┘
8 bytes hundreds of MB/GB tens of KB
问题在于:网页播放器必须先读取 moov 才能开始播放,而 moov 在文件末尾 → 必须下载完整个文件后才能开始播放。
解决方案:faststart(moov 放到最前面)
Faststart MP4:
┌─────────┬──────┬──────────────────────────────┐
│ ftyp │ moov │ mdat │
└─────────┴──────┴──────────────────────────────┘
↑
After reading this (< 1 MB), playback can begin
ffmpeg -i input.mov -c copy -movflags +faststart output.mp4
VOD 铁律:发布前务必应用 faststart。 否则用户按下播放后会长时间面对一片空白。
分片 MP4(fMP4):流媒体的选择
传统 MP4 有个问题:moov box 描述的是整个文件。对于时长数小时的电影,moov 会膨胀到数兆字节 —— 解析缓慢,更新代价也高。
fMP4(Fragmented MP4,分片 MP4) 把文件拆分成许多小分片,每个分片都有自己的迷你 moov(称为 moof):
fMP4 structure:
┌──────┬──────┐ ┌──────┬──────┐ ┌──────┬──────┐ ┌──────┬──────┐
│ moov │ - │ │ moof │ mdat │ │ moof │ mdat │ │ moof │ mdat │
│ init │ │ │ frag1│frag1 │ │ frag2│frag2 │ │ frag3│frag3 │
└──────┴──────┘ └──────┴──────┘ └──────┴──────┘ └──────┴──────┘
Standalone
init segment Each fragment is independently decodable
两大核心优势:
- 分片相互独立:每一对
moof+mdat都可以作为单独的文件(.m4s)存储,这正是流媒体协议所需要的。 - 启动只需要一个极小的 init segment(约几十 KB),而不是整个 moov。
现代 HLS、DASH 和 CMAF 全都基于 fMP4。 MPEG-TS(传统 HLS)正在被逐步淘汰。
CMAF:一份文件统御 HLS 与 DASH
历史上,Apple 主推 HLS(使用 TS 分片),而业界其余厂商主推 DASH(使用 fMP4 分片)。同一段视频,要存储两份。
CMAF(Common Media Application Format,通用媒体应用格式) 于 2018 年标准化,解决了这个问题:
HLS 与 DASH 共享同一批 fMP4 文件,只有清单(manifest)不同。
One set of CMAF fMP4 segments:
┌──────┐ ┌───────┐ ┌───────┐ ┌───────┐
│ init │ │ seg1 │ │ seg2 │ │ seg3 │ ← Only one copy on disk
└──────┘ └───────┘ └───────┘ └───────┘
↑ ↑
┌────────┴───────┐ ┌──┴──────────┐
│ HLS master.m3u8│ │ DASH .mpd │
│ points to same │ │ points to │
│ segments │ │ same segments│
└────────────────┘ └─────────────┘
收益:存储量减半,CDN 缓存命中率翻倍,转码只需一次。
代价:所有平台都必须支持 CMAF(现代平台都支持),且 DRM 必须使用 CBCS 模式(与 FairPlay 兼容)。
对于 2020 年之后启动的项目:直接使用 CMAF。 不要再搞“HLS TS + DASH fMP4”的双份发布。
分片时长:每一片应该多长?
| 分片时长 | 优点 | 缺点 | 最适合场景 |
|---|---|---|---|
| 1–2 秒 | 启动快,ABR 自适应切换迅速 | 文件多,HTTP 请求多 | 短视频、低延迟直播 |
| 2–4 秒 | 均衡 | — | HLS/DASH 推荐默认值(4 秒) |
| 6–10 秒 | HTTP 请求更少,对 CDN 友好 | 启动慢,跳转粒度粗 | 长片电影、传统广播电视 |
短视频应用应使用 2 秒分片(用户频繁滑动切换)。长片 VOD 应使用 4–6 秒。
分片时长必须是 GOP 时长的整数倍 —— 关于关键帧,请参见第 1 部分。
容器中的字幕
三种方式:
外挂(Sidecar / External)
字幕作为独立文件存在:
video.mp4
video.en.vtt ← English subtitles
video.zh.vtt ← Chinese subtitles
HLS/DASH 清单会引用这些文件。添加语言很方便;更换字幕无需重新编码视频。
内嵌(Embedded)
字幕作为一条轨道存放在 MP4 内部,一个文件包含所有内容。
硬字幕(Burned-In / Hardcoded)
字幕被直接渲染进视频像素中,无法关闭。
建议:需要多语言支持的 VOD → 使用外挂 WebVTT;字幕本身属于创作组成部分的短视频 → 使用硬字幕。
常见字幕格式
| 格式 | 关键特性 | 应用场景 |
|---|---|---|
| SRT | 最简单:文本 + 时间戳 | 通用 |
| WebVTT | SRT 的增强版(支持样式、定位) | HTML5 / HLS 标准 |
| TTML / IMSC1 | XML,支持复杂排版 | DASH、广播电视 |
| ASS / SSA | 强大的样式与动画能力 | 动漫社区 |
动手实践:用 ffmpeg 操作容器
查看 MP4 里面有什么
ffprobe -v error -show_streams input.mp4
列出所有视频 / 音频 / 字幕流。
把 .mov 转换为用于流媒体的 fMP4(不重新编码)
ffmpeg -i input.mov \
-c copy \
-movflags +faststart+frag_keyframe+empty_moov+default_base_moof \
output_fragmented.mp4
切分成 HLS 分片(fMP4 格式)
ffmpeg -i input.mp4 \
-c:v libx264 -preset slow -crf 22 -g 60 -keyint_min 60 -sc_threshold 0 \
-c:a aac -b:a 128k \
-f hls \
-hls_time 4 \
-hls_segment_type fmp4 \
-hls_playlist_type vod \
-hls_list_size 0 \
-hls_segment_filename "seg_%04d.m4s" \
output.m3u8
输出:
output.m3u8 ← HLS manifest
init.mp4 ← CMAF init segment
seg_0000.m4s
seg_0001.m4s
seg_0002.m4s
...
这就是一个可运行的最小 HLS 流。把它托管在任意 Web 服务器上(甚至 python3 -m http.server 都行),然后用 Safari 或 hls.js 播放即可。
关键要点
- 容器 ≠ 编解码器。MP4 是容器,H.264 是编解码器。
- MP4 内部基于 Box 结构:
moov是目录,mdat是数据。 - VOD 务必应用
-movflags +faststart—— 把 moov 移到文件最前面,以实现渐进式播放。 - fMP4 把文件拆分成相互独立的分片 —— 这是现代流媒体的基础。
- CMAF 让 HLS 与 DASH 共享同一批 fMP4 文件:存储减半,缓存翻倍。
- TS 已是遗留技术。新项目应使用 fMP4/CMAF。
- 分片时长:短视频 → 2 秒;长片 VOD → 4–6 秒。
- 字幕:多语言场景优先选择外挂 WebVTT。
上一篇: 第 3 部分:音频基础
参考资料
- HTTP Live Streaming — Apple Developer
- DASH-IF guidelines — DASH Industry Forum
- FFmpeg documentation — FFmpeg