近日某网盘对用户保存其中的部分私人视频进行篡改,使得这部分视频无论是在线或者下载后均无法播放。我们借着研究对应方法,修复被非法篡改的视频数据,恢复正常使用的机会,研究一下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格式并不适合应用在流视频传输和播放的场合。