AVI 文件格式解析
前言
AVI(Audio Video Interleaved 音频视频交错格式)是一种音视频的封装格式,于1992年由微软公司推出。它用RIFF(Resource Interchange File Format 资源交换文件格式)描述,包括Chunk和LIST两种结构。Chunk用来描述(音频/视频/字幕)的数据,包含在movi LIST中:
movi中Chunk标志是4字节的ASCII码标识符dwFourCC("four-character code") :
除了dc,wb,tx之后 ,还需要在其前面添加00,01,02这些数字,表示流的编号,如00dc,01wb,02tx。Chunk长度指出了Chunk内容的长度,这个长度不包括前面4字节的标识符和4字节的Chunk长度,它以小端的方式呈现。 如果Chunk的长度不是偶数,有1个字节的padding。AVI中其他常用FourCC及文件结构列举如下:
LIST其实用来包含Chunks和下一级LIST,一个AVI文件中可能有多个RIFF LIST,RIFF LIST紧接着后面的FourCC如果是"AVI ",表示标准的AVI文件,如果是"AVIX",则表示AVI文件的扩展部分,用来保存超过1G的movi数据,而其他信息则不应该保存在扩展部分的LIST中:
由于AVI格式的历史重要性,常常需要对其进行分析,本文旨在列出要点,以备查预阅需要。
格式分析
AVI的分析工具有:AVIMux_GUI,RIFFspot等。一个AVI格式的视频举例:
MainAVIHeader结构体
AVIStreamHeader结构体
BITMAPINFOHEADER结构体
WAVEFORMAT结构体
可能是WAVEFORMAT, PCMWAVEFORMAT or WAVEFORMATEX结构体中的一个,以下举例:
索引表
AVI格式的索引表不仅是关键帧的索引,它的索引是针对每一条流的每一个Chunk的,也就是说有多少个数据Chunk,就有多少个索引项,因此开销也特别的大。AVI的索引表存在几个版本:
它的FourCC是"idx1",并且放在于movi之后 ,idx1后的长度size,表示索引数组的个数。索引指向的位置其实是真实数据所在的FourCC开始的位置位置,用dwChunkLength来描述这个Chunk的长度。受限于DWORD的字节数,AVI1.0的索引表实际上只能索引到4G范围内,于是有了Open-DML Index的索引表,一种通用的Open-DML Index结构定义:
各字段含义说明:
实际应用中Open-DML Index包含了Upper Level Index(Super Index)、Standard Index和Field Index。
Super Index:
Super Index其实是索引表的索引,它的FourCC是"indx",bIndexSubType是Super index指向的索引类型,0表示指向标准索引,1表示指向场索引。Super Index不是放在movi之后 ,而是放在strf Chunk之后 ,也就是放在文件头部的每条流的描述信息中,它既可以指向索引,也可以指向真实的数据块。指向的索引应位于movi之后,并且它的FourCC应该是"ix01"这样的,这儿的01表示流的编号和dc前面的字符一致。
Standard Index:
单个Standard index只能索引到4G的范围内,wLongsPerEntry取值为2,bIndexSubType设置为0;bIndexType设置为AVI_INDEX_OF_CHUNKS,dwOffset指向数据的起点,绝对位置是qwBaseOffset + dwOffset, dwSize的BIT31位是1表示非关键帧。这个index可以放在movi之后,也可以放在文件头部。
Field Index:
单个Field index只能索引到4G的范围内,wLongsPerEntry取值3,dwOffsetField值为2表示第二个场的偏移量,dwSize的BIT31位是1表示非关键帧。这个index可以放在movi之后,也可以放在文件头部。
AVI时间戳
说了这么多,还没有列出AVI的时间戳计算方式,AVI的时间戳与索引表有关,下面给出一种实现方式。FFMPEG中索引表调用顺序如下:
avformat_open_input->avi_read_header->avi_load_index->avi_read_idx1->av_add_index_entry->ff_add_index_entry |
每一个Chunk都有一个索引项,因此,在遍历索引表的时候,就将索引表的时间戳根据前一个索引表的时候戳和dwSampleSize算出来,dwSampleSize异常的时候,每次将索引表的时间戳加1,ffmpeg在AVI中算的是DTS,这个值需要乘以wScale/dwRate,才能得出PTS。ffmpeg在读的时候,去查了每一个表的索引项,索引项的时间戳就等于要读取的Chunk的时间戳。因此没有索引表的AVI播放也是比较麻烦的。总结起来就是:
关于FFMPEG对AVI索引的处理
FFMPEG顺序读取AVI包,读取之初并没有目前也不能指定需要读取的stream_index,avi->stream_index最初是为-1。需要进入avi_sync函数重新查找下一个能够读取的包,这儿有几点值得注意:
1. AVI的索引表并不包含每个每个包的时间戳,时间戳是用累加的方式得来的,如帧率fps为50的码流(50/1),那第0个索引表项的时间戳是0,第1个索引表项的时间戳是1,第3232个索引表项的时间戳就是3232,对应真实时间就是3232/50
=64.64秒。所以AVI的码流每个包都需要一个索引项,否则不能计算出时间戳,这也是AVI视频索引表可能比较大的原因。
2.FFMPEG的AVI seek之初,调用av_find_default_stream_index匹配默认最佳的第一次搜索码注,一般为视频。SEEK完成之后,会将SEEK到的时间作为基准,去搜索其他流的索引表,这个时候可能改变第一次搜索过的码流的位置,如果此时 不加处理顺序读取码流,那么第一条流的输出结果可能会是错的,已经配置好的流的frame_offset其实已经改变,需要重新匹配。
3.每一条流都对应单独的AVIStream和AVStream
static int avi_read_packet(AVFormatContext *s, AVPacket *pkt)
{
……
resync:
if (avi->stream_index >= 0) {
……
}
if ((err = avi_sync(s, 0)) < 0)
return err;
goto resync;
}
参考网页:
https://en.wikipedia.org/wiki/Resource_Interchange_File_Format
http://www.daubnet.com/en/file-format-riff