WAV文件格式研究笔记
WAV文件格式是(WAV From format)的简写。WAV是指文件格式,而数据编码格式是多样的,目前微软提供的数据格式只有一种PCM -脉派编码调变(Pulse Code Modulation也就是最常见的无压缩WAV)。其他的数据格式有G.723.1、ACELP、CCITT A-Law、CCITT u-Law、TrueSpeed(TM)、GSM 6.10等,这些格式大多数是为电话或调制解调器等低速语音为主的设备而使用,它们一般采用比较窄的采样范围来产生比较大的压缩比,并没有统一标准。不在本文讨论范围。
·RIFF文件和WAV文件格式
在Windows环境下,大部分的多媒体文件都依循着一种结构来存放信息,这种结构称为"资源互换文件格式"(Resources lnterchange File Format),简称RIFF。例如声音的WAV文件、视频的AV1文件等等均是由此结构衍生出来的。RIFF可以看做是一种树状结构,其基本构成单位为chunk,犹如树状结构中的节点,每个chunk由"辨别码"、"数据大小"及"数据"所组成。
辨别码(ID)由4个ASCII码所构成,数据大小则标示出紧跟其后数据的长度(单位为Byte),而数据大小本身也用掉4个Byte,所以事实上一个chunk的长度为数据大小加8。一般而言,chunk本身并不允许内部再包含chunk,但有两种例外,分别为以"RIFF"及"LIST"为辨别码的chunk。而针对此两种chunk,RIFF又从原先的"数据"中切出4个Byte。 此4个Byte称为"格式辨别码",然而RIFF又规定文件中仅能有一个以"RIFF"为辨别码的chunk。
在Windows环境下,大部分的多媒体文件都依循着一种结构来存放信息,这种结构称为"资源互换文件格式"(Resources lnterchange File Format),简称RIFF。例如声音的WAV文件、视频的AV1文件等等均是由此结构衍生出来的。RIFF可以看做是一种树状结构,其基本构成单位为chunk,犹如树状结构中的节点,每个chunk由"辨别码"、"数据大小"及"数据"所组成。
辨别码(ID)由4个ASCII码所构成,数据大小则标示出紧跟其后数据的长度(单位为Byte),而数据大小本身也用掉4个Byte,所以事实上一个chunk的长度为数据大小加8。一般而言,chunk本身并不允许内部再包含chunk,但有两种例外,分别为以"RIFF"及"LIST"为辨别码的chunk。而针对此两种chunk,RIFF又从原先的"数据"中切出4个Byte。 此4个Byte称为"格式辨别码",然而RIFF又规定文件中仅能有一个以"RIFF"为辨别码的chunk。
·文件结构
WAV文件是chunk的集合,其中有两个chunk是不可缺少的,分别是“fmt”(format)和“data”chunk,fmt装载的是wav文件的各项参数,如采样率。data chunk装载的是音频数据。其他的chunk则是可选的。
所有音频应用程序必须能读取这两个主要的chunk,所有音频复制程序必须能复制所有chunk。
chunk在文件中的顺序是不受限制的,除了一条规则:fmt chunk必须在data chunk前面。
绝大部分人写程序的时候都以为fmt chunk必须放在文件的开头,紧跟riff标识。实际上这并不是必须的,因为微软白皮书没要求如此。但这样搞也没错。
下图显示一个简单的WAV文件结构
__________________________
| riff wave chunk |
| groupid = 'riff' |
| rifftype = 'wave' |
| __________________ |
| | format chunk | |
| | ckid = 'fmt ' | |
| |__________________| |
| __________________ |
| | sound data chunk | |
| | ckid = 'data' | |
| |__________________| |
|__________________________|
所有音频应用程序必须能读取这两个主要的chunk,所有音频复制程序必须能复制所有chunk。
chunk在文件中的顺序是不受限制的,除了一条规则:fmt chunk必须在data chunk前面。
绝大部分人写程序的时候都以为fmt chunk必须放在文件的开头,紧跟riff标识。实际上这并不是必须的,因为微软白皮书没要求如此。但这样搞也没错。
下图显示一个简单的WAV文件结构
__________________________
| riff wave chunk |
| groupid = 'riff' |
| rifftype = 'wave' |
| __________________ |
| | format chunk | |
| | ckid = 'fmt ' | |
| |__________________| |
| __________________ |
| | sound data chunk | |
| | ckid = 'data' | |
| |__________________| |
|__________________________|
·数据类型
所有数据存储在8bit字节中,按照intel 80x86 (ie, little endian)方式排列,多字节排列顺序如下。
7 6 5 4 3 2 1 0
+-----------------------+
char: | lsb msb |
+-----------------------+
+-----------------------+
char: | lsb msb |
+-----------------------+
7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8
+-----------------------+-----------------------+
short: | lsb byte 0 | byte 1 msb |
+-----------------------+-----------------------+
+-----------------------+-----------------------+
short: | lsb byte 0 | byte 1 msb |
+-----------------------+-----------------------+
7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 23 22 21 20 19 18 17 16 31 30 29 28 27 26 25 24
+-----------------------+-----------------------+-----------------------+-----------------------+
long: | lsb byte 0 | byte 1 | byte 2 | byte 3 msb |
+-----------------------+-----------------------+-----------------------+-----------------------+
+-----------------------+-----------------------+-----------------------+-----------------------+
long: | lsb byte 0 | byte 1 | byte 2 | byte 3 msb |
+-----------------------+-----------------------+-----------------------+-----------------------+
·采样频率,采样精度,声道数量
采样频率(sample rate):1秒钟采样的次数。采样次数越多,越能细分声音的频率。音质越好。
采样精度(bit resolution):(采样精度、采样位数、采样值或取样值,随便你怎么叫)用来描述每次采样结果的空间大小。也就是用多大的取值范围去描述一个采样点的值(振幅)。精度越高,对声音的分辨力越强。
声道数量(channels):那就是声道数量...
采样频率(sample rate):1秒钟采样的次数。采样次数越多,越能细分声音的频率。音质越好。
采样精度(bit resolution):(采样精度、采样位数、采样值或取样值,随便你怎么叫)用来描述每次采样结果的空间大小。也就是用多大的取值范围去描述一个采样点的值(振幅)。精度越高,对声音的分辨力越强。
声道数量(channels):那就是声道数量...
·采样点和采样帧(sample point and sample frame)
采样点是描述某个时刻的声音样本。采样精度就是这个时间上声音的振幅可以用多大的取值空间描述。16bit的采样精度取值范围是-32768 (0x8000)到32767 (0x7fff)。中点,也就是静音点是0。但对于8bit的采样精度来说,范围却是从0-255,静音点在128。(为何有这样的差异?问比盖去。他的人弄的)
因为cpu读数据都是用8bit的byte做单位。所以如果你用的是9-16bit的精度,每个采样点用2byte。17-24bit用3byte,25-32bit就用4byte双word。按左对齐,空出的bit补0(pad部分)。具体排列方式按下图。
___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___
| | | | | | | | | | | | | | | | |
| 1 0 1 0 0 0 0 1 0 1 1 1 0 0 0 0 |
|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|
<---------------------------------------------> <------------->
12bit 采样点按照左对齐方式先排满左边 右面4bit补0(pad)
注意,根据intel little endian存储顺序,按byte为单位从左到右从小到大,因此存到介质上面应该是下面这个顺序:
| | | | | | | | | | | | | | | | |
| 1 0 1 0 0 0 0 1 0 1 1 1 0 0 0 0 |
|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|
<---------------------------------------------> <------------->
12bit 采样点按照左对齐方式先排满左边 右面4bit补0(pad)
注意,根据intel little endian存储顺序,按byte为单位从左到右从小到大,因此存到介质上面应该是下面这个顺序:
___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___
| | | | | | | | | | | | | | | | | |
| 0 1 1 1 0 0 0 0 | | 1 0 1 0 0 0 0 1 |
|___|___|___|___|___|___|___|___| |___|___|___|___|___|___|___|___|
<-------------> <-------------> <----------------------------->
bits 0 to 3 4 pad bits bits 4 to 11
| | | | | | | | | | | | | | | | | |
| 0 1 1 1 0 0 0 0 | | 1 0 1 0 0 0 0 1 |
|___|___|___|___|___|___|___|___| |___|___|___|___|___|___|___|___|
<-------------> <-------------> <----------------------------->
bits 0 to 3 4 pad bits bits 4 to 11
如果当前的WAV文件是多声道的怎么办?
假如是2声道的,先放左边声道的采样点,然后放右面声道的。接着是下一个时刻的左声道采样点。这样用两个采样点分别表示左右声道,在播放的时候同一时间播放出来。那么一个时刻上的采样点的集合就成为采样帧。单声道文件每个采样帧包括1个采样点,两声道的采样帧就有2个采样点。如此类推。
sample sample sample
frame 0 frame 1 frame n
_____ _____ _____ _____ _____ _____
| ch1 | ch2 | ch1 | ch2 | . . . | ch1 | ch2 |
|_____|_____|_____|_____| |_____|_____|
_____
| | = one sample point
|_____|
frame 0 frame 1 frame n
_____ _____ _____ _____ _____ _____
| ch1 | ch2 | ch1 | ch2 | . . . | ch1 | ch2 |
|_____|_____|_____|_____| |_____|_____|
_____
| | = one sample point
|_____|
下面是大于两声道的情况下的排列标准。
channels 1 2
_________ _________
| left | right |
stereo | | |
|_________|_________|
1 2 3
_________ _________ _________
| left | right | center |
3 channel | | | |
|_________|_________|_________|
1 2 3 4
_________ _________ _________ _________
| front | front | rear | rear |
quad | left | right | left | right |
|_________|_________|_________|_________|
_________ _________ _________ _________
| front | front | rear | rear |
quad | left | right | left | right |
|_________|_________|_________|_________|
1 2 3 4
_________ _________ _________ _________
| left | center | right | surround|
4 channel | | | | |
|_________|_________|_________|_________|
_________ _________ _________ _________
| left | center | right | surround|
4 channel | | | | |
|_________|_________|_________|_________|
1 2 3 4 5 6
_________ _________ _________ _________ _________ _________
| left | left | center | right | right |surround |
6 channel | center | | | center | | |
|_________|_________|_________|_________|_________|_________|
_________ _________ _________ _________ _________ _________
| left | left | center | right | right |surround |
6 channel | center | | | center | | |
|_________|_________|_________|_________|_________|_________|
note:以上为无压缩WAVE格式排列方式。显然这样的方式可压缩空间很大(否则也不会跑出ape这类无损压缩格式了)。如果是有压缩格式,则不一定适用上述规则。
·the format chunk
format(fmt)chunk 描述了WAVEFROM数据的基本参数,例如采样频率,精度,声道数量等等。
#define formatid 'fmt ' /* chunkid for format chunk. note: 因为ID是4字节,所以fmt后面有个空格!切记 */
typedef struct ... {
id chunkid;
long chunksize;
short wformattag;
unsigned short wchannels;
unsigned long dwsamplespersec;
unsigned long dwavgbytespersec;
unsigned short wblockalign;
unsigned short wbitspersample;
/**//* note: there may be additional fields here, depending upon wformattag. */
} formatchunk;
typedef struct ... {
id chunkid;
long chunksize;
short wformattag;
unsigned short wchannels;
unsigned long dwsamplespersec;
unsigned long dwavgbytespersec;
unsigned short wblockalign;
unsigned short wbitspersample;
/**//* note: there may be additional fields here, depending upon wformattag. */
} formatchunk;
chunksize:大小,不包括chunkid和chunksize占用的8个字节,单位是byte
wformattag:如果data chunk里面的数据是无压缩的话,那么每个采样帧和每个采样点的长度应该是固定并且符合上面的采样点规定的,但如果是有压缩的话,这就不一定了。wformattag就是用来标识是否有压缩的。在无压缩的情况下,wformattag的值是1,并且没有fmt chunk没有附加fields(further fields)。wformattag不等于1的其他情况下的处理方式要看微软网站。
wchannels:就是声道数量。1、2、3、4、6...
dwsamplespersec:采样频率、每秒采样次数。单位赫兹。标准的取值如 11025, 22050, and 44100。
dwavgbytespersec:每秒采样数据的空间大小,也就是正常播放的话,每秒要读多少数据。用来方便程序评估留多大缓冲区。dwavgbytespersec的值必须根据下列公式。
dwavgbytespersec = round(dwsamplespersec * wblockalign)
wblockalign:块对齐位,值从以下公式得出。
wblockalign = round(wchannels * (wbitspersample % 8))
基本上说,wblockalign就等于采样帧大小。wbitspersample就是采样率。
每个合法的WAV文件都必须有唯一的一个fmt chunk。
·data chunk
放声音数据的地方了。
#define dataid 'data' /* chunk id for data chunk */
typedef struct ... {
id chunkid;
long chunksize;
unsigned char waveformdata[];
} datachunk;
typedef struct ... {
id chunkid;
long chunksize;
unsigned char waveformdata[];
} datachunk;
chunksize:不包括chunkid,和chunksize占用的8个字节。
waveformdata:这个数组里面每个元素就是一个采样帧,大小看wblockalign
每个合法的WAV文件都必须有唯一的一个data chunk。