VC_下声卡低层音频服务的编程技术

  • 格式:pdf
  • 大小:104.87 KB
  • 文档页数:3

收稿日期:2001-08-24 作者简介:马云升,男,硕士研究生,从事有线与无线数据通信,计算机软件编程技术的研究.文章编号:1001-9081(2002)2-0101-02VC ++下声卡低层音频服务的编程技术马云升,姚 晓,夏志忠(大连海事大学信息工程学院,辽宁大连116026)摘 要:文中阐述了在VC ++6.0环境下实现声卡低层音频服务的编程技术,重点研究了在Win32下音频数据缓存的分配和管理,及声卡混音器的软件编程控制。

关键词:声卡;低层音频服务;内存管理;混音器中图分类号:TP314 文献标识码:A 随着计算机技术的不断发展,人们一直希望可以利用计算机采集、保存和处理各种类型的信息,语音信号就是其中的一种。

本文即针对这一点,对如何利用声卡不失真的录制语音信号进行了讨论。

首先在VC ++6.0环境下以语音信号的录制为例介绍了低层音频服务,主要讨论了用来保存录制数据的音频缓存的管理方法,以实现语音信号不失真录制且又少占用系统内存资源;其次介绍了声卡混音器的软件控制技术,它使语音录制更有效率。

1 低层音频服务多媒体扩展(MultiMedia Extensions )的音频结构从高层和低层两个层次上提供了音频服务。

如图1所示:图1 W indows 多媒体扩展音频结构高层音频服务仅需调用一个函数就能实现语音的播放和录制,但提供较少的控制,它包括MCI (媒体控制接口)设备驱动程序、SndPlayS ond 和MessageBeep 函数。

如:播放路径为c :\windows \media \N otify.wav 的文件,可使用函数:sndPlayS ound (″c :\windows \media \N otify.wav ″,S ND —ASY NC );而低层音频服务则可以让应用程序直接与驱动程序通讯,提供了对声音播放和录制的更强控制,能更好的管理播放和录制过程,高层服务也是利用低层函数来实现的。

低层音频服务控制不同类型的音频设备,包括波形(waveform )、MI DI (Musical Intrument Data Interface )乐器指令数字化接口和辅助音频设备,分别用前缀wave 、midi 和aux 作为函数名的开头来加以区分,他们的使用方法类似,只是各自处理不同的音频设备。

这里主要讨论以wave 为前缀的波形音频函数的使用。

它具体包括查询音频设备、打开/关闭音频设备驱动程序、分配/准备/管理音频数据缓存区、使用M MTI ME 结构和错误处理几部分。

由于在本系统中为了使声卡不失真的录制语音信号,选择了较高的采样率(22050H z ),因此数据量很大,考虑到不丢失数据且有效的利用空间,本系统主要在分配/管理音频数据缓存以及M MTI ME 结构的使用上进行了一些特殊的操作,下面将对这两部分进行较详细的介绍。

其它部分的使用可见参考文献[1]。

1.1 分配/管理音频数据缓存区由于对波形设备而言,不论是播放还是录制,系统要处理的数据量都是很大的,因此,低层音频服务以数据缓存区为单位进行处理。

它要求应用程序自己分配内存,并将内存块的地址、大小等信息通过音频数据头结构告诉低层音频驱动程序,使其为录放音频操作做好准备。

在本系统的中,为了不丢失录制的数据,更合理的使用内存,程序中申请了两块较小的内存交替使用,使系统的性能得到了提高。

但对不同的声卡,由于结构的不同,交替使用两块缓存时,可能出现丢失数据的现象,这主要在于缓存的使用方法,将会在音频缓存区的管理中讨论。

1.1.1 分配音频缓存区在win32API 的线性32位环境中,局部堆和全局堆不再作区分,即通过G lobalAlloc 或LocalAlloc 都可分配一块能用32位虚拟地址表示的任意大小的内存,它们不再有区别,只受可用物理内存的限制;也不再有近指针和远指针的区分。

系统管理内存通过移动物理内存页面(RAM )进行,而不影响虚拟地址;进程使用内存通过由局部或全局分配所返回的32位虚拟地址进行[2]。

根据这一特点,本系统在申请内存时,用带有G ME M —M OVE ABLE 标志的G lobalAlloc 函数来获取内存对象的句柄,然后,再将这个句柄传给G lobalLock 函数获取内存目标的指针,即32位虚拟地址。

也可利用windowsx.h 中定义的G lobalAllocPtr 函数直接得到内存块指针。

具体程序如下:hData =G lobalAlloc (G ME M —M OVE ABLE|G ME M —SH ARE|G ME M —ZEROINIT ,DataLength );//获取句柄if (!hData ){A fxM essageBox (″Out of mem ory ″);return ;}lpData =G lobalLock (hData )第22卷第2期2002年2月计算机应用C om puter Applications Vol.22,No.2Feb.,2002//得到数据块的指针if(!lpData){A fxM essageBox(″Can′t Lock″);G lobalFree(hData);return;}与分配内存对应,释放内存时,使用G lobalUnlock和G lobalFree函数或windowsx.h中定义的G lobalFreePtr函数。

G lobalUnlock(lpData);//释放数据块指针G lobalFree(hData);//释放内存句柄1.1.2 传递音频缓存区低层音频函数需要应用程序传递分配给设备驱动程序的缓存区,用于记录和重放。

