MP4格式分析 #
MP4(MPEG-4 Part 14)是一种常见的多媒体容器格式,它是在“ISO/IEC 14496-14”标准文件中定义的,属于MPEG-4的一部分,是“ISO/IEC 14496-12(MPEG-4 Part 12 ISO base media file format)”标准中所定义的媒体格式的一种实现,后者定义了一种通用的媒体文件结构标准。MP4是一种描述较为全面的容器格式,被认为可以在其中嵌入任何形式的数据,各种编码的视频、音频等都不在话下,不过我们常见的大部分的MP4文件存放的AVC(H.264)或MPEG-4(Part 2)编码的视频和AAC编码的音频。MP4格式的官方文件后缀名是“.mp4”,还有其他的以mp4为基础进行的扩展或者是缩水版本的格式,包括:M4V, 3GP, F4V等。
mp4是由一个个“box”组成的,大box中存放小box,一级嵌套一级来存放媒体信息。box的基本结构是:

size指明了整个box所占用的大小,包括header部分。如果box很大(例如存放具体视频数据的mdat box),超过了uint32的最大数值,size就被设置为1,并用接下来的8位uint64来存放大小。
大部分mp4文件没有那么多的box类型,下图就是一个简化了的,常见的mp4文件结构:

在部分box中,还存在version、flags字段,这样的box叫做Full Box。当box body中嵌套其他box时,这样的box叫做container box。

-
ftyp
在文件的开始位置,描述的文件的版本、兼容协议等
16进制:66 74 79 70

-
moov
这个box中不包含具体媒体数据,但包含本文件中所有媒体数据的宏观描述信息,moov box下有mvhd和trak box。
>>mvhd中记录了创建时间、修改时间、时间度量标尺、可播放时长等信息。
>>trak中的一系列子box描述了每个媒体轨道的具体信息。
16进制:6D 6F 6F 76

-
mdat
实际媒体数据
16进制:6D 64 61 74

一般来说,解析媒体文件,最关心的部分是视频文件的宽高、时长、码率、编码格式、帧列表、关键帧列表,以及所对应的时戳和在文件中的位置,这些信息,在mp4中,是以特定的算法分开存放在stbl box下属的几个box中的,需要解析stbl下面所有的box,来还原媒体信息。下表是对于以上几个重要的box存放信息的说明:

音频数据的定位与读取流程 #
MP4 InspectorMP4解析工具
假设我们有一个包含 AAC 音频的 MP4 文件,读取音频数据的详细步骤如下:
步骤 1:找到 moov盒子
- 通常
moov在文件末尾(利于网络流媒体),也可能在文件开头。程序会先定位到它。
步骤 2:在 moov中定位音频轨道
- 在
moov盒子里,会有一个trak盒子。通常第一个trak是视频轨道,第二个就是音频轨道。 - 通过检查
trak盒子里的mdia.hdlr盒子中的HandlerType,可以确认这是音频轨道(其值通常为soun)。



步骤 3:获取音频格式信息
-
在音频的
trak盒子中,找到mdia.minf.stbl.stsd盒子。这个盒子描述了音频的采样率、声道数、采样位深、编码类型等。
00 00 00 6A - 盒子大小 (106 bytes) 73 74 73 64 - 盒子类型 'stsd' 00 00 00 00 - 版本和标志 (version=0, flags=0) 00 00 00 01 - 条目数量 (1个条目) -
对于 AAC 音频,这里会有一个
mp4a盒子,里面包含esds(Elementary Stream Descriptor)描述符,其中包含了 AAC 的详细配置信息(例如 Audio Object Type,采样率索引,声道配置索引),解码器需要这些信息来初始化。

