首页 > 代码库 > MP4格式分析
MP4格式分析
1.先说几个基本概念
Sample: 采样,对于音视频来说就是一个编码帧;Sample_count即总帧数,Sample_index即帧下标。
在一个Chunk里面Sample顺序紧凑排列。
Tarck: 轨,是音频或者视频流信息定义的集合。一个轨包括一个或者多个Chunk。各轨之间有同步的问题。
Box(也叫atom): 盒,MP4文件各种信息定义的结构。一个MP4文件就是有很多个各种类型的Box组成的。
Box有固定的格式
4Bytes 4Bytes 8Bytes (Box_length-8)Bytes
b. box_length 包含了自己和4字节的type。因此一般情况下Box的负载是Box_length-8字节。
Box有两大类,一种是Container Box,这种Box里可以嵌套别的Box。它的负载就是其他的Box。
下图加^的Box就是Container Box。
另外一种就是单独的Box,里面定义某一些信息数据。
time_scale: 很多个Box里面都有time_scale,它定义了该媒体在1秒中内的采样刻度, 可以理解为一秒钟有多少个单元。
duration: 相对于time_scale的时间长度。真实的时长 = duration/time_scale。
例如我手头这个MP4文件,视频track的time_scale为25000. duration=9670000.
这样time = 9670000/25000 = 386.8s = 6m27s
不同Box定义的time_scale和duration可能是不同的,原因在于:
a. 文件时长和track时长本身就不一定是相同的,Mp4格式容许杂合多个不同长短不同起始时间的Tracks在同一个文件里面;
b. 一般mvhd,vmhd, smhd里面的time_scale,duration都不相同。所以一定注意要用同一组来计算。
2.一个基本的MP4文件Box结构如下:
Root (虚拟的没有这个box类型)
|----ftyp
|----moov^
| |----mvhd
| |----trak^
| | |----tkhd
| | |----mdia^
| | |----mdhd
| | |----hdlr
| | |----minf^
| | |----vmhd/smhd
| | |----dinf^
| | | |----dref^
| | | |----url
| | |----stbl^
| | |----stsd^
| | | |----mp4v/mp4a^
| | |----stts
| | |----stss
| | |----stsc
| | |----stsz
| | |----stco
| |----trak^
| | |---- ....
| | ....
| |
| |----udta^
| |---- meta^
| |----hdlr
| |----ilist^
|----mdat
|----free
3. 几个重要的Box,先总结如下,再各个叙述。
stbl Box是Mp4文件里面最复杂的Box. 有多种stxxBox, 其中st是Sample Table.
stsd: Sample Description, 具体音视频解码器的定义
stts: Time to Sample, 定义每个Sample的duration, 这个duration也是相对time_scale的单位
stss: Sync Sample, 定义同步Sample Index, 即I帧Sample的Index.
stsc: Sample to Chunk,定义Sample在多个Chunk里面的划分情况。
stsz: Sample siZe,定义每个Sample的字节长度
stco: Chunk Offset,定义各个Chunk在文件中的起始地址。
stts: Sample duration. 定义的方法类似于游程编码。
(Sample_number1, duration1), (Sample_number2, duration2), ... (Sample_numberN, durationN).
一个游程对定义了相同duration的连续Sample个数。N定义了有多少个这种游程对。
最常见的一种情况是所有的Sample具有相同的duration, 这样stts:
stts.entry_count = 1,
stts.sample_couint[0] = Sample_Count;
stts.sample_delta[0] = duration.
当给定一个时间,找对应的sample_index时需要用到stts.
stss: I帧Sample Index.
由于视频编码帧间依赖性,不是从任意Samplea开始都可以连续解码的,只有从I帧的Sample处才可以。
stss定义了随机访问点的Index值。在做Seek的时候需要用到stss
stsc: Sample在Chunk里面的分布表。有4个主要参数。
a. uint32_t entry_count, 定义一个Track里面包含多少个Chunk组,注意这里不是Chunk count,而是 chunk组 count.
所谓chunk组是指那些包含相同个数Samples,而且这些Samples的Sample Description相同的Chunk集合,类似游程编码。
b. uint32_t *first_chunk, 定义每个Chunk组起始Chunk index
c. uint32_t *samples_per_chunk, 定义每个Chunk组内各个Chunk包含Sample个数
d. uint32_t *sample_description_index,定义每个Chunk组内各个Chunk所包含的Sample的解码描述。
for example
inx first_chunk samples_per_chunk sample_description_index
0 1 13 1
1 2 12 1
2 806 9 1
意义:有3个chunk组.
组0 有 ( 2-1)个chunk (下标为 1), 每个chunk含13个samples。 组0有(2-1)*13帧
组1 有 (806-2)个chunk (下标为2..805), 每个chunk含12个Sample。组1有(806-2)*12帧
组2 有 1 个chunk* (下标为806), 这个chunk含9个samples。组2有1*9帧.
一个Chunk组内chunk数目chunk_count = first_chunk[n+1] - first_chunk[n]。
一般最后一个chunk组就只有一个chunk. 在stco里面有chunk count的定义。
如果不放心可以用那个数字来计算最后一个chunk组的chunk数目。
根据这个表,可以展开得到每一个Sample在Chunk中的分布。
stsz: 定义每个Sample长度,这个Box很大。
在计算某一个Sample的偏移地址时需要stsz。
stco/co64:定义每个Chunk相对于文件头的偏移地址。stco里面的地址是32位,co64里面的是64位,根据实际情况选用。
4.计算
输入i_chunk, 输出inx是Chunk组下标
uint32_t sample_to_chunk_group_index(MP4_Box_data_trak_t *p_track, uint32_t i_chunk) {
uint32_t i;
for(i=0; i<p_track->i_stsc_count-1; i++)
if(i_chunk >= p_track->i_stsc_first_chunk[i] && i_chunk < p_track->i_stsc_first_chunk[i+1]) break;
return i;
}
// 展开stsc,计算得到每个Chunk的i_sample_first, i_sample_count,i_sample_description_index 和 i_offset
unsigned int i_chunk;
uint32_t i_first = 0;
uint32_t i_chunk_group_index = 0;
for( i_chunk = 0; i_chunk < p_track->i_chunk_count; i_chunk++ ) {
mp4_chunk_t *ck = &p_track->chunk[i_chunk];
ck->i_offset = p_track->i_co64_sample_offset[i_chunk];
i_chunk_group_index = chunk_group_index(p_track, i_chunk); // 输出Chunk组下标
ck->i_sample_description_index = p_track->i_stsc_sample_description_index[i_chunk_group_index];
ck->i_sample_count = p_track->i_stsc_samples_per_chunk[i_chunk_group_index];
ck->i_sample_first = i_first;
i_first += p_track->i_stsc_samples_per_chunk[i_chunk_group_index];
}
// 由Sample_index计算chunk_index
uint32_t sample_to_chunk(MP4_Box_data_trak_t *p_track, uint64_t sample) {
uint32_t i;
for(i=0; i<p_track->i_chunk_count-1; i++)
if(sample >= p_track->chunk[i].i_sample_first && sample < p_track->chunk[i+1].i_sample_first) break;
return i;
}
// 由Sample_index计算该Sample数据起始地址
// sample起始地址 = 该sample所在Chunk偏移地址(stco) + 该sample在Chunk里面的偏移地址 (stsz)
// sample在chunk里面的偏移地址 = 该chunk的第一帧到该帧之前的Sample长度和
uint64_t smaple_to_offset(MP4_Box_data_trak_t *p_track, uint64_t sample) {
uint32_t chunk_inx = sample_to_chunk(p_track, sample);
uint64_t start_address = p_track->i_co64_sample_offset[chunk_inx];
if(p_track->i_stsz_sample_size == 0) {
uint32_t i;
for(i=p_track->chunk[chunk_inx].i_sample_first; i<sample; i++)
start_address += p_track->i_stsz_entry_size[i];
} else
start_address += (sample - p_track->chunk[chunk_inx].i_sample_first) * p_track->i_stsz_sample_size;
return start_address;
}
// time的单位为毫秒
uint64_t time_to_duration(MP4_Box_data_trak_t *p_track, uint64_t time) {
return time * p_track->i_timescale/1000;
}
// 由time(单位是毫秒)计算sample_index.
// 根据stts计算,应该把每个Chunk第一个sample对应的duration一次性计算出来,这样查找起来会更快一些。
uint64_t time_to_sample(MP4_Box_data_trak_t *p_track, uint64_t time) {
uint64_t i_start = time_to_duration(p_track, time);
if(i_start > p_track->i_duration) return 0;
uint32_t i;
uint64_t i_sample = 0;
if(p_track->i_stts_count == 1) { // all the sample has same duration
i = 0;
} else {
for(i=0; i<p_track->i_stts_count; i++) {
uint64_t range = p_track->i_stts_sample_count[i] * p_track->i_stts_sample_delta[i];
if(range > i_start) break;
i_start -= range;
i_sample += p_track->i_stts_sample_count[i];
}
}
i_sample += i_start / p_track->i_stts_sample_delta[i];
if(i_sample > p_track->i_stsz_sample_count) i_sample = 0;
return i_sample;
}
Sample: 采样,对于音视频来说就是一个编码帧;Sample_count即总帧数,Sample_index即帧下标。
在一个Mp4文件里面,所有Box处理的Samples都是严格按照帧序号排列的。
删除或者修改一帧,很多个Box里面的内容需要从新计算。
在一个Chunk里面Sample顺序紧凑排列。
Tarck: 轨,是音频或者视频流信息定义的集合。一个轨包括一个或者多个Chunk。各轨之间有同步的问题。
Box(也叫atom): 盒,MP4文件各种信息定义的结构。一个MP4文件就是有很多个各种类型的Box组成的。
Box有固定的格式
4Bytes 4Bytes 8Bytes (Box_length-8)Bytes
Box_length | Box_type | Box_long_length | Box_data
b. box_length 包含了自己和4字节的type。因此一般情况下Box的负载是Box_length-8字节。
Box有两大类,一种是Container Box,这种Box里可以嵌套别的Box。它的负载就是其他的Box。
下图加^的Box就是Container Box。
另外一种就是单独的Box,里面定义某一些信息数据。
time_scale: 很多个Box里面都有time_scale,它定义了该媒体在1秒中内的采样刻度, 可以理解为一秒钟有多少个单元。
duration: 相对于time_scale的时间长度。真实的时长 = duration/time_scale。
例如我手头这个MP4文件,视频track的time_scale为25000. duration=9670000.
这样time = 9670000/25000 = 386.8s = 6m27s
不同Box定义的time_scale和duration可能是不同的,原因在于:
a. 文件时长和track时长本身就不一定是相同的,Mp4格式容许杂合多个不同长短不同起始时间的Tracks在同一个文件里面;
b. 一般mvhd,vmhd, smhd里面的time_scale,duration都不相同。所以一定注意要用同一组来计算。
2.一个基本的MP4文件Box结构如下:
Root (虚拟的没有这个box类型)
|----ftyp
|----moov^
| |----mvhd
| |----trak^
| | |----tkhd
| | |----mdia^
| | |----mdhd
| | |----hdlr
| | |----minf^
| | |----vmhd/smhd
| | |----dinf^
| | | |----dref^
| | | |----url
| | |----stbl^
| | |----stsd^
| | | |----mp4v/mp4a^
| | |----stts
| | |----stss
| | |----stsc
| | |----stsz
| | |----stco
| |----trak^
| | |---- ....
| | ....
| |
| |----udta^
| |---- meta^
| |----hdlr
| |----ilist^
|----mdat
|----free
3. 几个重要的Box,先总结如下,再各个叙述。
stbl Box是Mp4文件里面最复杂的Box. 有多种stxxBox, 其中st是Sample Table.
stsd: Sample Description, 具体音视频解码器的定义
stts: Time to Sample, 定义每个Sample的duration, 这个duration也是相对time_scale的单位
stss: Sync Sample, 定义同步Sample Index, 即I帧Sample的Index.
stsc: Sample to Chunk,定义Sample在多个Chunk里面的划分情况。
stsz: Sample siZe,定义每个Sample的字节长度
stco: Chunk Offset,定义各个Chunk在文件中的起始地址。
stts: Sample duration. 定义的方法类似于游程编码。
(Sample_number1, duration1), (Sample_number2, duration2), ... (Sample_numberN, durationN).
一个游程对定义了相同duration的连续Sample个数。N定义了有多少个这种游程对。
最常见的一种情况是所有的Sample具有相同的duration, 这样stts:
stts.entry_count = 1,
stts.sample_couint[0] = Sample_Count;
stts.sample_delta[0] = duration.
当给定一个时间,找对应的sample_index时需要用到stts.
stss: I帧Sample Index.
由于视频编码帧间依赖性,不是从任意Samplea开始都可以连续解码的,只有从I帧的Sample处才可以。
stss定义了随机访问点的Index值。在做Seek的时候需要用到stss
stsc: Sample在Chunk里面的分布表。有4个主要参数。
a. uint32_t entry_count, 定义一个Track里面包含多少个Chunk组,注意这里不是Chunk count,而是 chunk组 count.
所谓chunk组是指那些包含相同个数Samples,而且这些Samples的Sample Description相同的Chunk集合,类似游程编码。
b. uint32_t *first_chunk, 定义每个Chunk组起始Chunk index
c. uint32_t *samples_per_chunk, 定义每个Chunk组内各个Chunk包含Sample个数
d. uint32_t *sample_description_index,定义每个Chunk组内各个Chunk所包含的Sample的解码描述。
for example
inx first_chunk samples_per_chunk sample_description_index
0 1 13 1
1 2 12 1
2 806 9 1
意义:有3个chunk组.
组0 有 ( 2-1)个chunk (下标为 1), 每个chunk含13个samples。 组0有(2-1)*13帧
组1 有 (806-2)个chunk (下标为2..805), 每个chunk含12个Sample。组1有(806-2)*12帧
组2 有 1 个chunk* (下标为806), 这个chunk含9个samples。组2有1*9帧.
一个Chunk组内chunk数目chunk_count = first_chunk[n+1] - first_chunk[n]。
一般最后一个chunk组就只有一个chunk. 在stco里面有chunk count的定义。
如果不放心可以用那个数字来计算最后一个chunk组的chunk数目。
根据这个表,可以展开得到每一个Sample在Chunk中的分布。
stsz: 定义每个Sample长度,这个Box很大。
在计算某一个Sample的偏移地址时需要stsz。
stco/co64:定义每个Chunk相对于文件头的偏移地址。stco里面的地址是32位,co64里面的是64位,根据实际情况选用。
4.计算
输入i_chunk, 输出inx是Chunk组下标
uint32_t sample_to_chunk_group_index(MP4_Box_data_trak_t *p_track, uint32_t i_chunk) {
uint32_t i;
for(i=0; i<p_track->i_stsc_count-1; i++)
if(i_chunk >= p_track->i_stsc_first_chunk[i] && i_chunk < p_track->i_stsc_first_chunk[i+1]) break;
return i;
}
// 展开stsc,计算得到每个Chunk的i_sample_first, i_sample_count,i_sample_description_index 和 i_offset
unsigned int i_chunk;
uint32_t i_first = 0;
uint32_t i_chunk_group_index = 0;
for( i_chunk = 0; i_chunk < p_track->i_chunk_count; i_chunk++ ) {
mp4_chunk_t *ck = &p_track->chunk[i_chunk];
ck->i_offset = p_track->i_co64_sample_offset[i_chunk];
i_chunk_group_index = chunk_group_index(p_track, i_chunk); // 输出Chunk组下标
ck->i_sample_description_index = p_track->i_stsc_sample_description_index[i_chunk_group_index];
ck->i_sample_count = p_track->i_stsc_samples_per_chunk[i_chunk_group_index];
ck->i_sample_first = i_first;
i_first += p_track->i_stsc_samples_per_chunk[i_chunk_group_index];
}
// 由Sample_index计算chunk_index
uint32_t sample_to_chunk(MP4_Box_data_trak_t *p_track, uint64_t sample) {
uint32_t i;
for(i=0; i<p_track->i_chunk_count-1; i++)
if(sample >= p_track->chunk[i].i_sample_first && sample < p_track->chunk[i+1].i_sample_first) break;
return i;
}
// 由Sample_index计算该Sample数据起始地址
// sample起始地址 = 该sample所在Chunk偏移地址(stco) + 该sample在Chunk里面的偏移地址 (stsz)
// sample在chunk里面的偏移地址 = 该chunk的第一帧到该帧之前的Sample长度和
uint64_t smaple_to_offset(MP4_Box_data_trak_t *p_track, uint64_t sample) {
uint32_t chunk_inx = sample_to_chunk(p_track, sample);
uint64_t start_address = p_track->i_co64_sample_offset[chunk_inx];
if(p_track->i_stsz_sample_size == 0) {
uint32_t i;
for(i=p_track->chunk[chunk_inx].i_sample_first; i<sample; i++)
start_address += p_track->i_stsz_entry_size[i];
} else
start_address += (sample - p_track->chunk[chunk_inx].i_sample_first) * p_track->i_stsz_sample_size;
return start_address;
}
// time的单位为毫秒
uint64_t time_to_duration(MP4_Box_data_trak_t *p_track, uint64_t time) {
return time * p_track->i_timescale/1000;
}
// 由time(单位是毫秒)计算sample_index.
// 根据stts计算,应该把每个Chunk第一个sample对应的duration一次性计算出来,这样查找起来会更快一些。
uint64_t time_to_sample(MP4_Box_data_trak_t *p_track, uint64_t time) {
uint64_t i_start = time_to_duration(p_track, time);
if(i_start > p_track->i_duration) return 0;
uint32_t i;
uint64_t i_sample = 0;
if(p_track->i_stts_count == 1) { // all the sample has same duration
i = 0;
} else {
for(i=0; i<p_track->i_stts_count; i++) {
uint64_t range = p_track->i_stts_sample_count[i] * p_track->i_stts_sample_delta[i];
if(range > i_start) break;
i_start -= range;
i_sample += p_track->i_stts_sample_count[i];
}
}
i_sample += i_start / p_track->i_stts_sample_delta[i];
if(i_sample > p_track->i_stsz_sample_count) i_sample = 0;
return i_sample;
}
MP4格式分析
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。