MP4格式分析

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

5分钟入门MP4文件格式

一般来说,解析媒体文件,最关心的部分是视频文件的宽高、时长、码率、编码格式、帧列表、关键帧列表,以及所对应的时戳和在文件中的位置,这些信息,在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
    ...
    
  • stcoco64:块偏移量表。定义了每个数据块(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中的音频数据

现在,程序可以像查数据库一样,根据索引表读出音频数据:

  1. 定位块:假设要读取第 N 个音频样本。程序先查 stsc表,找到这个样本属于哪个数据块(比如第 M 个块)。
  2. 计算偏移:查 stco表,得到第 M 个块在文件中的起始偏移量(比如 offset_M)。
  3. 计算样本在块内的位置:查 stsz表,累加第 M 个块内、目标样本之前的所有样本的大小,得到块内偏移 offset_in_chunk
  4. 读取数据:最终,这个音频样本的物理位置是 offset_M + offset_in_chunk,大小是 stsz表中记录的对应值。程序会从文件的这个位置读取相应字节的数据。
  5. 解码播放:将这些压缩的音频数据(例如一个 AAC 帧)送入 AAC 解码器,解码成 PCM 数据,然后交给声卡播放。