封装格式详解:MP4、FLV、TS、MKV 容器格式对比
封装格式,说白了就是视频、音频、字幕这些数据流的「打包方式」。你想想看,H.264 视频流和 AAC 音频流本身只是一堆二进制数据,没有容器格式,播放器根本不知道哪段是视频、哪段是音频、什么时候该切换。
我在项目中遇到过不少因为封装格式选错导致的坑。有一次做低延迟直播,选了 FLV,结果发现客户端对 HTTP-FLV 的支持参差不齐。后来换成了 TS over UDP,问题才解决。所以今天咱们就把 MP4、FLV、TS、MKV 这四种主流容器掰开揉碎讲清楚。
四种容器的核心差异
先看一张对比表,心里有个底:
| 特性 | MP4 | FLV | TS | MKV |
|---|---|---|---|---|
| 设计目标 | 本地存储/流媒体 | 实时流媒体 | 广播/传输 | 通用多媒体容器 |
| 索引方式 | moov box(文件头/尾) | 文件头元数据 | 无全局索引,依赖分段 | SeekHead + Cues |
| 流式支持 | 需 moov 前置 | 天然支持 | 天然支持 | 需分段优化 |
| 容错性 | 低(损坏后难恢复) | 中 | 高(可丢包恢复) | 中 |
| 元数据丰富度 | 高 | 低 | 低 | 极高 |
| 典型场景 | 点播、短视频 | 直播、RTMP | 数字电视、HLS | 本地收藏、多轨 |
核心结论:没有「最好」的封装格式,只有「最合适」的场景。选错了,轻则播放卡顿,重则整个流媒体系统崩掉。
MP4:点播之王
MP4 是基于 ISO Base Media File Format(ISOBMFF)的容器。它的核心结构是「Box」—— 一种树形嵌套的数据块。每个 Box 都有 type、size 和 data。
我个人习惯把 MP4 的 Box 结构想象成一个文件系统:
- ftyp box:文件类型标识,告诉播放器「我是 MP4」
- moov box:元数据核心,包含所有轨道信息、时长、编码参数
- mdat box:实际的音视频数据
- moof box:分段索引(用于 fMP4)
moov box 与元数据
这里有个关键点:moov box 的位置决定了 MP4 的流式播放体验。
正常情况下,moov box 放在文件末尾。播放器必须先下载整个文件才能读取 moov,然后才能开始播放。对于点播文件,这没问题。但对于流媒体,用户得等整个文件下载完才能看 —— 这谁受得了?
我的经验:做点播系统时,一定要用 qt-faststart 或 MP4Box 把 moov box 移到文件头部。我见过一个项目,上线后发现用户首屏加载要 10 秒,排查半天发现就是 moov 在文件尾。移到头部后,首屏降到 1 秒以内。
moov box 内部包含:
- trak box:每个轨道一个,包含视频、音频、字幕等
- mdia box:媒体信息,包含采样率、分辨率、编码器
- stbl box:采样表,包含时间戳、偏移量、关键帧位置
stbl box 里的 stss box(同步采样表)特别重要。它记录了所有关键帧(I帧)的位置。播放器跳转时,就是通过 stss 找到最近的关键帧,然后从那里开始解码。
# 用 ffprobe 查看 MP4 的 box 结构
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4
# 查看 moov box 位置
ffmpeg -i input.mp4 -v trace 2>&1 | grep -i "moov"
# 将 moov 移到文件头
qt-faststart input.mp4 output.mp4
FLV:直播老将
FLV 是 Adobe 搞出来的容器,结构比 MP4 简单得多。它由文件头 + 一系列 Tag 组成。每个 Tag 要么是视频、要么是音频、要么是脚本数据。
FLV 没有全局索引。播放器必须从头到尾顺序解析。这意味着:
- 优点:天然支持流式,边下载边播放
- 缺点:跳转困难,必须从最近的关键帧开始解析
我记得早期做 RTMP 直播时,FLV 是唯一的选择。它的 Tag 结构非常紧凑,延迟可以做到 1-2 秒。但 FLV 的元数据能力很弱,只能通过 onMetaData 脚本传递一些基本信息(时长、宽度、高度)。
注意:FLV 不支持 HEVC(H.265)和 Opus 编码。如果你要用这些编码,别选 FLV。我见过有人硬把 HEVC 塞进 FLV,结果大部分播放器都不认。
TS:广播级传输
MPEG-TS(Transport Stream)是为数字电视设计的。它的核心特点是 固定长度包(188 字节)。每个包都有一个同步字节(0x47),即使数据损坏,播放器也能快速找到下一个同步点继续播放。
TS 没有全局索引。它的索引是通过 PAT(节目关联表) 和 PMT(节目映射表) 实现的。PAT 告诉播放器「有哪些节目」,PMT 告诉播放器「每个节目有哪些音视频流」。
我做过一个 DVB 项目,TS 流的容错性让我印象深刻。当时网络丢包率高达 5%,但 TS 流依然能播放,只是偶尔出现马赛克。换成 MP4 的话,早就崩了。
分段与索引
TS 的索引机制和 MP4 完全不同。它不依赖文件级别的索引,而是依赖 时间戳(PTS/DTS) 和 分段。
在 HLS 中,TS 被切成一个个小片段(通常 2-10 秒)。每个片段独立可播放。播放器通过 M3U8 播放列表来索引这些片段。
# 生成 HLS 分段的 TS 流
ffmpeg -i input.mp4 -c copy -hls_time 6 -hls_list_size 0 output.m3u8
# 查看 TS 流的 PAT/PMT
ffprobe -v quiet -show_packets -show_data output.ts | grep -A 10 "PAT\|PMT"
关键点:TS 的「分段」思想后来被 fMP4(Fragmented MP4)借鉴了。fMP4 用 moof + mdat 替代了传统的 moov + mdat,实现了类似 TS 的分段流式能力。
MKV:万能容器
MKV(Matroska)是基于 EBML(Extensible Binary Meta Language)的容器。它的结构比 MP4 更灵活,支持几乎所有的编码格式、无限多的轨道、章节、附件、字幕。
MKV 的索引机制是 SeekHead 和 Cues。SeekHead 指向文件中的关键元素位置,Cues 提供关键帧的时间戳索引。播放器通过 Cues 实现快速跳转。
我个人很喜欢 MKV 的元数据能力。你可以给每个轨道加标题、语言、编码参数,甚至可以内嵌字体和图片。但 MKV 的缺点也很明显:
- 流式支持差:SeekHead 和 Cues 通常放在文件末尾,需要先下载才能跳转
- 解码复杂度高:EBML 的解析比 ISOBMFF 慢
- 浏览器支持差:原生 HTML5 播放器不支持 MKV
我的建议:MKV 适合本地收藏和离线播放。如果你要做流媒体,别用 MKV。我曾经试过把 MKV 直接丢给 HLS 转码器,结果转码器直接崩溃 —— 因为 MKV 的轨道结构太复杂了。
分段与索引:流媒体的核心
不管是 MP4、FLV 还是 TS,分段和索引都是流媒体系统的核心。没有索引,播放器就不知道「从哪里开始」;没有分段,播放器就不知道「从哪里切换」。
下面这张图展示了三种容器的索引结构对比:
如何选择?
嗯,这里我直接给结论:
- 点播场景:选 MP4,记得把 moov 移到文件头
- 低延迟直播:选 FLV(RTMP)或 TS(UDP)
- HLS 直播/点播:选 TS 或 fMP4(取决于客户端兼容性)
- 本地收藏/多轨:选 MKV
- 浏览器播放:只考虑 MP4(H.264 + AAC)或 fMP4(MSE)
避坑指南:我曾经在一个项目中同时用了 MP4 和 FLV,结果发现 CDN 对 FLV 的缓存支持很差。后来全部切成了 fMP4 + HLS,问题才解决。所以,选容器时一定要考虑整个链路 —— 编码、封装、传输、缓存、解码,缺一不可。