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的骨架结构。

关键认知:MP4文件 = Box树 + 媒体数据(通常位于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时,必须遵循以下流程:

  1. 读取前4字节得到size,读取后4字节得到type。
  2. 若size == 1,则继续读取8字节的largesize作为实际大小;若size == 0,表示该Box延伸至文件末尾(通常只用于mdat)。
  3. 根据type判断是容器Box(如moov)还是叶Box(如mvhd)。容器Box内部继续按相同逻辑解析子Box。
  4. 跳过size指定的字节数,进入下一个兄弟Box。

这种“先读头、再按大小跳转”的机制,使得解析器可以快速跳过不关心的Box,也允许在文件中间插入或删除Box而不破坏整体结构。

MP4文件Box树结构示意 MP4文件 ftyp 文件类型与兼容性 major_brand, compatible free 可跳过 mdat 媒体数据(视频/音频帧) 无内部结构,由moov索引 moov 元数据容器 mvhd 时长、时间尺度 trak (视频) tkhd mdia mdhd hdlr minf → stbl trak (音频) tkhd mdia minf → stbl 解析顺序:ftyp → moov(获取索引)→ mdat(按偏移读取帧数据)

文件类型(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流(用于后续编码分析或转封装),解析步骤如下:

  1. 读取ftyp,确认文件为MP4且兼容avc1。
  2. 定位moov,遍历其子Box找到视频轨的trak。
  3. 进入mdia → hdlr,确认轨道类型为vide。
  4. 进入minf → stbl → stsd,查看avc1条目中的avcC(AVCDecoderConfigurationRecord),获取SPS/PPS。
  5. 读取stts、stsc、stsz、stco,建立每个样本的偏移与大小列表。
  6. 根据偏移从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类型。
复盘提醒:在编写解析器时,务必先实现一个通用的“Box遍历器”,它只负责按size跳转并返回Box类型和内容范围,再在此之上实现具体Box的解析。这样可以避免因某个未知Box导致整个解析流程中断。