android 多媒體文件之mp4分析—base on jellybean(六)

我們講多媒體,涉及到的最多的就是MP4文件和MP3文件瞭,但是我們對這兩個文件的格式瞭解多少呢,它的由有哪些部分部分組成呢?它的核心部件是哪些?它哪些部分是供解碼器去解析的呢?帶著這些疑問,我們首先來探索下MP4文件。
我們首先用MP4Info這個工具來看下MP4的大貌:

 
從上圖我們可以看到MP4文件中的所有數據都裝在box中,也就是說MP4文件由若幹個box組成,每個box有類型和長度,可以將box理解為一個數據對象塊。box中可以包含另一個box,這種box稱為container box。一個MP4文件首先會有且隻有一個“ftyp”類型的box,作為MP4格式的標志並包含關於文件的一些信息;之後會有且隻有一個“moov”類型的box(Movie Box),它是一種container box,子box包含瞭媒體的metadata信息;一個moov可以由多個tracks組成。每個track就是一個隨時間變化的媒體序列,例如,視頻幀序列。track裡的每個時間單位是一個sample,它可以是一幀視頻,或者音頻。sample按照時間順序排列。註意,一幀音頻可以分解成多個音頻sample,所以音頻一般用sample作為單位,而不用幀。MP4文件的媒體數據包含在“mdat”類型的box(Midia Data Box)中,該類型的box也是container box,可以有多個,也可以沒有(當媒體數據全部引用其他文件時),媒體數據的結構由metadata進行描述。“free”類型的box,就是一些自由的信息,可以寫,也可以不寫。
box中的字節序為網絡字節序,也就是大端字節序(Big-Endian),簡單的說,就是一個32位的4字節整數存儲方式為高位字節在內存的低端。Box由header和body組成,其中header統一指明box的大小和類型,body根據類型有不同的意義和格式。
BOX
標準的box開頭的4個字節(32位)為box size,該大小包括box header和box body整個box的大小,這樣我們就可以在文件中定位各個box。如果size為1,則表示這個box的大小為large size,真正的size值要在largesize域上得到。(實際上隻有“mdat”類型的box才有可能用到large size。)如果size為0,表示該box為文件的最後一個box,文件結尾即為該box結尾。(同樣隻存在於“mdat”類型的box中。)
size後面緊跟的32位為box type,一般是4個字符,如“ftyp”、“moov”等,這些box type都是已經預定義好的,分別表示固定的意義。如果是“uuid”,表示該box為用戶擴展類型。如果box type是未定義的,應該將其忽略。
對應的代碼片段為:framework/av/media/libstagefright/MPEG4Extrator.cpp
status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
    ALOGV("entering parseChunk %lld/%d", *offset, depth);
    uint32_t hdr[2];
    static const char* mQTMajorBrand = "qt  ";
    if (mDataSource->readAt(*offset, hdr, 8) < 8) {
        return ERROR_IO;
    }
    uint64_t chunk_size = ntohl(hdr[0]);—box size
    uint32_t chunk_type = ntohl(hdr[1]);—box type
    off64_t data_offset = *offset + 8;
 
    if (chunk_size == 1) {
        if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {—讀取box size的大小
            return ERROR_IO;
        }
        chunk_size = ntoh64(chunk_size); —將64位的網絡字節轉換為主機字節
        data_offset += 8;
……….
    char chunk[5];
    MakeFourCCString(chunk_type, chunk);  —-FOURCC全稱Four-Character Codes,是在編程
中非常常用的東西,一般用作標示符。它是一個32位的標示符,其實就是typedef unsigned long FOURCC
 
}
…………
}
 
 
static void MakeFourCCString(uint32_t x, char *s) {
    s[0] = x >> 24;
    s[1] = (x >> 16) & 0xff;
    s[2] = (x >> 8) & 0xff;
    s[3] = x & 0xff;
    s[4] = '\0';
}
 
File Type Box(ftyp)
File Type Box(ftyp):該box有且隻有1個,並且隻能被包含在文件層,而不能被其他box包含。該box應該被放在文件的最開始,指示該MP4文件應用的相關信息。 “ftyp” body依次包括1個32位的major brand(4個字符),1個32位的minor version(整數)和1個以32位(4個字符)為單位元素的數組compatible brands。這些都是用來指示文件應用級別的信息。該box的字節實例如下:
對應的的代碼如下:
framework/av/media/libstagefright/MPEG4Extrator.cpp
status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
switch(chunk_type) {
        case FOURCC('f', 't', 'y', 'p'):
        {
            if (chunk_data_size < 4) {
                return ERROR_MALFORMED;
            }
 
            uint32_t ftype;
            if (mDataSource->readAt(data_offset, &ftype, 4) < 4) {
                return ERROR_IO;
            }
 
            MakeFourCCString(ntohl(ftype), mMajorBrand); —–major brand
 
            *offset += chunk_size;
            break;
        }
}
 