00 00 00 5A - 条目大小 (90 bytes) 6D 70 34 61 - 格式类型 'mp4a' (MPEG-4音频) 00 00 00 00 - 保留 (6 bytes) 00 00 00 00 00 00 00 01 - 数据引用索引 (data reference index) 00 00 00 00 - 保留 (8 bytes) 00 00 00 00 00 01 - 编码器版本 (version=1) 00 10 - 编码器修订级别 (revision level=16) FF FE - 编码器厂商 (0xFFFE表示未注册) 00 00 - 声道数 (placeholder, 实际在esds中) 3E 80 00 00 - 音频采样率 (16000 Hz)
00 00 00 36 - 下一个盒子大小 (54 bytes) 65 73 64 73 - 盒子类型 'esds' (Elementary Stream Descriptor) 00 00 00 00 - 版本和标志 (version=0, flags=0) 03 - 标签类型 (ES_DescrTag) 80 80 80 25 - 长度 (37 bytes) 00 02 - ES_ID (2) 00 - 流优先级 (0) 04 - 标签类型 (DecoderConfigDescrTag) 80 80 80 17 - 长度 (23 bytes) 40 - 对象类型 (Audio ISO/IEC 14496-3) 15 - 流类型 (AudioStream) 00 20 00 00 - 缓冲区大小 (8192 bytes) B8 19 00 00 - 最大比特率 (6600 bps) B8 19 00 00 - 平均比特率 (6600 bps) 05 - 标签类型 (DecSpecificInfoTag) 80 80 80 05 - 长度 (5 bytes) 14 08 00 00 00 - 音频特定配置 (AAC LC, 16kHz, 单声道) 06 - 标签类型 (SLConfigDescrTag) 80 80 80 01 - 长度 (1 byte) 02 - 预定义SL配置
步骤 4:找到音频数据在 mdat中的位置索引(最关键的一步)
-
这是通过 “时间戳 -> 物理位置”的映射表 实现的。这些表在
moov盒子的trak.mdia.minf.stbl下:stts:解码时间戳表。定义了每个样本(sample)的持续时间,用于计算显示时间。
00 00 00 18 - 盒子大小 (24 bytes) 73 74 74 73 - 盒子类型 'stts' 00 00 00 00 - 版本和标志 (version=0, flags=0) 00 00 00 01 - 条目数量 (1个条目) 00 00 03 1D - 样本数量 (797个样本) 00 00 04 00 - 样本时间增量 (1024时间单位)在MP4文件中,时间单位的具体含义由
mdhd(Media Header)盒子中的timescale字段决定。要计算每个样本的实际持续时间(秒),需要使用公式:样本持续时间(秒) = 样本时间增量 / timescale -
stsc:样本-块映射表。定义了数据块(chunk)与样本(sample)的对应关系。一个块是mdat中的一段连续数据。
00 00 00 1C - 盒子大小 (28 bytes) 73 74 73 63 - 盒子类型 'stsc' 00 00 00 00 - 版本和标志 (version=0, flags=0) 00 00 00 01 - 条目数量 (1个条目) 00 00 00 01 - 第一个块 (first chunk): 1 00 00 00 01 - 每个块的样本数 (samples per chunk): 1 00 00 00 01 - 样本描述索引 (sample description index): 1 -
stsz:样本大小表。定义了每个样本(sample)占多少字节。
00 00 0C 88 - 盒子大小 (3208 bytes) 73 74 73 7A - 盒子类型 'stsz' 00 00 00 00 - 版本和标志 (version=0, flags=0) 00 00 00 00 - 样本统一大小 (0表示样本大小不统一) 00 00 03 1D - 样本数量 (797个样本)接下来的数据是797个样本的大小值(每个4字节):
00 00 01 78 - 样本1大小: 376 bytes 00 00 01 73 - 样本2大小: 371 bytes 00 00 01 77 - 样本3大小: 375 bytes 00 00 01 79 - 样本4大小: 377 bytes 00 00 01 83 - 样本5大小: 387 bytes 00 00 01 82 - 样本6大小: 386 bytes 00 00 01 76 - 样本7大小: 374 bytes ... -
stco或co64:块偏移量表。定义了每个数据块(chunk)在mdat盒子(或整个文件)中的起始字节位置。
00 00 0C 84 - 盒子大小 (3204 bytes) 73 74 63 6F - 盒子类型 'stco' 00 00 00 00 - 版本和标志 (version=0, flags=0) 00 00 03 1D - 条目数量 (797个条目)接下来的数据是797个块的偏移量(每个4字节):
步骤 5:根据索引读取 mdat中的音频数据
现在,程序可以像查数据库一样,根据索引表读出音频数据:
- 定位块:假设要读取第 N 个音频样本。程序先查
stsc表,找到这个样本属于哪个数据块(比如第 M 个块)。 - 计算偏移:查
stco表,得到第 M 个块在文件中的起始偏移量(比如offset_M)。 - 计算样本在块内的位置:查
stsz表,累加第 M 个块内、目标样本之前的所有样本的大小,得到块内偏移offset_in_chunk。 - 读取数据:最终,这个音频样本的物理位置是
offset_M + offset_in_chunk,大小是stsz表中记录的对应值。程序会从文件的这个位置读取相应字节的数据。 - 解码播放:将这些压缩的音频数据(例如一个 AAC 帧)送入 AAC 解码器,解码成 PCM 数据,然后交给声卡播放。