04 · 文件封装:.mp4 不是一种编码

VOD 流媒体技术全解 · 第 4 章 / 共 12 章

本章你会理解:文件后缀和编码格式的区别、MP4/MKV/TS/WebM 是什么、为什么流媒体都用 fMP4、CMAF 又是干嘛的。

预计阅读时间:18 分钟

4.1一个最容易搞混的概念:容器 ≠ 编码

先看一个每个新人都会犯的错:

❌ "视频编码是 MP4。"

✅ "视频容器是 MP4,编码是 H.264(或 H.265、AV1 等)。"

MP4 是一个容器(container),是一个盒子。盒子里可以装任何支持的编码流。

┌──────────────────────────────────────┐
 │   MP4 容器(一个 .mp4 文件)          │
 │  ┌────────────────────────────────┐   │
 │  │ 视频流:H.264 / H.265 / AV1 …  │   │
 │  └────────────────────────────────┘   │
 │  ┌────────────────────────────────┐   │
 │  │ 音频流:AAC / Opus / AC-3 …    │   │
 │  └────────────────────────────────┘   │
 │  ┌────────────────────────────────┐   │
 │  │ 字幕流:TTML / WebVTT …        │   │
 │  └────────────────────────────────┘   │
 │  ┌────────────────────────────────┐   │
 │  │ 元数据:标题、时长、时间戳索引 │   │
 │  └────────────────────────────────┘   │
 └──────────────────────────────────────┘

类比:MP4 就像"快递盒";H.264 是盒子里装的"商品"。同样一个快递盒可以装手机(H.264)、装衣服(H.265)、装水果(AV1)。

4.2为什么需要容器?

直接存编码后的二进制流不行吗?

不行。编码流里没有这些信息:

•  视频和音频怎么同步播放
•  字幕怎么对齐?
•  用户拖进度条到 1 分钟,从哪个字节开始读?
•  视频有几条音轨?
•  视频是什么编码?参数?

容器格式负责组织这一切,让播放器能按时间轴读取。

4.3主流容器格式对比

容器 后缀 发明者 主要装什么 适合
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/AC3 广电、老版 HLS
FLV .flv Adobe H.264 + AAC 已退役,RTMP 残留
AVI .avi Microsoft - 已过时,不要用
3GP .3gp 3GPP H.263 + AMR 2G 时代手机,已过时

VOD 项目 99% 的情况只需要两种容器

•  MP4 / fMP4(流媒体发布)
•  MOV(素材中转、母版)

4.4MP4 / ISO BMFF 的内部结构

MP4 的正式标准叫 ISO Base Media File Format(ISO BMFF),标准号 ISO/IEC 14496-12。

内部由一个个叫 Box(盒子,也叫 atom)的单元组成,Box 可以嵌套:

File start

├── [ftyp]  File Type Box  ← 告诉播放器"这是 MP4"

├── [moov]  Movie Box       ← 元数据"目录表":
│    ├── [mvhd]  Movie Header (总时长、时间尺度)
│    ├── [trak]  Track Box (每条音频/视频轨一个)
│    │    ├── [tkhd] Track Header
│    │    └── [mdia] Media
│    │         ├── [mdhd] Media Header
│    │         ├── [hdlr] Handler (vide/soun/subt)
│    │         └── [minf]
│    │              └── [stbl] Sample Table  ← 每一帧在文件哪个字节
│    └── [trak] ... (更多轨)

└── [mdat]  Media Data Box  ← 真正的音视频二进制数据
      └── (大块二进制)

关键概念

•  moov元数据 / 目录。告诉播放器"第 1 帧在字节 12345,第 2 帧在字节 13800..."。没有它不知道怎么播。
•  mdat:真正的数据。纯粹的 H.264 + AAC 二进制。

4.5关键陷阱:moov 的位置决定能不能"边下边播"

传统 MP4 生成时 moov 会放在文件末尾(因为编码完成后才知道完整的时间戳信息)。

常规 MP4:
┌─────────┬─────────────────────────────────┬──────┐
│  ftyp   │         mdat (99.9%)            │ moov │
└─────────┴─────────────────────────────────┴──────┘
   8字节          几百 MB / GB              几十 KB

问题来了:Web 播放器要播必须先读 moov,但 moov 在文件末尾 → 要把整个文件下载完才能开始播

这就是你看到"网页视频要等进度条走到 100% 才能播"的原因。

解决:moov 前置(faststart)

重新打包,把 moov 挪到 ftyp 之后:

faststart MP4:
┌─────────┬──────┬──────────────────────────────┐
│  ftyp   │ moov │           mdat               │
└─────────┴──────┴──────────────────────────────┘
          ↑
          读完这里 (< 1 MB) 就能开始播

用 ffmpeg 加 -movflags +faststart 就行:

ffmpeg -i input.mov -c copy -movflags +faststart output.mp4

VOD 发布前必做 faststart。没做的话用户点播后会很久没反应。

4.6fMP4(Fragmented MP4):流媒体的正确选择

传统 MP4 有个大缺点:moov 描述的是整个文件的时间轴。如果内容很长(几小时的电影),moov 会非常大(MB 级),启动慢、修改成本高。

fMP4(Fragmented MP4) 的做法:

把文件拆成很多小片段(fragment),每个片段都自带自己的小 moov(叫 moof)。播放器可以独立解析每个片段。

fMP4 结构:

┌──────┬──────┐  ┌──────┬──────┐  ┌──────┬──────┐  ┌──────┬──────┐
│ moov │  -   │  │ moof │ mdat │  │ moof │ mdat │  │ moof │ mdat │
│ init │      │  │ 片񫭑 │片񫭑 │  │ 片񫭒 │片񫭒 │  │ 片񫭓 │片񫭓 │
└──────┴──────┘  └──────┴──────┘  └──────┴──────┘  └──────┴──────┘
  独立的
  初始化段                   每个片段都是独立可解码的

