MP4基础概念:ISO BMFF、Box结构、文件类型与主要Box
核心原理:MP4的底层标准——ISO BMFF
MP4并非一种“从零发明”的格式,而是建立在 ISO/IEC 14496-12(ISO Base Media File Format,简称ISO BMFF) 之上的具体封装实现。ISO BMFF定义了一种以“Box”(或称“Atom”)为基本单位的层级化存储模型。理解MP4,本质上就是理解这一套Box的嵌套规则与字段语义。
ISO BMFF采用 面向对象的树形结构:一个文件由若干个顶层Box组成,每个Box内部又可以包含子Box,子Box内部还可以继续嵌套。每个Box都有明确的类型标识(通常为4个字符的ASCII码,如moov、mdat),以及记录自身大小和类型的头部字段。这种设计使得播放器可以“随机访问”文件中的特定部分,而不必像读取AVI那样从头扫描到尾。
MP4文件与ISO BMFF的关系可以概括为:MP4是ISO BMFF的一个“应用规范”,它规定了视频轨、音频轨、字幕轨等媒体数据如何映射到Box中,并额外定义了部分扩展Box(如uuid用于扩展)。因此,凡是支持ISO BMFF的解析器,理论上都能解析MP4的骨架结构。
mdat中)+ 元数据(通常位于moov中)。元数据与媒体数据分离是MP4高效编辑与流式播放的基础。
Box结构:一切的基础单元
每一个Box都遵循统一的二进制布局,其头部结构如下:
Box头部(至少8字节):
| 字段名 | 大小(字节) | 说明 |
|------------|------------|----------------------------------------|
| size | 4 | 整个Box的大小(含头部和内容),若为1则用largesize |
| type | 4 | Box类型,如'moov'、'mdat'、'trak'等 |
| [largesize]| 8 | 当size==1时出现,表示64位超大Box大小 |
| [usertype] | 16 | 当type=='uuid'时出现,扩展类型标识 |
解析Box时,必须遵循以下流程:
- 读取前4字节得到
size,读取后4字节得到type。 - 若
size == 1,则继续读取8字节的largesize作为实际大小;若size == 0,表示该Box延伸至文件末尾(通常只用于mdat)。 - 根据
type判断是容器Box(如moov)还是叶Box(如mvhd)。容器Box内部继续按相同逻辑解析子Box。 - 跳过
size指定的字节数,进入下一个兄弟Box。
这种“先读头、再按大小跳转”的机制,使得解析器可以快速跳过不关心的Box,也允许在文件中间插入或删除Box而不破坏整体结构。
文件类型(ftyp)与品牌兼容
MP4文件的开头通常是ftyp Box,它声明了文件遵循的规范版本与兼容品牌。其内部字段包括:
- major_brand(4字节):主品牌,如
isom(ISO基础媒体)、mp42(MP4 v2)、avc1(H.264视频)、qt(QuickTime兼容)。 - minor_version(4字节):次版本号,通常为0。
- compatible_brands(列表):一组4字节品牌标识,表示该文件兼容的其他规范。例如一个文件可以同时声明
isom、mp42和avc1,以便在不同设备上播放。
解析器通过读取ftyp可以快速判断文件类型,并决定后续解析策略。例如,若major_brand为qt,则可能包含QuickTime特有的扩展Box;若为dash,则可能用于流媒体分片。
主要Box介绍:moov、trak、mdia与stbl
在MP4文件中,moov是最核心的元数据容器,它内部通常包含以下层级:
| Box层级 | 典型子Box | 作用 |
|---|---|---|
moov |
mvhd, trak, udta |
电影级元数据,包含全局时长、时间尺度、轨道列表 |
trak |
tkhd, mdia, edts |
单个轨道(视频/音频/字幕)的属性和引用 |
mdia |
mdhd, hdlr, minf |
媒体信息容器,声明媒体类型(视频/音频)和媒体头 |
minf |
stbl, dinf |
媒体信息细节,包含采样表和数据引用 |
stbl |
stsd, stts, stsc, stsz, stco/co64 |
采样表,记录每个样本的编码类型、时间戳、大小、偏移量 |
其中,stbl是解析媒体数据的“地图”:stsd描述编码格式(如avc1、mp4a),stts记录时间戳增量,stsc描述样本到块的映射,stsz给出每个样本的字节大小,stco或co64则指向每个块在文件中的绝对偏移。播放器正是通过读取这些表,才能从mdat中精确提取出每一帧数据。
案例场景:从MP4中提取H.264视频流
假设我们需要从一个MP4文件中提取裸H.264流(用于后续编码分析或转封装),解析步骤如下:
- 读取
ftyp,确认文件为MP4且兼容avc1。 - 定位
moov,遍历其子Box找到视频轨的trak。 - 进入
mdia → hdlr,确认轨道类型为vide。 - 进入
minf → stbl → stsd,查看avc1条目中的avcC(AVCDecoderConfigurationRecord),获取SPS/PPS。 - 读取
stts、stsc、stsz、stco,建立每个样本的偏移与大小列表。 - 根据偏移从
mdat中读取每个样本,去掉样本中的长度前缀(Length Size Minus One字段决定),拼接SPS/PPS与NALU数据,即可得到Annex-B格式的H.264流。
stsd中的avc1时,注意avcC Box中的lengthSizeMinusOne字段,它决定了每个NALU长度前缀占几个字节(通常为4)。若该值为0,则样本数据直接是NALU流,无需剥离长度前缀。
误区与复盘:常见解析陷阱
- 误区1:认为
mdat一定在moov之后。实际上,mdat可以出现在文件任何位置,甚至可以有多个mdat。解析时必须依赖stco/co64中的偏移量,而不是假设顺序。 - 误区2:忽略
size == 0的Box。某些编码器会将最后一个mdat的size设为0,表示“直到文件末尾”。解析器需要处理这种边界情况。 - 误区3:混淆
stts与ctts。stts记录解码时间戳(DTS)的增量,而ctts(若存在)记录合成时间戳(PTS)与DTS的偏移。对于B帧较多的视频,忽略ctts会导致画面顺序错乱。 - 误区4:未处理64位偏移。当文件超过4GB时,
stco会改用co64(64位偏移)。解析器必须同时支持两种Box类型。