MP4封装格式介绍及解析

概述

MP4文件由许多box组成,每个box包含不同的信息, 这些box以树形结构的方式组织。以下是主要box的简要说明:

 

根节点之下,主要包含三个节点:ftyp、moov、mdat。

  • ftyp:文件类型。描述遵从的规范的版本。
  • moov box:媒体的metadata信息。
  • mdat:具体的媒体数据。

说明:在 mp4 中默认写入字节序是 Big-Endian的。


分析mp4文件的工具:

  1. mp4box.js:一个在线解析mp4的工具。
  2. bento4:包含mp4dump、mp4edit、mp4encrypt等工具。
  3. MP4Box:类似于bento4,包含很全面的工具。
  4. mp4info.exe: windows平台图形界面展示mp4基本信息的工具。

下图为使用mp4info.exe打开mp4文件的界面:

1 文件,由许多BoxFullBox组成。

2 Box,每个BoxHeaderData组成。

3 FullBox,是Box的扩展,Box结构的基础上在Header中增加8bits version24bits flags

4 Header,包含了整个Box的长度size和类型type。当size==0时,代表这是文件中最后一个Box;当size==1时,意 味着Box长度需要更多bits来描述,在后面会定义一个64bitslargesize描述Box的长度;当typeuuid时,代表Box中的数 据是用户自定义扩展类型。

5 Data,是Box的实际数据,可以是纯数据也可以是更多的子Boxes

6 当一个BoxData中是一系列子Box时,这个Box又可成为Container Box

结构如下图:

1  ftypbox,在文件的开始位置,描述的文件的版本、兼容协议等;

2  moovbox,这个box中不包含具体媒体数据,但包含本文件中所有媒体数据的宏观描述信息,moov box下有mvhdtrak box

        >>mvhd中记录了创建时间、修改时间、时间度量标尺、可播放时长等信息。

        >>trak中的一系列子box描述了每个媒体轨道的具体信息。

3  moofbox,这个box视频分片的描述信息并不是MP4文件必须的部分,但在我们常见的可在线播放的MP4格式文件中(例如Silverlight Smooth Streaming中的ismv文件)确是重中之重

4  mdatbox,实际媒体数据。我们最终解码播放的数据都在这里面。

5  mfrabox,一般在文件末尾,媒体的索引文件,可通过查询直接定位所需时间点的媒体数据

附:Smooth Streamingismv文件结构,文件分为了多个Fragments,每个Fragment中包含moofmdat。这样的结构符合渐进式播放需求。(mdat及其描述信息逐步传输,收齐一个Fragment便可播放其中的mdat)。

 

 

MP4文件结构

MP4是由Atom嵌套来存放媒体信息。Atom的基本结构是:

[4bytes atom length] [4bytes atom name] [8bytes largesize, if size ==1] [contents of the atom, if any]

结构如图:

SIZE表示整个Atom所占用的大小,包括header部分。如果Atom很大,超过了uint32的最大数值(例如存放具体视频数据的mdat),size就被设置为1,并用接下来的LARGESIZE来存放数据大小。
TYPE表示这个Atom的类型,主要有ftyp、moov、mdat等。
LARGESIZE,如果SIZE==1,则使用8bytes uint64来存储该Atom大小。
DATA是实际的数据。

MP4文件含有很多Atom,主要的Atom和嵌套结构如图。

一个典型的MP4文件实例
MP4文件需要有ftyp、moov、mdat,它们都是顶级Atom,不能被其他Atom嵌套。
ftyp标示了MP4文件,必须出现在第一个。
moov保存了视频的基本信息,

mdat保存视频和音频数据。这两个Atom顺序不固定。

一个moov在mdat之后的MP4文件的Atom结构:

由于moov中保存了视频数据的索引,对于在线播放的场景,需要moov在mdat之前,才能流式读取视频数据。

部分摄像设备生成的MP4文件中,moov在mdat之前,两者之间可能还存在一个Atom free,即moov-free-mdat。
free中为全0,只是用于占位。

一个moov在mdat之前的MP4文件的Atom结构:

 

FTYP

ftyp是整个文件的第一个Atom,通过判断该Atom来确定文件的类型。该Atom有且只有1个,并且只能被包含在文件层,而不能被其他Atom包含。该Atom应该被放在文件的最开始,指示文件的相关信息。
文件的最开始的四个字节就是“ftyp”Atom的大小,然后是该Atom的类型。 “ftyp”的body依次包括1个32位的major brand(4个字符),1个32位的minor version(整数)和1个以32位(4个字符)为单位元素的数组compatible brands。这些都是用来指示文件应用级别的信息。以一个MP4文件的“ftyp”Atom为例,如下所示:

