【多媒體封裝格式詳解】---MP4【4】
前面介紹過的幾種格式flv、mkv、asf等。他們音視頻的數據包一般都是按照文件的順序交叉安放。你解析完頭部信息后。剩下的一般就按照文件順序一個數據包一個數據包的解析就行了。但是MP4完全不是這種概念。他的媒體信息和數據是分開存放的。就是你想獲得數據之前必須要解析出每個幀數據所有的位置。mp4存放這個幀信息的是放在stbl這個box里。而真實的數據放在mdat中。接下來就講講stbl與mdat的對應關系。
Sample Table Box(stbl)
來一張典型的stbl結構圖:
stts: Decoding Time to Sample Box 時間戳和Sample映射表
stsd: Sample Description Box
stsz, stz2: Sample Size Boxes 每個Sample大小的表。
stsc: Sample to chunk 的映射表。
‘stco’, ‘co64’: Chunk位置偏移表
stss:關鍵幀index。
1.解析stsd可獲得coding類型、視頻寬高、音頻samplesize、channelcount這些和解碼器有關信息。
[cpp] view plaincopy
- aligned(8) class SampleDescriptionBox (unsigned int(32) handler_type)
- extends FullBox('stsd', 0, 0){
- int i ;
- unsigned int(32) entry_count;
- for (i = 1 ; i u entry_count ; i++){
- switch (handler_type){
- case ‘soun’: // for audio tracks
- AudioSampleEntry();
- break;
- case ‘vide’: // for video tracks
- VisualSampleEntry();
- break;
- case ‘hint’: // Hint track
- HintSampleEntry();
- break;
- }
- }
- }
[cpp] view plaincopy
- aligned(8) abstract class SampleEntry (unsigned int(32) format)
- extends Box(format){
- const unsigned int(8)[6] reserved = 0;
- unsigned int(16) data_reference_index;
- }
- class HintSampleEntry() extends SampleEntry (protocol) {
- unsigned int(8) data [];
- }
- // Visual Sequences
- class VisualSampleEntry(codingname) extends SampleEntry (codingname){
- unsigned int(16) pre_defined = 0;
- const unsigned int(16) reserved = 0;
- unsigned int(32)[3] pre_defined = 0;
- unsigned int(16) width;
- unsigned int(16) height;
- template unsigned int(32) horizresolution = 0x00480000; // 72 dpi
- template unsigned int(32) vertresolution = 0x00480000; // 72 dpi
- const unsigned int(32) reserved = 0;
- template unsigned int(16) frame_count = 1;
- string[32] compressorname;
- template unsigned int(16) depth = 0x0018;
- int(16) pre_defined = -1;
- }
- // Audio Sequences
- class AudioSampleEntry(codingname) extends SampleEntry (codingname){
- const unsigned int(32)[2] reserved = 0;
- template unsigned int(16) channelcount = 2;
- template unsigned int(16) samplesize = 16;
- unsigned int(16) pre_defined = 0;
- const unsigned int(16) reserved = 0 ;
- template unsigned int(32) samplerate = {timescale of media}<<16;
- }
2.解析stsz box 可以獲得一個sample size的表
[cpp] view plaincopy
- aligned(8) class SampleSizeBox extends FullBox(‘stsz’, version = 0, 0) {
- unsigned int(32) sample_size;
- unsigned int(32) sample_count;
- if (sample_size==0) {
- for (i=1; i u sample_count; i++) {
- unsigned int(32) entry_size;
- }
- }
- }
3.解析stts
[cpp] view plaincopy
- aligned(8) class TimeToSampleBox
- extends FullBox(’stts’, version = 0, 0) {
- unsigned int(32) entry_count;
- int i;
- for (i=0; i < entry_count; i++) {
- unsigned int(32) sample_count;
- unsigned int(32) sample_delta;
- }
- }
4.解析stsc 還原Sample 與chunk的映射表
Sample 是存儲的最基本單元,mp4把Sample 存在chunk中。chunk的長度、chunk的大小、chunk中Sample的數量及大小都是不定的。
通過解析這部分box來還原這個映射表。
[cpp] view plaincopy
- aligned(8) class SampleToChunkBox
- extends FullBox(‘stsc’, version = 0, 0) {
- unsigned int(32) entry_count;
- for (i=1; i u entry_count; i++) {
- unsigned int(32) first_chunk;
- unsigned int(32) samples_per_chunk;
- unsigned int(32) sample_description_index;
- }
- }
每個entry 表示著一組數據,entry_count 表示這數量。這一組其實是相同類型的chunk。
first_chunk 表示 這一組相同類型的chunk中 的第一個chunk數。
這些chunk 中包含的Sample 數量,即samples_per_chunk 是一致的。
每個Sample 可以通過sample_description_index 去stsd box 找到描述信息。
看ffmpeg中mov_read_stsc() 它把這些數據放在一個結構體數組中備用。
[cpp] view plaincopy
- static int mov_read_stsc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
- {
- AVStream *st;
- MOVStreamContext *sc;
- unsigned int i, entries;
- if (c->fc->nb_streams < 1)
- return 0;
- st = c->fc->streams[c->fc->nb_streams-1];
- sc = st->priv_data;
- avio_r8(pb); /* version */
- avio_rb24(pb); /* flags */
- entries = avio_rb32(pb);
- av_dlog(c->fc, "track[%i].stsc.entries = %i\n", c->fc->nb_streams-1, entries);
- if (!entries)
- return 0;
- if (entries >= UINT_MAX / sizeof(*sc->stsc_data))
- return AVERROR_INVALIDDATA;
- sc->stsc_data = av_malloc(entries * sizeof(*sc->stsc_data));
- if (!sc->stsc_data)
- return AVERROR(ENOMEM);
- for (i = 0; i < entries && !pb->eof_reached; i++) {
- sc->stsc_data[i].first = avio_rb32(pb);
- sc->stsc_data[i].count = avio_rb32(pb);
- sc->stsc_data[i].id = avio_rb32(pb);
- }
- sc->stsc_count = i;
- if (pb->eof_reached)
- return AVERROR_EOF;
- return 0;
- }
在獲得完整的映射表,我們還需要chunk總個數信息。這些信息放在‘stco’, ‘co64’中。
5.解析‘stco’, ‘co64’
“stco”定義了每個thunk在媒體流中的位置。位置有兩種可能,32位的和64位的,后者對非常大的電影很有用。
32位
[cpp] view plaincopy
- aligned(8) class ChunkOffsetBox
- extends FullBox(‘stco’, version = 0, 0) {
- unsigned int(32) entry_count;
- for (i=1; i u entry_count; i++) {
- unsigned int(32) chunk_offset;
- }
- }
64位
[cpp] view plaincopy
- aligned(8) class ChunkLargeOffsetBox
- extends FullBox(‘co64’, version = 0, 0) {
- unsigned int(32) entry_count;
- for (i=1; i u entry_count; i++) {
- unsigned int(64) chunk_offset;
- }
- }
從這個box我們就可以獲得 chunk 的總數量,entry_count。
from:http://blog.csdn.net/tx3344/article/details/8506131
1.解析‘stco’, ‘co64’我們有了chunk 表,知道了chunk 的總數及每個chunk所在文件的位置。
2.解析stsc 配合著上面的chunk表,我們就能弄個Sample與chunk的關系表。我們也就能獲得每個Sample的位置信息。
3.配合上面的stts 時間表和解碼器信息等。搞出ES流已經不成問題了。
4.想獲得關鍵幀的index,需要解析stss’
[cpp] view plaincopy
- aligned(8) class SyncSampleBox
- extends FullBox(‘stss’, version = 0, 0) {
- unsigned int(32) entry_count;
- int i;
- for (i=0; i < entry_count; i++) {
- unsigned int(32) sample_number;
- }
- }