号在整个动态范围内的信噪比基本一致。下面是这两种编码与LPCM的 对比图。
前面讲的PCM编码后的声音数据是需要保存的,WAVE文件常常用来保存PCM编码数据。WAVE文件是微软公司(Microsoft)开发的一种声音文件格式,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持,WAVE文件默认打开工具是WINDOWS的媒体播放器。
3.1 RIFF文件格式标准
WAVE文件是以微软RIFF格式为标准的,RIFF全称为资源互换文件格式(Resources Interchange File Format),是Windows下大部分多媒体文件遵循的一种文件结构。RIFF文件所包含的数据类型由该文件的扩展名来标识,能以RIFF格式存储的数据有很多:音频视频交错格式数据(.AVI)、波形格式数据(.WAV)、位图数据格式(.RDI)、MIDI格式数据(.RMI)、调色板格式(.PAL)、多媒体电影(.RMN)、动画光标(.ANI)等。
如下代码所示的CK结构体是RIFF文件的基本单元,该基本单元也称 Chunk。其中ckID用于标识块中所包含的数据类型,其取值可有'RIFF'、'LIST'、'fmt '、'data'等;ckSize表示存储在ckData域中的数据长度(不包含ckID和ckSize的大小);ckData存储数据,数据以字节为单位存放,如果数据长度为奇数,则最后添加一个空字节。
由于RIFF文件结构最初是由Microsoft和IBM为PC机所定义,RIFF文件是按照小端little-endian字节顺序写入的。
typedef unsigned long DWORD;
typedef unsigned char BYTE;
typedef DWORD FOURCC; // Four-character code
typedef struct {
FOURCC ckID; // The unique chunk identifier
DWORD ckSize; // The size of field <ckData>
BYTE ckData[ckSize]; // The actual data of the chunk
} CK;
Chunk是可以嵌套的,但是只有ckID为'RIFF'或者'LIST'的Chunk才能包含其他的Chunk。标志为'RIFF'的Chunk是比较特殊的,每一个RIFF文件首先存放的必须是一个'RIFF' Chunk,并且只能有这一个标志为'RIFF'的Chunk。
更多RIFF的知识详见这个网站链接 RIFF (Resource Interchange File Format),链接里收集了很多介绍RIFF的资源。
3.2 WAVE文件结构
WAVE是Microsoft开发的一种音频文件格式,它符合上面提到的RIFF文件格式标准,可以看作是RIFF文件的一个具体实例。既然WAVE符合RIFF规范,其基本的组成单元也是Chunk。一个 WAVE文件 通常有三个Chunk以及一个可选Chunk,其在文件中的排列方式依次是:RIFF Chunk,Format Chunk,Fact Chunk(附加块,可选),Data Chunk,如下图所示:
根据上面的WAVE文件结构图,可以定义如下44bytes的wave_head_t用来描述WAVE文件的头。如果你曾经接触过Windows的音频接口API,你会发现wave_fmt_t中的部分结构与标准MSDN里的 WAVEFORMAT 是一致的。
typedef char int8_t; //有符号8位整数
typedef short int16_t; //有符号16位整数
typedef int int32_t; //有符号32位整数
struct _wave_tag
{
int8_t riff[4]; //"RIFF",资源交换文件标志
int32_t filesize; //文件大小(从下个地址开始到文件尾的总字节数)
int8_t wave[4]; //"WAVE",文件标志
} wave_tag_t;
struct _wave_format
{
int8_t fmt[4]; //"fmt ",波形格式标志
int32_t chunksize; //文件内部Chunk信息大小
int16_t wFormatTag; //音频数据编码方式
int16_t wChanles; //声道数
int32_t nSamplesPerSec; //采样率
int32_t nAvgBytesPerSec; //波形数据传输速率(每秒平均字节数)
int16_t nBlockAlign; //数据的调整数(按字节计算)
int16_t wBitsPerSample; //样本数据位数
} wave_fmt_t;
struct _wave_data
{
int8_t data[4]; //"data",数据标志符
int32_t datasize; //采样数据总长度
} wave_dat_t;
struct _wave_head
{
wave_tag_t waveTag;
wave_fmt_t waveFmt;
wave_dat_t waveDat;
} wave_head_t;
wave_head_t结构体内除了wFormatTag成员之外,其他都可以根据字面上的意思来理解。关于wFormatTag的具体定义,可见Windows SDK里的 mmreg.h文件,下面列举了几个最常见Format的Tag值定义:
当WAVE文件的头被解析成功后,下一步便是获取WAVE文件里的声音源数据,我们知道声音文件有单声道和多声道之分,对于单声道文件很好理解,声音数据就是按序排放,而如果是立体声(2声道)文件,那么左右声道的声音数据到底是怎么排放的呢?下面以一个示例立体声文件数据(仅分析前72bytes)进行解释:
offset(h)
00000000: 52 49 46 46 24 08 00 00 57 41 56 45 66 6d 74 20
00000010: 10 00 00 00 01 00 02 00 22 56 00 00 88 58 01 00
00000020: 04 00 10 00 64 61 74 61 00 08 00 00 00 00 00 00
00000030: 24 17 1e f3 3c 13 3c 14 16 f9 18 f9 34 e7 23 a6
00000040: 3c f2 24 f2 11 ce 1a 0d
下图是这个72bytes数据解析图,从图中可以看到,左右声道的声音数据是按块(nBlockAlign指定)交替排放的。
更多WAVE的知识详见这两个网站链接 WAVE Audio File Format 和 Audio File Format Specifications,链接里收集了很多介绍WAVE的资源。
3.3 WAVE文件实例分析
WAVE文件格式我们都了解透彻了,下面我们尝试分析一个经典的WAVE文件:"Windows XP 启动.wav",这个文件可以说是最知名的WAVE文件了,痞子衡特别喜欢这段music,让我们直接用二进制编辑器HxD打开它:
按wave_head_t解析WAVE头可知,这段wave是44.1kHz/16bit双声道线性PCM码音频