# VOD 深度剖析（四）：容器格式 —— .mp4 并不是编解码器

> 容器与编解码器的区别、MP4 内部结构（Box 结构）、faststart 陷阱、分片 MP4、用于统一 HLS 与 DASH 的 CMAF、分片时长权衡，以及字幕格式。

- 作者: zhuermu
- 发布: 2026-05-10
- 网页版: https://zhuermu.com/blog/vod-deep-dive-part-4-container-formats-mp4-fmp4-cmaf/

---
> *这是 [VOD 流媒体深度剖析](/blog/vod-deep-dive-part-1-video-fundamentals/)系列的第 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` | Google | 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
```

```bash
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
```

两大核心优势：

1. **分片相互独立**：每一对 `moof+mdat` 都可以作为单独的文件（`.m4s`）存储，这正是流媒体协议所需要的。
2. **启动只需要一个极小的 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 部分](/blog/vod-deep-dive-part-1-video-fundamentals/)。

---

## 容器中的字幕

三种方式：

### 外挂（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 里面有什么

```bash
ffprobe -v error -show_streams input.mp4
```

列出所有视频 / 音频 / 字幕流。

### 把 .mov 转换为用于流媒体的 fMP4（不重新编码）

```bash
ffmpeg -i input.mov \
  -c copy \
  -movflags +faststart+frag_keyframe+empty_moov+default_base_moof \
  output_fragmented.mp4
```

### 切分成 HLS 分片（fMP4 格式）

```bash
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 播放即可。

---

## 关键要点

1. **容器 ≠ 编解码器**。MP4 是容器，H.264 是编解码器。
2. MP4 内部基于 Box 结构：`moov` 是目录，`mdat` 是数据。
3. **VOD 务必应用 `-movflags +faststart`** —— 把 moov 移到文件最前面，以实现渐进式播放。
4. **fMP4** 把文件拆分成相互独立的分片 —— 这是现代流媒体的基础。
5. **CMAF** 让 HLS 与 DASH 共享同一批 fMP4 文件：存储减半，缓存翻倍。
6. TS 已是遗留技术。新项目应使用 fMP4/CMAF。
7. 分片时长：短视频 → 2 秒；长片 VOD → 4–6 秒。
8. 字幕：多语言场景优先选择外挂 WebVTT。

---

*上一篇：* [第 3 部分：音频基础](/blog/vod-deep-dive-part-3-audio-fundamentals/)

*下一篇：* **[第 5 部分：流媒体协议 —— HLS 与 DASH](/blog/vod-deep-dive-part-5-streaming-protocols-hls-dash/)**

---

## 常见问题

### MP4 到底是编码格式还是容器格式？

MP4 是容器，不是编解码器。容器就像一个快递箱，里面装着编码好的视频流（H.264、H.265 或 AV1）、音频流（如 AAC）、字幕轨道和元数据，并记录音画同步、进度跳转所需的时间索引；真正负责压缩画面的是编解码器。所以正确说法是「MP4 文件里装的是 H.264 视频 + AAC 音频」，而不是「MP4 编码」。

### 为什么 MP4 要等整个文件下载完才能开始播放？

因为编码器默认把 moov box（播放器必须先读取的元数据索引）写在文件末尾，网页播放器只能等整个文件下载完才能起播。解决办法是 faststart：用 ffmpeg 加上 -movflags +faststart 参数，把 moov 移到文件开头，之后只需读取不到 1 MB 数据即可开始播放。VOD 视频发布前务必做这一步。

### CMAF 是什么？新项目为什么建议直接用 CMAF？

CMAF 是 2018 年标准化的通用媒体应用格式，让 HLS 和 DASH 共享同一套 fMP4 分片，只有清单文件不同。好处是存储减半、CDN 缓存命中率翻倍、只需转码一次，不用再维护「HLS TS + DASH fMP4」两套文件。代价是播放平台需支持 CMAF（现代平台都支持），且 DRM 必须用 CBCS 模式。2020 年后启动的项目建议直接采用 CMAF。


---

## 参考资料

- [HTTP Live Streaming](https://developer.apple.com/documentation/http-live-streaming) — Apple Developer
- [DASH-IF guidelines](https://dashif.org/guidelines/) — DASH Industry Forum
- [FFmpeg documentation](https://ffmpeg.org/documentation.html) — FFmpeg
