WINDOWS下对音频的处理过程
WINDOWS 下对音频的处理,大致可分为两部分,即音频的输入、输出,和 ACM 压缩处理。一般情况下在 WINDOWS 下可以调用诸如 sndPlaySound 等 API(MCI) 来播放一个 WAV 文件,但那显然不是我们需要做的。必须能够直接对音频数据流进行处理。在 WINDOWS 下,也为之提供了一系列的 API ,以 waveIn 和 waveOut 开头的一组 API 就是干这个的。
先说输入吧。常用的相关 API 为 waveInOpen (打开一个音频输入设备)、 waveInPrepareHeader (为一个即将在 waveInAddBuffer 中调用的输入缓冲区准备头部)、 waveInAddBuffer (添加一个输入用的数据缓冲区)、 waveInStart (开始录音)、 waveInClose (关闭音频输入设备)等几个,以及需要在 waveInOpen 中指定的一个回调函数或者线程,其作用是在一个数据缓冲区被录满后被调用,以对这些数据进行处理,和其他一些相关的操作。
首先你得确定一下需要用什么回调方式,即在某个时间片的音频数据被录完后, Windows 将通过这个回调来激活对这些数据的处理过程,一般用到的无非是 FUNCTION 、 THREAD 和 EVENT 这几类,而比较方便简单的就是 FUNCTION 和 THREAD 了。 FUNCTION 方式是指 Windows 会调用你这个函数,而 THREAD 则是由 Windows 来激活你所指定的线程。这些都在 waveInOpen 中指定。其函数原型为:
MMRESULT waveInOpen( LPHWAVEIN phwi,
UINT uDeviceID,
LPWAVEFORMATEX pwfx,
DWORD dwCallback,
DWORD dwCallbackInstance,
DWORD fdwOpen
);
其中: phwi 是返回的句柄存放地址, uDeviceID 是要打开的音频设备 ID 号,一般都指定为 WAVE_MAPPER 。 dwCallback 则为指定的回调函数或线程等的地址, fdwOpen 指定回调方式, dwCallbackInstance 为需要向回调 函数或线程送入的用户参数。至于那个 pwfx ,则比较关键,它指定了要以什么音频格式打开音频输入设备, 它是一个结构 WAVEFORMATEX :
typedef struct { WORD wFormatTag;
WORD nChannels;
DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec;
WORD nBlockAlign;
WORD wBitsPerSample;
WORD cbSize;
} WAVEFORMATEX;
机器上的 WIN9X 安装时选择了音频压缩,可以在 wFormatTag 中指定一些压缩的音频格式,如 G723.1 , TURE DSP ,等之类。不过一般都是选用 WAVEFORMAT_PCM 格式,即未压缩的音频格式,至于压缩,可以在录完后调用下面将要谈到的 ACM 单独进行。
nChannels 为声道数, 1 或者 2 。 nSamplesPerSec 为每秒采样数, 8000 、 11025 、 22050 、 44100 为几个标准值,其他的非标准值我倒没试过行不行。 nAvgBytesPerSec 为每秒平均的字节数,在 PCM 方式中就等于 nChannels*nSamplesPerSec*wBitsPerSample/8 ,但对于其它的压缩的音频格式,由于很多压缩方式是按时间片进行的,如 G723.1 ,就是以 30ms 为一个压缩单位,这样, nAvgBytesPerSec 只是一个大概的数字,并不准确,程序中的计算是不应该以这个量为准的。这一点在下面的压缩音频输出和 ACM 音频压缩中非常重要。 nBlockAlign 是一个比较特殊的值,表示对音频处理时的最小处理单位,对于 PCM 非压缩,它就是 wBitsPerSample*nChannels/8 ,而对于非压缩格式,则表示压缩 / 解压处理的最小单位了,如 G723.1 ,就是 30ms 的数据大小( 20bytes 或者 24bytes )。 wBitsPerSample 就是每采样值的位数, 8 或者 16 。 cbSize 则是表示该 WAVEFORMATEX 的结构在标准的头部之后还有多少字节数,对于很多非 PCM 的音频格式,有一些自己的定义格式参数,这些就紧跟在标准的 WAVEFORMATEX 后面,其大小就由 cbSize 指定。对于 PCM 格式而言,为 0 ,或者忽略不检查。
这样,指定了这些参数后,你应该就能够打开音频输入设备了。下面要做的事情就是准备几个用做录音的缓冲区。常准备多个缓冲区,并在回调中循环使用。另外,还得考虑好录得的音频数据放哪儿,比如一个临时文件,就得准备好文件的句柄。对于缓冲区,得使用 waveInPerpareHeader 准备一下头部,这个 API 比较简单,如果你是循环使用缓冲区,对每个缓冲区也只需要调用一次 waveInPrepareHeader 。
一切准备好之后,就可以调用 waveInAddBuffer 和 waveInStart 开始录音了,只要你一调用这个 waveInStart ,录音就开始了,即使这个缓冲区录满之后你没有加入新的缓冲区进去,录音也不会停,只是这中间的语音数据全都丢了。当通过 waveInAddBuffer 送入的缓冲区被录满后, Windows 就会通过你在 waveInOpen 中指定的方式进行回调,在回调中把录好的语音数据取出来,并且,如果还想继续录音的话,得将下一个缓冲区添加进去。考虑到这个处理是有时间延迟的,而且音频对时间很敏感,一般都要先预加入若干个缓冲区,比如,一共定义了 8 个缓冲区,而为了保险起见,最好保证任一时刻至少有 3 个缓冲区可被录音使用,那么在开始录音时,则先加入 4 个缓冲区,然后在回调中,如果当前录好的缓冲区第 n 个,则对第 (n+4)%8 调用 waveInAddBuffer ,这时,还有第 (n+1)%8,(n+2)%8, (n+3)%8 这三个缓冲区可用,即基本上就可以保证所录得音频中不会有断开的间隔。
想结束录音时,最好在 waveInClose 之前调用一下 waveInReset ,这样可以清掉尚在等待录音的缓冲区,同时在回调中还必须注意一下送入参数中的消息种类。
音频输出部分相对简单一点。对应的 API 有 waveOutOpen 、 waveOutPrepareHeader 、 waveOutWrite 以及 waveOutClose 。如果希望直接输出压缩格式的音频的话,必须注意 waveOutOpen 中指定的音频格式参数。你必须很清楚这类格式的具体参数及其含义。不过,你可以通过下面说到的 ACM ( Audio Compress Manager )得到你需要的音频格式的具体参数,这个格式参数可以直接用于 waveOutOpen 。如同音频的输入, waveOutPrepareHeader 也是必需的。 waveOutWrite 则是填入输出缓冲区,为了避免间断,也应该保证某一时刻缓冲区队列中数目足够。
如果安装 WIN98 时在附件中选择了音频压缩,那么机器上的 ACM 就可用了。 ACM 为 Audio Compress Manager 。 WIN98 提供了一些常用的音频压缩算法程序包,供用户调用。可以通过 ACM 获得本机上所有的音频压缩驱动及其所支持的音频格式。不过,似乎不是每种 ACM 格式都能被调用来进行压缩 , 不过 ACM 中的压缩驱动大多都是针对语音频段,如果用来压缩频带较宽的音频,如音乐,则效果很差 .