Mp4文件格式解析
1.ISO/IEC 14496标准
ISO/IEC 14496是MPEG专家组制定的MPEG-4标准,分为多个部分(仍在更新)。
第一部分(ISO/IEC 14496-1):系统:描述视频和音频数据流的控制、同步以及混合方式(即混流Multiplexing,简写为MUX)。
第二部分(ISO/IEC 14496-2):视频:定义一个对各种视觉信息(包括自然视频、静止纹理、计算机合成图形等等)的编解码器。(例如XviD编码就属于MPEG-4 Part 2)
第三部分(ISO/IEC 14496-3):音频:定义一个对各种音频信号进行编码的编解码器的集合。包括高级音频编码(Advanced Audio Coding,缩写为AAC)的若干变形和其他一些音频/语音编码工具。
第四部分(ISO/IEC 14496-4):一致性:定义对本标准其他的部分进行一致性测试的程序。
第五部分(ISO/IEC 14496-5):参考软件:提供用于演示功能和说明本标准其他部分功能的软件。
第六部分(ISO/IEC 14496-6):多媒体传输集成框架(DMIF for Delivery Multimedia Integration Framework)
第七部分(ISO/IEC 14496-7):优化的参考软件:提供对实现进行优化的例子(这里的实现指的是第五部分)。
第八部分(ISO/IEC 14496-8):在IP网络上传输:定义在IP网络上传输MPEG-4内容的方式。
第九部分(ISO/IEC 14496-9):参考硬件:提供用于演示怎样在硬件上实现本标准其他部分功能的硬件设计方案。
第十部分(ISO/IEC 14496-10):高级视频编码或称高级视频编码(Advanced Video Coding,缩写为AVC):定义一个视频编解码器(codec)。AVC和XviD都属于MPEG-4编码,但由于AVC属于MPEG-4 Part 10,在技术特性上比属于MPEG-4 Part2的XviD要先进。另外,它和ITU-T H.264标准是一致的,故又称为H.264。
第十二部分(ISO/IEC 14496-12):基于ISO的媒体文件格式:定义一个存储媒体内容的文件格式。
第十三部分(ISO/IEC 14496-13):知识产权管理和保护(IPMP for Intellectual Property Management and Protection)拓展。
第十四部分(ISO/IEC 14496-14):MPEG-4文件格式:定义基于第十二部分的用于存储MPEG-4内容的视频文件格式。
第十五部分(ISO/IEC 14496-15):AVC文件格式:定义基于第十二部分的用于存储第十部分的视频内容的文件格式。
第十六部分(ISO/IEC 14496-16):动画框架扩展(AFX : Animation Framework eXtension)。
第十七部分(ISO/IEC 14496-17):同步文本字幕格式。
第十八部分(ISO/IEC 14496-18):字体压缩和流式传输(针对开放字体格式Open Font Format)。
第十九部分(ISO/IEC 14496-19):合成材质流(Synthesized Texture Stream)。
第二十部分(ISO/IEC 14496-20):简单场景表示(LASeR for Lightweight Scene Representation。
第二十一部分(ISO/IEC 14496-21):用于描绘(Rendering)的MPEG-J拓展。
第二十二部分(ISO/IEC 14496-22):开放字体格式(Open Font Format)。
第二十三部分(ISO/IEC 14496-23):符号化音乐表示(Symbolic Music Representation)。
第二十四部分(ISO/IEC 14496-24):音频与系统交互作用(Audio and systems interaction)。
第二十五部分(ISO/IEC 14496-25):3D图形压缩模型(3D Graphics Compression Model)。
第二十六部分(ISO/IEC 14496-26):音频一致性检查:定义测试音频数据与ISO/IEC 14496-3是否一致的方法(Audio conformance)。
第二十七部分(ISO/IEC 14496-27):3D图形一致性检查:定义测试3D图形数据与ISO/IEC 14496-11:2005, ISO/IEC 14496-16:2006, ISO/IEC 14496-21:2006,和ISO/IEC 14496-25:2009是否一致的方法(3D Graphics conformance)。
MP4是在“ISO/IEC 14496-14”标准文件中定义的一种多媒体容器格式,它是MPEG4 (ISO/IEC 14496)标准的一部分。是“ISO/IEC 14496-12(MPEG-4 Part 12 ISO base media file format)”标准中所定义的媒体格式的一种实现。
1.1.本文章参考的标准文档
本篇文章主要参考的标准文档如下:
- c068960_ISO_IEC_14496-12_2015.pdf
- ISO_IEC_14496-14_2003-11-15.pdf
1.2.文档中的部分术语解释
- box:由唯一类型标识符和长度定义的面向对象的构件
- container box:用来容纳一组相关box的box,container box通常都不是fullbox
- chunk:同一轨道的一组连续的采样
- hint track:不包含媒体数据,但包含了将一个或多个轨打包到流频道的指示
- media data box:用来容纳实体数据的box
- movie box:子box定义了元数据(metadata)的容器box
- sample:与单个时间戳相关联的所有数据,video sample即为一帧视频,或一组连续视频帧,audio sample即为一段连续的压缩音频
- sample description:定义和描述轨中的采样的格式的结构
- sample table:指明sampe时序和物理布局的表
- track:按时间排序的相关的采样,对于媒体数据来说,track表示一个视频或音频序列
2.MP4容器格式
MP4是一种描述较为全面的容器格式,被认为可以在其中嵌入任何形式的数据,以及各种编码的音视频等,不过我们常见的大部分的MP4文件存放的AVC(H.264)或MPEG-4(Part 2)编码的视频和AAC编码的音频。
MP4的结构就像俄罗斯的套娃有很多box套box,也可以理解为一棵Box树。下面这张图是常见的box的树结构图,可以用来大致了解MP4文件的构造:
2.1.MP4中的box结构
通过上面的介绍,我们了解了MP4格式就是由一个个的box组合成的box树,所有的数据都包含在box里,下面来了解一下box的基本结构。一个box是由box header和box里面包含的数据组成的,如下图所示
整个box以box header开头,box header中包含了box的大小(size)和类型(type)等信息。其中,size指明了整个box所占用的大小,包括header部分,如果box很大(例如存放具体视频数据的mdat box),超过了uint32的最大数值,size就被设置为1,并用接下来的8位uint64的largesize来存放大小。box中的字节序为网络字节序,也就是大端字节序(Big-Endian)。
box根据header部分包含的信息的不同可以分为box和full box,如下图所示:
其中box和full box在ISO_IEC_14496-12_2015文档中的定义为
aligned(8) class Box (unsigned int(32) boxtype, optional unsigned int(8)[16] extended_type) { unsigned int(32) size; unsigned int(32) type = boxtype; if (size==1) { unsigned int(64) largesize; } else if (size==0) { // box extends to end of file } if (boxtype==‘uuid’) { unsigned int(8)[16] usertype = extended_type; } }
aligned(8) class FullBox(unsigned int(32) boxtype, unsigned int(8) v, bit(24) f) extends Box(boxtype) { unsigned int(8) version = v; bit(24) flags = f; }
full box中的version是一个用来指定该box的文件的格式的整数
flags 是一个标志图
2.2.MP4中各种box分析
- ftyp:file type box,表明文件类型,该box只有一个并且只能被包含在文件层,不能被其他box包含。同时,他应该出现在文件的最开始的位置。ftyp box包含一个32位的major brand(4个字符),一个32位的minor version(整数)和一个以32位为单位的compatible数组。这些都是用来指示文件应用级别的信息,ftyp box在标准文档中的定义如下。
aligned(8) class FileTypeBox extends Box(‘ftyp’) { unsigned int(32) major_brand; unsigned int(32) minor_version; unsigned int(32) compatible_brands[]; // to end of the box }
1.major_brand: 是一个标识符,如mp42
2.minor_version: 是一个major brand 的次版本标识
3.compatible_brands:是一个list,一直到box的结尾
下面来分析一个示例文件
box的类型为ftyp box大小为24个字节,其中major_brand和minor_version都是mp42。
- mdat:该box包含于文件层,可以有多个,也可以没有(当媒体数据全部为外部文件引用时),用来存储媒体数据。数据直接跟在box type字段后面,它的结构是由metadata来描述的,metadata通过文件中的绝对偏移来引用媒体数据。它在标准文档中的定义如下
aligned(8) class MediaDataBox extends Box(‘mdat’) { bit(8) data[]; }
- free:free box中的内容是无关紧要的,可以被忽略。该box被删除后,不会对播放产生任何影响,它的type域可以是free或skip。freebox 在标准文档中的定义如下
aligned(8) class FreeSpaceBox extends Box(free_type) { unsigned int(8) data[]; }
- moov:movie box,用来存放媒体的metadata信息,其内容信息由子box诠释。该box有且只有一个并且包含在文件层,一般情况下moov box会紧随ftyp box出现,但也有放在文件末尾的。它在标准文档中的定义为
aligned(8) class MovieBox extends Box(‘moov’) { }
- mvhd:用来存放文件的总体信息,如时长和创建时间等。它是独立于媒体的并且与整个播放相关。mvhd box在标准文档中的定义如下
aligned(8) class MovieHeaderBox extends FullBox(‘mvhd’, version, 0) { if (version==1) { unsigned int(64) creation_time; unsigned int(64) modification_time; unsigned int(32) timescale; unsigned int(64) duration; } else { // version==0 unsigned int(32) creation_time; unsigned int(32) modification_time; unsigned int(32) timescale; unsigned int(32) duration; } template int(32) rate = 0x00010000; // typically 1.0 template int(16) volume = 0x0100; // typically, full volume const bit(16) reserved = 0; const unsigned int(32)[2] reserved = 0; // Unity matrix template int(32)[9] matrix = { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 }; bit(32)[6] pre_defined = 0; unsigned int(32) next_track_ID; }
1.version:用来指定该box的版本,取值为0或1,一般为0
2.creation_time:用来指定创建时间,单位为相对于UTC时间1904-01-01零点的秒数
3.modification_time:用来指定最后修改时间
4.timescale:用来指定文件媒体在1秒时间内的刻度值,可以理解为1秒长度的时间单元数
5.duration:用来指定该track的时间长度,用duration和time scale值可以计算track时长,比如audio track的time scale = 8000, duration = 560128,时长为70.016,video track的time scale = 600, duration = 42000,时长为70
6.rate:用来指定推荐播放速率,高16位和低16位分别为小数点整数部分和小数部分,即[16.16] 格式,该值为1.0(0x00010000)表示正常前向播放7.volume:用来指定推荐的音量,与rate类似,[8.8] 格式,1.0(0x0100)表示最大音量
8.matrix:用来指定视频变换矩阵
9.next_track_ID:用来指定下一个track使用的id号
下面来分析一个示例文件
- trak:trak box也是一个container box,其子box包含了该track的媒体数据引用和描述(hint track除外)。一个MP4文件中的媒体可以包含多个track,且至少有一个track,这些track之间彼此独立,有自己的时间和空间信息。trak box必须包含一个tkhd box和一个mdia box,trak box 在标准文档中的定义如下
aligned(8) class TrackBox extends Box(‘trak’) { }
- tkhd:包含了该track的特性和总体信息,如时长,宽高等。tkhd box在标准文档中的定义如下
aligned(8) class TrackHeaderBox extends FullBox(‘tkhd’, version, flags) { if (version==1) { unsigned int(64) creation_time; unsigned int(64) modification_time; unsigned int(32) track_ID; const unsigned int(32) reserved = 0; unsigned int(64) duration; } else { // version==0 unsigned int(32) creation_time; unsigned int(32) modification_time; unsigned int(32) track_ID; const unsigned int(32) reserved = 0; unsigned int(32) duration; } const unsigned int(32)[2] reserved = 0; template int(16) layer = 0; template int(16) alternate_group = 0; template int(16) volume = {if track_is_audio 0x0100 else 0}; const unsigned int(16) reserved = 0; // unity matrix template int(32)[9] matrix= { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 }; unsigned int(32) width; unsigned int(32) height; }
1.creation_time :指定创建时间(相对于UTC时间1904-01-01零点的秒数)
2.modification_time:指定修改时间
3.track_ID: 指定track的id号,不能重复且不能为0
4.reserved: 保留位
5.duration: 指定track的时长
6.reserved: 保留位
7.layer:指定视频层,默认为0,值小的在上层
8.alternate_group:指定rack分组信息,默认为0表示该track未与其他track有群组关系
9.volume: 指定[8.8] 格式的音量信息,如果为音频track,1.0(0x0100)表示最大音量;否则为0
10.reserved: 保留位
11.matrix:指定视频变换矩阵
12.width:宽,为 [16.16] 格式值,与sample描述中的实际画面大小比值,用于播放时的展示宽高
13.height:高
- mdia:包含类整个track的媒体信息,比如媒体类型和sample信息,它在标准文档中的定义如下
aligned(8) class MediaBox extends Box(‘mdia’) { }
- mdhd:包含了了该track的总体信息,mdhd 和 tkhd 内容大致都是一样的。tkhd 通常是对指定的 track 设定相关属性和内容,而 mdhd 是针对于独立的 media 来设置的,一般情况下二者相同。它在标准文档中的定义如下
aligned(8) class MediaHeaderBox extends FullBox(‘mdhd’, version, 0) { if (version==1) { unsigned int(64) creation_time; unsigned int(64) modification_time; unsigned int(32) timescale; unsigned int(64) duration; } else { // version==0 unsigned int(32) creation_time; unsigned int(32) modification_time; unsigned int(32) timescale; unsigned int(32) duration; } bit(1) pad = 0; unsigned int(5)[3] language; // ISO-639-2/T language code unsigned int(16) pre_defined = 0; }
1.creation time:创建时间(相对于UTC时间1904-01-01零点的秒数)
2.modification time:修改时间
3.time scale:同前表
4.duration: track的时长
5.language:媒体语言码。最高位为0,后面15位为3个字符(见ISO 639-2/T标准中定义)
- hdlr:解释了媒体的播放过程信息,该box也可以被包含在meta box(meta)中,它在标准文档中的定义如下
aligned(8) class HandlerBox extends FullBox(‘hdlr’, version = 0, 0) { unsigned int(32) pre_defined = 0; unsigned int(32) handler_type; const unsigned int(32)[3] reserved = 0; string name; }
1.handler_type: 在media box中,该值为4个字符:
“vide”— video track
“soun”— audio track
“hint”— hint track
2.name :human‐readable name for the track type
- minf:Media Information Box,minf box包含了所有描述该track中的媒体信息的对象,信息存储在其子box中,它在标准文档中的定义为
aligned(8) class MediaInformationBox extends Box(‘minf’) { }
- vmhd:用在视频track中,包含当前track的视频描述信息(如视频编码等信息)。它在标准文档中的定义为
aligned(8) class VideoMediaHeaderBox extends FullBox(‘vmhd’, version = 0, 1) { template unsigned int(16) graphicsmode = 0; // copy, see below template unsigned int(16)[3] opcolor = {0, 0, 0}; }
1.graphicsmode: 视频合成模式,为0时拷贝原始图像,否则与opcolor进行合成
2.opcolor:{red,green,blue}
- smhd:用在音频track中,包含当前track的音频描述信息(如编码格式等信息)。它在标准文档中的定义为
aligned(8) class SoundMediaHeaderBox extends FullBox(‘smhd’, version = 0, 0) { template int(16) balance = 0; const unsigned int(16) reserved = 0; }
1.balance:立体声平衡,[8.8] 格式值,一般为0,-1.0表示全部左声道,1.0表示全部右声道
- dinf:dinf box解释如何定位媒体信息,是一个container box。dinf box一般包含一个dref box,即data reference box。它在标准文档中的定义如下
aligned(8) class DataInformationBox extends Box(‘dinf’) { }
- dref:dref box是用来设置当前 Box 描述信息的 data_entry,dref box下会包含若干个“url”或“urn”,这些box组成一个表,用来定位track数据。简单的说,track可以被分成若干段,每一段都可以根据“url”或“urn”指向的地址来获取数据,sample描述中会用这些片段的序号将这些片段组成一个完整的track。一般情况下,当数据被完全包含在文件中时,“url”或“urn”中的定位字符串是空的。它在标准文档中的定义如下
aligned(8) class DataEntryUrlBox (bit(24) flags) extends FullBox(‘url ’, version = 0, flags) { string location; } aligned(8) class DataEntryUrnBox (bit(24) flags) extends FullBox(‘urn ’, version = 0, flags) { string name; string location; } aligned(8) class DataReferenceBox extends FullBox(‘dref’, version = 0, 0) { unsigned int(32) entry_count; for (i=1; i <= entry_count; i++) { DataEntryBox(entry_version, entry_flags) data_entry; } }
1.entry_version: 用来指明当前 entry 的格式
2.entry_flags: 其值不是固定的,但是有一个特殊的值, 0x000001 用来表示当前 media 的数据和 moov 包含的数据一致
- stbl: stbl box几乎是普通的MP4文件中最复杂的一个box了,首先需要回忆一下sample的概念。sample是媒体数据存储的单位,存储在media的chunk中,chunk和sample的长度均可互不相同,如下图所示
“stbl”包含了关于track中sample所有时间和位置的信息,以及sample的编解码等信息。利用这个表,可以解释sample的时序、类型、大小以及在各自存储容器中的位置。“stbl”是一个container box,其子box包括:sample description box(stsd)、time to sample box(stts)、sample size box(stsz或stz2)、sample to chunk box(stsc)、chunk offset box(stco或co64)、composition time to sample box(ctts)、sync sample box(stss)等。
“stsd”必不可少,且至少包含一个条目,该box包含了data reference box进行sample数据检索的信息。没有“stsd”就无法计算media sample的存储位置。“stsd”包含了编码的信息,其存储的信息随媒体类型不同而不同。
stbl box在标准文档中的定义如下
aligned(8) class SampleTableBox extends Box(‘stbl’) { }
- stsd:box header和version字段后会有一个entry count字段,根据entry的个数,每个entry会有type信息,如“vide”、“sund”等,根据type不同sample description会提供不同的信息,例如对于video track,会有“VisualSampleEntry”类型信息,对于audio track会有“AudioSampleEntry”类型信息。视频的编码类型、宽高、长度,音频的声道、采样等信息都会出现在这个box中
aligned(8) abstract class SampleEntry (unsigned int(32) format) extends Box(format) { const unsigned int(8)[6] reserved = 0; unsigned int(16) data_reference_index; } aligned(8) class SampleDescriptionBox (unsigned int(32) handler_type) extends FullBox('stsd', version, 0) { int i ; unsigned int(32) entry_count; for (i = 1 ; i <= entry_count ; i++) { SampleEntry(); // an instance of a class derived from SampleEntry } }
视频track的stsd
[stsd] size=12+149 entry-count = 1 [avc1] size=8+137 data_reference_index = 1 width = 720 height = 576 compressor = [avcC] size=8+51 Configuration Version = 1 Profile = High Profile Compatibility = 0 Level = 40 NALU Length Size = 4 Sequence Parameter = [67 64 00 28 ac d1 00 b4 12 6c 08 40 00 00 03 00 40 00 00 0c b8 08 00 16 e3 40 00 5b 8d e4 93 00 f8 c1 88 90] Picture Parameter = [68 eb ef 2c]
音频track的stsd
[stsd] size=12+79 entry-count = 1 [mp4a] size=8+67 data_reference_index = 1 channel_count = 2 sample_size = 16 sample_rate = 48000 [esds] size=12+27 [ESDescriptor] size=2+25 es_id = 0 stream_priority = 31 [DecoderConfig] size=2+17 stream_type = 5 object_type = 64 up_stream = 0 buffer_size = 531 max_bitrate = 129336 avg_bitrate = 125368 DecoderSpecificInfo = 11 90 [Descriptor:06] size=2+1
- ctts:cts box的作用可以参考下面文章http://blog.csdn.net/w839687571/article/details/41725811,它在标准文档中的定义为
aligned(8) class CompositionOffsetBox extends FullBox(‘ctts’, version, 0) { unsigned int(32) entry_count; int i; if (version==0) { for (i=0; i < entry_count; i++) { unsigned int(32) sample_count; unsigned int(32) sample_offset; } } else if (version == 1) { for (i=0; i < entry_count; i++) { unsigned int(32) sample_count; signed int(32) sample_offset; } } }
- stts:stts box存储了sample的duration,描述了sample时序的映射方法,我们通过它可以找到任何时间的sample。stts box可以包含一个压缩的表来映射时间和sample序号,用其他的表来提供每个sample的长度和指针。表中每个条目提供了在同一个时间偏移量里面连续的sample序号,以及samples的偏移量。递增这些偏移量,就可以建立一个完整的time to sample表(时间戳到sample序号的映射表)。它在标准文档中的定义为
aligned(8) class TimeToSampleBox extends FullBox(’stts’, version = 0, 0) { unsigned int(32) entry_count; int i; for (i=0; i < entry_count; i++) { unsigned int(32) sample_count; unsigned int(32) sample_delta; } }
- stsz:“stsz” 定义了每个sample的大小,包含了媒体中全部sample的数目和一张给出每个sample大小的表。这个box相对来说体积是比较大的。它在标准文档中的定义为
aligned(8) class SampleSizeBox extends FullBox(‘stsz’, version = 0, 0) { unsigned int(32) sample_size; unsigned int(32) sample_count; if (sample_size==0) { for (i=1; i <= sample_count; i++) { unsigned int(32) entry_size; } } }
- stsc: 用chunk组织sample可以方便优化数据获取,一个thunk包含一个或多个sample。“stsc”中用一个表描述了sample与chunk的映射关系,查看这张表就可以找到包含指定sample的thunk,从而找到这个sample。它在标准文档中的定义为
aligned(8) class SampleToChunkBox extends FullBox(‘stsc’, version = 0, 0) { unsigned int(32) entry_count; for (i=1; i <= entry_count; i++) { unsigned int(32) first_chunk; unsigned int(32) samples_per_chunk; unsigned int(32) sample_description_index; } }
- stss:“stss”确定media中的关键帧。对于压缩媒体数据,关键帧是一系列压缩序列的开始帧,其解压缩时不依赖以前的帧,而后续帧的解压缩将依赖于这个关键帧。“stss”可以非常紧凑的标记媒体内的随机存取点,它包含一个sample序号表,表内的每一项严格按照sample的序号排列,说明了媒体中的哪一个sample是关键帧。如果此表不存在,说明每一个sample都是一个关键帧,是一个随机存取点。它在标准文档中的定义为
aligned(8) class SyncSampleBox extends FullBox(‘stss’, version = 0, 0) { unsigned int(32) entry_count; int i; for (i=0; i < entry_count; i++) { unsigned int(32) sample_number; } }
- stco:“stco”定义了每个thunk在媒体流中的位置,sample的偏移可以根据其他box推算出来。位置有两种可能,32位的和64位的,后者对非常大的电影很有用。在一个表中只会有一种可能,这个位置是在整个文件中的,而不是在任何box中的,这样做就可以直接在文件中找到媒体数据,而不用解释box。需要注意的是一旦前面的box有了任何改变,这张表都要重新建立,因为位置信息已经改变了。它在标准文档中的定义为:
aligned(8) class ChunkOffsetBox extends FullBox(‘stco’, version = 0, 0) { unsigned int(32) entry_count; for (i=1; i <= entry_count; i++) { unsigned int(32) chunk_offset; } } aligned(8) class ChunkLargeOffsetBox extends FullBox(‘co64’, version = 0, 0) { unsigned int(32) entry_count; for (i=1; i <= entry_count; i++) { unsigned int(64) chunk_offset; } }
2.3.解析moov box流程
TODO
2018-6-27:
stsc中找到该帧在哪个chunk中-->stco找到每个chunk的offset-->stsz计算出该帧所在的chunk前面所有帧大小,进而计算出该帧在文件中的offset
参考资料:
c068960_ISO_IEC_14496-12_2015.pdf