常見視頻封裝格式(1) — AVI

-> 概述

日常生活中,看到的視頻文件的後綴名如 .mp4、.avi、.rmvb 都是屬於視頻文件的封裝格式。所謂封裝格式,就是以怎樣的方式將視頻軌、音頻軌、字幕軌等信息組合在一起。說得通俗點,視頻軌相當於飯,而音頻軌相當於菜,封裝格式就是一個碗或者一個鍋,是用來盛放飯菜的容器。

視頻文件的封裝格式並不影響視頻的畫質,影響視頻畫面質量的是視頻的編碼格式。

下面介紹常見的視頻封裝格式 -AVI。

1 AVI

容器 AVI(Audio Video Interleaved)即音視頻交錯格式是一門成熟的老技術,儘管國際學術界公認 AVI 已經屬於被淘汰的技術,但是簡單易懂的開發 API,還在被廣泛使用。

AVI 符合 RIFF(Resource Interchange File Format)文件規範,使用四字符碼 FOURCC(four-character code)來表徵數據類型。AVI 的文件結構分爲頭部、主體和索引三部分。 主體中圖像數據和聲音數據是交互存放的,從尾部的索引可以索引跳到自己想放的位置。

AVI 本身只是提供了這麼一個框架,內部的圖像數據和聲音數據格式可以是任意的編碼形式。因爲索引放在了文件尾部,所以在播網絡流媒體時已屬力不從心。一個很簡單的例子,從網絡上下載 AVI 文件,如果沒有下載完成,是很難正常播放出來。

1.1 基本數據單元

AVI 中有兩種最基本的數據單元,一個是 chunk,一個是 list。這兩種結構如下:

Chunks
typedef struct {
    DWORD dwFourCC
    DWORD dwSize      //data
    BYTE data[dwSize] // contains headers or video/audio data
} CHUNK;
 
Lists
typedef struct {
    DWORD dwList
    DWORD dwSize        //dwFourcc + data
    DWORD dwFourCC
    BYTE data[dwSize-4] // contains Lists and Chunks
} LIST;

如上可知,Chunks 數據塊由一個四字符碼、4 字節 data size(指下面的數據大小)以及數據組成

List 由四部分組成,四個字節四字符碼(“list”)、4 字節數據大小(指後面列的兩部分數據大小)、四字節 list 類型以及數據組成,與 Chunk 數據塊不同的是,List 數據內容可以包含字塊(Chunk 或 List)。

1.2 AVI 文件結構

AVI 文件採用 RIFF 文件結構方式,使用四字符碼 FOURCC(four-character code)來表徵數據類型,比如 ‘RIFF’、‘AVI’、‘LIST’ 等,通常我們稱四字符碼爲數據塊 ID。因此首先我們需要了解一個標準 RIFF 文件結構。

  1. RIFF 文件的基本單元叫做數據塊(Chunk),如上面基本數據單元的介紹,由數據塊四字符碼(數據塊 ID) + 數據長度 + 數據組成。
  2. 整個 RIFF 文件可以看成一個數據塊,其數據塊 ID 爲 “RIFF”,稱爲 RIFF 塊。一個 RIFF 文件中只允許存在一個 RIFF 塊。
  3. RIFF 塊中包含一系列其他子塊,其中 ID 爲 “LIST” 稱爲 LIST 塊,LIST 塊中可以再包含一系列其他子塊,但除了 LIST 塊外的其他所有的子塊都不能再包含子塊。

有了 RIFF 文件結構的瞭解,下面這張 AVI 文件結構圖就比較好理解了。需要說明的是,一個 AVI 通常都包含以下幾個字塊:

  • ID 爲 “hdrl” 的 list 塊,包含了音視頻信息,描述媒體流信息
  • ID 爲 “info” 的 list 塊,包含編碼該 AVI 的程序信息
  • ID 爲 “junk” 的 chunk 數據塊,無用數據,用於填充
  • ID 爲 “movi” 的 list 塊,包含了交錯排列的音視頻數據
  • ID爲 “idxl” 的 chunk 塊,包含音視頻排列的索引數據(可選塊)

圖:AVI 文件結構

image

1.3 AVI 結構詳解

AVI 文件直接用文本編輯如(UltraEdit)打開分析其結構即可,下面以一個 AVI 文件爲例,逐字節往下分析。

