Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1605786
  • 博文数量: 585
  • 博客积分: 14610
  • 博客等级: 上将
  • 技术积分: 7402
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-15 10:52
文章存档

2013年(5)

2012年(214)

2011年(56)

2010年(66)

2009年(44)

2008年(200)

分类: C/C++

2012-01-27 16:43:20

wave文件格式 (2007-02-25 20:03)转载
分类: 读书笔记

Wave文件的格式非常混乱。如果把wave文件的格式比作盆汤,毫无疑问有太多的厨师在完全未经协调的情况下,向这道汤里添加了太多的佐料。Wave文 件的格式规范中,有太多相互独立而且缺乏协调的组织向其中增加内容。结果是wave文件中有很多chunk是在重复别的chunk中的数据,而且通常是用 一种完全不同的方式。下面的讲解中我们尽量把注意力集中于wave文件中那些最经常出现的chunk上。

____________________________
| RIFF WAVE Chunk          |
|   groupID  = 'RIFF'      |
|   riffType = 'WAVE'      |
|    __________________    |
|   | Format Chunk     |   |
|   |    ckID = 'fmt ' |   |
|   |__________________|   |
|    __________________    |
|   | Sound Data Chunk |   |
|   |    ckID = 'data' |   |
|   |__________________|   |
|__________________________|

采样点和采样帧

解释一个Wave文件内容的过程很大部分围绕在采样点和采样帧这两个概念上。一个采样点是代表了给定声音在给定的时间点上的一个样本值。如果我们假设才用 的是PCM格式的wave文件,并且样点精度大于8bit,每一个采样点被保存为一个占用9-32bit的2的补码表示的数值。样点精度值保存在 Format chunk中的wBitsPerSample属性中。例如一个16bit波形中的一个采样点是一个线性的16bit有符号整数,取值范围- 32768~32767.但是8bit的波形文件中,每一个样本点是一个线性的无符号数,范围0~255。上述带符号和无符号表示方法的差异,又是微软员 工的杰作。

绝大多数cpu的读写指令都是真对8bit的“字节”的,如果一个波形文件的采样点精度不是8的整数倍,它必须被凑到最接近的8的整数倍。1- 8bits/sample的波形文件用8bit保存一个样本,9-16bits/sample的波形文件用16bit保存一个样本……依此类推。

此外,数据比特是左对齐的,多余的填充bit都被设置为0。例如一个12bit的采样点,在wave文件中占用16bits,所以这两个字节的4-15号bit是数据,0-3号bit是填充的0。下图就是一个占据12bit的样点101000010111在内存中的情况:
| 1   0   1   0   0   0   0   1   0   1   1   1   0   0   0   0 |
|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|
 <---------------------------------------------> <------------->
    12 bit sample point is left justified          rightmost
                                                  4 bits are
                                                  zero padded