fMP4 有两个核心好处:

1.  可独立切片:每个 moof+mdat 对可以单独存为一个文件(.m4s)。这正是流媒体分发需要的。
2.  启动只需很小的 init segment(几十 KB),而不是整个 moov。

现代 HLS、DASH、CMAF 全部基于 fMP4。TS(老派 HLS)正在被淘汰。

4.7MPEG-TS:老 HLS 的默认载体

MPEG-TS(.ts 文件)是为广播电视设计的格式:

•  所有数据切成固定 188 字节的小包
•  每个包自带同步字(0x47 sync byte)和时间戳
•  设计目标:卫星信号偶尔丢包也能恢复

TS 结构:
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬ ...
│188B │188B │188B │188B │188B │188B │188B │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴
 pkt1  pkt2  pkt3  ...

HLS 2009 年诞生时 iOS 强制使用 .ts。但 TS 有几个问题:

•  开销大:每 188 字节就要一个 4 字节头(开销约 2%)
•  PAT/PMT 表 每隔一段时间就要重复
•  和 MP4 生态割裂:要多存一套 TS 版本

iOS 10(2016)起支持 fMP4,新项目应统一用 fMP4,可以被 HLS/DASH 共用。

4.8CMAF:一份文件通吃 HLS 和 DASH

问题:历史上 Apple 主推 HLS(TS),Google/工业界主推 DASH(fMP4),同一个视频要存两份。

CMAF(Common Media Application Format) 2018 年标准化,核心约定:

HLS 和 DASH 共用一份 fMP4 文件。Manifest 不同,segment 完全相同。

一份 CMAF fMP4 源:
┌──────┐  ┌───────┐ ┌───────┐ ┌───────┐
│ init │  │ seg1  │ │ seg2  │ │ seg3  │  ← 物理存储只有一份
└──────┘  └───────┘ └───────┘ └───────┘
            ↑              ↑
   ┌────────┴───────┐   ┌──┴──────────┐
   │ HLS master.m3u8 │   │ DASH .mpd   │
   │ 指向同样的片段   │   │ 指向同样的片段│
   └────────────────┘   └─────────────┘

收益

•  存储减半
•  CDN 缓存命中率翻倍
•  转码只跑一次

代价

•  所有平台要兼容 CMAF(现代平台都支持)
•  DRM 需要用 CBCS 模式(兼容 FairPlay)

2020 年后新项目请直接用 CMAF。不要再做"HLS TS + DASH fMP4 双发"。

4.9段(Segment)的长度选多少?

VOD 切片长度是一个工程权衡:

切片长度 优点 缺点 适合
1-2 秒 起播快、ABR 反应快、低延迟直播 文件多、HTTP 请求多 短剧、短视频、低延迟直播
2-4 秒 平衡 - HLS/DASH 默认推荐(4 秒)
6-10 秒 HTTP 请求少、CDN 友好 起播慢、拖动定位粗 长电影、传统广电

短剧/短视频选 2 秒(因为用户频繁滑动切剧集);长视频 VOD 选 4-6 秒。

切片长度必须是 GOP 的整数倍,见第 1 章关键帧部分。

4.10字幕装进容器的几种方式

字幕有三种组织方式:

① Sidecar(外挂字幕)

字幕是独立文件,不在视频容器里:

video.mp4
video.en.vtt   ← 英文字幕
video.zh.vtt   ← 中文字幕

HLS/DASH manifest 引用这些字幕文件。

优点:容易多语言切换、改字幕不用重做视频。
缺点:多一些 HTTP 请求。

② 内嵌(Embedded)

字幕作为一条 track 放在 MP4 里:

video.mp4
├── video track
├── audio track
└── subtitle track (TTML / WebVTT)

③ 烧录(Burned-in / Hardcoded)

把字幕直接绘制到视频画面上。像素级合并,不可关闭。

适合哪种?

•  VOD 多语言:用 Sidecar(外挂 WebVTT/IMSC1)
•  短视频:有时烧录(字幕是创意的一部分)
•  广电:内嵌 EIA-608/708

4.11常见字幕格式

格式 特点 用在哪
SRT 最简单,文本+时间戳 通用
WebVTT SRT 增强(样式、定位) HTML5 / HLS 标准
TTML / IMSC1 XML,支持复杂排版 DASH、广电
ASS / SSA 强大样式,动画特效 番剧爱好者
EIA-608 / 708 广电字幕编码 传统电视、直播 CC

一个 WebVTT 例子:

WEBVTT

00:00:01.000 --> 00:00:03.500
Hello, and welcome to our show.

00:00:03.500 --> 00:00:06.000
Today we talk about video streaming.

4.12动手:用 ffmpeg 做封装相关操作

动手试一试

① 查看一个 MP4 里有什么

ffprobe -v error -show_streams input.mp4

会列出所有 video / audio / subtitle 流。

② 把 .mov 无损转成 fMP4 供流媒体使用

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

解释:

•  -c copy:不重新编码,直接复制
•  +faststart:moov 前置
•  +frag_keyframe:在每个关键帧切片
•  +empty_moov +default_base_moof:让 moov 为空(只放 init),数据进 moof/mdat

③ 把长视频切成 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 容器是老派 HLS 用的,新项目请用 fMP4/CMAF。
7.  切片长度:短剧短视频 2 秒、长 VOD 4-6 秒。
8.  字幕优先用 Sidecar WebVTT。

← 上一章 目录 下一章 →

© 2026 Zmead · VOD 流媒体技术全解