封装格式详解: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 box 索引 ftyp moov (stbl → stss) mdat (音视频数据) FLV: 顺序 Tag 流 FLV Header Tag #1 (Video Keyframe) Tag #2 (Audio) Tag #3 (Video P-frame) TS: 固定包 + PAT/PMT PAT (节目关联表) PMT (节目映射表) 188B 包 #1 (PES Video) 188B 包 #2 (PES Audio) MP4 依赖 moov 中的 stss 表实现跳转;FLV 无索引,只能顺序播放;TS 通过 PAT/PMT 和分段实现流式 分段索引对比 MP4 (fMP4): moof + mdat 分段,每个分段独立索引 FLV: 无分段概念,整个文件一个流 TS (HLS): 2-10秒分段,M3U8 播放列表索引

如何选择?

嗯,这里我直接给结论:

  • 点播场景:选 MP4,记得把 moov 移到文件头
  • 低延迟直播:选 FLV(RTMP)或 TS(UDP)
  • HLS 直播/点播:选 TS 或 fMP4(取决于客户端兼容性)
  • 本地收藏/多轨:选 MKV
  • 浏览器播放:只考虑 MP4(H.264 + AAC)或 fMP4(MSE)

避坑指南:我曾经在一个项目中同时用了 MP4 和 FLV,结果发现 CDN 对 FLV 的缓存支持很差。后来全部切成了 fMP4 + HLS,问题才解决。所以,选容器时一定要考虑整个链路 —— 编码、封装、传输、缓存、解码,缺一不可。