1. 概述

AVI(Audio Video Interleaved)是一种音视频的封装格式,于 1992 年由微软公司推出。

AVI 主要应用在多媒体光盘上,用来保存电视、电影等各种影像信息,尽管国际学术界公认 AVI 已经属于被淘汰的技术,但是简单易懂的开发 API,还在被广泛使用。

AVI 符合 RIFF(Resource Interchange File Format)文件规范,使用四字符码 FOURCC(four-character code)来表征数据类型。AVI 的文件结构分为头部、主体和索引三部分。 主体中图像数据和声音数据是交互存放的,从尾部的索引可以索引跳到自己想放的位置。

AVI 本身只是提供了这么一个框架,内部的图像数据和声音数据格式可以是任意的编码形式。因为索引放在了文件尾部,所以在播网络流媒体时已属力不从心。一个很简单的例子,从网络上下载 AVI 文件,如果没有下载完成,是很难正常播放出来。

2.1 基本数据单元

AVI 中有两种最基本的数据单元,一个是 chunk,一个是 list。这两种结构如下:

Chunks
typedef struct {
    DWORD dwFourCC    // 四字节码,表示块类型
    DWORD dwSize      // 占 4 个字节,记录块的大小
    BYTE data[dwSize] // 实际的块数据
} CHUNK;
 
Lists
typedef struct {
    DWORD dwList        // 四字节码,固定为 "LIST"
    DWORD dwSize        // 占 4 个字节, 记录整个列表的大小
    DWORD dwFourCC      // 四字节码,表示本 List 的具体类型;
    BYTE data[dwSize-4] // 实际的列表数据
} LIST;

Chunk 由三部分组成:4 字节码 + 4 字节 size + 数据

List 由四部分组成:4 字节码(固定 “LIST”) + size + 4 字节码(List 类型) + 数据

List 相当于目录,可以包含多个 Chunk 或者多个 List。而 Chunk 是数据保存的基本单元,可用于保存音视频数据或者一些参数信息。

3. AVI 文件结构

AVI 文件采用 RIFF 文件结构方式,使用四字符码 FOURCC(four-character code)来表征数据类型,比如 “RIFF”、“AVI”、“LIST” 等。

  • 整个 RIFF 文件可以看成一个数据块,其数据块 ID 为 “RIFF”,称为 RIFF 块。一个 RIFF 文件中只允许存在一个 RIFF 块。
  • RIFF 块中包含其他一系列子块,其中 ID 为 “LIST” 的称为 List 块,List 块中可以再包含一系列其他子块,但除了 List 块外的其他所有的子块都不能再包含子块。

一个 AVI 通常都包含以下几个字块:

- ID 为 "hdrl" 的 list 块,包含了音视频信息,描述媒体流信息
- ID 为 "info" 的 list 块,包含编码该 AVI 的程序信息
- ID 为 "junk" 的 chunk 块,无用数据,用于填充
- ID 为 "movi" 的 list 块,包含了交错排列的音视频数据
- ID 为 "idxl" 的 chunk 块,包含音视频排列的索引数据(可选块),无索引 seek 速度会变慢。

AVI 文件整体架构如下图所示:

image

我们直接使用文本编辑工具打开一个 AVI 文件,来分析其文件结构。

3.1 RIFF

image

四字节码 “RIFF” 后跟四个字节 RIFF 文件大小,默认采用小端存储,因此长度为 0x0AEFF752 = 183498578 字节。实际存储 AVI 文件的大小为 183498578 + 8 = 183498586 字节。

Note:有一些 AVI 文件尾部会存在一个 junk List 块,文件实际占用空间需要加上 junk 块的大小。

3.2 hdrl

1) hdrl 头部

image

HeaderList,AVI 文件中必需的一个 List,用于描述 AVI 文件中各个流的格式信息(每一路媒体数据都称为一个流)。hdrl list 中嵌套了一系列块和子列表,首先是一个 “avih” 块,用于记录 AVI 文件的全局信息,比如流的数量、视频图像的宽和高等。

如截图,四字节码 “LIST”,然后是 4 字节的 List Size,接着是 4 字节 list 类型 “hdrl”,接着是 List 数据内容。

hdrl List 大小为 0x00002272 = 8818 字节。

2) avih 块

image

Avi Header,该数据块是主信息头,这意外着该 header 数据块之后就是文件多媒体流信息。

如截图,四字节码 “avih”,四字节大小(0x38 即 56),接下来是 56 个字节数据

该块可以用如下结构体表示,可以看到 avih 块的很多填充内容都是缺省掉的。

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

Stream List,用于记录流信息,文件中有几个流,就对应有多少个 strl list。

image

四字节码 “LIST” + 四字节 List 大小 + 四字节标识符 “strl” 。

该 strl size 为 0x0194 = 404 字节

一个 Stream List 一般会存在以下块:

- strh,stream header,用于描述流的头信息
- strf,stream format,用于描述流的具体信息
- strd,可选配置,保存编解码器需要的配置信息
- strn,可选配置,保存流的名字
3.1) strh 块

image

用于描述流的头信息

四字节码 “strh” + 四字节块大小(0x38 即 56),后面是 56 字节大小数据。

该块用如下结构体表示,该 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;    
3.1) 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;

该块首先四字节码 “strf” 标识,4 字节数据大小(0x28 即 40个字节),接着的数据用一个 40 字节大小的 BitmapInfo 结构体表示。

4) movi list

Movie List 用于保存真正的媒体流数据,音视频数据块在该 list 中交错方式存放着。

当 AVI 文件中包含有多个流时,数据块与数据块之间如何来区别呢?

同样的,这些数据块使用了一个四字符码来表征它的类型,这个四字符码由 2 个字节的类型码和 2 个字节的流编号组成。

可用类型码有:

db:未压缩的视频帧
dc:压缩的视频帧
wb:音频数据
pc:改用新的调色板

比如第一个流(Stream 0)是音频,则表征音频数据块的四字符码为 “00wb” ,第二个流(Stream 1)是视频,则表征视频数据块的四字符码为 “01db” 或 “01dc”。

4) 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 进行快进时需要计算位置,会很耗时。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