★ 1. RIFF 文件頭

用 UltraEdit 打開一個 AVI 文件

image

可以看到前四個字節爲 “RIFF”,在接着四個字節爲 RIFF 文件大小(0x01526E34 即 22176380 字節),22176380 字節只包含 "AVI " RIFF 文件類型四字符碼及 RIFF 塊數據長度,因此 22176380 + 8 是整個 RIFF 文件的大小。再接着 4 字節爲 RIFF 文件類型 "avi "。

★★ 2. hdrl list

1)hdrl list 頭部

image

AVI 文件中必需的第一個 list 就是 hdrl list,用於描述 AVI 文件中各個流的格式信息(AVI 文件中的每一路媒體數據都稱爲一個流)。hdrl list 中嵌套了一系列塊和子列表,首先是一個 “avih” 塊,用於記錄 AVI 文件的全局信息,比如流的數量、視頻圖像的寬和高等。

可以看到首先是 4 字節的 “list”,然後是 4 字節的 list size,接着是 4 字節 list 類型 “hdrl”,接着是 list 數據內容。

2)avih 塊

avih 即 avi_header,該數據塊是主信息頭,這意外着該 header 數據塊之後就是文件多媒體流信息。

image

4 字節的 “avih” 標識碼,4 字節大小(0x38 即 56),接下來是 56 個字節數據。該塊可以用如下結構體表示:

typedef struct {
    FourCC fcc;                  // "avih" 特徵碼
    DWORD cb;                    // 數據大小
    DWORD dwMicroSecPerFrame;    // 視頻幀間隔時間(以毫秒爲單位)
    DWORD dwMaxBytesPerSec;      // AVI 文件的最大數據率
    DWORD dwPaddingGranularity;  // 數據填充的粒度
    DWORD dwFlags;               // AVI 文件的全局標記,比如是否含有索引塊等
    DWORD dwTotalFrames;         // 總幀數
    DWORD dwInitialFrames;       // 爲交互格式指定初始幀數(非交互格式應該指定爲 0)
    DWORD dwStreams;             // 本文件包含的流的個數
    DWORD dwSuggestedBufferSize; // 建議讀取本文件的緩存大小(應能容納最大的塊)
    DWORD dwWidth;               // 視頻圖像的寬(以像素爲單位)
    DWORD dwHeight;              // 視頻圖像的高(以像素爲單位)
    DWORD dwReserved[4];         // 保留
} AVIMainHeader;

3)strl list 頭部

image

一個 strl list 中至少包含一個 strh 塊和一個 strf 塊。文件中有多少個流,就對應有多少個 strl list。

上圖可知,依次爲 4 字節 “list”,4 字節數據大小,4 字節 “strl” 四字符碼標識符。

4)strh 塊

image

用於描述流的頭信息

4 字節 “strh”,4 字節 “strh” 塊大小(0x38 即 56),後面是 56 字節大小數據。該塊用如下結構體表示,由上面 strh 塊可知,該 AVI 第一個流是視頻流(vids)。

// AVI流頭部
typedef struct
{
    FourCC fcc;                 // "strh"
    DWORD cb;                   // 數據大小
    FourCC fccType;             // 流類型: auds(音頻流) vids(視頻流) mids(MIDI流) txts(文字流)
    FourCC fccHandler;          // 指定流的處理者,對於音視頻來說就是解碼器
    DWORD dwFlags;              // 標記:是否允許這個流輸出?調色板是否變化?
    WORD wPriority;             // 流的優先級(當有多個相同類型的流時優先級最高的爲默認流)
    WORD wLanguage;             // 語言
    DWORD dwInitialFrames;      // 爲交互格式指定初始幀數
    DWORD dwScale;              // 每幀視頻大小或者音頻採樣大小
    DWORD dwRate;               // dwScale/dwRate,每秒採樣率
    DWORD dwStart;              // 流的開始時間
    DWORD dwLength;             // 流的長度(單位與 dwScale 和 dwRate 的定義有關)
    DWORD dwSuggestedBufferSize;// 讀取這個流數據建議使用的緩存大小
    DWORD dwQuality;            // 流數據的質量指標(0 ~ 10,000)
    DWORD dwSampleSize;         // Sample的大小
    RECT rcFrame;               // 指定這個流(視頻流或文字流)在視頻主窗口中的顯示位置
} AVIStreamHeader;    