应该注意,因为wave格式使用intel体系的little endian字节顺序,所以LSB(Least Significant 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

对多声道声音,(例如一个立体声波形文件),来自每一个声道的采样点数据被交织存放。例如,对于一个立体声波形来说,有两个声道的数据。文件中并不是先把 左声道的全部采样点数据放在文件前半部分,然后把右声道的全部采样点数据集中放在文件后半部分。实际的做法是将两个声道的数据“混合”放置,即:
左声道的第一个样点,右声道的第一个样点,左声道的第二个样点,右声道的第二个样点,左声道的第三个样点,右声道的第三个样点……左右声道的样点交替出现,这就是我们所说的“交织数据”。这样,当播放这些数据的时候,应该被同时播放的数据在文件中的位置总是相邻的。

所有需要被同时“播放”(也就是发送到数-模转换器)的样点,总称为一个“采样帧”。对立体声波形来说,每两个采样点构成一个采样帧。如下图所示: 

  sample       sample              sample
  frame 0      frame 1             frame N
 _____ _____ _____ _____         _____ _____
| ch1 | ch2 | ch1 | ch2 | . . . | ch1 | ch2 |
|_____|_____|_____|_____|       |_____|_____|

对于一个单声道的波形来说,一个采样帧就由一个采样点构成,无需任何交织。对多声道的波形来说,应该按照下面所示的习惯顺序,来安排一个采样帧中的各个采样点的顺序。下面的每一幅图,展示了一种多声道波形文件中的一个采样帧中,各个采样点出现的习惯顺序。

  channels       1         2
             _________ _________ 
  stereo    | left    | right   |
            |_________|_________|

                 1         2         3
             _________ _________ _________ 
  3 channel | left    | right   | center  |
            |_________|_________|_________|

                 1         2         3         4
             _________ _________ _________ _________ 
            | front   | front   | rear    | rear    |
  quad      | left    | right   | left    | right   |
            |_________|_________|_________|_________|

                 1         2         3         4
             _________ _________ _________ _________ 
  4 channel | left    | center  | right   | surround|
            |_________|_________|_________|_________|

                 1         2         3         4         5         6
             _________ _________ _________ _________ _________ _________
            | left    | left    | center  | right   | right   |surround |
  6 channel | center  |         |         | center  |         |         |
            |_________|_________|_________|_________|_________|_________|

一个采样帧中的各个采样点之间没有填充字节,各个采样帧之间也没有填充字节。注意上面的讨论中我们所说的都是非压缩的data chunk中的数据格式。还有一些data chunk中可能存储压缩过的数据,显然这种情况下需要解压缩,解压缩之后的数据还是严格遵守上述格式的。


Format chunk
Format (fmt) chunk描述了波形文件的基本参数,比如抽样频率,bit精度,声道数量。 

#define FormatID 'fmt '   /* chunkID for Format Chunk. 注意这个ID是四个字符,最后的t后面有一个空格! */

typedef struct {
  ID             chunkID;
  long           chunkSize;

  short          wFormatTag;
  unsigned short wChannels;
  unsigned long  dwSamplesPerSec;
  unsigned long  dwAvgBytesPerSec;
  unsigned short wBlockAlign;
  unsigned short wBitsPerSample;

/* 根据wFormatTag的不同取值,这里还可能出现更多的fields */
} FormatChunk;

ID总是"fmt "。chunkSize是chunk的长度,单位是字节,这个长度不包括ID和Size两个域所占用的8字节。针对不同的wFormatTag的取值, chunkSize也可能取不同的值。Wave数据可以以非压缩的格式存放,也可以用很多不同的压缩格式存放在data chunk内。压缩格式存放时,每一个采样点占用的字节数可能是各不相同的。wFormatTag指明数据存储时是否经过压缩。如果经过压缩 (wFormatTag的取值不是1),FormatChunk末尾会被追加更多的域,用以说明压缩格式,以便解压缩。这些关于压缩格式的信息,首先是一 个unsigned short类型数据,指明这个unsigned short之后还有多少字节的附加数据。压缩格式的数据还必须包括一个Fact chunk,这个chunk包括了一个unsigned long数据指明数据被解压缩之后包含多少个样点。压缩格式太多了,详细信息参考微软站点。当wFormatTag=1,用的就是非压缩格式。

wChannels:该声音文件中的声道个数。1-单声道,2-立体声,4-四声道声音,等等。多声道情况下的采样点,采样帧及其格式参见上面的叙述。

dwSamplesPerSec:采样帧频率,即每秒播放的采样帧个数。标准的采样帧频率有11025, 22050,  44100 Hz。但是也有其它非标准的采样帧频率被使用。

dwAvgBytesPerSec:每秒钟有多少帧被播放。dwAvgBytesPerSec被音乐播放工具用于估计顺畅播放声音所需的RAM缓冲区大小。这个值应该用下面的公式计算并四舍五入到最接近的整数上:
dwSamplesPerSec * wBlockAlign

wBlockAlign该用下面的公式计算并四舍五入到最接近的整数上:
wChannels * (wBitsPerSample / 8)
这个值是一个采样帧的大小,单位是字节。例如一个16bit立体声声音文件中的一个采样帧大小是4字节。

The wBitsPerSample指定一个采样点的数据精度是多少个bit。

每个wave文件中有且只有一个format chunk。

举个例子说明format chunk。下面是windows标准的开机音乐的format chunk内容:
00000000h: 52 49 46 46 24 6B 07 00 57 41 56 45 66 6D 74 20 ; RIFF$k..WAVEfmt 
00000010h: 10 00 00 00 01 00 02 00 22 56 00 00 88 58 01 00 ; ........"V..ˆX..
00000020h: 04 00 10 00                                     ; ....
从0ch开始是format chunk。
0ch-0fh是四个字节的chunkID: “fmt “。
10h-13h是chunkSize: 16。这个值是刨去chunkID和chunkSize之后format chunk的长度:16字节。
14h-15h是wFormatTag: 0x0001, 非压缩wave音频。
16h-17h是wChannels: 0x0002, 立体声音乐。
18h-1bh是dwSamplesPerSec: 0x5622=22050,即采样帧频率22050Hz.
1ch-1fh是dwAvgBytesPerSec: 0x015888=88200,采样帧频率22050,双声道,采样精度16bits=2bytes(见下面wBitsPerSample的取值),所以 每秒播放的字节数是22050*2*2=88200字节。有dwAvgBytesPerSec = dwSamplesPerSec * wBlockAlign。
20h-21h是wBlockAlign,0x0004,有wBlockAligh = wChannels * (wBitsPerSample / 8)。
22h-23h是wBitsPerSample,0x0010,16bits每样点。

继续说明data chunk的格式。它包含了真正的声音样点数据:

#define DataID 'data'  /* chunk ID for data Chunk */

typedef struct {
  ID             chunkID;
  long           chunkSize;

  unsigned char  waveformData[];
} DataChunk;

Data chunk的chunkID总是data。chunkSize是chunk占据的字节数,不计ID和size两个域占用的8个字节,不计为了使chunk占用的字节数为偶数而添加的填充字节。下面的讨论都基于非压缩格式的wave数据。

waveformData数组中就是真正的波形数据。这些数据被划分为成为“采样帧”的单元,前面已经介绍过。从chunkSize属性可以知道波形数据 的长度,以字节为单位。Data chunk中保存的采样帧的个数,可以由data chunk的chunkSize除以format chunk中的wBlockAligh得到。Data chunk是必须有的,每个wave文件中必须有且只有一个data chunk。

波形文件的存储,远远不止这一种形式。且不论那些数不胜数的压缩格式,此外还有一些脑子进水了的人,把数据存储在wave文件中内嵌的IFF List中,这个list会包含多个data chunk和slnt chunk,此时Type ID是wavl。不要在你的程序里支持这种wave文件格式,除非你脑子里也进水了。

我的更多文章
阅读(812) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~