0000000: 0000 0018 6674 7970 6d70 3432 0000 0000 ....ftypmp42....
0000010: 6d70 3432 6d70 3431 0528 834f 6d64 6174 mp42mp41.(.Omdat

其中,
(1)0x00 00 00 18是“ftyp”Atom的大小,为24个字节,这在一般情况下为一个固定值。
(2)0x66 74 79 70是“ftyp”四个字符的ASCII值,也就是该Atom的类型。
(3)0x6D 70 34 32是major brand,这里为“mp42”,对于不同的文件,该值可能是不一样的。
(4)0x00 00 00 00是minor version。
(5)0x6D 70 34 32和0x6D 70 34 31是compatible brands,”mp42”和“mp41”

FTYP到底是什么呢?
ftyp就是一个由四个字符组成的码字,用来标识编码类型、兼容性或者媒体文件的用途。它存在于MP4文件和MOV文件中,当然,也存在于3GP文件中。
虽然MP4文件、MOV文件和3GP文件采用了相同的封装标准,但由于是由不同的厂商合成,因此还是存在差别的。即使是同一种媒体文件,比如MP4文件,由不同developers开发的MP4也是存在差别的。ftyp简单的说就是为了标识它的developer是谁,兼容哪些标准等。
比如上面的例子,“mp42”表示它的major brand是MP4 v2 [ISO 14496-14],而“mp42”和“mp41”则表示它的compatible brands是MP4 v2 [ISO 14496-14]和MP4 v1 [ISO 14496-1:ch13]。

 

FREE(可选的)

free是可选的,如果存在,则通常出现在moov与mdat之间,即moov-free-mdat。
free中的数据通常为全0,其作用相当于占位符,在实时拍摄视频,moov数据增多时分配给moov使用。
因为设备录制视频时并不能预先知道视频数据大小,如果moov在mdat之前,随着拍摄mdat的数据会增加,moov数据也会增多,如果没有free预留的空间,则要不停的向后移动mdat数据以腾出moov空间。

 

MOOV

moov中主要保存了媒体的时间信息、trak信息和媒体索引等。

3.1 媒体时间信息
moov-mvhd中有一个time scale,以1/n秒的形式给出一个总的时间粒度,moov-trak-tkhd中以此时间粒度给出各个track的duration;

3.2 trak信息
moov中通常包含两个trak,一个视频索引,一个音频索引。

3.2.1 trak类型
trak的类型在moov-trak-mdia-hdlr中给出,包括’vide’, ‘soun’和’hint’三种。

3.2.2 trak的时间
moov-trak-mdia-mdhd中以1/n秒的形式给出各个媒体的时间粒度以及以此时间粒度为单位的duration。
moov-trak-mdia-minf-stbl-stts中有媒体帧之间的时间间隔,单位是moov-trak-tkhd中的时间粒度。

3.2.3 索引信息
moov-trak-mdia-minf-stbl比较重要,其中保存了解码器需要的信息和索引信息,以下Atom都是stbl Atom的孩子。
stsd中保存了解码器需要的媒体描述信息。

stss, Sync Sample Atom
标识了媒体流中的关键帧,提供了随机访问点。每个entry标识了一个关键帧。
 

关键帧号是按照增长顺序排列的。如果该Atom不存在,表示所有帧都是关键帧。

Sync Sample Table的布局

stts, Time-To-Sample Atoms
stts给出每个数据帧之间的时间间隔,单位是moov-trak-tkhd中的时间粒度。
Atom的每个entry给出了具有相同时间间隔的连续帧的个数,这些帧的时间间隔值,结构如图。

Time-To-Sample的table entry布局

如果连续的帧有相同的时长,他们会被放在同一个entry中。如果所有的帧具有相同的时长,那么Atom中就只有一个entry。

stts实例
下图通过3个entries来描述9个帧。需要说明的是,这里的entry和chunk不是对应的。例如,4、5、6帧可以在同一个chunk中,但是,由于它们的时长不同,4帧的时长为3,而5、6帧的时长为1,因此,保存在不同的entry中。

stco/co64, Chunk Offset Atom
stco/co64给出每个数据Chunk在文件中的偏移。Chunk Offset Atom的每个entry给出了每个chunk在文件中的偏移。
如果Chunk Offset Atom的类型为stco,则保存的偏移量是32位;如果是co64,则保存的偏移量是64位的。布局如图。

chunk offset table的布局
需要注意的是,该Atom中只给出了每个chunk的偏移量,并没有给出每个sample的偏移量。因此,如果要获得每个sample的偏移量,还需要用到Sample Size Table和Sample-To-Chunk Table。

stsc, Sample-To-Chunk Atom
stsc给出各个数据Chunk中包含的数据帧。一个chunk可能会包含一个或者几个帧。每个chunk会有不同的size,每个chunk中的帧也会有不同的size。
entry中保存了第一个chunk号、每个chunk包含的帧数、帧描述ID。

Sample-To-Chunk Atom的table entry布局

每个entry包含一组chunk,其中每个chunk的帧数相同。而且,这些chunk中的每个帧都必须使用相同的帧描述。
如果chunk中的帧数或者帧描述改变,必须创建一个新的entry。
如果所有的chunk包含的帧数和帧描述相同,那么只有一个entry。

stsc实例
图中表示至少有5个chunk,第1、2个chunk分别包含3个帧,帧描述ID是23;第3、4个chunk分别包含1个帧,帧描述ID是23;第5个及以后的chunk,包含1个帧,帧描述ID是24。
对于最后一个entry需要特殊的处理,因为无法判断什么时候结束。

stsz, Sample Size Atom

sample size table的布局

3.3 用户定义数据
udta中保存了用户定义数据,例如iTune使用的meta数据就保存在udta中。

3.4 用户扩展数据
Atom的扩展通过uuid实现。用户可以使用类型为’uuid’的Atom,以16个特定的字节作为标识,定义自己的数据格式。

 

MDAT

所有媒体数据统一存放在mdat中,没有同步字,没有分隔符,只能根据索引进行访问。
mdat的位置比较灵活,可以位于moov之前,也可以位于moov之后,但必须和stbl中的信息保持一致。

另外,在写mp4文件的时候,对于mdat这个Atom,一般是先将Atom size填写0,待数据写完之后,再回过来填入具体大小。

 

参考

https://www.cnblogs.com/jingzhishen/p/3698570.html

https://blog.csdn.net/sdsszk/article/details/90719075

https://www.jianshu.com/p/529c3729f357

https://blog.csdn.net/uyy203/article/details/79283087

https://blog.csdn.net/hejjunlin/article/details/73162841

https://www.jianshu.com/p/529c3729f357

 

  • 6
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
封装了开源工程, mp4v2, mpeg4ip代码, /******************************************/ /* Name:Mp4Interface.h /* Mark:mp4封装解析接口 /* author: machh /* date:2012.5.12. /******************************************/ #ifndef _MP4_INTERFACE_ #define _MP4_INTERFACE_ #include "MP4Writer.h" #include "MP4Reader.h" typedef struct _tagPACKET_HEADER_ { _tagPACKET_HEADER_() { nType = 0; nLen = 0; nTime = 0; } unsigned long nTime; // 不依赖于I帧的参考时间 int nLen; // 不包括该头结构本身大小 int nType; }PACKET_HEADER; enum FILE_OPEN_MODEL { OPEN_MODEL_W, // 写文件 OPEN_MODEL_R // 读文件 r+b }; class Mp4Interface { public: Mp4Interface(void); ~Mp4Interface(void); public: /* * @mark 打开文件 * @param[in] nModel文件打开方式,如果nModel为OPEN_MODEL_W 则创建文件 * @param[in] nMediaType 值为 MP4_VIDEOTYPE_H264/MP4_VIDEOTYPE_MPEG4 * @return 操作结果 */ int OpenFile(const char* strPath, FILE_OPEN_MODEL nModel, int nMediaType ); /* * @Name:Close * @mark: 读写操作结束之后,必须调用此函数 */ int Close(); public: //写操作 /* * @mark: 添加视频轨道 * @param[out] nTrackId 轨道ID * @param[in] nWidth,nHeight 画面宽高 * @param[out] fps 帧率 */ int AddVideoTrack (int& nTrackId, const int nWidth, const int nHeight, const double fps ); /* * @mark: 写一帧数据 * @param[in] lpData 要写入的数据 * @param[in] nSize 要写入的数据大小 * @param[in] nTimestamp 时间戳 */ BOOL WriteFrame( const int nTrackId, const uint8_t* const lpData, const int nSize, const MP4Timestamp nTimestamp, const MP4Duration nDuration = -1 ); /* * @mark: 添加音频轨道 aac * @param[out] nTrackId * @param[in] nSamplePerSec (timescale) */ int AddAudioTrack( int& nTrackId, const int nSamplePerSec, const int nSamplePerFrame = -1 ); public: //读操作 /* * @mark : 全局函数,读取整个MP4文件结构信息, * 函数内部会fopen/fclose,该函数应当在OpenFile之前调用 * @param[in] strFile 文件路径 * @param[out] lsTrackInfo 输出文件信息结构 */ bool static GetMP4FileInfo( const char* strFile, MP4_TRACKINFO_LIST& lsTrackInfo ); /* * @mark:SetReadTrackId设置轨道ID, */ int SetReadTrackId( int nTrackID ); /* * @mark:获取轨道信息,该函数必须在OpenFile(……)之后调用才有效 */ int GetTrackInfo( MP4_TRACK_INFO* pstTrackInfo ); /* * @mark: 读取一帧数据 */ int ReadFrameData( DWORD nSampleId, BYTE** ppFrame, DWORD* pnBufSize,int& nFrameType ); protected: int m_nOpenModel; int m_nMediaType; CMP4Writer * m_pMp4Writer; CMP4Reader * m_pMp4Reader; }; #endif// end

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值