RIFF文件格式介绍

1,439 阅读10分钟

RIFF(Resource Interchange File Format)

RIFF——资源交换文件格式,由多个分离的数据块(chunk)组成。

FOURCC Identifier(four-character code identifier)

数据块中的数据类型由FOURCC标识符表示,一个FOURCC标识符是一个32位(四字节)的无符号整形,由四个表征RIFF文件中数据块类型的ASCII字符组合而成。例如在一个小端字节序的系统中,FOURCC标识符“abcd”表示成0x64636261。FOURCC标识符可以包含空格,所以“abc”是一个有效的FOURCC标识符。音频文件利用FOURCC来表征音频格式块、音频数据块或者其他块类型。例如:

RIFF、fmt、data这三个是非常常见的FOURCC标识符,对它们的描述可以见下面。

标准的RIFF块在其数据部分最开始的四字节包含一个“WAVE”或者“XWMA”的文件类型。fmt块包含了音频文件的格式头,这个块中的数据取自WAVEFORMATEX,WAVEFORMATEXTENSIBLE,ADPCMWAVEFORMAT三个结构体中的一种。data块包含了音频文件的音频数据。

RIFF文件

一个RIFF文件是一个包含零个或者多个其他块的RIFF块。

RIFF块由"RIFF",fileSize,fileType,data组成。“RIFF”是ASCII码表示的FOURCC标识符。fileSize是一个四字节的值,给出了文件中的数据长度,该长度包含了fileType和其后跟着的数据,但是不包括“RIFF”标识符这四个字节以及fileSize本身的长度。

其余块由chunkID,chunkSize,data组成。chunkID是一个FOURCC标识符。chunkSize是一个四字节的值,给出了其后跟着的数据的长度。数据长度总是向上对齐到最近的一个WORD边界上,chunkSize表示的是有效的数据长度,不包括对齐填充的数据和chunkID以及chunkSize本身的长度。比如data块、fmt块和fact块都是这种类型的块。

fmt块

fmt块的chunkID是FOURCC标识符“fmt ”,chunkSize就是后面数据的长度,这里的数据指的是WAVEFORMATEX,WAVEFORMATEXTENSIBLE,ADPCMWAVEFORMAT之类的结构体。

1.typedef struct tWAVEFORMATEX {  
2.  WORD  wFormatTag;		// 音频格式类型
3.  WORD  nChannels;			// 通道数  
4.  DWORD nSamplesPerSec;  	// 采样率
5.  DWORD nAvgBytesPerSec;	// 每秒字节数,也即平均数据传输速率  
6.  WORD  nBlockAlign;  		// 以字节为单位的快对齐大小
7.  WORD  wBitsPerSample;  	// 每个样本的字节数
8.  WORD  cbSize;  // 表征附在WAVEFORMATEX结构体后面的额外数据的长度,以字节为单位,没有额外数据就是0,在PCM中,该字段甚至都可以没有,则chunkSize就是16
9.} WAVEFORMATEX, *PWAVEFORMATEX, *NPWAVEFORMATEX, *LPWAVEFORMATEX;  

以上是结构体WAVEFORMATEX,共18字节。

对于wBitsPerSample,如果是PCM,则等于8或者16,如果wFormatTag = WAVE_FORMAT_IEEE_FLOAT,则等于32。对于G726(它编码后的比特率有16kbit/s、24kbit/s、32kbit/s、40kbit/s,分别对应数据压缩比为8:1、16:3、4:1和16:5,码字分别为2、3、4和5 bits),如果是16:5的压缩比,也即输出的单个样本占5bit,那么wBitsPerSample就等于5。所以我的理解是该字段表征的是编码后每个样本所占的比特数。

对于nBlockAlign,微软给的说明是,如果是PCM或者32位的FLOAT,则等于 (nChannels*wBitsPerSample)/8,表示的是单个音频样本的大小。对于其他的格式,需要根据制造商对于该格式的详细信息计算该值。处理音频数据时,需要以BlockAlign为单位进行处理,而不应该从该单位的中间某个地方处取数据进行处理。所以可以理解成数据处理的最小单元大小。以上面G726为例进行说明,每个采样点编码成5bit,那么40bit就是一个BlockAlign。

