前言
MP4,又称为MPEG-4 Part 14,是一种多媒体容器格式(container),扩展名为.mp4
发展历史
- 2001年,apple的QuickTime格式,.qt和.mov的后缀名。
- 2001年,MPEG-4 Part1,把基于QuickTime的box布局的容器格式添加到了MPEG-4标准。
- 2004年,MPEG-4标准文档把编码和容器格式分开为两部分进行说明。
MPEG-4 Part12:定义了容器格式通用的box结构,即ISO基本媒体文件格式(ISO base media file format, ISOBMFF)。
MPEG-4 Part14:基于Part12进行了细化,是对Part-12的一种实现,定义了用于存储MPEG-4内容的容器格式,即.mp4格式。
两者间的关系:

MP4在线分析工具
- mp4parser
界面如下:

- onlinemp4parser
界面如下:

MPEG-4
MPEG-4协议中不同部分描述的对象:

MP4解析
MP4文件中,媒体信息储存在“moov”的box中,一个moov通常包含若干个track信息,每个track都是一个随时间变化的媒体序列,Track里面基本时间单位为sample,sample是按照时间顺序排列,为了方便存取,若干个sample会被组织成一个chunk;而媒体数据存储在mdat box中,即sample是存储在mdat box中。如下图为sample和chunk关系:

重要概念
Box
MP4文件有若干个box组成。
下图为box的内部结构图:

可以看到:
- Box由header和body组成,header大小为8 bytes, 主要有size和type两个栏位。
size: 是包含box header的整个box的大小。
type: 通常是4个ASCII码的字符如“ftpy”,“moov”。这些box type都是mp4 spec已经预定义好的,表示特定的含义。!! 两种特殊情况: 当size为1,则表示box长度超出4字节表示范围,就会用largesize(8 bytes)来表示box大小。 当size为0,表示该box为文件的最后一个box,这种情况只存在“mdat”类型的box中。如果是“uuid”,表示该box是用户自定义的拓展类型 - 规范还定义了一种Fullbox,Fullbox是box的扩展。header中加入了1字节的version和3字节的flags字段,整个header大小是12bytes。
结构如下图:

- box中可以嵌套box,这种box被称为container box,比如moov box。而ftyp box不是container box,因为没有包含其它box。
Track
相同类型的sample的集合,对于媒体数据来说,一个track表示一个视频序列、一个音频序列或一个字幕序列。
Sample
Mp4中多媒体数据存储的最小单位。
video sample表示一帧或者一组连续的视频帧,audio sample表示一段连续的音频(也可以说是一帧音频帧)。
Chunk
一个track的几个sample组成的单元。
BOX详解
MP4文件由许多box组成,每个box包含不同信息,这些box以树型结构的方式组织。如下图:

