实时录音并显示波形、频谱

来源:互联网 发布:什么软件里有牛人翻唱 编辑:程序博客网 时间:2024/06/06 08:50

原文出处:http://vip.6to23.com/NowCan1/tech/snd_wave.htm    感谢作者分享他的成果!

本文讲述如何实时录音,以及将录音波形频谱实时显示的方法。Windows提供了一个多媒体控制接口(MCI),用它可以录音,很方便。但是这种方法不能实时给出录音的原始数据,因此要显示波形和频谱都是不可能实现的。要达到实时的效果,就要使用Windows提供的另一套函数,即低级音频函数。其中和录音有关的是以waveIn开头的一组函数。

    低级音频函数的使用比较繁琐,大致要有以下几个步骤。

  1. 用waveInOpen打开设备,并设置回调。因为要保证实时性,所以不能用查询的方式,而必须设置回调。
  2. 为设备分配足够的内存做缓冲区,动态分配或静态数组都可以。为了保证实时性,程序用了双缓冲技术,在处理一个缓冲区数据的同时另一个缓冲区用于录音。为了便于说明写成Buffer1、Buffer2。
  3. 将Buffer1关联到设备上去,waveInPrepareBuffer、waveInAddBuffer。
  4. 开始录音,waveInStart
  5. 当驱动程序填满这个缓冲区(Buffer1)时就会产生回调(消息为WIM_DATA),这时立刻将Buffer2关联到设备上继续录音,然后处理Buffer1,当驱动程序填满Buffer2时又会产生回调,这是再将Buffer1关联到设备上,而去处理Buffer2,如此反复就使得录音能够实时的进行下去。
  6. 停止录音,waveInStop
  7. 关闭设备,waveInClose

    上面就是实时录音的一个基本流程,产生回调时,其lParam就是一个指向WAVEHDR结构的指针,通过这个指针就可以得到原始数据(注意了:我这个程序只是单声道8位的PCM格式,因此正好一个字节就是一个采样点的值,对于其他格式的我就不知道了)。有了原始数据就可以很容易的画出波形了,而频谱也就是多一次FFT变换而已,没什么太难的。

    这里是我用LCC写的例子,非常简单,而且相信大家都可以很容易的移植到BCB上来。另外,我偷了一下懒,FFT用了我以前封装到DLL中的函数(现在已经忘了怎么写了^_^),可以在此下载 (不要觉得奇怪,就是这个时钟,将其中的DEMO.DLL拿来用就是了)。当然你也可以自己写一个FFT放到程序里去。

-*-*-PATCH-*-*-2002-06-11

    上次那个程序还不好,原因是双缓冲还不能满足实时的要求,因此这次用了8缓冲循环技术。新的程序也是用LCC写的。
    开始时依次将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发现这个问题。

    另外,《网络语音传输》中的程序也有这个问题,请大家自己改吧。

-*-*-PATCH-*-*-2003-04-07