Movie Box(moov)
該box包含瞭文件媒體的metadata信息,“moov”是一個container box,具體內容信息由子box詮釋。同File Type Box一樣,該box有且隻有一個,且隻被包含在文件層。一般情況下,
“moov”會緊隨“ftyp”出現。一般情況下, “moov”中會包含1個“mvhd”和若幹個“trak”。其中“mvhd”為header box,一般作為“moov”的第一個子box出現(對於其他container box來說,header box都應作為首個子box出現)。“trak”包含瞭一個track的相關信息,是一個container box。結構如下圖:

Movie Header Box(mvhd)
字段
字節數
意義
box size
4
box大小
box type
4
box類型
version
1
box版本,0或1,一般為0。(以下字節數均按version=0)
flags
3
 
creation time
4
創建時間(相對於UTC時間1904-01-01零點的秒數)
modification time
4
修改時間
time scale
4
文件媒體在1秒時間內的刻度值,可以理解為1秒長度的時間單元數
duration
4
該track的時間長度,用duration和time scale值可以計算track時長,比如audio track的time scale = 8000, duration = 560128,時長為70.016,video track的time scale = 600, duration = 42000,時長為70
rate
4
推薦播放速率,高16位和低16位分別為小數點整數部分和小數部分,即[16.16] 格式,該值為1.0(0x00010000)表示正常前向播放
volume
2
與rate類似,[8.8] 格式,1.0(0x0100)表示最大音量
reserved
10
保留位
matrix
36
視頻變換矩陣
pre-defined
24
 
next track id
4
下一個track使用的id號
 
 
status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
    ALOGV("entering parseChunk %lld/%d", *offset, depth);
    uint32_t hdr[2];
    static const char* mQTMajorBrand = "qt  ";
    if (mDataSource->readAt(*offset, hdr, 8) < 8) {
        return ERROR_IO;
    }
    uint64_t chunk_size = ntohl(hdr[0]);—box size
    uint32_t chunk_type = ntohl(hdr[1]);—box type
………………….
 case FOURCC('m', 'v', 'h', 'd'):
        {
            if (chunk_data_size < 12) { //increase to 16?—
                return ERROR_MALFORMED;
            }
 
            uint8_t header[16];
            if (mDataSource->readAt(
                        data_offset, header, sizeof(header))
                    < (ssize_t)sizeof(header)) {
                return ERROR_IO;
            }
 
            int64_t creationTime;
            if (header[0] == 1) {
                creationTime = U64_AT(&header[4]);
                mFileMetaData->setInt64(kKeyEditOffset, 0 );
            } else if (header[0] != 0) {
                return ERROR_MALFORMED;
            } else {
                creationTime = U32_AT(&header[4]);——-創建時間,4個字節
                int32_t mvTimeScale = U32_AT(&header[12]);—時間刻度,4個字節
 
                mFileMetaData->setInt32(kKeyEditOffset, mvTimeScale );
            }
 
            String8 s;
            convertTimeToDate(creationTime, &s);
 
            mFileMetaData->setCString(kKeyDate, s.string());
 
            *offset += chunk_size;
            break;
        }
 
Track Box(trak)
“trak”也是一個container box,其子box包含瞭該track的媒體數據引用和描述(hint track除外)。一個MP4文件中的媒體可以包含多個track,且至少有一個track,這些track之間彼此獨立,有自己的時間和空間信息。“trak”必須包含一個“tkhd”和一個“mdia”,此外還有很多可選的box其中“tkhd”為track header box,“mdia”為media box,該box是一個包含一些track媒體數據信息box的container box。

 
status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
    ALOGV("entering parseChunk %lld/%d", *offset, depth);
    uint32_t hdr[2];
    static const char* mQTMajorBrand = "qt  ";
    if (mDataSource->readAt(*offset, hdr, 8) < 8) {
        return ERROR_IO;
    }
    uint64_t chunk_size = ntohl(hdr[0]);—box size
    uint32_t chunk_type = ntohl(hdr[1]);—box type
……………………..
if (chunk_type == FOURCC('t', 'r', 'a', 'k')) {
                isTrack = true;
                Track *track = new Track; — 如果是Track,new 個track
                track->next = NULL;
                if (mLastTrack) {
                    mLastTrack->next = track;
                } else {
                    mFirstTrack = track;
                }
                mLastTrack = track;
 
                track->meta = new MetaData;
                track->includes_expensive_metadata = false;
                track->skipTrack = false;
                track->timescale = 0;
                track->meta->setCString(kKeyMIMEType, "application/octet-stream");
            }
 
            off64_t stop_offset = *offset + chunk_size;
            *offset = data_offset;
            while (*offset < stop_offset) {
                if (stop_offset – *offset >= 8) {
                    status_t err = parseChunk(offset, depth + 1);
                    if (err != OK) {
                        if(chunk_type == FOURCC('u', 'd', 't', 'a')){
                            ALOGW("error in udta atom, ignoring %llu bytes",stop_offset – *offset);
                            *offset = stop_offset;
                        } else {
                            return err;
                        }
                    }
                }
………….
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *