4、容器格式探秘:封装格式原理、常见格式对比(MP4/MKV/TS/FLV)、muxer与demuxer参数

说到封装格式,我经常被问到:「编码和封装到底有啥区别?」

打个比方你就明白了。编码是「怎么把肉做成牛排」——H.264、AAC 这些是烹饪方法。封装是「用什么盘子端上来」——MP4、MKV 就是不同的盘子。你煎了块顶级和牛,结果拿个漏勺端上来,那也白搭。

这一章,我们就来聊聊这些「盘子」的讲究。

4.1 封装格式的核心原理

封装格式,说白了就是一套打包规则。它把视频流、音频流、字幕、元数据(metadata)这些东西,按特定结构塞进一个文件里。

一个典型的封装文件包含三个部分:

  • 容器头(Header):文件类型、时长、编码信息、索引表。
  • 媒体数据(Media Data):实际的视频帧、音频采样,按时间顺序排列。
  • 元数据(Metadata):封面、章节信息、标签(Title、Artist 等)。

为什么需要封装?因为播放器需要知道「第 10 秒的视频数据在哪」。没有索引表,播放器就得从头扫到尾,那拖动进度条就成噩梦了。

核心要点:封装格式决定了文件的兼容性、功能丰富度和流式传输能力。选错容器,轻则播放器不认,重则直播卡顿。

下面这张图,是我画的一个封装结构示意,帮你直观理解数据是怎么组织的:

封装格式内部结构示意 容器头(Header) 文件类型 | 时长 | 编码信息 | 索引表偏移 媒体数据(Media Data) 视频帧 1 (I) 视频帧 2 (P) 视频帧 3 (B) 音频帧 1 音频帧 2 字幕 1 关键帧索引 元数据(Metadata) 封面 | 章节 | 标签(Title / Artist / Album)

4.2 常见格式对比:MP4 / MKV / TS / FLV

我这些年经手过的项目,基本就这四种格式打转。每个都有它的脾气。

特性 MP4 MKV TS FLV
全称 MPEG-4 Part 14 Matroska MPEG Transport Stream Flash Video
兼容性 极高(浏览器、手机、电视) 中高(需播放器支持) 中(广电、直播设备) 中(Flash 已淘汰)
流式传输 支持(需 moov 前置) 不支持原生流式 原生支持(直播标准) 支持(RTMP 协议)
字幕支持 有限(仅 timed text) 极强(ASS/SSA/SRT 等) 有限(DVB 字幕) 不支持
多音轨 支持 支持 支持 有限
容错性 低(损坏后难恢复) 中(可部分恢复) 极高(丢包仍可播放) 低
典型场景 点播、移动端、Web 高清收藏、多轨封装 数字电视、HLS 切片 老旧直播、RTMP 推流

我的经验:如果你做的是 Web 点播,无脑选 MP4。但记得把 moov box 挪到文件头,不然用户得等整个文件下载完才能播放。用 -movflags +faststart 就能搞定。

4.3 深入理解 MP4:moov box 与 faststart

MP4 的结构基于「box」的概念。每个 box 有类型、大小、数据。最关键的是 moov box——它存着整个视频的索引表。

默认情况下,FFmpeg 把 moov 放在文件末尾。这意味着播放器必须下载完整个文件,才能知道「第 5 分钟的视频块在哪」。你想想看,一个 2GB 的电影,用户得等多久?

解决方案就是 -movflags +faststart:

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

这个参数把 moov box 挪到文件开头。播放器拿到前几 KB 就能开始拖动了。

注意:如果你用 -c copy 做流式拷贝,记得先确认源文件的 moov 位置。我曾经遇到一个项目,直播录制的 MP4 没有 moov,结果播放器直接崩溃。后来加了个 -movflags +faststart 重新 remux 才解决。

4.4 MKV:为什么它适合做收藏格式

MKV 是开源社区的心头好。它的设计哲学就是「什么都能装」。H.264、H.265、VP9、AV1,甚至 VC-1 都能塞进去。音频更是通吃:AAC、MP3、FLAC、DTS、TrueHD。

我个人习惯用 MKV 做蓝光备份。因为蓝光原盘通常有多条音轨(英语 DTS-HD、日语 AAC、评论音轨),MKV 能完美保留,还能带上 ASS 特效字幕。

一个典型的 MKV 封装命令:

ffmpeg -i video.h264 -i audio.flac -i subtitles.ass \
       -c copy -map 0 -map 1 -map 2 \
       output.mkv

这里 -map 是流映射,0 是视频,1 是音频,2 是字幕。不加 map 的话,FFmpeg 会按默认规则选流,容易丢东西。

避坑指南:MKV 虽然强大,但浏览器原生不支持。如果你做 Web 播放,别用 MKV。我见过有人把 MKV 直接丢到 <video> 标签里,结果 Chrome 直接报错。

4.5 TS:直播场景的硬通货

TS(Transport Stream)是广电行业的老兵。它的设计目标是在有损信道(比如卫星信号、有线电视)上稳定传输数据。所以 TS 的包很小——188 字节一个包,每个包都有同步头。

为什么直播用 TS?因为容错。如果网络丢了一个包,TS 流不会整个崩溃,后面的包还能继续解析。MP4 就不行,丢一个 moov 就全废了。

HLS(HTTP Live Streaming)就是基于 TS 的。Apple 把 TS 切片成一个个小文件(.ts),然后用一个 m3u8 索引文件管理。FFmpeg 生成 HLS 的典型命令:

ffmpeg -i input.mp4 -c:v libx264 -c:a aac \
       -f hls -hls_time 10 -hls_list_size 0 \
       output.m3u8

-hls_time 10 表示每个切片 10 秒。-hls_list_size 0 表示保留所有切片(不限制列表长度)。

小技巧:如果你做低延迟直播,可以把切片时长降到 2-3 秒。但切片越小,服务器压力越大。我一般取 4 秒作为平衡点。

4.6 FLV:老古董但还没死

FLV 是 Flash 时代的产物。按理说 Flash 都淘汰了,FLV 也该进坟墓。但 RTMP 协议还在用,而 RTMP 的默认封装就是 FLV。

为什么?因为 FLV 结构简单,头部固定,非常适合流式写入。你想想看,直播推流时,编码器一边编码一边把数据塞进 FLV,播放器一边接收一边解码。这种「边写边读」的能力,MP4 做不到(除非用 fragmented MP4)。

FFmpeg 推 RTMP 流时,默认就是 FLV 封装:

ffmpeg -i input.mp4 -c copy -f flv rtmp://server/live/stream

注意 -f flv 必须指定。如果不加,FFmpeg 会尝试根据输出后缀推断格式,但 RTMP URL 没有后缀,所以得手动指定。

警告:FLV 不支持 HEVC(H.265)和 AAC 的某些高级配置。如果你推 HEVC 视频,FLV 会直接报错。这时候得用 fragmented MP4(fMP4)或者 TS 替代。

4.7 Muxer 与 Demuxer 参数详解

Muxer(复用器)负责把多个流合成一个容器。Demuxer(解复用器)负责从容器里拆出各个流。FFmpeg 里,muxer 和 demuxer 都有各自的参数。

4.7.1 常用 Muxer 参数

参数 适用格式 说明
-movflags +faststart MP4 将 moov box 移到文件头,支持流式播放
-movflags +frag_keyframe+empty_moov MP4 生成 fragmented MP4,适合直播录制
-hls_time HLS 设置每个 TS 切片的时长(秒)
-hls_list_size HLS 限制 m3u8 索引中的切片数量(0 表示不限)
-hls_segment_filename HLS 自定义切片文件名模板
-metadata 通用 添加元数据,如 -metadata title="My Video"
-map_metadata 通用 从源文件复制元数据到输出

4.7.2 常用 Demuxer 参数

参数 适用格式 说明
-ss 通用 跳转到指定时间点开始解码(精确 seek 依赖关键帧)
-t 通用 限制解码时长
-to 通用 解码到指定时间点结束
-analyzeduration 通用 设置分析时长,用于探测流信息(默认 5 秒)
-probesize 通用 设置探测数据量(字节),影响流识别准确性
-correct_ts_overflow MP4/TS 修正时间戳溢出(直播流常见问题)

实战经验:有一次我处理一个损坏的 MP4 文件,用 -analyzeduration 100M -probesize 100M 强制 FFmpeg 多读一些数据,才把流信息完整探测出来。默认值太小,遇到大文件或损坏文件容易报错。

4.8 格式转换实战:MP4 转 MKV 保留多轨

假设你有一个 MP4 文件,里面有一条视频流、两条音频流(英语、日语)、一条字幕流。你想转成 MKV 保留所有轨道。

ffmpeg -i input.mp4 \
       -map 0:v -c:v copy \
       -map 0:a:0 -c:a:0 copy \
       -map 0:a:1 -c:a:1 copy \
       -map 0:s -c:s copy \
       -metadata:s:a:0 language=eng \
       -metadata:s:a:1 language=jpn \
       -metadata:s:s:0 language=chi \
       output.mkv

这里 -map 0:a:0 表示「源文件的第 0 个输入的第 0 条音频流」。不加这个,FFmpeg 默认只选一条音频。

建议:转换前先用 ffprobe input.mp4 看看流信息。我每次都会先跑一下 ffprobe,确认轨道数量和编码格式,避免转出来发现少东西。

4.9 本章小结

封装格式这东西,选对了事半功倍,选错了处处踩坑。我的建议是:

  • 点播分发:用 MP4,加 +faststart。
  • 高清收藏:用 MKV,保留多轨和字幕。
  • 直播推流:用 TS 或 fragmented MP4。
  • RTMP 推流:用 FLV,但注意编码限制。