示例

上面是一张从网上截取的标准PCM的WAV格式文件,按照上面的介绍来对其进行分析。

文件由RIFF块、fmt块和data块组成。data块从序号12开始直到最后,块的chunkID是“data”,chunkSize是0xF83,不包含填充部分(填充了一个字节)和chunkID以及chunkSize本身的长度。前面的RIFF块由FOURCC标识符“RIFF”开始,紧接着是fileSize0xFA8(=4008=4016-8,除去了“RIFF”标识符这四个字节以及fileSize本身的长度),然后是WAVE文件类型和fmt块。fmt块中包含了音频文件的相关参数,比如压缩类型、采样率、通道数等等。然后就是后面的data块,包含了实际的音频文件数据。

这个示例中的fmt块没有额外数据,连字段cbSize也没有,所以fmt块的chunkSize是16,比上面介绍的WAVEFORMATEX结构体少了2个字节的cbSize。

微软的ADPCM就是一个利用额外信息的例子。在这种格式中,cbSize等于32,额外信息就是编码和解码过程中用到的系数对。

1.typedef struct adpcmwaveformat_tag {
2.    WAVEFORMATEX wfx;  		// WAVEFORMATEX部分,下面是额外信息部分
3.    WORD wSamplesPerBlock;  
4.    WORD wNumCoef;  
5.    ADPCMCOEFSET aCoef[];  
6.} ADPCMWAVEFORMAT;  

WAVEFORMATEX结构描述的音频格式是WAVEFORMATEXTENSIBLE结构的子集。例如,WAVEFORMATEX可以描述单声道或者双声道,8bit宽或者16bit宽的PCM音频,或者是32bit浮点型采样值。此外,它还可以描述常见的非PCM型音频,如AC-3、WMA Pro。WAVEFORMATEX可以准确地描述单声道或者双声道格式,这些格式每个样本的有效比特位数和样本容器的大小一致。比如8bit或者16bit的PCM,可以认为它们的样本容器尺寸就是8bit或者16bit。如果要描述多于两个声道的PCM格式,或者PCM格式中单个样本的有效比特位数比样本容器尺寸小(比如单个样本20bit存储在3个字节中),则需要WAVEFORMATEXTENSIBLE结构。WAVEFORMATEXTENSIBLE结构利用通道掩码对多个通道的配置进行了映射,也指出了单样本的有效比特位数和样本容器尺寸。

WAVEFORMATEX中的wFormatTag成员描述了格式类型,16bit宽度(wFormatTag中第一个字母w(word)应该就是表示16bit宽度的意思!?)的格式类型定义在头文件Mmreg.h中。如果要描述一个非PCM类型的格式,并且这种格式类型也没有定义在头文件Mmreg.h中,用WAVEFORMATEXTENSIBLE结构,里面的GUID来表示类型。必要时,硬件提供商可以独立地产生一个GUID来表示一个新的类型。这个新产生的GUID类型也不需要向微软注册。

WAVEFORMATEX和PCMWAVEFORMAT十分类似,区别就是后者没有cbSize。通常来说,对于PCM类型,cbSize应该被忽略,因此在处理PCM时,WAVEFORMATEX和PCMWAVEFORMAT被同等对待。在处理IEEE 32位float型时,cbSize也设成零。对于其他的类型,cbSize表示附在WAVEFORMATEX结构后面的额外信息的长度,以字节为单位。

微软文档中有这么一段文字“If wFormatTag = WAVE_FORMAT_EXTENSIBLE,set cbSize to sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) plus the size of any format-specific data that is appended to the WAVEFORMATEXTENSIBLE structure。”这个不是非常理解,先摘录在此。

