超详细MP4格式分析
这⾥先给出来的是视频的stts,Numberofentries,这个参数需要注意并不是sample的个数,sample的实际数量需要将每个entry的samplecount进⾏累加才是真正的sample个数。下面的解释就是chunk1-84,都是一一对应1个sample,chunk85,一一对应2个sample,chunk86-88,一一对应一个sample,chunk89,一一对应2个sampl
1.MP4解析工具
mediainfo
mp4box:http://download.tsi.telecom-paristech.fr/gpac/mp4box.js/filereader.html
对于Mp4来说,都是一个个box来去组织元素。比如竖着的box,就是一个个box,这里就是重点关注moov。mp4⽂件由box组成,每个box分为Header和Data。其中Header部分包含了box的类型和⼤⼩,Data包含了⼦box或者数据,box可以嵌套⼦box。MP4⽂件的基本组成单元是box,也就是说MP4⽂件是由各种各样的box组成的,有parent box,还有children box。因此,这些boxes之间存在⼀定的层次关系,总结如下表所示,表中标记出了各个box必选或可选特性,√代表Box必选。
下图是⼀个典型mp4⽂件的基本结构:
Hexinator:
Mp4box和mediainfo对比。
这个trak,就是表示是多少路音频和视频。每一个AVstream都对应一个track。每个track,可能都有宽高信息,编译器信息,采样率,声道,time_base(time_scale)。
表示track width和heigth使用了4个字节。
视频和音频的时间刻度不一样。
音视频的handler也不一样。
比较重要的sample table。
一个MP4文件分为多个track,一个track分为多个chunk,一个chunk有多个sample。mp4数据索引和真正的数据分开区域存储。
先用数据索引找到moov,然后才能找到mdat。
下面的解释就是chunk1-84,都是一一对应1个sample,chunk85,一一对应2个sample,chunk86-88,一一对应一个sample,chunk89,一一对应2个sample,chunk90,一一对应一个sample。
可以根据sample size去读取每一帧数据的大小。
ftyp
File Type Box,⼀般在⽂件的开始位置,描述的⽂件的版本、兼容协议等。
moov
Movie Box,包含本⽂件中所有媒体数据的宏观描述信息以及每路媒体轨道的具体信息。⼀般位于放在⽂件末尾,但如果为了⽀持http边下载边播放则需要将moov提前。注意,当改变moov位置时,内部⼀些值需要重新计算。
mdat
Media Data Box,存放具体的媒体数据。
Moov Insider
mp4的媒体数据信息主要存放在Moov Box中,是我们需要分析的重点。moov的主要组成部分如下:
mvhd
Movie Header Box,记录整个媒体⽂件的描述信息,如创建时间、修改时间、时间度量标尺、可播放时⻓等。
下图示例中,可以获取⽂件信息如时⻓为 Duration: 5016 ms秒。
udta
User Data Box,⾃定义数据。
track
Track Box,记录媒体流信息,⽂件中可以存在⼀个或多个track,它们之间是相互独⽴的。
每个track包含以下⼏个组成部分:
tkhd
Track Header Box,包含关于媒体流的头信息。下图示例中,可以看到流信息如视频流宽度800,⻓度1920。
⾳频的tkhd,则⽐如duration、volume等。
mdia
Media Box,这是⼀个包含track媒体数据信息的container box。⼦box包括:
mdhd:Media Header Box,存放视频流创建时间,⻓度等信息。
hdlr:Handler Reference Box,媒体的播放过程信息。
minf:Media Information Box,解释track媒体数据的handler-specific信息。minf同样是个containerbox,其内部需要关注的内容是stbl,这也是moov中最复杂的部分。stbl包含了媒体流每⼀个sample在⽂件中的offset,pts,duration等信息。想要播放⼀个mp4⽂件,必须根据stbl正确找到每个sample并送给解码器。
mdia展开如下图所示:
mdhd
Media Header Box,存放视频流创建时间,⻓度等信息。视频的mdhd,Time scale,Duration等信息。
⾳频的mdhd,也类似视频,但要注意Time scale,我们在计算时间戳的时候都要使⽤该Time scale,对应我们流⾥⾯的AVStream->time_base。
hdlr
Handler Reference Box,媒体的播放过程信息。视频的hdlr,重点Component subtype: vide。
⾳频的hdlr,Component subtype: soun,如果我们多个⾳轨的时候,Component name:粤语。
我们分析的⽂件另⼀路⾳轨,根据不同的名字来进行区别。
minf
minf:Media Information Box,解释track媒体数据的handler-specific信息。minf同样是个containerbox,其内部需要关注的内容是stbl,这也是moov中最复杂的部分。stbl包含了媒体流每⼀个sample在⽂件中的offset,pts,duration等信息。想要播放⼀个mp4⽂件,必须根据stbl正确找到每个sample并送给解码器。
⽽且需要注意的是,minf⾥⾯的⼦容器,⾳频和视频轨是有区别的,⽐如视频轨:vmhd, ⾳频轨则为:smhd
vmhd
smhd
Stbl Insider
Sample Table Box,上⽂提到mdia中最主要的部分是**存放⽂件中每个sample信息的stbl。**在解析stbl前,我们需要区分chunk和sample这两个概念。
在mp4⽂件中,sample是⼀个媒体流的基本单元,例如视频流的⼀个sample代表实际的nal数据。chunk是数据存储的基本单位,它是⼀系列sample数据的集合,⼀个chunk中可以包含⼀个或多的sample。
stbl⽤来描述每个sample的信息,包含以下⼏个主要的⼦box:
stsd
Sample Description Box,存放解码必须的描述信息。下图示例中,对于h264的视频流,其具体类型为 avc1 ,extensions中其中存放有sps,pps等解码必要信息。
视频的stsd
⾥⾯包含了avc1,avc1⾥⾯⼜包含了avcC和pasp。
avc1:包含了视频Width、Height。
avcC:包含了视频编码器相关的信息,包括sps、pps等信息。
⾳频的stsd
用Hexinator分析,包含了音频相关的信息,比如采样率,通道数。
stts
Time-to-Sample Box,定义每个sample时⻓。Time-To-Sample的table entry布局如下:
stts table entry布局
sample count:sample个数。
sample duration:sample持续时间。
持续时间相同的连续sample可以放到⼀个entry⾥达到节省空间的⽬的。这⾥先给出来的是视频的stts,Number of entries,这个参数需要注意并不是sample的个数,sample的实际数量需要将每个entry的sample count进⾏累加才是真正的sample个数。
下图示例中,第1个sample时间为3720,单位⽤mdhd的time scale进⾏换算,⽐如视频的是90000,此时换算成秒为3720/90000 = 0.0413333333333333秒。
再给出个⾳频的stts,只是mdhd的time scale的差别,之前我们看到⾳频为44100,则计算第⼀个sample的时间。1024/44100=0.0232199546485261秒。
stss
Sync Sample Box,同步sample表,存放关键帧列表,关键帧是为了⽀持随机访问。
stss的table entry布局如下:
stss table entry布局
下图示例中,该视频track有3个关键帧:
stsc
Sample-To-Chunk Box,sample-chunk映射表。
上⽂提到mp4通常把sample封装到chunk中,⼀个chunk可能会包含⼀个或者⼏个sample。Sample-To-Chunk Atom的table entry布局如下图所示:
First chunk:使⽤该表项的第⼀个chunk序号
Samples per chunk:使⽤该表项的chunk中包含有⼏个sample
Sample description ID:使⽤该表项的chunk参考的stsd表项序号
下图示例中,可以看到该视频track⼀共有1个stsc表项,chunk序列1-x,每个chunk包含⼀个sample。这⾥则说明每个chunk⾥⾯只有⼀个sample(⼀个chunk是可以有多个sample)。
stsz
Sample Size Box,指定了每个sample的size。Sample Size Atom包含两sample总数和⼀张包含了每个sample size的表。
sample size 表的entry布局如下图:
下图示例中,该视频流⼀共有110个sample,第1个sample⼤⼩为42072字节,第2个sample⼤⼩为7354个字节。
stco
Chunk Offset Box,指定了每个chunk在⽂件中的位置,这个表是确定每个sample在⽂件中位置的关键。该表包含了chunk个数和⼀个包含每个chunk在⽂件中偏移位置的表。每个表项的内存布局如下:
需要注意,这⾥stco只是指定的每个chunk在⽂件中的偏移位置,并没有给出每个sample在⽂件中的偏移。想要获得每个sample的偏移位置,需要结合 Sample Size box(stsz)和Sample-To-Chunk(stsc) 计算后取得。
下图示例中,该视频流第1个chunk在⽂件中的偏移为4750,⽽这⾥是每个chunk只有⼀个sample,此时第⼀个sample的起始位置就为4750->0x1D78,数据⼤⼩则参照stsz,第⼀个sample size为172818。
⽐如偏移位置,7544->0x1D78。
如何计算sample偏移位置
上⽂提到通过stco并不能直接获取某个sample的偏移位置,下⾯举例说明如何获取某⼀个pts对应的sample在⽂件中的位置。
⼤体需要以下步骤:
(1)将pts转换到媒体对应的时间坐标系。
(2)根据stts((decoding) time-to-sample)计算某个pts对应的sample序号。
(3)根据stsc(sample-to-chunk)计算sample序号存放在哪个chunk中。
(4)根据stco(chunk offset)获取对应chunk在⽂件中的偏移位置。
(5)根据stsz获取sample在chunk内的偏移位置并加上第4步获取的偏移,计算出sample在⽂件中的偏移。
例如,想要获取3.64秒视频sample数据在⽂件中的位置。
(1)根据time scale参数,将3.64秒转换为视频时间轴对应的3640000 (假如时间刻度不为毫秒)。
视频轨:time scale为90000,转成对应的时间戳为3.64秒*90000。
(2)遍历累加下表所示stts所有项⽬,计算得到3640000位于第110个sample = 327600。
(3)计算出多个sample_deltas叠加才到了327600, 我们这⾥姑且按3780作为平均值计算,实际是37201+37801+36901+37802 … 这样⼀直叠加进⾏。327600/3780 =86.66666666666667,取整为86。
(4)查询下表所示stsc所有项⽬,计算得到第86个sample位于第86个chunk,并且在该chunk中位于第1个sample(因为我们的码流是每个chunk对应了⼀个sample)。
(5)查询下表所示stco所有项⽬,得到第86个chunk在⽂件中偏移位置为1004678。使⽤hexinator。
(6)查询下表所示stsz所有项⽬,得到第86个sample的size为20934。计算得到3.64秒视频sample数据在⽂件中。
offset:1004678+0 = 1004678
size:20934。
验证:⽤编辑器打开mp4⽂件,定位到⽂件偏移1004678位置。09分隔符,这⾥占⽤了6个字节, 再看真正的数据区域,前4字节也为 NALU的⻓度0x000051bc=20924。
总共占⽤的字节计算 4+2+4+20924 = 20934。
《整理mp4协议重点,将协议读薄》:
⼀个chunk含有多个sample的情景参考下⾯链接进⾏分析:
mp4⽂件格式重点解析:https://www.jianshu.com/p/44c9567d8fcb
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)