MP4格式分析

1.先說幾個基本概念
  Sample: 採樣,對於音視頻來說就是一個編碼幀;Sample_count即總幀數,Sample_index即幀下標。

                  在一個Mp4文件裏面,所有Box處理的Samples都是嚴格按照幀序號排列的。
                  刪除或者修改一幀,很多個Box裏面的內容需要從新計算。

  Chunk: 塊,一個Chunk包括一個或者多個同類型Samples,使用Chunk的目的是爲了加快Sample數據訪問效率;
                在一個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

      a. Box_long_length 僅當Box_length=1時出現, 他定義超長Box, 超過32位數字的範圍的就用64位表述
      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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章