封装格式深入:MP4文件结构(box/atom)、FLV与TS格式分析、音视频交错与时间线管理
封装格式,说白了就是音视频数据的「集装箱」。你想想看,摄像头采集的原始视频流、麦克风录的PCM音频,这些裸数据直接存起来?那文件体积大到离谱,播放器也不知道怎么读。所以我们需要一个标准化的容器,把编码后的视频帧、音频帧、字幕、元数据统统装进去,还要告诉播放器「第几秒该显示哪一帧」。
我个人习惯把封装格式比作一个图书馆的管理系统。MP4、FLV、TS就是三种不同的书架排列方式。今天我们就来拆开看看,这些书架内部到底是怎么设计的。
MP4文件结构:Box与Atom的嵌套世界
MP4是基于ISO Base Media File Format(ISOBMFF)的。它的基本单元叫Box(也叫Atom)。每个Box都有一个类型标识(比如ftyp、moov、mdat),以及长度信息。Box可以嵌套Box,就像俄罗斯套娃。
我在项目中遇到过最头疼的问题,就是MP4文件损坏后,播放器直接崩溃。后来我明白了,MP4的索引信息全在moov box里,如果moov丢了,整个文件就废了。所以很多流媒体场景会把moov box挪到文件头部,这叫「fast start」优化。
- ftyp:文件类型标识,告诉播放器这是MP4还是M4A
- moov:元数据容器,包含轨道信息、时长、采样率等
- mdat:真正的音视频数据,按帧存放
- moof:分片元数据,用于流式传输(fMP4)
来看一个典型的MP4文件结构示意图:
mp4box -info input.mp4 可以查看所有Box的层级结构。我调试时经常用这个命令来确认moov是否在文件头部。
FLV格式分析:流媒体的老将
FLV是Adobe家的格式,结构比MP4简单得多。它由文件头(FLV Header)和一系列Tag组成。每个Tag要么是视频数据,要么是音频数据,要么是脚本元数据。
FLV最大的特点是「边下边播」。它不需要像MP4那样先读完moov才能播放。所以早期的直播平台几乎清一色用FLV。我记得2015年做直播项目时,RTMP推流底层就是封装成FLV Tag再发送的。
| FLV组件 | 大小 | 说明 |
|---|---|---|
| FLV Header | 9字节 | 签名"FLV" + 版本 + 流类型标志 |
| PreviousTagSize | 4字节 | 前一个Tag的大小(第一个为0) |
| Tag | 可变 | Tag类型(18=音频/9=视频/12=脚本)+ 数据 |
FLV的Tag结构很紧凑。每个Tag头部11字节,包含类型、数据大小、时间戳、流ID。时间戳是相对时间,单位毫秒。播放器就是靠这个时间戳来同步音视频的。
TS格式分析:广播电视的基石
TS(Transport Stream)是MPEG-2标准的一部分,主要用于数字电视广播。它的核心是188字节的固定长度包。每个包都有一个同步字节(0x47),方便接收端快速定位。
TS格式的容错性极强。即使丢了一两个包,播放器也能从下一个同步字节重新同步。这在卫星信号不稳定时特别重要。我做过一个DVB-T的接收项目,TS流的PID过滤机制让我印象深刻——每个节目(频道)由一组PID标识,接收端只提取需要的PID即可。
TS流的层次结构是这样的:
- TS包:188字节,包含同步头、PID、适配字段、有效载荷
- PES包:打包的基本流,承载视频或音频帧
- PSI/SI:节目特定信息,告诉接收端如何解析
TS流的音视频交错方式很巧妙。视频帧可能被拆成多个TS包,音频帧则穿插其间。播放器通过PCR(节目时钟参考)来恢复系统时钟,确保音画同步。
音视频交错与时间线管理
这是封装格式最核心的设计之一。你想想看,视频帧和音频帧是独立编码的,但播放时必须同步。封装格式要解决的就是「谁先谁后」以及「什么时候该放谁」的问题。
MP4的做法是:在mdat里按时间顺序交错存放音视频帧。比如:视频帧1、音频帧1、视频帧2、音频帧2……这样播放器顺序读取就能同时解码音视频。交错间隔(Interleave)可以调整,间隔越小,同步越精确,但文件碎片化越严重。
FLV的做法类似,每个Tag自带时间戳,播放器根据时间戳排序播放。TS则更复杂,它通过PTS(显示时间戳)和DTS(解码时间戳)来管理时间线。PTS告诉播放器「什么时候显示」,DTS告诉解码器「什么时候解码」。对于B帧来说,解码顺序和显示顺序是不同的。
- PTS:显示时间戳,单位毫秒或90kHz时钟
- DTS:解码时间戳,通常与PTS相同(无B帧时)
- CTS:PTS - DTS,表示解码到显示的延迟
- 时间基:MP4用timescale,FLV用毫秒,TS用27MHz时钟
我在做播放器开发时,最常遇到的问题就是音画不同步。排查思路很简单:先看PTS是否连续递增,再看音视频PTS差值是否在合理范围内(通常音频领先视频不超过100ms)。如果PTS乱跳,多半是封装时时间戳计算错了。
ffprobe -show_packets input.mp4 | grep pts 可以快速查看所有包的PTS值。我习惯用这个命令来验证时间线是否正确。
最后说一句,封装格式的选择取决于应用场景。点播用MP4,直播用FLV或TS,广播用TS。没有银弹,只有最合适的。理解它们的内部结构,你才能在遇到问题时快速定位——是编码问题、封装问题,还是播放器问题。
公众号:蓝海资料掘金营,微信deep3321