VOD 深度剖析(四):容器格式 —— .mp4 并不是编解码器

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

zhuermu··18 分钟
vodstreamingmp4fmp4cmafcontainer

这是 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 .m4aMPEGH.264/H.265/AV1 + AAC最通用,流媒体默认选择
MOV.movApple几乎什么都能装macOS 制作、母版素材传输
MKV.mkvCoreCodec几乎什么都能装高清下载、开源社区
WebM.webmGoogleVP9/AV1 + OpusWeb(非 iOS)
MPEG-TS.tsMPEGH.264/HEVC + AAC/AC-3广播电视、传统 HLS
FLV.flvAdobeH.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

两大核心优势:

  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 部分


容器中的字幕

三种方式:

外挂(Sidecar / External)

字幕作为独立文件存在:

video.mp4
video.en.vtt   ← English subtitles
video.zh.vtt   ← Chinese subtitles

HLS/DASH 清单会引用这些文件。添加语言很方便;更换字幕无需重新编码视频。

内嵌(Embedded)

字幕作为一条轨道存放在 MP4 内部,一个文件包含所有内容。

硬字幕(Burned-In / Hardcoded)

字幕被直接渲染进视频像素中,无法关闭。

建议:需要多语言支持的 VOD → 使用外挂 WebVTT;字幕本身属于创作组成部分的短视频 → 使用硬字幕。

常见字幕格式

格式关键特性应用场景
SRT最简单:文本 + 时间戳通用
WebVTTSRT 的增强版(支持样式、定位)HTML5 / HLS 标准
TTML / IMSC1XML,支持复杂排版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 播放即可。


关键要点

  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 部分:音频基础

下一篇: 第 5 部分:流媒体协议 —— HLS 与 DASH

参考资料

  1. HTTP Live Streaming — Apple Developer
  2. DASH-IF guidelines — DASH Industry Forum
  3. FFmpeg documentation — FFmpeg