下面的图描述了WAVEFORMAT、PCMWAVEFORMAT、WAVEFORMATEX和WAVEFORMATEXTENSIBLE之间的结构关系。

代码

1.typedef struct{  
2.    char riff[4];  
3.    unsigned int u32ChunkSize;  
4.    char rifftype[4];  
5.    char chunkID[4];  
6.    unsigned int u32ChunkDataSize;  
7.    unsigned short u16EncodeMode;  
8.    unsigned short u16NbChannels;  
9.    unsigned int u32SampleRate;  
10.   unsigned int u32AverageBytesPerSecond;  
11.   unsigned short u16BlockAlign;  
12.   unsigned short u16BitWidth;  
13.   char chunkIDEx1[4];  
14.   unsigned int u32ChunkExDataSize;  
15.}WAVEFORMATEX_S; 

这个WAVEFORMATEX_S是完全按照上面的图来构造的,实际上是有其他形式的,可以自行查阅相关资料。

下面的代码在addr地址起始处加上WAVEFORMATEX_S结构体(结构体后面添加裸音频流数据代码自行完成)。实际测试中,在该结构体后面加上海思3516D芯片编码g711u的裸码流后可以用ffplay播放。相反如果不加WAVEFORMATEX_S结构体,而是在ffplay命令行里面将g711u相关的音频参数输入进去,则不能播放。

