媒体文件格式以及ISO/IEC 14496-12规范

网络上很多讲解媒体文件格式(比如MP4档案)的文章,有不少是作者懂了相关的东西,然后再自己总结出来的,优点就是他们抓住重点,写出注意的地方,缺点就是有时候你会觉得很突然,突然就出来一个新的概念,定义,因为他们默认你应该熟悉相应的规范。我在这里从基本点入手,写点关于媒体文件格式的东西。

首先你应该有一份ISO/IEC 14496-12(Part 12: ISO base media file format),这个可以从http://standards.iso.org/ittf/PubliclyAvailableStandards/index.html免费下载到。虽然是英文的,但是一些基本的东西还是不难。
当然你应该知道一些基本的概念,比如container file, codec, track等等。比如通常看到的一个MP4文件它就是一个container file,它里面装了来自codec编码后的数据,可能包括一个或者多个track(通常有一个视频track,一个音频track,有的可能有多个音频track,比如中文/英文/广东话/西班牙语,还有可能有字幕track)。

首先需要阅读最基本的几个章节就可以
3 Terms, definitions, and abbreviated terms
4 Object-structured File Organization
5 Design Considerations
即一些基本术语,定义,比如Box, Chunk, Container Box, Hint Box, Sample等等
其中文件组织结构当中规定了文件是有一系列的Box组成,在文件当中除了Box没有其他东西,所有数据都是包含在Box当中,以及为什么要采取这样面相对象的设计。
通常Box由header和包含在其中的data组成,header中包含了这个Box的size和type信息,当然size是包含了这个Box当中所有数据在内,也包含这个header。
如果Box的type不能被识别,那么这个Box就该被忽略。

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) { // 如果size为1就用largesize来表示这个Box的大小
		unsigned int(64) largesize;
	} else if (size == 0) {
		// box extends to end of file
	}
	if (boxtype == 'uuid') { // 这里表示自定义的类型
		unsigned int(8)[16] usertype = extended_type;
	}
}

这里都是按照8位对齐,所以最小单元是8位,即一个字节,本文章中所指的单元均是这里表述的意思。
所以你就可以很容易的知道,属性size和type分别各自占用4个单元。

另外很多Box都包含version和flag这两个字段,所以又抽象出来一个FullBox

aligned(8) class FullBox(unsigned int(32) boxtype, unsigned int(8) v, bit(24) f)
		extends Box(boxtype) {
	unsigned int(8) version = v; // 1个单元
	bit(24) flags = f; // 3个单元
}

如果Box的version不能被识别,那么这个Box就该被忽略。

所有符合这规范的文件都应当包含一个File Type Box,并且这个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个单元,也可以是2,什么时候结束靠前面的size决定
}

那我们还可以知道,一个典型的符合规范的文件包括moov和mdat这两个Boxes,那这又是什么?

这个时候就要继续往下看
6 ISO Base Media File organization
了,Table 1 — Box types, structure, and cross-reference这张图写出了基本上所有的Boxes,可以简单的理解就是moov(Movie Box)是包含描述信息的Box,mdat(Media Data Box)是包含真实多媒体数据的Box,而且这些Boxes都是并列或者嵌套的关系,It’s tree-like。到这里最重要的东西已经都接触了,然后你还要知道这些并列或者嵌套的Boxes要怎么摆放,也就是它的顺序,特别是moov(Movie Box)当中的子Boxes的顺序,另外因为有Streaming Support/Realtime downloading and playing的需求,moov放在mdat的前面,这样只要下载完moov数据就可以解析出来这段视频有多长,什么语言,等等信息,然后就开始播放,后面mdat数据慢慢来下载,因为mdat通常比moov大的多;因为录影功能,我们不知道什么时候录影结束,而且moov相对较小别且大小可能受到mdat大小的影响,所以我们也会把moov放到mdat后面(录影过程当中先把moov保存在内存当中,等mdat写完了再追加到尾部去)。

这是一些基本的理论,当然还有很多东西没有在这里指出,你需要阅读规范来了解,关于具体Box相关的,建议你用到这个Box的时候就去读,因为实在是太多的Boxes了。

现在我们通过一个实例来看下

