Sample Table Box(stbl)
“stbl”几乎是普通的MP4文件中最复杂的一个box了。sample是媒体数据存储的单位,存储在media的chunk中,chunk和sample的长度均可互不相同。chunk是几个sample的集合。“stbl”包含了关于track中sample所有时间和位置的信息,以及sample的编解码等信息。利用这个表,可以解释sample的时序、类型、大小以及在各自存储容器中的位置。“stbl”是一个container box,其子box包括:sample description box(stsd)、time to sample box(stts)、sample size box(stsz或stz2)、sample to chunk box(stsc)、chunk offset box(stco或co64)、composition time to sample box(ctts)、sync sample box(stss)等。“stsd”必不可少,且至少包含一个条目,该box包含了data reference box进行sample数据检索的信息。没有“stsd”就无法计算media sample的存储位置。“stsd”包含了编码的信息,其存储的信息随媒体类型不同而不同。
if (chunk_type == FOURCC('s', 't', 'b', 'l')) {
ALOGV("sampleTable chunk is %d bytes long.", (size_t)chunk_size);
if (mDataSource->flags()
& (DataSource::kWantsPrefetching
| DataSource::kIsCachingDataSource)) {
sp<MPEG4DataSource> cachedSource =
new MPEG4DataSource(mDataSource);
if (cachedSource->setCachedRange(*offset, chunk_size) == OK) {
mDataSource = cachedSource;
}
}
mLastTrack->sampleTable = new SampleTable(mDataSource);----创建sampletable,每个track对应一个sampletable
}
Sample Description Box(stsd)
box header和version字段后会有一个entry count字段,根据entry的个数,每个entry会有type信息,如“vide”、“sund”等,根据type不同sample description会提供不同的信息,例如对于video track,会有“VisualSampleEntry”类型信息,对于audio track会有“AudioSampleEntry”类型信息。视频的编码类型、宽高、长度,音频的声道、采样等信息都会出现在这个box中。
case FOURCC('s', 't', 's', 'd'):
{
,…………………………….
uint32_t entry_count = U32_AT(&buffer[4]);
off64_t stop_offset = *offset + chunk_size;
*offset = data_offset + 8;
if (entry_count > 1) {----针对3GPP,有可能有多个entry_count,但目前我们每个track支持单类型的media
// For 3GPP timed text, there could be multiple tx3g boxes contain
// multiple text display formats. These formats will be used to
// display the timed text.
const char *mime;
CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));
if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {
ALOGV("Text track found");
for (uint32_t i = 0; i < entry_count; ++i) {
status_t err = parseChunk(offset, depth + 1);
if (err != OK) {
return err;
}
}
// For now we only support a single type of media per track.
}
else {
status_t err = mLastTrack->sampleTable->setSampleDescParams(entry_count, *offset, chunk_data_size);
if (err != OK) {
return ERROR_IO;
}
//视频的编码类型、宽高、长度,音频的声道、采样等信息
mHasVideo = true;
uint8_t avc1[86];//(avc1-avcc) which is fixed
if (mDataSource->readAt(*offset, avc1, sizeof(avc1)) < (ssize_t)sizeof(avc1)) {
return ERROR_IO;
}
uint32_t chunk_type = U32_AT(&avc1[4]);
uint16_t data_ref_index = U16_AT(&avc1[14]);
uint16_t width = U16_AT(&avc1[32]);
uint16_t height = U16_AT(&avc1[34]);
mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));
mLastTrack->meta->setInt32(kKeyWidth, width);
mLastTrack->meta->setInt32(kKeyHeight, height);
uint8_t *avcc;
uint32_t avccSize;
mLastTrack->sampleTable->getSampleDescAtIndex(1, &avcc, &avccSize);
mLastTrack->meta->setData(kKeyAVCC, kTypeAVCC, avcc, avccSize);
*offset = stop_offset;
}
} else {
for (uint32_t i = 0; i < entry_count; ++i) {
status_t err = parseChunk(offset, depth + 1);
if (err != OK) {
return err;
}
} // end of for
}//end of entry count 1
if (*offset != stop_offset) {
return ERROR_MALFORMED;
}
break;
}
Time To Sample Box(stts)
“stts”存储了sample的duration,描述了sample时序的映射方法,我们通过它可以找到任何时间的sample。“stts”可以包含一个压缩的表来映射时间和sample序号,用其他的表来提供每个sample的长度和指针。表中每个条目提供了在同一个时间偏移量里面连续的sample序号,以及samples的偏移量。递增这些偏移量,就可以建立一个完整的time to sample表。
case FOURCC('s', 't', 't', 's'):
{
status_t err =
mLastTrack->sampleTable->setTimeToSampleParams(---该方法在SampleTable.cpp,映射时间和sample序号
data_offset, chunk_data_size);
if (err != OK) {
return err;
}
*offset += chunk_size;
break;
}
Sample Size Box(stsz)
“stsz” 定义了每个sample的大小,包含了媒体中全部sample的数目和一张给出每个sample大小的表。这个box相对来说体积是比较大的。
case FOURCC('s', 't', 's', 'z'):
case FOURCC('s', 't', 'z', '2'):
{
status_t err =
mLastTrack->sampleTable->setSampleSizeParams(-----该方法在SampleTable.cpp,设置sample大小
chunk_type, data_offset, chunk_data_size);
if (err != OK) {
return err;
}
size_t max_size;
err = mLastTrack->sampleTable->getMaxSampleSize(&max_size);
if (err != OK) {
return err;
}
// Assume that a given buffer only contains at most 10 fragments,
// each fragment originally prefixed with a 2 byte length will
// have a 4 byte header (0x00 0x00 0x00 0x01) after conversion,
// and thus will grow by 2 bytes per fragment.
mLastTrack->meta->setInt32(kKeyMaxInputSize, max_size + 10 * 2);
*offset += chunk_size;
// Calculate average frame rate.
const char *mime;
CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));
if (!strncasecmp("video/", mime, 6)) {
size_t nSamples = mLastTrack->sampleTable->countSamples();
int64_t durationUs;
if (mLastTrack->meta->findInt64(kKeyDuration, &durationUs)) {
if (durationUs > 0) {
int32_t frameRate = (nSamples * 1000000LL +
(durationUs >> 1)) / durationUs;
mLastTrack->meta->setInt32(kKeyFrameRate, frameRate);
}
}
}
break;
}
Sample To Chunk Box(stsc)
用chunk组织sample可以方便优化数据获取,一个chunk包含一个或多个sample。“stsc”中用一个表描述了sample与chunk的映射关系,查看这张表就可以找到包含指定sample的chunk,从而找到这个sample。
case FOURCC('s', 't', 's', 'c'):
{
status_t err =
mLastTrack->sampleTable->setSampleToChunkParams(该方法在SampleTable.cpp,映射sample和chunk的关系,一个或多个sample组成一个chunk
data_offset, chunk_data_size);
if (err != OK) {
return err;
}
*offset += chunk_size;
break;
}
Sync Sample Box(stss)
“stss”确定media中的关键帧。对于压缩媒体数据,关键帧是一系列压缩序列的开始帧,其解压缩时不依赖以前的帧,而后续帧的解压缩将依赖于这个关键帧。“stss”可以非常紧凑的标记媒体内的随机存取点,它包含一个sample序号表,表内的每一项严格按照sample的序号排列,说明了媒体中的哪一个sample是关键帧。如果此表不存在,说明每一个sample都是一个关键帧,是一个随机存取点。
如何查找关键帧呢?
1:确定给定时间的sample序号检查sync sample atom来发现这个sample序号之后的key frame
2:检查sample-to-chunk atom来发现对应该sample的chunk
3:从chunk offset atom中提取该chunk的偏移量
4:利用sample size atom找到sample在trunk内的偏移量和sample的大小
case FOURCC('s', 't', 's', 's'):
{
status_t err =
mLastTrack->sampleTable->setSyncSampleParams(----设置关键帧
data_offset, chunk_data_size);
if (err != OK) {
return err;
}
*offset += chunk_size;
break;
}
Chunk Offset Box(stco)
“stco”定义了每个chunk在媒体流中的位置。位置有两种可能,32位的和64位的,后者对非常大的电影很有用。在一个表中只会有一种可能,这个位置是在整个文件中的,而不是在任何box中的,这样做就可以直接在文件中找到媒体数据,而不用解释 box。需要注意的是一旦前面的box有了任何改变,这张表都要重新建立,因为位置信息已经改变了。
case FOURCC('s', 't', 'c', 'o'):
case FOURCC('c', 'o', '6', '4'):
{
status_t err =
mLastTrack->sampleTable->setChunkOffsetParams(---设置chunk的偏移量
chunk_type, data_offset, chunk_data_size);
if (err != OK) {
return err;
}
*offset += chunk_size;
break;
}
Free Space Box(free或skip)
“free”中的内容是无关紧要的,可以被忽略。该box被删除后,不会对播放产生任何影响。
Meida Data Box(mdat)
该box包含于文件层,可以有多个,也可以没有(当媒体数据全部为外部文件引用时),用来存储媒体数据。
下图为总的概括:
参考资料:http://mpeg.chiariglione.org/standards/mpeg-4/mpeg-4.htm
具体源码:frameworks/av/media/libstagefright/MPEG4Extractor.cpp
frameworks/av/media/libstagefright/sampletable.cpp
好了,MP4文件格式已经介绍完了,video recoder也会用到这些知识,望大家好好研究研究。