常见视频封装格式(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 文件结构

常见视频封装格式(1) — AVI_第1张图片

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 头部

常见视频封装格式(1) — AVI_第2张图片

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 数据块之后就是文件多媒体流信息。

常见视频封装格式(1) — AVI_第3张图片

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 头部

常见视频封装格式(1) — AVI_第4张图片

一个 strl list 中至少包含一个 strh 块和一个 strf 块。文件中有多少个流,就对应有多少个 strl list。

上图可知,依次为 4 字节 “list”,4 字节数据大小,4 字节 “strl” 四字符码标识符。

4)strh 块

常见视频封装格式(1) — AVI_第5张图片

用于描述流的头信息

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 块

常见视频封装格式(1) — AVI_第6张图片

该块用于描述流的具体信息。如果是视频流(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

常见视频封装格式(1) — AVI_第7张图片

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 索引块数据) ]
)

你可能感兴趣的:(android,multimedia)