每一个函数都有一个头结构来描述它的数据缓存区,并将缓存区的地址、大小等信息告诉低层音频驱动程序,音频录制和播放使用的头结构是W AVEH DR。

音频录制时,用waveInPrepareHeader函数准备缓冲区,用waveInAddBu ffer向设备驱动程序传递音频数据缓存区。

1.1.3 管理音频数据缓冲区上面提到在本系统中,要处理的数据量很大,为提高系统性能,应用程序开辟两个缓冲区,这样当存满一个缓存时,新到的数据可以保存在另一个缓存中而不会丢失,同时应用程序将录制的数据从已存满的缓存中读出并保存到文件中,然后,将该缓存清0,再传递给驱动程序,即两缓存交替使用。

共有三种方法来实现:1)指定一个窗口,在设备驱动程序用完缓存时接收驱动发出的消息;2)指定一个回调函数来接收驱动发出的消息,要求回调函数驻留在动态连接库D LL中,而且其代码段和数据段在D LL的模块定义中指定为FIXE D;3)查询W AVEH DR数据结构中的dwFlags字段。

当驱动用完缓存时置dwFlags为WH DR—DONW。

我们使用第一种方法即窗口回调处理驱动程序消息,该方法要求在打开音频设备时将waveInOpen函数的dwFlags参数赋为C A LLBACK—WI NDOW,将dwCallback参数指定为窗口句柄m—hWnd。

当设备驱动程序用完由waveInAddBu ffer函数传送的缓存时发送M M—WI M—DAT A消息,为了响应这个消息,我们定义响应函数OnAudioInMsg,在.cpp的消息映射中增加: ON—MESS AGE(M M—WIM—DAT A,OnAudioInMsg)并同时在.cpp文件中增加函数定义:LRES U LT CaudioView::OnAudioInMsg(WPARAM w param,LPARAM lParam){m—Bu ffIndex=1-m—Bu ffIndex;//数据缓存索引值为1或0waveInAddBu ffer(m—hW aveIn,m—lpAudioHdr[m—Bu ffIndex],W AVEH DRSIZE);//将另一数据缓存发送给设备驱动程序fwrite(m—lpAudioData[1-m—Bu ffIndex],AUDIOBUFFSIZE,1,m—fp);//保存已录制的数据memset(m—lpAudioData[1-m—Bu ffIndex],0,AUDIOBUFFSIZE);//将数据缓存清0return TRUE;}在实际测试过程中,有的声卡在录制时会出现声音断断续续的现象(主板集成的声卡都会有这个问题),这是因为当一个缓存录满数据后,驱动程序将向应用程序发送消息,应用程序收到消息后,即会将另一个缓存传递给驱动程序,在另一个缓存还未传递给驱动程序的这段时间中,如果有新的数据到来,而使用的声卡如果在结构上没有缓存或没有一个足够大的缓存来保存这些数据,则会导致驱动程序丢弃这些数据。

解决的办法就是在开始录音时,两个缓存区一次都传给驱动程序,则一块用完后,驱动程序发送消息,同时驱动程序也会自动使用另一块缓存来保存新录制的数据。

这时,在应用程序的消息处理程序中就可以从容的保存已录制的数据,将缓存清0,然后再把该缓存区传给驱动程序。

即使得驱动程序在任何时候至少有一个缓存可用。

v oid CAudioView::OnRecord(){∗∗wResult=waveInAddBu ffer(m—hW aveIn,m—lpAudioHdr[0],W AVEH DRSIZE);wResult=waveInAddBu ffer(m—hW aveIn,m—lpAudioHdr[1],W AVEH DRSIZE);if(M MSY SERR—NOERROR!=wResult)return;∗∗}1.2 使用M MTI ME结构使用waveInG etP osition函数和M MTI ME结构来查询设备录制状态。

M MTI ME结构的详细说明见参考文献[2]。

在本系统中使用M MTI ME结构来查询已录制数据的字节数,根据该字节数可以只将缓存中的录制数据保存在文件中,而不用保存整个缓存;因为有时缓存可能没有存满,那么其中有一部分将是无用的字节。

使用M MTI ME结构的具体方法是先将wT ype参数赋为TI ME—BY TES,再调用waveInG etP osition函数,参数cb的返回值即是想要查询的字节数。

需注意的是,调用M MTI ME结构的函数前,要先设置wT ype字段来指定所希望查询的时间格式;在调用了此函数后,一定要检查wT ype字段,看看设备驱动程序是否支持所指定的时间格式,如果不支持,设备驱动程序将自动修改wT ype字段内容。

M MTIME mm;unsigned int mn;mm.wT ype=TIME—BY TES;//指定查询的时间格式waveInG etP osition(hW aveIn,&mm,sizeof(M MTIME));if(mm.wT ype!=TIME—BY TTES){//查询是否支持所需时间格式A fxM essageBox(″Can′t support struct″);return;}mn=mm.u.cb;//得到已录制数据的字节数fwrite(m—lpAudioData[1-m—Bu ffIndex],1,mn,m—fp);//保存录制的数据2 混音器的编程在程序运行过程中,有时会出现录不上音、采集不到数据的现象。