编解码:AVI格式解析
1 音视频及AVI知识
一个完整的音视频文件格式有mp4、mov、flv、avi、rmvb、mkv、ts等,它们是封装数据的容器,包括音频、视频、字幕、基础元信息等,通过一些特定的编码算法,对各种信息进行编码压缩过后得到的。视频文件的封装格式并不影响视频的画质,影响视频画面质量的是视频的编码格式。
H264、HEVC、VP9、AV1等就是视频编码格式,MP3、AAC、AC-3等就是音频编码格式,SRT、SSA等就是字幕编码格式。
例如:将一个视频流以Xvid格式编码、一个音频流以MP3格式编码、一个字幕流以SRT格式进行编码,按AVI封装标准封装以后,就得到一个AVI视频文件。
AVI即Audio Video Interleaved,音视频交错格式,基于RIFF文件结构。多用于音视频捕捉、编辑、回放等应用程序中。通常情况下,一个AVI文件可以包含多个不同类型的媒体流(典型的情况下有一个音频流和一个视频流),不过含有单一音频流或单一视频流的AVI文件也是合法的。AVI可以算是Windows操作系统上最基本的、也是最常用的一种媒体文件格式。
RIFF即Resource Interchange File Format,资源交互文件格式 。是由Microsoft提出的一种多媒体文件存储方式,不同编码的视频、音频文件按照RIFF保存,当提取文件时,可以根据RIFF的规则解析文件。常见的RIFF文件有:WAV、AVI等。
2 最基本的数据单元
//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字节的数据流格式ID,4字节的数据大小,数据。
Lists由四部分组成:4字节LIST(LIST块中可以再包含一系列的子块),4字节数据加fourCC大小,4字节数据流格式ID,数据。
3 AVI主要结构介绍
一个AVI文件通常有如下几个子块组成:
- ID为"AVI"的RIFF,文件头
- ID为"hdrl"的list,信息块,包含了音视频信息,描述媒体流信息
- ID为"info"的list,包含编码该AVI的程序信息
- ID为"junk"的chunk,无用的数据,用于字节对齐
- ID为"movi"的list,数据块,包含了交错排列的音视频数据
- ID为"idxl"的chunk,索引块,包含音视频排列的索引数据(可选块,不存在时seek会慢很多)
文件信息:
分析工具分别是MediaInfo以及MediaConch。
4 AVI主要结构解析
1.RIFF文件头
4个字节的"RIFF",4字节的RIFF文件大小(342872字节),4字节为RIFF文件类型"AVI"
2.hdrl列表
1)hdrl列表头
4字节的"LIST"描述信息,4字节list大小(8952字节),4字节list类型"hdrl"
2)avih块
用于描述主信息,该块可以用如下结构体表示
typedef struct
{
DWORD dwMicroSecPerFrame; //显示每帧所需的时间ns,定义avi的显示速率
DWORD dwMaxBytesPerSec; // 最大数据传输率
DWORD dwPaddingGranularity; //记录块的长度须为此值的倍数,通常是2048
DWORD dwFlags; // AVI文件的特殊属性,包含文件中的任何标志字。如:有无索引块,是否是interlaced,是否含版权信息等
DWORD dwTotalFrames; // 数据帧的总数
DWORD dwInitialFrames; // 在开始播放前需要的帧数
DWORD dwStreams; //文件中包含的数据流种类
DWORD dwSuggestedBufferSize;//建议使用的缓冲区的大小,通常为存储一帧图像以及同步声音所需要的数据之和,大于最大的CHUNK的大小
DWORD dwWidth; //图像宽,像素
DWORD dwHeight; //图像高,像素
DWORD dwReserved[4]; //保留值dwScale,dwRate,dwStart,dwLength
} MainAVIHeader;
3)strl list头部
一个strl list中至少包含一个strh块和一个strf块。文件中有多少个流,就对应有多少个strl list。
上图可知存在两个流,Stream info 0,Stream info 1。
4)strh块
// AVI流头部
typedef struct
{
FourCC fcc; // 必须为 strh
DWORD cb; // 本数据结构的大小,不包括最初的8个字节(fcc和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; // 指定这个流(视频流或文字流)在视频主窗口中的显示位置,视频主窗口由AVIMAINHEADER结构中的dwWidth和dwHeight决定
} AVIStreamHeader;
5)strf块
该块用于描述流的具体信息
- 视频流,fccType = “vids”
// 位图头
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;
- 音频流,fccType = “auds”
// 音频波形信息
typedef struct
{
WORD wFormatTag;
WORD nChannels; // 声道数
DWORD nSamplesPerSec; // 采样率
DWORD nAvgBytesPerSec; // 每秒的数据量
WORD nBlockAlign; // 数据块对齐标志
WORD wBitsPerSample; // 每次采样的数据量
WORD cbSize; // 大小
} WaveFormatEx;
6)strd块与strn块
strd:可选的额外的头信息数据
strn:可选的流的名字
这两个块是可选的,本AVI文件不包含,故不分析。
3.info列表
该list用于描述编码该AVI文件的程序信息,包含一个isft块。
4.junk块
一些垃圾填充数据,用于内部数据的队齐(填充),直接跳过。
5.movi列表
存储音视频数据,音视频数据在其中以交错方式存放着,视频clip,n音频clip,视频clip,n音频clip…方便seek。
音视频数据子块的种类有:##db,##dc,##pc,##wb。
–##:数据所属的流的序号,视频是00dc或00db,音频是01wb。
–db:未压缩的视频帧
–dc:压缩的视频帧
–wb:音频数据
–pc:改用新的调色板
6.idx1块
该块是可选的,描述音视频数据的索引块信息,在AVIMainHeader的dwFlags中指出是否包含索引块。有了索引块可以方便文件快进,如果没有索引块,在对AVI进行快进时需要计算位置,会很耗时。索引块可用如下结构体表示:
// 索引节点信息
typedef struct
{
DWORD dwChunkId; // 本数据块的四字符码(00dc 01wb)
DWORD dwFlags; // 说明本数据块是不是关键帧、是不是‘rec ’列表等信息
DWORD dwOffset; // 本数据块在文件中的偏移量
DWORD dwSize; // 本数据块的大小
} AVIIndexEntry;
5 总结
可以编译FFmpeg源码,对AVI文件进行解码,在分离器Demuxer中进一步研究其格式,会有更清晰的认识。
RIFF ('AVI'
LIST('hdrl'
'avih'(主 AVI 信息头数据)
LIST('strl'
'strh' (流的头信息数据)
'strf' (流的格式信息数据)
['strd' (可选的额外的头信息数据)]
['strn' (可选的流的名字) ]
)
... // 其他流信息
)
LIST('movi'
{
// 媒体流数据
SubChunk | LIST ('rec'
SubChunk1
SubChunk2
...
}
)
['idx1' (可选的 AVI 索引块数据) ]
)