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是由一个个“box”组成的,大box中存放小box,一级嵌套一级来存放媒体信息。一个mp4文件有可能包含非常多的box,在很大程度上增加了解析的复杂性,这个网页上http://mp4ra.org/atoms.html记录了一些当前注册过的box类型。看到这么多box,如果要全部支持,一个个解析,是非常困难的。还好,大部分mp4文件没有那么多的box类型,下图就是一个简化了的,常见的mp4>文件结构:

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

要获取到mp4文件的帧列表,需要一层层解析,然后综合stts stsc stsz stss stco等这几个box的信息,才能还原出帧列表,每一帧的时戳和偏移量。而且,你要照顾可能出现或者可能不出现的那些box。可以看的出来,mp4把帧sample进行了分组,也就是chunk,需要间接的通过chunk来描述帧,这样做的理由是可以压缩存储空间,缩小媒体信息所占用的文件大小。
MP4文件格式中,所有的内容存在一个称为movie的容器中。一个movie可以由多个tracks 组成。每个track就是一个随时间变化的媒体序列,例如,视频帧序列。track 里的每个时间单位是一个sample,它可以是一帧视频,或者音频。sample按照时间顺序排列。注意,一帧音频可以分解成多个音频sample,所以音频一般用sample 作为单位,而不用帧。MP4 文件格式的定义里面,用sample 这个单词表示一个时间帧或者数据单元。每个track会有一个或者多个sample descriptions。track 里面的每个sample通过引用关联到一个sample description。这个sample descriptions 定义了怎样解码这个sample,例如使用的压缩算法。与其他的多媒体文件格式不同的是,MP4 文件格式经常使用几个不同的概念,理解其不同是理解这个文件格式的关键。
有两种比较常见的封装格式,一种是整体封装,一种是分段封装(fmp4)
第一种格式,整个mp4文件的meta数据都在文件头,所有媒体数据为一整体。当文件较大的时候,meta数据就较大。这样对mp4文件本地播放是没有问题。但对于一些视频播放网站而言,用户播放器必须下载全meta数据才能开始播放,这就意味着用户的缓冲时间将因为mp4文件的存储结构而延长。目前一种解决方法是将大的mp4文件切成物理分离的多段,使得每段的meta都比较小,从而在一定程度上减少缓冲时间。
第二种格式,mp4文件被分成多个frag分片,而原来的meta数据大大变小,且没个frag都可以单独索引、传输和播放,这样就可以解决mp4不能流式传输播放的问题。对用户体验比较好,本文主要讲述的还是分段的mp4方式,来应用于H264实时预览。
fmp4增加了一个moof box来描述视频分片信息,文件分为了多个Fragments,每个Fragment中包含moof和mdat,这样的结构符合渐进式播放要求,同时可以用来适配直播等实时预览的场景。

mp4由一个个box组成,box可以嵌套子box,所有的流媒体数据极其描述信息都封装到box,box中的字节序为网络字节序,也就是大端字节序(Big-Endian),封装和解封装的时候需要注意。
Box由header和body组成,其中header统一指明box的大小和类型,body根据类型有不同的意义和格式。标准的box开头的4个字节(32位)为box size,box size值包括box header和box body整个box的大小,如果数据过大超过4个字节能够表征的最大值,则可把box size = 1,则表示这个box的大小为large size,真正的size值要在largesize域上得到,如果size为0,表示该box为文件的最后一个box,文件结尾即为该box结尾,size后面紧跟的32位为box type,一般是4个字符,如“ftyp”、“moov”等,这些box type都是已经预定义好的,分别表示固定的意义,其值为类型的ASCII值,当type是uuid时,代表Box中的数据是用户自定义扩展类型。Data是Box的实际数据,可以是纯数据也可以是更多的子Boxes,其结构图如下:

FullBox是Box的扩展,在Box结构的Header中增加8bits version和24bits flags。
当一个Box的Data中是一系列子Box时,这个Box又可成为Container Box,其结构如下:

