最近在做视频文件的解析,需要将视频文件中封装的视频与音频解析出来,然后用自己的解码器解码。这个过程专业点叫做叫做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块与strn块
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块
该块是可选的,描述音视频数据的索引块信息。索引块可用如下结构体表示:
1 2 3 4 5 6 7 8 |
// 索引节点信息 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)odmlff2.pdf:https://pan.baidu.com/s/1CXiargLfspQwaWoRYjqHqw 提取码: haj8
文章评论
楼主。。您好。。最近在分析AVI文件格式。。有文中提到的AVI2.0相关的文档参考下嘛??请给我一份。。谢谢了。。
@陈群 这是官方文档OpenDML AVI File Format Extensions:http://jchblog.u.qiniudn.com/doc/odmlff2.pdf 你可以参考下
@Jianchihu 恩恩。。好的谢谢楼主。。
楼主你好,请问能否发一份你的AVI解析程序我啊?我这边想参考一下,麻烦了,谢谢!
@樊聪 这个给不了,公司写的代码。你可以参考ffmepg或vlc源码。
博主你好,最近在研究avi格式,看了你的文章很受启发。
但是发现文章中有几处错误。
1.首先是第一幅avi结构的那个图,信息块中,那个avih和下面的LIST,应该是并列关系,而不是包含。因为chunk不可能再包含LIST
2.还是那幅图,数据块中的LIST,标识应该是“movi”,而不是图中的“move”
3.在“四.详细解析”的“6)strd块与strh块”,目测应该是 strn,而不是strh,应该是笔误。
@没有昵称 感谢指正,那张图是网上盗的图,为了不误导人家,我还是删了吧。