1.int GetSampleRates(AUDIO_SAMPLE_E enSampleRate)  
2.{  
3.    if (enSampleRate == SAMPLE_RATE_8000)  
4.    {  
5.        return 8000;  
6.    }  
7.    else if (enSampleRate == SAMPLE_RATE_11025)  
8.    {  
9.        return 11025;  
10.    }  
11.    else if (enSampleRate == SAMPLE_RATE_12000)  
12.    {  
13.        return 12000;  
14.    }  
15.    else if (enSampleRate == SAMPLE_RATE_22050)  
16.    {  
17.        return 22050;  
18.    }  
19.    else if (enSampleRate == SAMPLE_RATE_24000)  
20.    {  
21.        return 24000;  
22.    }  
23.    else if (enSampleRate == SAMPLE_RATE_48000)  
24.    {  
25.        return 48000;  
26.    }  
27.    else if (enSampleRate == SAMPLE_RATE_64000)  
28.    {  
29.        return 64000;  
30.    }  
31.    else if (enSampleRate == SAMPLE_RATE_96000)  
32.    {  
33.        return 96000;  
34.    }  
35.    else if (enSampleRate == SAMPLE_RATE_16000)  
36.    {  
37.        return 16000;  
38.    }  
39.    else if (enSampleRate == SAMPLE_RATE_32000)  
40.    {  
41.        return 32000;  
42.    }  
43.    else if (enSampleRate == SAMPLE_RATE_44100)  
44.    {  
45.        return 44100;  
46.    }  
47.    else  
48.    {  
49.        return -1;  
50.    }  
51.  
52.}  
53.int GetAIOCHNums(SOUND_MODE_E enSoundMode){  
54.    if (enSoundMode == AUDIO_SOUND_SINGLE)  
55.    {  
56.        return 1;  
57.    }  
58.    else if (enSoundMode == AUDIO_SOUND_DOUBLE)  
59.    {  
60.        return 2;  
61.    }  
62.    else  
63.    {  
64.        return -1;  
65.    }  
66.}  
67.int GetBitsWidth(BIT_WIDTH_E enBitWidth){  
68.    if (enBitWidth == BIT_WIDTH_16)  
69.    {  
70.        return 16;  
71.    }  
72.    else if (enBitWidth == BIT_WIDTH_8)  
73.    {  
74.        return 8;  
75.    }  
76.    else if (enBitWidth == BIT_WIDTH_24)  
77.    {  
78.        return 24;  
79.    }  
80.    else  
81.    {  
82.        return -1;  
83.    }  
84.}  
85.int GetWaveEncodeMode(AUDIO_STREAM_MODE_E enMode){  
86.    if (enMode == STREAM_MODE_G711A)  
87.    {  
88.        return 6;  
89.    }  
90.    else if (enMode == STREAM_MODE_G711U)  
91.    {  
92.        return 7;  
93.    }  
94.    else if (enMode == STREAM_MODE_G726)  
95.    {  
96.        return 64;  
97.    }  
98.    else if (enMode == STREAM_MODE_PCM)  
99.    {  
100.        return 1;  
101.    }  
102.    else if (enMode == STREAM_MODE_ADPCMA)  
103.    {  
104.        return 2;  
105.    }  
106.    else  
107.    {  
108.        return -1;  
109.    }  
110.}  
111.  
112.int add_waveformatex(unsigned char* addr, int s32DataLen, AUDIO_PARAM_S* pstAudioParams){  
113.    WAVEFORMATEX_S stWaveFormatExHeader;  
114.    int s32HeadLen = sizeof(WAVEFORMATEX_S);  
115.  
116.    stWaveFormatExHeader.riff[0] = 'R';  
117.    stWaveFormatExHeader.riff[1] = 'I';  
118.    stWaveFormatExHeader.riff[2] = 'F';  
119.    stWaveFormatExHeader.riff[3] = 'F';  
120.    stWaveFormatExHeader.u32ChunkSize = (s32DataLen + s32HeadLen + 4) / 4 * 4 - 8;  // 向上取整到4字节对齐  
121.    stWaveFormatExHeader.rifftype[0] = 'W';  
122.    stWaveFormatExHeader.rifftype[1] = 'A';  
123.    stWaveFormatExHeader.rifftype[2] = 'V';  
124.    stWaveFormatExHeader.rifftype[3] = 'E';  
125.    stWaveFormatExHeader.chunkID[0] = 'f';  
126.    stWaveFormatExHeader.chunkID[1] = 'm';  
127.    stWaveFormatExHeader.chunkID[2] = 't';  
128.    stWaveFormatExHeader.chunkID[3] = ' ';  
129.    stWaveFormatExHeader.u32ChunkDataSize = 16;  
130.    stWaveFormatExHeader.u16EncodeMode = GetWaveEncodeMode(pstAudioParams->enMode);  
131.    stWaveFormatExHeader.u16NbChannels = GetAIOCHNums(pstAudioParams->enSoundMode);  
132.    stWaveFormatExHeader.u32SampleRate = GetSampleRates(pstAudioParams->enReSampleRate);  
133.    stWaveFormatExHeader.u32AverageBytesPerSecond = stWaveFormatExHeader.u32SampleRate * GetBitsWidth(pstAudioParams->enBitWidth) / 8;  
134.    stWaveFormatExHeader.u16BlockAlign = GetBitsWidth(pstAudioParams->enBitWidth) / 8 * stWaveFormatExHeader.u16NbChannels;  
135.    stWaveFormatExHeader.u16BitWidth = GetBitsWidth(pstAudioParams->enBitWidth);  
136.    stWaveFormatExHeader.chunkIDEx1[0] = 'd';  
137.    stWaveFormatExHeader.chunkIDEx1[1] = 'a';  
138.    stWaveFormatExHeader.chunkIDEx1[2] = 't';  
139.    stWaveFormatExHeader.chunkIDEx1[3] = 'a';  
140.    stWaveFormatExHeader.u32ChunkExDataSize = s32DataLen;  
141.    memcpy(addr, &stWaveFormatExHeader, s32HeadLen);  
142.      
143.    return 0;  
144.}  

但是,对于海思3516d编码出来的ADPCM类型的音频数据,采用下面的WAVEFORMATEX_ADPCM_S结构体,实测不能播放,不知为何!?现记录如下:

1.typedef struct{  
2.    char riff[4];  
3.    unsigned int u32ChunkSize;  
4.    char rifftype[4];  
5.    char chunkID[4];  
6.    unsigned int u32ChunkDataSize;  
7.    unsigned short u16EncodeMode;  
8.    unsigned short u16NbChannels;  
9.    unsigned int u32SampleRate;  
10.    unsigned int u32AverageBytesPerSecond;  //编码后每秒的字节数  
11.    unsigned short u16BlockAlign;  
12.    unsigned short u16BitWidthPerEncSmp;  //编码后每个样本点的比特数  
13.    unsigned short u16CbSize;  
14.    unsigned short u16SmpPerBlock;  
15.    unsigned short u16NumCoef;  
16.    signed short aCoeff[14];  
17.    char chunkIDEx1[4];  
18.    unsigned int u32ChunkExDataSize;  
19.}WAVEFORMATEX_ADPCM_S;  

1.int add_waveformatex(unsigned char* addr, int s32DataLen, AUDIO_PARAM_S* pstAudioParams){  
2.    WAVEFORMATEX_ADPCM_S stWaveFormatExHeader;  
3.    signed short aCoeff[14] = {256,0,  512,-256,  0,0,  192,64,  240,0,  460,-208,  392,-232};  
4.    int s32HeadLen = sizeof(WAVEFORMATEX_ADPCM_S);  
5.  
6.    stWaveFormatExHeader.riff[0] = 'R';  
7.    stWaveFormatExHeader.riff[1] = 'I';  
8.    stWaveFormatExHeader.riff[2] = 'F';  
9.    stWaveFormatExHeader.riff[3] = 'F';  
10.    stWaveFormatExHeader.u32ChunkSize = s32DataLen + s32HeadLen - 8;  
11.    stWaveFormatExHeader.rifftype[0] = 'W';  
12.    stWaveFormatExHeader.rifftype[1] = 'A';  
13.    stWaveFormatExHeader.rifftype[2] = 'V';  
14.    stWaveFormatExHeader.rifftype[3] = 'E';  
15.    stWaveFormatExHeader.chunkID[0] = 'f';  
16.    stWaveFormatExHeader.chunkID[1] = 'm';  
17.    stWaveFormatExHeader.chunkID[2] = 't';  
18.    stWaveFormatExHeader.chunkID[3] = ' ';  
19.    stWaveFormatExHeader.u32ChunkDataSize = 18;  
20.    stWaveFormatExHeader.u16EncodeMode = GetWaveEncodeMode(pstAudioParams->enMode);  
21.    stWaveFormatExHeader.u16NbChannels = GetAIOCHNums(pstAudioParams->enSoundMode);  
22.    stWaveFormatExHeader.u32SampleRate = GetSampleRates(pstAudioParams->enReSampleRate);  
23.    stWaveFormatExHeader.u32AverageBytesPerSecond = stWaveFormatExHeader.u32SampleRate * GetAencOutBitsWidth(pstAudioParams->enMode) / 8;  
24.    stWaveFormatExHeader.u16BlockAlign = 244;  // 这个值有待进一步确认  
25.    stWaveFormatExHeader.u16BitWidthPerEncSmp = GetAencOutBitsWidth(pstAudioParams->enMode);  
26.    stWaveFormatExHeader.u16CbSize = 32;  
27.    stWaveFormatExHeader.u16SmpPerBlock = 488;  
28.    stWaveFormatExHeader.u16NumCoef = 7;  // 7对14个系数  
29.    memcpy(stWaveFormatExHeader.aCoeff, aCoeff, stWaveFormatExHeader.u16NumCoef * sizeof(signed short)*2);  
30.    stWaveFormatExHeader.chunkIDEx1[0] = 'd';  
31.    stWaveFormatExHeader.chunkIDEx1[1] = 'a';  
32.    stWaveFormatExHeader.chunkIDEx1[2] = 't';  
33.    stWaveFormatExHeader.chunkIDEx1[3] = 'a';  
34.    stWaveFormatExHeader.u32ChunkExDataSize = s32DataLen;  
35.    memcpy(addr, &stWaveFormatExHeader, s32HeadLen);  
36.  
37.    return 0;  
38.}