最近在做视频文件的解析,需要将视频文件中封装的视频与音频解析出来,然后用自己的解码器解码。这个过程专业点叫做叫做Demultiplex,视频播放器中负责这部分的叫做Demuxer。我们平时看到的各种格式视频,比如:avi,mp4,mkv等相当于一种容器,里面包含了音视频,字幕的信息以及数据,Demuxer的工作就是解析视频文件,取出里面的音视频或者字幕送到指定的解码器解码。
我刚开始接触的avi文件的解析。首先介绍下AVI文件。AVI英文全称为Audio Video Interleaved,即音视频交错格式,AVI基于RIFF文件结构。
一.基本数据单元
在AVI中有两种最基本的数据单元,一个是chunk,一个是list。这两种结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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;
|
可知chunk由三部分组成:4字节的标记ID,4字节大小(指数据大小),以及数据。
list由四部分组成,4字节“list”,4字节大小(指后面两部分),4字节列表类型,数据。
二.AVI文件类型
1.AVI 1.0 最基本的格式,由于索引地址与大小用4字节表示,所以最大支持4G容量,而且与文件系统类型有关,如下:
–Video for Windows (AVI 1.0)
-FAT (FAT16): 4 GB (2 GB practical, safe)
-FAT32: 4 GB (2 GB practical, safe)
-NTFS: 4 GB (2 GB practical, safe)
为了安全一般限制为2G(参考链接1)
2.AVI 2.0(Open—DML) AVI的扩展格式,解决AVI 1.0大小限制(AVI2.0详细资料见参考链接2)
本文主要讨论AVI 1.0,AVI2.0解析后续再做研究。
三.AVI结构
一个AVI文件通常有如下几个子块组成:
1)ID为”hdrl”的list,包含了音视频信息,描述媒体流信息
2)ID为”info”的list,包含编码该AVI的程序信息
3)ID为”junk”的chunk,都是些无用的数据
4)ID为”movi”的list,包含了交错排列的音视频数据
5)ID为”idxl”的chunk,包含音视频排列的索引数据(可选块)
具体如下图所示(盗的图):
四.详细解析
1.RIFF文件头
用UltraEdit(或者其他文本编辑器)打开一个AVI文件(本文分析用到的avi文件下载地址)
可以看到前4个字节为”RIFF”,接着4字节RIFF文件大小(0x01811050即25235536字节,因为AVI文件以小端方式存储数据),再接着4字节为RIFF文件类型”avi”
2.hdrl列表
1)hdrl list头部
可以看到接下来为hdrl list。首先是4字节的”list”,然后4字节list大小,接着是4字节list类型”hdrl”。
2)avih块
用于描述主信息头。
4字节的”avih”标识,4字节大小(0x38即56),接下来是56个字节数据。该块可以用如下结构体表示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// AVI主头部
typedef struct
{
FourCC fcc; // 必须为 avih
DWORD cb; // 本数据结构的大小,不包括最初的8个字节(fcc和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头部
一个strl list中至少包含一个strh块和一个strf块。文件中有多少个流,就对应有多少个strl list。
上图可知依次为4字节”list”,4字节的list大小,4字节”strl”标识。
4)strh块
用于描述流的头信息。
4字节”strh”,4字节”strh”块大小(0x38即56),后面是56字节大小数据。该块用如下结构体表示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 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块
该块用于描述流的具体信息。如果是视频流(vids,由strh块得知),用一个BitmapInfo结构体表示,如果是音频流(auds),用WaveFormatEx结构体表示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// 位图头
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:可选的流的名字
这两个块是可选的,本AVI文件不包含,故不分析。
7)strl list
由于该AVI文件有两个流,所以有两个strl list。该strl list用于描述音频流信息。具体分析同上。
3.info list
该list用于描述编码该AVI文件的程序信息,包含一个isft块。
4.junk块
该块都是一些垃圾填充数据,用于内部数据的队齐(填充),直接跳过。
5.movi list
该list存储音视频数据块,音视频数据块在该list中交错方式存放着。
movi lst中音视频数据子块的种类有:##db,##dc,##pc,##wb。
–##表示数据所属的流的序号(由于第一个流是视频(第一个hdrl list描述视频),故音频用01wb表示,视频用00dc或00db)
–db:未压缩的视频帧
–dc:压缩的视频帧
–wb:音频数据
–pc:改用新的调色板
6.idx1块
该块是可选的,描述音视频数据的索引块信息。索引块可用如下结构体表示:
|
// 索引节点信息
typedef struct
{
DWORD dwChunkId; // 本数据块的四字符码(00dc 01wb)
DWORD dwFlags; // 说明本数据块是不是关键帧、是不是‘rec ’列表等信息
DWORD dwOffset; // 本数据块在文件中的偏移量
DWORD dwSize; // 本数据块的大小
} AVIIndexEntry;
|
在AVIMainHeader的dwFlags中指出是否包含索引块。有了索引块可以方便文件快进,如果没有索引块,在对AVI进行快进时需要计算位置,会很耗时。
五.程序编写
通过如上分析对AVI结构有了清楚了解。在这基础上自己写了个AVI解析程序,可以解析AVI1.0格式文件,同时生成对应的视频与音频文件。
我用ffmpeg自带的ffplay测试解析生成的h264视频文件,可以正常播放。
参考链接
1)http://www.myvideoproblems.com/Tutorials/dv-aviFiles.htm
2)http://jchblog.u.qiniudn.com/doc/odmlff2.pdf