本文讲述如何实时录音,以及将录音波形频谱实时显示的方法。Windows提供了一个多媒体控制接口(MCI),用它可以录音,很方便。但是这种方法不能实时给出录音的原始数据,因此要显示波形和频谱都是不可能实现的。要达到实时的效果,就要使用Windows提供的另一套函数,即低级音频函数。其中和录音有关的是以waveIn开头的一组函数。
低级音频函数的使用比较繁琐,大致要有以下几个步骤。
- 用waveInOpen打开设备,并设置回调。因为要保证实时性,所以不能用查询的方式,而必须设置回调。
- 为设备分配足够的内存做缓冲区,动态分配或静态数组都可以。为了保证实时性,程序用了双缓冲技术,在处理一个缓冲区数据的同时另一个缓冲区用于录音。为了便于说明写成Buffer1、Buffer2。
- 将Buffer1关联到设备上去,waveInPrepareBuffer、waveInAddBuffer。
- 开始录音,waveInStart
- 当驱动程序填满这个缓冲区(Buffer1)时就会产生回调(消息为WIM_DATA),这时立刻将Buffer2关联到设备上继续录音,然后处理Buffer1,当驱动程序填满Buffer2时又会产生回调,这是再将Buffer1关联到设备上,而去处理Buffer2,如此反复就使得录音能够实时的进行下去。
- 停止录音,waveInStop
- 关闭设备,waveInClose
上面就是实时录音的一个基本流程,产生回调时,其lParam就是一个指向WAVEHDR结构的指针,通过这个指针就可以得到原始数据(注意了:我这个程序只是单声道8位的PCM格式,因此正好一个字节就是一个采样点的值,对于其他格式的我就不知道了)。有了原始数据就可以很容易的画出波形了,而频谱也就是多一次FFT变换而已,没什么太难的。
这里是我用LCC写的例子,非常简单,而且相信大家都可以很容易的移植到BCB上来。另外,我偷了一下懒,FFT用了我以前封装到DLL中的函数(现在已经忘了怎么写了^_^
-*-*-PATCH-*-*-2002-06-11
上次那个程序还不好,原因是双缓冲还不能满足实时的要求,因此这次用了8缓冲循环技术。
开始时依次将0-6号缓冲区关联到设备上,而7号缓冲区不关联,然后开始录音,这是驱动程序开始向0号缓冲区输出数据,当0号缓冲区数据满时产生回调,这时驱动程序会自动向1号缓冲区输出数据,但这时应将7号缓冲区关联到设备上去,然后处理0号缓冲区的数据。然后当1号缓冲区数据满时又会产生回调,这时驱动程序会自动向2号缓冲区输出数据,而将0号缓冲区关联到设备上去。这样如此反复,始终保持1个缓冲区用于驱动输出数据,1个缓冲区用于处理数据,6个缓冲区处于等待状态。
而如果只有两个缓冲区,就只能一个用于驱动输出,一个用于处理数据。这样在产生回调的时候就会出现没有处于等待状态的缓冲区的情况,这样驱动程序就不能自动向缓冲区输出数据。而要等waveInPrepareBuffer、waveInAddBuffer调用之后,这样就会出现一个小小的间隙,而导致录音不连续。
-*-*-PATCH-*-*-2002-06-11
只是我的个人观点,有什么不对的地方,还望指教。
-*-*-PATCH-*-*-2003-04-07
程序中有一个错误,在WIM_DATA的处理过程里。
case WIM_DATA:
waveInUnprepareHeader(hwi,pwhi,sizeof(WAVEHDR));//释放错误。
nextWavHdr=(currWavHdr-1+MAX_INQUEU)%MAX_INQUEU;
currWavHdr=(currWavHdr+1)%MAX_INQUEU;
if(b_playing)
{
pwhi=&whis[nextWavHdr];
pwhi->dwFlags=0;
pwhi->dwLoops=0;
waveInPrepareHeader(hwi,pwhi,sizeof(WAVEHDR));
waveInAddBuffer(hwi,pwhi,sizeof(WAVEHDR));
}
当0号缓冲区数据满时,应该释放0号句柄。这第一次是对的,但是第二次当1号缓冲满的时候,程序却释放了7号句柄。这里是错的,解决方法就是计算正确的句柄号。
或者,
waveInUnprepareHeader(hwi,LPWAVEHDR(lParam),sizeof(WAVEHDR));
这样就可以了,感谢flinming发现这个问题。