5)strf 塊

image

該塊用於描述流的具體信息。如果是視頻流(vids,由 strh 塊得知),用一個 BitmapInfo 結構體表示,如果是音頻流(auds),用 WaveFormatEx 結構體表示。

// 位圖頭
typedef struct
{
    DWORD  biSize;
    LONG   biWidth;
    LONG   biHeight;
    WORD   biPlanes;
    WORD   biBitCount;
    DWORD  biCompression;
    DWORD  biSizeImage;
    LONG   biXPelsPerMeter;
    LONG   biYPelsPerMeter;
    DWORD  biClrUsed;
    DWORD  biClrImportant;
} BitmapInfoHeader;

// 位圖信息
typedef struct
{
    BitmapInfoHeader bmiHeader;   // 位圖頭
    RGBQUAD bmiColors[1];         // 調色板
} BitmapInfo;

// 音頻波形信息
typedef struct
{
    WORD wFormatTag;
    WORD nChannels;               // 聲道數
    DWORD nSamplesPerSec;         // 採樣率
    DWORD nAvgBytesPerSec;        // 每秒的數據量
    WORD nBlockAlign;             // 數據塊對齊標誌
    WORD wBitsPerSample;          // 每次採樣的數據量
    WORD cbSize;                  // 大小
} WaveFormatEx;

該塊首先 4 字節 “strf” 標識,4 字節數據大小(0x28 即 40個字節),接着的數據用一個 40 字節大小的 BitmapInfo 結構體表示。

6)strd 塊與strh 塊

  • strd:保存編解碼器需要的一些配置信息
  • strn:保存流的名字

這兩個塊是可選的,一個 strl list 可以不包含,不加分析。

★★ 3. info list

info list 用於描述編碼該 AVI 文件的程序信息,包含一個 isft 塊

★★ 4. movi list

image

movi list 用於保存真正的媒體流數據,音視頻數據塊在該 list 中交錯方式存放着。

當 AVI 文件中包含有多個流時,數據塊與數據塊之間如何來區別呢?

同樣的,這些數據塊使用了一個四字符碼來表徵它的類型,這個四字符碼由 2 個字節的類型碼和 2 個字節的流編號組成。

可用類型碼有:

  1. db:未壓縮的視頻幀
  2. dc:壓縮的視頻幀
  3. wb:音頻數據
  4. pc:改用新的調色板

比如第一個流(Stream 0)是音頻,則表徵音頻數據塊的四字符碼爲 “00wb” ,第二個流(Stream 1)是視頻,則表徵視頻數據塊的四字符碼爲 “01db” 或 “01dc”。

★★ 5. idx1 塊

最後,緊跟在 hdrl list 和 movi list 之後的,就是 AVI 文件的索引塊(可選),這個索引塊爲 AVI 文件中每一個媒體數據塊進行索引,並且記錄它們在文件中的偏移(可能相對於 movi list,也可能相對於 AVI 文件開頭)。索引塊使用一個四字符碼 “idx1” 來表徵,索引信息使用一個數據結構來 AVIOLDINDEX 定義。

typedef struct _avioldindex {
   FOURCC  fcc;         // "idx1"
   DWORD   cb;          // 數據大小
   struct _avioldindex_entry {
      DWORD   dwChunkId; // 數據塊 id
      DWORD   dwFlags;
      DWORD   dwOffset; // 數據塊偏移
      DWORD   dwSize;   // 數據塊大小
  } aIndex[];
} AVIOLDINDEX;

在 AVIMainHeader 的 dwFlags 中指出是否包含索引塊。有了索引塊可以方便文件快進,如果沒有索引塊,在對 AVI 進行快進時需要計算位置,會很耗時。

3. 總結

RIFF ('AVI'
     LIST('hdrl'
        'avih'(主 AVI 信息頭數據)
        LIST('strl'
                'strh' (流的頭信息數據)
                'strf' (流的格式信息數據)
                ['strd' (可選的額外的頭信息數據)]
                ['strn' (可選的流的名字) ]
        )
        
        ... // 其他流信息
    )
        
    LIST('movi'
            { 
                // 媒體流數據
                SubChunk | LIST ('rec'
                SubChunk1
                SubChunk2
                ...
            }
    )
    ['idx1' (可選的 AVI 索引塊數據) ]
)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章