guohai@KNIGHT:~$ AtomicParsley /path/to/VID_FILE.mp4 -T
Atom ftyp @ 0 of size: 24, ends @ 24
Atom moov @ 24 of size: 53884, ends @ 53908
     Atom mvhd @ 32 of size: 108, ends @ 140
     Atom trak @ 140 of size: 45719, ends @ 45859
         Atom tkhd @ 148 of size: 92, ends @ 240
         Atom mdia @ 240 of size: 45619, ends @ 45859
             Atom mdhd @ 248 of size: 32, ends @ 280
             Atom hdlr @ 280 of size: 44, ends @ 324
             Atom minf @ 324 of size: 45535, ends @ 45859
                 Atom vmhd @ 332 of size: 20, ends @ 352
                 Atom dinf @ 352 of size: 36, ends @ 388
                     Atom dref @ 360 of size: 28, ends @ 388
                 Atom stbl @ 388 of size: 45471, ends @ 45859
                     Atom stsd @ 396 of size: 151, ends @ 547
                         Atom avc1 @ 412 of size: 135, ends @ 547
                             Atom avcC @ 498 of size: 33, ends @ 531
                             Atom pasp @ 531 of size: 16, ends @ 547			 ~
                     Atom stts @ 547 of size: 21024, ends @ 21571
                     Atom stss @ 21571 of size: 232, ends @ 21803
                     Atom stsz @ 21803 of size: 10536, ends @ 32339
                     Atom stsc @ 32339 of size: 7072, ends @ 39411
                     Atom stco @ 39411 of size: 6448, ends @ 45859
     Atom trak @ 45859 of size: 8049, ends @ 53908
         Atom tkhd @ 45867 of size: 92, ends @ 45959
         Atom mdia @ 45959 of size: 7949, ends @ 53908
             Atom mdhd @ 45967 of size: 32, ends @ 45999
             Atom hdlr @ 45999 of size: 44, ends @ 46043
             Atom minf @ 46043 of size: 7865, ends @ 53908
                 Atom smhd @ 46051 of size: 16, ends @ 46067
                 Atom dinf @ 46067 of size: 36, ends @ 46103
                     Atom dref @ 46075 of size: 28, ends @ 46103
                 Atom stbl @ 46103 of size: 7805, ends @ 53908
                     Atom stsd @ 46111 of size: 69, ends @ 46180
                         Atom samr @ 46127 of size: 53, ends @ 46180
                             Atom damr @ 46163 of size: 17, ends @ 46180
                     Atom stts @ 46180 of size: 32, ends @ 46212
                     Atom stsz @ 46212 of size: 20, ends @ 46232
                     Atom stsc @ 46232 of size: 52, ends @ 46284
                     Atom stco @ 46284 of size: 7624, ends @ 53908
Atom free @ 53908 of size: 351116, ends @ 405024
Atom mdat @ 405024 of size: 39866486, ends @ 40271510

看到这些不要慌张,这其实就是一个MP4文件当中所有的Boxes的树形展示,我们是通过AtomicParsley把它显示出来,看看最外面有ftyp,moov,free和mdat这四个Boxes,Box在有的地方又被称为Atom,是一个意思。如果你不明白free是什么Box,那么你就需要去读下规范了。

具体看下第一行

Atom ftyp @ 0 of size: 24, ends @ 24

表示ftyp从第0个单元开始,大小是24,结束在24单元之前。
其它行以类似的方法可以读懂。

或者你可以去下载一个叫做Mp4Info的图形化工具来查看这棵由Boxes组成的树,不过这个软件只能在Windows下使用,而且有时候会crash,有些Box的Hex数据也显示不全,不过基本上使用还是可以的,查看数据可以用专门的16进制编辑器。我这里用的是一个叫做Bless的工具。
还有很多工具,可以到网上去搜索。

现在以实际例子来分析下。

我们知道第一个Box是ftyp,从0开始,大小是24。那么它的数据就是下面(用Bless打开MP4档案看到的)。

00 00 00 18 66 74 79 70 69 73 6F 6D 00 00 00 00 69 73 6F 6D 33 67 70 34

最开始4个单元是size,其值为00 00 00 18,换成decimal为24

接下来4个单元是type,其值为66 74 79 70,对应’f’, ‘t’, ‘y’, ‘p’这4个字符的ASCII编码

major_brand从第9单元开始,其值为69 73 6F 6D,对应’i’, ‘s’, ‘o’, ‘m’

minor_version从第13单元开始,其值为00 00 00 00,也就是0

compatible_brands从第17单元开始,包含了2组4个单元的数据,也就是8个单元,之前我们说过了,它是不定长的,大小受整个Box的大小控制,其值为69 73 6F 6D 33 67 70 34,对应’i’, ‘s’, ‘o’, ‘m’, ‘3’, ‘g’, ‘p’, ‘4’

这就是ftyp这个Box的所有信息。

接下来我们再看下mvhd,它是moov的header,长度共计108个单元(version为0,其它版本请查看规范),它的数据如下:

00 00 00 6C 6D 76 68 64 00 00 00 00 50 4C 2C 57 50 4C 2C 57 00 00 03 E8 00 03 7B 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 00 00 00 03

规范中MovieHeaderBox的定义如下:

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;
	template int(32)[9] matrix =
		{ 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 }; // Unity matrix
	bit(32)[6] pre_defined = 0;
	unsigned int(32) next_track_ID;
}

最开始4个单元是size,其值为00 00 00 6C,换成decimal为108(6 * 16 + 12)

接下来4个单元是type,其值为6D 76 68 64,对应’m’, ‘v’, ‘h’, ‘d’这4个字符的ASCII编码

接下来就是version, flag, creation_time, modification_time等等

这里有点需要注意的是,规范上说对于version为0和为1有些属性的长度是不一样的,我们这里可以来看一下。

比如这里version是9单元,它只占用一个字节,其值为00,也就是0,那么后面creation_time,modification_time,time scale和duration均每个属性占用4个单元。

creation_time从13单元开始,其值为50 4C 2C 57,这是一个timestamp,转化成string类型表示为”2012-09-09 13:42:47″

modification_time从17单元开始,其值为50 4C 2C 57,如上,所以这个档案是在2012-09-09 13:42:47创建过之后也没有修改过。

time scale从21单元开始,其值为00 00 03 E8,换算成decimal为1000

duration从25单元开始,其值为00 03 7B 90,换算成decimal为228240

该duration是所有track当中最长的那个,在规范当中有写,另外这里的duration是通过time scale表示的。

换算成我们通常的时间坐标系就是228240 / 1000 = 228.24,约为228秒,即228240毫秒。

媒体档案格式基本就是按照此类方法来分析的,比如这段视频有几个track,以及是分别是什么track,就只需要去解析它的moov/trak的Box。

遇到不是很明白的概念的时候尽可能的先到规范当中找找看有没有定义,一般解释的都还是比较清楚。比如下面这个最基本的。

[mvhd]
timescale is an integer that specifies the time-scale for the entire presentation; this is the number of time units that pass in one second. For example, a time coordinate system that measures time in sixtieths of a second has a time scale of 60.

[mvhd]
duration is an integer that declares length of the presentation (in the indicated timescale). This property is derived from the presentation’s tracks: the value of this field corresponds to the duration of the longest track in the presentation. If the duration cannot be determined then duration is set to all 1s.

[mvhd]
rate is a fixed point 16.16 number that indicates the preferred rate to play the presentation; 1.0 (0x00010000) is normal forward playback

[trak]
duration is an integer that indicates the duration of this track (in the timescale indicated in the Movie Header Box). The value of this field is equal to the sum of the durations of all of the track’s edits. If there is no edit list, then the duration is the sum of the sample durations, converted into the timescale in the Movie Header Box. If the duration of this track cannot be determined then duration is set to all 1s.

[trak]
layer specifies the front-to-back ordering of video tracks; tracks with lower numbers are closer to the viewer. 0 is the normal value, and -1 would be in front of track 0, and so on.

有根据规范和AOSP的代码写一个在Linux x86_64下验证过的MPEG4文件解析工具,https://github.com/guohai/gmpe4,欢迎大家使用,提意见或者完善它。

推荐参考的资料:
MP4文件格式解析系列(http://blog.sina.com.cn/s/blog_48f93b530100jz4b.html)
QuickTime container(http://wiki.multimedia.cx/index.php?title=QuickTime_container)
http://mp4ra.org/atoms.html

Leave a Reply

Your email address will not be published. Required fields are marked *