近日某网盘对用户保存其中的部分私人视频进行篡改,使得这部分视频无论是在线或者下载后均无法播放。我们借着研究对应方法,修复被非法篡改的视频数据,恢复正常使用的机会,研究一下avi的数据格式。
avi视频的格式分析
avi是“音视频交错(Audio-Video Interlance)"的缩写,是非常常见的视频文件封装格式。avi是一种适用于采集、编辑、播放的RIFF格式,对不同的编码标准和播放工具具有很强的适应性。
1、文件主体结构
RIFF文件的组成方式由多个chuck组成,组成方式为:
- FOURCC字符:表示当前chuck的名称;
- chuck大小:一个uint32类型整数,使用little-endian保存;该值仅表示下一部分“文件内容”的字节数大小;
- chuck数据:表示当前chuck中实际包含的信息内容;
每一个文件有且仅有一个RIFF chuck,它可以包含多个子chuck,其中的list可以再包含下一级子chuck。相比普通chuck,在RIFF和LIST的chuck大小和数据之间多了一个表示“Form Type/List Type”4个FOURCC字符,如“AVI[ ]”或“WAVE”等,即组成为:
- FOURCC字符:表示当前chuck的名称,对于最上层的chuck,固定为“RIFF”;
- chuck大小:一个uint32类型整数,使用little-endian保存;该值仅表示下一部分“文件内容”的字节数大小,不包含riff字符和文件大小本身;
- 4字节的形式类型或者列表类型:在此为“AVI[ ]”;
- chuck数据:表示当前chuck中实际包含的信息内容;
下一部分数据是AVI的LIST子块。AVI数据块一般包含3个LIST,分别是hdrl、movi和idxl三个,分别表示头信息、音视频数据和索引信息。
每一个list的结构如下:
- FOURCC字符:“LIST”
- LIST数据块的大小;
- LIST类型;
- LIST数据;
首先研究第一个LIST,即hdrl list。首先包括了一个数据结构表示当前数据的AVI Main Header结构,用AVIMAINHEADER表示。这个数据结构包含了当前AVI文件的整体信息,包括视频分辨率、视频中的流数目等。AVIMAINHEADER实现方式如下:
typedef struct _avimainheader { FOURCC fcc;<span style="white-space:pre"> </span>//fourcc字符“avih” DWORD cb;<span style="white-space:pre"> </span>//当前结构占据多少个字节 DWORD dwMicroSecPerFrame;<span style="white-space:pre"> </span>//显示相邻两帧的间隔,以毫秒为单位 DWORD dwMaxBytesPerSec;<span style="white-space:pre"> </span>//每秒钟传输的最大数据量的估计值 DWORD dwPaddingGranularity;//字节对齐单位,数据块长度必须是该值的倍数 DWORD dwFlags;<span style="white-space:pre"> </span>//一些标志位 DWORD dwTotalFrames;<span style="white-space:pre"> </span>//数据帧的总数 DWORD dwInitialFrames;<span style="white-space:pre"> </span>//第一个视频帧开始播放之前需预先准备的帧数 DWORD dwStreams;<span style="white-space:pre"> </span>//文件中流的个数(如一个视频流+一个音频流=2个流) DWORD dwSuggestedBufferSize;//读取数据的缓存区的建议大小 DWORD dwWidth;<span style="white-space:pre"> </span>//视频像素宽度 DWORD dwHeight;<span style="white-space:pre"> </span>//视频像素高度 DWORD dwReserved[4];<span style="white-space:pre"> </span>//保留位 } AVIMAINHEADER;
在AVIMAINHEADER之后是hdrl的子list——strl,包括strh和strf,分别表示stream header和stream format信息。另外,还可能包括strd和strn等部分分别表示header data和stream name等数据。
以下为strh包含的 AVISTREAMHEADER结构:
typedef struct _avistreamheader { FOURCC fcc;//“strh”四字符 DWORD cb;//当前结构大小 FOURCC fccType;//表示当前stream所包含的数据类型 FOURCC fccHandler;//对于音频、视频数据,该部分表示所采用的解码器 DWORD dwFlags; WORD wPriority;//表示当前流的优先级 WORD wLanguage;//语言 DWORD dwInitialFrames; DWORD dwScale;//与下一个元素一起表示sample的频率,如视频帧率等。 DWORD dwRate; DWORD dwStart;//表示当前流的起始时间 DWORD dwLength;//该stream的长度 DWORD dwSuggestedBufferSize;//建议缓存区大小 DWORD dwQuality;//表示数据质量;在视频流中表示编码的QP DWORD dwSampleSize;//sample的大小 struct { short int left; short int top; short int right; short int bottom; } rcFrame;//数据显示的目标区域 } AVISTREAMHEADER;
紧跟着strh之后是一个strf数据块,该数据块表示当前流中的数据格式。对于视频,以BITMAPINFO表示;对于音频,以WAVEFORMATEX表示。数据结构如下:
typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; } BITMAPINFO; typedef struct tagBITMAPINFOHEADER { 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 tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD;在实验中,BITMAPINFOHEADER中的biCompression成员为一个fourCC字符“avc1”,且不包含后面的RGBQUAD部分。
对于音频部分,WAVEFORMATEX的实现如下:
typedef struct { WORD wFormatTag; WORD nChannels; DWORD nSamplesPerSec; DWORD nAvgBytesPerSec; WORD nBlockAlign; WORD wBitsPerSample; WORD cbSize; } WAVEFORMATEX;
这部分的后面还存在一个odml的list,包含一个dmlh的部分,用来表示avi扩展头部序列块。
以上部分即avi文件的文件信息hdrl部分,紧接着便是保存实际音视频数据的movi list。一个fourcc字符“movi”之后的fourcc字符可能含有一下双字符组合中的一个,表示chuck中包含的数据的种类:
字符组合 | 含义 |
db | 未压缩视频帧 |
dc | 压缩的视频数据 |
pc | 调色信息 |
wd | 音频信息 |
在文件的最后,包含一个可选的chuck——索引数据“idx1”。索引chuck包含了各个数据chuck在文件中的位置,其实现方式如下:
typedef struct _avioldindex { FOURCC fcc; DWORD cb; struct _avioldindex_entry { DWORD dwChunkId; DWORD dwFlags; DWORD dwOffset; DWORD dwSize; } aIndex[]; } AVIOLDINDEX;
由此可以看出,avi格式在文件末尾包含了索引信息,所以播放需要获得从开始到结束的完整文件数据。由于这种特性,avi格式并不适合应用在流视频传输和播放的场合。