常见视频封装格式(1) — AVI
-> 概述日常生活中,看到的视频文件的后缀名如.mp4、.avi、.rmvb 都是属于视频文件的封装格式。所谓封装格式,就是以怎样的方式将视频轨、音频轨、字幕轨等信息组合在一起。说得通俗点,视频轨相当于饭,而音频轨相当于菜,封装格式就是一个碗或者一个锅,是用来盛放饭菜的容器。视频文件的封装格式并不影响视频的画质,影响视频画面质量的是视频的编码格式。下面介绍常见的视频封装格式...
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 文件整体架构如下图所示:
我们直接使用文本编辑工具打开一个 AVI 文件,来分析其文件结构。
3.1 RIFF
四字节码 “RIFF” 后跟四个字节 RIFF 文件大小,默认采用小端存储,因此长度为 0x0AEFF752 = 183498578 字节。实际存储 AVI 文件的大小为 183498578 + 8 = 183498586 字节。
Note:有一些 AVI 文件尾部会存在一个 junk List 块,文件实际占用空间需要加上 junk 块的大小。
3.2 hdrl
1) hdrl 头部
HeaderList,AVI 文件中必需的一个 List,用于描述 AVI 文件中各个流的格式信息(每一路媒体数据都称为一个流)。hdrl list 中嵌套了一系列块和子列表,首先是一个 “avih” 块,用于记录 AVI 文件的全局信息,比如流的数量、视频图像的宽和高等。
如截图,四字节码 “LIST”,然后是 4 字节的 List Size,接着是 4 字节 list 类型 “hdrl”,接着是 List 数据内容。
hdrl List 大小为 0x00002272 = 8818 字节。
2) avih 块
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。
四字节码 “LIST” + 四字节 List 大小 + 四字节标识符 “strl” 。
该 strl size 为 0x0194 = 404 字节
一个 Stream List 一般会存在以下块:
- strh,stream header,用于描述流的头信息
- strf,stream format,用于描述流的具体信息
- strd,可选配置,保存编解码器需要的配置信息
- strn,可选配置,保存流的名字
3.1) strh 块
用于描述流的头信息
四字节码 “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 块
该块用于描述流的具体信息。如果是视频流(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 进行快进时需要计算位置,会很耗时。
更多推荐
所有评论(0)