可以看到在根节点之下,主要包含三个重要节点:ftyp、moov、mdat。
- ftyp:file type 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 } - moov:记录了媒体数据的时空间信息
是一个container box
一般情况下包含一个mvhd box和若干个trak box。 - mdat:具体的媒体数据。
内部结构为:aligned(8) class MediaDataBox extends Box(‘mdat’) { bit(8) data[];//e.g h264 bit stream }
下图为具体每个box的描述:

需要说明的是:在 mp4 中默认写入字节序是Big-Endian的。
大端字节序(big-endian):按照内存地址增长的方向,高位数据存储于低位内存中。
小端字节序(little-endian):按照内存地址增长的方向,高位数据存储于高位内存中。
如存储 0x12 34 56 78:
-------------------------------------------------------
地址 0x100 0x101 0x102 0x103
big 12 34 56 78
little 78 56 34 12
STBL
stbl box是moov中的container box,储存该MP4文件的媒体数据时空间信息,是解析mp4文件的关键。
其中包括:
Stsd(sample description)
Full Box,该box记录trak的编解码信息。如编码类型、分辨率等等。
如果是H.264或者H.265的话,该box中还包含关键解码信息(SPS、PPS)的box:AVCC box 或者HVCC box。

Stts(Decompressing time to sample):
该box记录编解码时间戳与样本的关系。
关键参数:
Entry_count: 时间戳与样本对应关系的组数。
Sample_count: 遵循该对应关系的样本数量。
Sample_delta: 每个样本解码时间戳的差值,解码时间戳初始值是0。
例:

那说明该片源有1434个DTS相差为1000的sample。(因为entry_count为1,所以该片源只有1434个sample)
Ctts(composition time to sample):
该box记录显示时间戳(PTS)与编码时间戳(DTS)的差值,从而间接的说明显示时间戳与样本的关系,对于H.264 && H.265,只有当存在B帧的时候,DTS和PTS才会有差异,这个box才会存在。
关键参数:
Entry_count: 时间戳与样本对应关系的组数。
Sample_count:遵循该对应关系的样本数量。
Composition_offset:显示时间戳与解码时间戳的差值。
对应关系是:
pts(n) = dts(n) + composition_offset(n)
例:

那说明该片源有1334组显示时间戳和编码时间戳的差值对应关系,其中:
第一组:第一个sample的pts与dts的差值为1000,即pts(0) = dts(0) + 1000
第二组:第二个sample的pts与dts的差值为4000,即pts(1) = dts(1) + 4000
...
Stss(sync sample):
该box记录每个参考帧的样本序号。在做跳转的操作时,需要从参考帧开始解码,否则会花屏。
关键参数:
Entry_count: 参考帧的数量。
Sync_sample.n:每个参考帧的样本序号。
例:

那说明该片源总共有19个关键帧(entry_count):
第一个关键帧所在sample的序号为1,
第二个关键帧所在sample的序号为32,
...
Stsz(sample size):
该box记录每个样本的大小,单位为byte。
关键参数:
Sample_count: 样本总数量。
Sample_size.n: 每个样本的大小。
例:

那说明该片源总共有1434个video sample,
其中第一个sample大小为1345bytes,
第二个sample大小为216bytes
…
Stsc(sample to chunk):
该box记录chunk和sample数量的对应关系。chunk是MP4中为方便跳转而定义的一种数据组织形式
关键参数:
Entry_count:chunk和样本数量的对应关系的组数。
First_chunk:chunk序号,从1开始。
Sample_per_chunk:该对应关系中每个chunk里面样本数量。
例:

对应关系只说明了对应关系的起始chunk序号,至于还有多少个chunk有这种对应关系,需要看下一个对应关系的frist_chunk,
比如:
Chunks.0这个对应关系:序号1的chunk里面有10个sample。
Chunks.1这个对应关系:从序号为2的chunk开始到序号为143的chunk结束,这之间的每个chunk都有10个sample。
chunks.2这个对应关系:从序号为144开始到最后一个chunk结束,这之间的每个chunk有4个sample。
Stco(chunk offset)
该box记录每个chunk在文件中的偏移位置。
关键参数:
Entry_count: chunk的数量。
Chunk_offset.n:每个chunk的偏移位置。
例:

那说明该片源有144个video chunk,其中:
第一个chunk在文件开始第32个字节,
第二个chunk在文件开始第29235个字节,
…
注意:结合stsz和stsc两个table,就可以得到每个sample在文件中的偏移量。
比如,需要知道序号为n的sample在文件中的偏移量,那么:
- 通过查找stsc box,得知序号为n的sample在哪个chunk,并知道该chunk的起始sample序号start_n
- 通过查找stsz知道sample序号为start_n到序号为n的sample这中间的大小,如diff_size.
- 通过查找stco知道序号n所在chunk的偏移位置pos。
- 最后得出序号n的sample在文件中的偏移是 (pos + diff_size).
关键参数的计算
确定trak box类型
从moov-trak-mdia-hdlr中找到handler_type即可确认。
“soun” --> 该trak box是audio
“vide” --> 该trak box是video
例:

计算片源播放时长(duration)。
从moov-mvhd中找到time_scale和duration。
T = duration/time_scale。
(time_scale表示1秒内有多少个基本时间单位,而duration表示这个片源有多少个时间单位)
例:

Mvhd box的timescale = 90000,duration = 5381224,所以时长是 5381224/90000 ≈ 59秒
计算视频或音频的播放时长(duration).
从moov-mvhd-trak-mdia-mdhd中找到time_scale和duration。
T = duration / time_scale
例如:

则 T(video) = 16659643 / 24000 = 694.1518 s。
计算视频的分辨率(resolution)。
从moov-(vide)trak-tkhd中找到视频的length和width既可。
例:

则视频分辨率为 1280*720。
对于AVC和HEVC编码格式,可以从
moov-(vide)trak-mdia-minf-stbl-stsd-avc1或者moov-(vide)trak-mdia-minf-stbl-stsd-hvc1中找分辨率。

计算视频的帧率(framerate)
从moov-(vide)trak-mdia-mdhd中找到timescale
例:

从moov-(vide)trak-mdia-minf-stbl-stts找到每个sample的时间差值
例:

则帧率为f = time_scale/ sample_delta = 24000 / 1000 = 24fps。
计算音频采样率(sample rate)。
从moov-(aud)trak-tkhd中找到timescale
例:

则音频采样率是 44100 Hz
计算video的dts和pts。
- 从moov-(vide)trak-mdia-minf-stbl-stts中找到sample_count和sample_delta。
则dts就是从0开始,步长为sample_dalta的等差数列。 - 从moov-(vide)trak-mdia-minf-stbl-ctts中找到entry_count以及sample_count和composition_offset。
则每一帧的pts就是 [该帧dts] 与 [composition_offset] 的和。
!!注意:
1. 计算出来的dts和pts要除以moov-trak-mdia-mdhd中的timescale,单位才是秒
2. 在多个box中都存在timescale参数,表示意义是相同的(都表示1秒内有多少个基本时间单位),但使用场景不同。
moov-mvhd中的timescale用于与moov-mvhd中的duration配合,计算总播放时长
moov-trak-mdia-mdhd中的timescale用于进行dts和pts的单位转化以及计算视频时长和音频时长。。
例:
Stts:

Ctts(composition time-to-sample, 创作时间与样本的转化关系):

那么,
DTS : 0 1000 2000 3000 4000 5000
CTTS: 1000 4000 1000 -999 0 2000
PTS : 1000 5000 3000 2001 4000 7000
type: I1 P1 B1 B2 B3 P2
注意:
如果某个sample_count > 1,说明有连续若干帧的pts与dts的差值是一样的。
如果没有ctts box,那么说明该视频没有B帧,解码顺序和显示顺序是一致的,DTS == PTS。
示例代码:
else if (box_type_equa(uint32_to_str(bh.type, sbuffer), "ctts"))// 存在ctts,说明dts与pts不相等
{
...
uint32_t i = 0, j = 0, num = 0, pos = 16;
for (i = 0; i < entry_cnt; i++) //entry_cnt从stts中获取
{
uint32_t sample_cnt;
read_net_bytes_to_host_uint32(&box[pos], &sample_cnt);
pos += 4;
uint32_t sample_offset;
read_net_bytes_to_host_uint32(&box[pos], &sample_offset);
pos += 4;
for (j = 0; j < sample_cnt; j++) //sample_cnt从ctts中获取
{
//通过dts与offset的和计算得到pts
PushBack_Array(pts_array, At_Array(dts_array, num++) + sample_offset);
printf("dts : %9.3f ms | pts : %9.3f ms | \n",
At_Array(dts_array, num - 1) / (mdhd_time_scale * 1.0),
At_Array(pts_array, num - 1) / (mdhd_time_scale * 1.0));
}
...
}
文件中Sample数量。
- 从moov-trak-mdia-minf-stbl-stsz中的sample_count得到。
- 从moov-trak-mdia-minf-stbl-stts中将所有entry的sample_count相加得到。
- 从moov-trak-mdia-minf-stbl-ctts中将所有entry的sample_count相加得到。
- 从moov-trak-mdia-minf-stbl-stsc中将所有entry中每个chunk中的sample数量相加得到。
文件中Chunk数量。
- moov-trak-mdia-minf-stbl-stco中的entry_count就是chunk数量。
- 从moov-trak-mdia-minf-stbl-stsc中最后一个entry的frist_chunk就是chunk数量。
如何probe一个数据流为MP4类型?
数据流开头第5个字节到第8个字节是’ftyp’、‘moov’、'mdat’和’pnot’之一,则说明该数据流为MP4。
如何判断一个box是普通box还是full box?
规范中有规定具体哪些box是普通box,哪些是full box。
常见普通box有:ftyp,moov,trak
常见full box有:mvhd、tkhd
如何判断该mp4文件中的video/audio codec type?
stsd box的container box的type就是code type 4cc,
比如下面:

code type 4cc为"avc1",codec type是H264。
又比如:

code type 4cc为"mp4a",codec type是aac。

本文详细介绍了MP4的格式,包括其发展历史、在线分析工具和MPEG-4协议。重点解析了MP4文件中的Box结构,如STBL、Stsd、Stts、Ctts、Stss等,以及如何计算关键参数,如trak box类型、播放时长、分辨率、帧率、音频采样率等。此外,还提供了如何识别MP4文件、Box类型和codec type的方法。
1万+

被折叠的 条评论
为什么被折叠?