如果我们要用代码封装box,则其封装格式如下:
[4bytes box size] [4bytes box type] [8bytes largesize, if size ==1] [contents of the box, if any]
一般的MP4文件需要有ftyp、moov、mdat,它们都是顶级box,不能被其他box嵌套,ftyp标示了MP4文件,必须出现在第一个,moov保存了媒体的基本信息,mdat保存视频和音频数据,后两个顺序不确定,但是一般会把moov放到mdat前面,以便播放器可以快速识别解码信息并播放。moov在mdat之前,可实现流式读取视频数据,适配在线播放场景。

fmp4主要为了适配流式播放场景,其视频流一直在播放和传输,没有终止,因此无法准确的给出媒体的整体播放长度信息,因此增加了一个moof类型的box,来描述当前传输的数据samples信息,其一般在关键帧打包时发送moov,其他帧发送时采用moof+mdat的方式进行打包和传输。

本章节主要根据fmp4封装格式,来对各个box进行详细解释,fmp4的封包示意图如下:

ftyp(File Type) box是fmp4第一个box,用来确定文件的类型,版本及兼容的协议,每个文件有且只有一个,由于要支持流式传输,一般在客户端第一次发起请求时,发送一次带有ftypbox的数据,一般与moov合并发送给客户端,也可定时发送(通过chrome解码验证,可以多次发送此box),其格式如下:
[4字节size][4字节类型(ftyp的ASCII)][4字节major brand][4字节minor version][4字节为单位元素的数组compatible brands]
实例如下(16进制):
00 00 00 18 66 74 79 70 69 73 6F 6D 00 00 00 01 69 73 6F 6D 61 76 63 31
Movie box包含本文件中所有媒体数据的宏观描述信息以及每路媒体轨道的具体信息, fmp4格式需要紧跟在ftyp box之后,moov顶层box中没有具体信息,其信息都记录在其子box中,具体子box可参照:

