目录
一、RIFF概念
在Windows环境下,大部分的多媒体文件都依循着一种结构来存放信息,这种结构称为"资源互换文件格式"(Resources lnterchange File Format),简称RIFF。例如声音的WAV文件、视频的AV1文件等等均是由此结构衍生出来的。RIFF可以看做是一种树状结构,其基本构成单位为chunk,犹如树状结构中的节点,每个chunk由辨别码、数据大小及数据所组成。
块的标志符(4BYTES) |
数据大小 (4BYTES) |
数据 |
辨别码由4个ASCII码所构成,数据大小则标示出紧跟其后数据的长度(单位为Byte),而数据大小本身也用掉4个Byte,所以事实上一个chunk的长度为数据大小加8。一般而言,chunk本身并不允许内部再包含chunk,但有两种例外,分别为以"RIFF"及"LIST"为辨别码的chunk。而针对此两种chunk,RIFF又从原先的"数据"中切出4个Byte。 此4个Byte称为"格式辨别码",然而RIFF又规定文件中仅能有一个以"RIFF"为辨别码的chunk。
RIFF/LIST标志符 | |
数据1大小 | |
数据1 | 格式/列表类型 |
数据 |
只要依循此一结构的文件,我们均称之为RIFF档。此种结构提供了一种系统化的分类。如果和MS一DOS文件系统作比较,"RIFF"chunk就好比是一台硬盘的根目录,其格式辨别码便是此硬盘的逻辑代码(C:或D:),而"LiST"chunk即为其下的子目录,其他的chunk则为一般的文件。至于在RIFF文件的处理方面,微软提供了相关的函数。视窗下的各种多媒体文件格式就如同在磁盘机下规定仅能放怎样的目录,而在该目录下仅能放何种数据。
二、AVI文件格式解析
1. 概述
AVI的英文全称为Audio Video Interleaved,即音频视频交错格式,是将语音和影像同步组合在一起的文件格式。AVI于1992年被Microsoft公司推出,随 Windows3.1一起被人们所认识和熟知。AVI文件格式多用于音视频捕捉、编辑、回放等应用程序中。通常情况下,一个AVI文件可以包含多个不同类型的媒体流(典型的情况下有一个音频流和一个视频流),不过含有单一音频流或单一视频流的AVI文件也是合法的。AVI可以算是Windows操作系统上 最基本的、也是最常用的一种媒体文件格式。
2. AVI文件层次划分
RIFF (‘AVI ’
LIST (‘hdrl’
‘avih’(主AVI信息头数据)
LIST (‘strl’
‘strh’ (流的头信息数据)
‘strf’ (流的格式信息数据)
[‘strd’ (可选的额外的头信息数据) ]
[‘strn’ (可选的流的名字) ]
...
)
...
)
LIST (‘movi’
{ SubChunk | LIST (‘rec ’
SubChunk1
SubChunk2
...
)
...
}
...
)
[‘idx1’ (可选的AVI索引块数据) ]
)
AVI文件目前使用最复杂的RIFF文件,可同时存储同步表现的音频视频数据。AVI的RIFF块的形式类型是AVI,它包含3个子块:
信息块——包括文件的通用信息,定义数据格式,所用的压缩算法等参数
数据块——包含实际数据流,即图像和声音序列数据。是文件的主体,也是决定文件容量的主要部分,视频文件的大小等于该文件的数据率乘以该视频播放的时间长度
索引块——索引块包含数据块列表好它们在文件中的位置,以提供文件内数据随机存取能力
- 信息块
ID为“hdrl”的LIST块,定义AVI文件的数据格式。
“hdrl”LIST块包含两个子块,一个是ID为“avih”的子块和一个是ID为”strl”的LIST块。
“avih”块结构:用于记录AVI文件的全局信息,比如流的数量,视频图像的宽和高等:
typedef struct
{
DWORD ChunID; // 必须为'avih'
DWORD ChunkSize; //本数据结构的大小,不包括最初的8個位元組(ID和Size兩個域)
DWORD dwMicroSecPerFrame ; //显示每帧所需的时间ns,定义avi的显示速率
DWORD dwMaxBytesPerSec; //最大的数据传输率
DWORD dwPaddingGranularity; //记录块的长度需为此值的倍数,通常是2048
DWORD dwFlages; //AVI文件的特殊属性,如是否包含索引块,音视频数据是否交叉存储
DWORD dwTotalFrame; //文件中的总帧数
DWORD dwInitialFrames; //说明在开始播放前需要多少桢
DWORD dwStreams; //文件中包含的数据流个数
DWORD dwSuggestedBufferSize; //建议使用的缓冲区的大小,
//通常为存储一桢图像以及同步声音所需要的数据之和
DWORD dwWidth; //图像宽
DWORD dwHeight; //图像高
DWORD dwReserved[4]; //保留值
}MainAVIHeader;
“avih”块的数据部分包含一个或多个“strl”子列表
① 文件中有多少个流,这里就对应有多少个“strl”子列表
② 每个“strl”字列表至少包含一个“strh”块和一个“strf”块
③ “strd”块(保存编解码器需要的一些配置信息)和“strn”块(保存流的名字)是可选的。
④ “strl”子列表出现的顺序与媒体流的编号是对应的,比如第一个“strl”字列表说明的是第一个流(Stream 0),第二个“strl”字列表说明的是第二个流(Stream 1),以此类推
“strh”块结构:用于说明这个“strl”LIST块对应的数据流的头信息:
typedef struct
{
FOURCC fccType; //4字节,表示数据流的种类,vids 表示视频数据流,auds 音频数据流
FOURCC fccHandler; //4字节 ,表示数据流解压缩的驱动程序代号
DWORD dwFlags; //数据流属性
WORD wPriority; //此数据流的播放优先级
WORD wLanguage; //音频的语言代号
DWORD dwInitalFrames; //说明在开始播放前需要多少桢
DWORD dwScale; //数据量,视频每桢的大小或者音频的采样大小
DWORD dwRate; //dwScale /dwRate = 每秒的采样数
DWORD dwStart; //数据流开始播放的位置,以dwScale为单位
DWORD dwLength; //数据流的数据量,以dwScale为单位
DWORD dwSuggestedBufferSize; //建议缓冲区的大小
DWORD dwQuality; //解压缩质量参数,值越大,质量越好
DWORD dwSampleSize; //音频的采样大小
RECT rcFrame; //视频图像所占的矩形
}AVIStreamHeader;
“strf” 块结构:“strf”子块紧跟在“strh”子块之后,其结构是“strh”子块的类型而定,如下所述:
如果strh子块是视频数据流,则strf子块的内容是一个BITMAPINFO结构,如下。
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;
如果strh子块是音频数据流,则strf子块的内容是一个WAVEFORMAT结构,如下:
typedef struct
{
WORD wFormatTag;
WORD nChannels; //声道数
DWORD nSamplesPerSec; //采样率
DWORD nAvgBytesPerSec; //WAVE声音中每秒的数据量
WORD nBlockAlign; //数据块的对齐标志
WORD biSize; //此结构的大小
}WAVEFORMAT
“strd”块结构:“strd”子块紧跟在strf子块后,存储供压缩驱动程序使用的参数,不一定存在,也没有固定的结构
“strl”List块定义的AVI数据流依次将“hdrl”LIST块中的数据流头结构与“movi”LIST块中的数据联系在一起,第一个数据流头结构用于数据流0,第二个用于数据流1,依次类推。
- 数据块
ID为“movi”的LIST块,包含AVI的音视频序列数据,用于保存真正的媒体流数据(视频图像帧数据或音频采样数据等)。保存方式为:
① 将数据块直接嵌套在“movi”列表里面
② 将几个数据块分组成一个“rec”列表后再编排进“movi”列表
(注意:在读取AVI文件内容时,建议将一个“rec”列表中的所有数据块一次性读出)
但是,当文件中包含有多个流的时候,数据块与数据块之间如何区别呢?于是数据块使用了一个四字符吗来表征它的类型,这个四字符码由2个字节的类型吗和2个字节的流编号组成。
“db”——非压缩视频
“dc”——压缩视频
“pc”——改用新的调色板
“wb”——音缩视频
例如:
第一个流(Stream 0)是音频,则表征音频数据块的四字符码为“00wb”;
第二个流(Steam 1)是视频,则表征视频数据块的四字符码为“01db”或“01dc”。
对于视频数据来说,在AVI数据序列中间还可以定义一个新的调色板,每个改变的调色板数据块永“xxpc”来表征,新的调色板使用一个数据结构AVIPALCHANGE来定义。(注意:如果一个流的调色板中途改变,则应在这个流格式的描述中,也及时AVISTREMAHEADER结构的dwFlags中包含一个AVISF_VIDEO_PALCHANGES标记)另外,文字数据块可以使用随意的类型码表征。
- 索引块
ID为“idxl”的子块,定义“movi”LIST块的索引数据,是可选块。
最后紧跟在“hdr”列表块和“movi”列表块之后的,就是AVI文件可选的索引块。这个索引块为AVI文件中每一个媒体数据块进行索引,并且记录它们在文件中的偏移(可能相对于“movi”列表,也可能相对于AVI文件开头)。索引块使用一个四字符码“idxl”来表征,索引信息使用一个数据结构AVIOLDINDEXl来定义。
typedef struct _avioldindex {
FOURCC fcc; // 必须为‘idx1’
DWORD cb; // 本数据结构的大小,不包括最初的8个字节(fcc和cb两个域)
struct _avioldindex_entry {
DWORD dwChunkId; // 表征本数据块的四字符码
DWORD dwFlags; // 说明本数据块是不是关键帧、是不是‘rec ’列表等信息
DWORD dwOffset; // 本数据块在文件中的偏移量
DWORD dwSize; // 本数据块的大小
} aIndex[]; // 这是一个数组!为每个媒体数据块都定义一个索引信息
} AVIOLDINDEX;
注:如果一个AVI文件包含有索引块,则应在AVI信息头的描述中,也及时AVIMAINHEADER结构的dwFlags中包含一个AVI_HASINDEX标记。还有一种特殊的数据块用一个四字符码“JUNK”来表征,它用于内部数据的对齐(填充),应用程序应该忽略这些数据块的实际意义。
三、WAV文件格式分析
1. 概述
WAV文件是属于RIFF结构形式的文件,这种结构是一种树状结构,其最基本的组成单元是chunk(块),而每一个chunk则由辨识码、数据大小、数据本身组成,其中辨识码就是RIFF四个字母的ASCII码,共占用4个字节,用十六进制表示就是52 49 46 46;辨识码之后的四个字节表示除去文件辨识码和它自己之后剩余文件的大小,最后就是文件的数据。
一个WAV文件通常有三个chunk以及一个可选chunk,其在文件中的排列方式依次是:RIFF chunk、Format chunk、Fact chunk(附加块,可选)、Data chunk。
2. 各子块作用
WAV格式的文件除了前面的文件头之后,还包含若干子块。
fmt子块:用于描述该wav文件的信息,例如,采样率,数据量,编码格式,声道数等,其结构也如RIFF,最开始4个字节表示标识符“fmt”,接着四个字符表示该块大小,后面的18个字节则表示文件的信息,也是fmt子块的“数据”。
fact子块:主要存储关于该文件内容的重要信息,并不是必须的,在标准的WAV文件中没有该子块,只有一些软件转化而来的才会增加该子块。
data子块:包含WAVE文件的数字化波形声音数据,其存放格式依赖于"fmt"子块中SubchunkID, SubchunkSize ,AudioFormat 成员指定的格式种类,在多声道WAVE文件中,样本是交替出现的。如16bit的单声道WAVE文件和双声道WAVE文件的数据采样格式分别如下图所示:
16位单声道:
采样一 | 采样二 | …… | ||
低字节 | 高字节 | 低字节 | 高字节 | …… |
16位双声道:
采样一 | …… | |||
左声道 | 右声道 | …… | ||
低字节 | 高字节 | 低字节 | 高字节 | …… |
文件结构图:
3. 实例分析
打开语音分析实验所用vowel.wav文件
- RIFF chunk
包括RIFF、Size和四字节标识符,均为4字节。
Size:由于是小端顺序,实际上的十六进制数应该是 00 08 08 5A,这个数值+8就是文件的长度。
- Format chunk
ChunkId:fmt
Chunk Size:由于是小端顺序,实际上的十六进制数应该是 00 00 00 10,为16,也就是后续data的长度。
Chunk Data:如下表格
名称 | 偏移地址 | 字节数 | 端序 | 内容 | 当前值 |
---|---|---|---|---|---|
AudioFormat | 0x08 | 2Byte | 小端 | 音频格式 | 1,PCM音频数据的值为1。则当前没有Fact chunk |
NumChannels | 0x0A | 2Byte | 小端 | 声道数 | 1,表示音频数据的声道数,1:单声道,2:双声道。 |
SampleRate | 0x0C | 4Byte | 小端 | 采样率 | 44100 |
ByteRate | 0x10 | 4Byte | 小端 | 每秒数据字节数 | 00 01 77 00->96000。SampleRate * NumChannels * BitsPerSample / 8 |
BlockAlign | 0x14 | 2Byte | 小端 | 数据块对齐 | 2。NumChannels * BitsPerSample / 8 |
BitsPerSample | 0x16 | 2Byte | 小端 | 采样位数 | 采样深度16bit。8:8bit,16:16bit,32:32bit |
- Data
名称 | 偏移地址 | 字节数 | 端序 | 内容 | 当前值 |
---|---|---|---|---|---|
ID | 0x00 | 4Byte | 大端 | 'data' (0x64617461) | “0x77000”,转为十进制为 487424 。 |
Size | 0x04 | 4Byte | 小端 | N | 等于 ByteRate * seconds ,音频数据长度,转为十进制487424。 |
Data | 0x08 | NByte | 小端 | 音频数据 | ... |
这是一个比较特殊的wav文件,因为Format chunk结束后并未直接跟Data chunk,而是有"List"在其后:
ChunkId:LIST
Chunk Size:0x2E,十进制为46
为什么会有List chunk呢?
当其他格式的音频经过格式转换成WAV文件时,会存在"LIST"。当时录制此音频(含元音的音频vowel.wav)时,先保存为MP4文件后转为wav格式,故可以解释"List"的存在。
四、参考资料
https://blog.csdn.net/chenyonken/article/details/79174500
https://blog.csdn.net/sunnylion1982/article/details/38376453
WAV文件格式解析及处理 - 简书 (jianshu.com)
https://blog.csdn.net/ljrsunshine/article/details/89320026