mvhd(Movie Header Box),为moov的子box,记录整个媒体文件的描述信息,如创建时间、修改时间、时间度量标尺、可播放时长、播放速率、音量等,由于fmp4很多信息放入moof中,这里需要注意的是时间度量,一般H264视频为90000,其格式如下:
[4字节size][4字节type][4字节版本和flags][4字节创建时间][4字节修改时间][4字节timescale][4字节deration][4字节播放速率][2字节音量][10字节预留][36字节 unity matrix][24字节pre_defined][4字节next_track_ID]
示例如下:
00 00 00 6C 6D 76 68 64 00 00 00 00 00 00 00 01 00 00 00 02 00 01 5F 90 00 01 5F 90 00 01 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF
moov中可以存在一个或多个 track,通常包含两个trak box,一个视频索引,一个音频索引,它们之间是相互独立的。每个 track 包含tkhd和mdia子box,trak为顶层box无具体含义,其Data为其子box
tkhd(Track Header) box包含关于媒体流的头信息,如trackid,视频分辨率等信息,其格式如下:
[4字节size][四字节type][4字节版本和flags][4字节创建时间][4字节修改时间][4字节trackid][4字节预留][4字节持续时间][8字节预留][2字节layer][2字节备份分组][2字节音量][2字节预留][36字节 unity matrix][4字节宽][4字节高]
示例及详解如下:
00 00 00 5C 74 6B 68 64 00 00 00 07 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 01 5F 90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 07 80 00 00 04 38 00 00
mdia box包含 track 媒体数据信息的 container box,其子box主要包含视频流长度/创建时间/媒体播放过程/sample的偏移量/解码格式等
Media Header Box,存放视频流创建时间,长度等信息,其格式如下:
[4字节size][4字节type][4字节版本和flags][4字节创建时间][4字节修改时间][4字节timescale][4字节持续时间][2字节语言][2字节pre-define]
示例及详解如下:
00 00 00 20 6D 64 68 64 00 00 00 00 00 00 00 00 00 00 00 00 00 01 5F 90 00 01 5F 90 55 C4 00 00
Handler Reference Box,媒体的播放过程信息,例如video track 将由video handle box来处理,其格式如下:
[4字节size][4字节type][4字节版本和flags][4字节pre-define][4字节handler type][24字节预留][n字节处理器名称]
00 00 00 2D 68 64 6C 72 00 00 00 00 00 00 00 00 76 69 64 65 00 00 00 00 00 00 00 00 00 00 00 00 56 69 64 65 6F 48 61 6E 64 6C 65 72 00
Media Information Box,解释 track 媒体数据的 handler-specific 信息。minf 同样是个 container box,Media Information Box,解释 track 媒体数据的 handler-specific 信息。minf 同样是个 container box,其内部需要关注的内容是 stbl,这也是 moov 中最复杂的部分。
其他几个子box较为简单,简单介绍:
MOOV::TRAK::MDIA::VMHD/SMHD/HMHD/NMHD: Media Information Header Boxes。每种音轨类型都有不同的媒体信息头(对应media handler-type),其只包含版本和flags,fmp4中版本赋值为0,VMHD的flags=1,SMHD的flags=0,格式如下:
[4字节size][4字节type][4字节版本和flags][2字节graphicsmode][2字节opcolor[3]]
VMHD示例如下:
00 00 00 14 76 6D 68 64 00 00 00 01 00 00 00 00 00 00 00 00
MOOV::TRAK::MDIA::MINF::DINF:是一个container box,Data Reference Box 其包含dref box,fmp4给默认赋值即可,是示例如下:
00 00 00 1C 64 72 65 66 00 00 00 00 00 00 00 01 00 00 00 0C 75 72 6C 20 00 00 00 01
Sample Table Box(stbl)是mdia中最主要的部分,存放文件中每个 Sample信息.首先介绍下Sample和trunk的概念,在 MP4文件中,Sample 是一个媒体流的基本单元,例如视频流的一个 Sample 代表实际的nal数据。Chunk是数据存储的基本单位,它是一系列Sample数据的集合,一个 Chunk 中可以包含一个或多的Sample,stbl 用来描述每个Sample 的信息,对mp4来说非常重要,但是对于fmp4,其描述在moof中,这里可以大大简化,stbl消息体是子box,无其他信息
Sample Description Box(stbl-stsd),存放解码必须的描述信息,其也是一个container box,对于H264码流来说其包含avc1子box,stsd消息格式如下:
[4字节size][4字节type][4字节版本和flags][4字节entry_count][entry box]
其中:entry count字段,根据entry的个数,每个entry会有type信息,如“vide”、“sund”等,根据type不同sample description会提供不同的信息,例如对于video track,会有“VisualSampleEntry”类型信息,对于audio track会有“AudioSampleEntry”类型信息。视频的编码类型、宽高、长度,音频的声道、采样等信息都会出现在这个box中。这里以H264为例,其子box为avc1
stsd整体示例及详解:
00 00 00 A9 73 74 73 64 00 00 00 00 00 00 00 01 00 00 00 99 61 76 63 31 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 07 80 04 38 00 48 00 00 00 48 00 00 00 00 00 00 00 01 12 62 69 6E 65 6C 70 72 6F 2E 72 75 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 FF FF 00 00 00 2F 61 76 63 43 01 4D
00 2A FF E1 00 18 67 4D 00 2A 96 35 40 F0 04 4F CB 37 01 01 01 40 00 01 C2 00 00 57 E4 01 01 00 04 68 EE 3C 80 00 00 00 14 62 74 72 74 00 1C 9C 80 00 2D C6 C0 00 2D C6 C0
stsd-avc1 box说明H264视频封装格式为avc1,即是H.264 bitstream without start codes,不带由00000001起始码的数据,这里注意mdat box里封装的H264需要去掉起始码;此box中带有H264编码的信息,具体格式如下:
[4字节size][4字节type][6字节预留][2字节data_reference_index][2字节pre_defined][2字节预留][12字节pre_defined][2字节宽][2字节高][4字节horizresolution][四字节 vertresolution][4字节预留][2字节视频帧数量][Compression name长度][Compression name][Compression name预留位(32-Compression name长度-1)][2字节深度][2字节pre_defined][avcC box][btrt box]
stsd-avc1后会跟avcC box和btrt box两个子box
stsd-avc1-avcC box主要是记录视频编码规格和编码水平、sps/pps原始数据,其格式如下:
[4字节size][4字节type][1字节版本][1字节profile][1字节compat][1字节level][1字节nalu长度头字节数][1字节sps个数][2字节sps长度][sps][1字节pps个数][2字节pps长度][pps]
示例及详解如下:
00 00 00 2F 61 76 63 43 01 4D 00 2A FF E1 00 18 67 4D 00 2A 96 35 40 F0 04 4F CB 37 01 01 01 40 00 01 C2 00 00 57 E4 01 01 00 04 68 EE 3C 80
stsd-avc1-btrt bitrate box描述码率信息,其包含最大码率,平均码率等信息,其格式如下:
[4字节size][4字节type][4字节bufferSizeDB][4字节maxBitrate][4字节avgBitrate]
示例如下,不做过多解释:
00 00 00 14 62 74 72 74 00 1C 9C 80 00 2D C6 C0 00 2D C6 C0
Decoding Time to Sample Box(stbl-stts),存储了sample的duration,描述了sample时序的映射方法,我们通过它可以找到任何时间的sample,“stts”也可包含一个压缩的表来映射时间和sample序号,用其他的表来提供每个sample的长度和指针,表中每个条目提供了在同一个时间偏移量里面连续的sample序号,以及samples的偏移量。递增这些偏移量,就可以建立一个完整的time to sample表。在fmp4中这些信息以moof中为主,这里可不必赋值,只做一个box即可,示例如下:
00 00 00 10 73 74 74 73 00 00 00 00 00 00 00 00
Sample To Chunk Box(stbl-stsc),用chunk组织sample可以方便优化数据获取,一个chunk包含一个或多个sample。“stsc”中用一个表描述了sample与chunk的映射关系,查看这张表就可以找到包含指定sample的thunk,从而找到这个sample,当然每个table entry可能包含一个或者多个chunk。fmp4方式,此box不必赋值,简单封装一个box即可,示例如下:
00 00 00 10 73 74 73 63 00 00 00 00 00 00 00 00
Sample Size Boxes (stbl-stsz),指定了每个sample的size,针对fmp4这里无需赋值,示例如下:
00 00 00 14 73 74 73 7A 00 00 00 00 00 00 00 00 00 00 00 00
Chunk Offset Box(stbl-stco),Chunk的偏移量表,指定了每个chunk在文件中的位置。fmp4方式,此box不必赋值,简单封装一个box即可,示例如下:
00 00 00 10 73 74 63 6F 00 00 00 00 00 00 00 00
Movie Extends Box (mvex)(fMP4专有) mvex是fMP4 的标准盒子。它的作用是告诉解码器这是一个fMP4的文件,具体的 samples信息内容不再放到 trak里面,而是在每一个 moof中
Trac kExtends Box (trex) 是 mvex 的子box,用来给fMP4的sample设置默认值。基格式如下:
[4字节size][4字节type][4字节版本和flags][4字节track_ID][4字节default_sample_description_index][4字节default_sample_duration][4字节default_sample_size][4字节default_sample_flags]
示例及详解如下:
00 00 00 20 74 72 65 78 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 01 00 01
Movie Fragment Box(moof),是 fmp4中数据追加的box,moof 和mdat是成对出现的,这个box是视频分片的描述信息,每个Fragment中包含moof和mdat,这样的结构符合渐进式播放需求。moof是一个顶级box,同时是一个容器box,下面紧跟一个mfhd,自身无内容。
Movie Fragment header Box(mfhd),包含分片序列信息,标识每个track的分片序列,其格式如下:
[4字节size][4字节type][4字节版本和flags][4字节序列号]
示例及详解如下:
00 00 00 10 6D 66 68 64 00 00 00 00 00 00 00 01
Track Fragment Box(traf), 每个分片可能包含一个或多个track分片,每个分片可能包含1个或多个连续的sample,traf主要用来描述这些信息,包含trackid,基准解码时间,sample序列,持续时间、数据偏移等信息,traf也是容器box,包含多个子box,本身无内容
Track Fragment Header box(tfhd),对制定的track进行相关的默认配置,包含trackid、sample时长、大小、偏移量等信息,其格式如下:
[4字节size][4字节type][4字节版本和flags][4字节trackid][8字节Base data offset][4字节Sample description index][4字节Default sample duration][4字节Default sample size][4字节Default sample flags]
示例及详解:
00 00 00 10 74 66 68 64 00 02 00 00 00 00 00 01
Track Fragment Base Media Decode Time Box(tfdt), 主要是用来存放相关sample编码的绝对时间的,因为FMP4是流式的格式,所以不像MP4一样可以直接根据sample直接seek到具体位置。这里就需要一个标准时间参考,来快速定位都某个具体的fragment。其格式如下:
[4字节size][4字节type][4字节版本和flags][4字节Base media decode time]
示例及详解:
00 00 00 10 74 66 64 74 00 00 00 00 00 00 00 00
Track Fragment Run Box(trun),记录moof中有关sample的相关信息,如每个sample的size,duration,offset等信息,其格式如下:
[4字节size][4字节type][4字节版本和flags][4字节sample count][4字节data offset][sample count个16字节的sample描述信息]
sample描述信息格式如下:
[4字节持续时间][4字节sample size][4字节sample flags][4字节cts]
示例及详解:
00 00 00 24 74 72 75 6E 00 00 0F 01 00 00 00 01 00 00 00 79 00 00 0E 10 00 03 6B FB 02 00 00 00 00 00 00 00
(sample.IsLeading << 2) | sample.DependsOn,
(sample.IsDependedOn << 6) | (sample.HasRedundancy << 4) | (0x00 << 1) | sample.IsNonSync,
sample.DegradPrio & 0xF0 << 8,
sample.DegradPrio & 0x0F,
sample flags的含义可参照https://blog.csdn.net/CHYabc123456hh/article/details/125100494,这里以单个分片单个sample的模式,只要碰到关键帧,DependsOn=2 ,其他等于0,非关键帧DependsOn=1,IsNonSync等于1,其他等于0
Independent and Disposable Samples Box(sdtp),主要是用来描述具体某个 sample 是否是 I 帧,是否是 leading frame 等相关属性值,主要用来作为当进行点播回放时的同步参考信息,这里简单赋值即可,其格式如下:
[4字节size][4字节type][4字节预留][sample count个1字节flags]
其flags的1字节表示方式:(sample.DependsOn << 4) | (sample.IsDependedOn << 2) | (sample.HasRedundancy)
示例如下:
00 00 00 0D 73 64 74 70 00 00 00 00 20
Media Data Box(mdat), 该box包含媒体数据, 对于h264,aac编码的媒体来说,其视频mdat中内容是nalu,对于音频来说,其内容为aac的一帧。mdat中的帧依次存放,每个帧的位置、时间、长度都由moov中的信息指定,本文实例以H264为例,每个mdat包含一帧或多帧H264,采用avc1封装格式,即是去掉起始头后,在帧前面加上4字节帧size,其格式如下:
[4字节box size][4字节type][4字节数据包size][流媒体数据包]
此box很简单,不做过多解释,只需要注意,如果采用avc1封装格式,请去掉H264起始头,在前面加上四个字节的帧大小size
至此有关H264打包为fmp4的内容已经写完,可参照ffmpeg相关源码进行对照阅读。
有关fmp4示例文件及分析工具mp4reader安装包获取,请关注微信公众号壹零仓,发送fmp4,获取
参考资料:
音视频学习之fmp4