用VC++实现语音全双工实时通信
王晓军,王生保
(井冈山职业技术学院,江西吉安343000)
摘要:计算机间的语音通信在现代社会中具有重要应用前景。本文介绍在Windows环境下用Wins ock实现语音全双工实时通信的方法。这里主要涉及如何用Wins ock完成话音数据的传输;如何实现话音的实时采集、处理、播放;如何用AC M 实现音频数据的压缩。噪音和延时是此类软件要解决的重要的问题。本分析了噪音和延时产生的原因,并且采取了相应的算法来抑制噪音和减小延时。因为某些因素是矛盾的,本文努力使它们达到最佳组合。
关键词:语音全双工实时通信;Wins ock;音频数据压缩;AC M接口编程
Implementation of R ealtime Diplex Audio Communication with Winsock
W ANG X iao-jun,W ANG Sheng-bao
Abstract:The audio communication between computers has many applications in m odern s ociety.This paper discusses how to realize re2 altime diplex audio communication by WinS ock in Windows environ
ment.The paper mainly deals with how to complete audio data transmission by Wins ock,how to realize realtime audio collecting,processing and playing,and how to compress audio data with AC M.N oise and delay are the primary problems to be s olved during developing this kind of s oftware.The paper analyzes the causes of noise and delay,and employs ap2 propriate method to control them.S ince s ome factors are contradictory with each other,the paper tries to obtain the best combination of them.
K eyw ords:Realtime diplex audio communication;Wins ock;Audio data compression;AC M interface programming
0 引言
互联网或局域网上的实时语音通信可以应用在现代社会的许多方面,如计算机辅助教学等。这里需要解决三个问题:(1)语音的实时采集和播放;(2)数据的传输;(3)数据的压缩。本文介绍在W indows 环境下利用VC++实现语音双工实时通信的方法,并对如何提高通信质量,进行较详细的探讨。
1 语音的实时采集和播放
第一个问题是语音的实时采集和播放,即将一个用户所讲的话录制下来,等待传输,同时将从网络上接
受到的其它用户的语音信息进行还原,使本地用户听到声音。在VC++中,根据不同的应用要求,有如下三种方法实现声音的播放。
(1)简单的播放声音方法。在VC++中利用函数可以方便地播放声音。最简单的播放声音方法就是直接调用VC++中提供的声音播放函数BOO LsndPlay2 S ound(LPCSTRlpszS ound,UINT fuS ound);其中参数lpszS ound是需要播放声音的W AV文件的路径和文件名,hm od在这里为NU LL,fuS ound是播放声音的标志。
(2)MCI多媒体编程。MCI是M edia C ontrol Inter2 face的简称。它是W indows提供的一个比较层的多媒体接口,为程序员使用和控制各种多媒体设备提供了一个统一的接口。这些多媒体设备被概括为W AVE 设备、MIDI设备、C D设备、视频(AVI)设备等。这些所谓的设备掩盖了它们对应的实际的物理设备的差别。在这些设备的基础上,MCI针对某些媒体文件提供了一个统一的播放接口。因为不用了解文件本身,所以MCI非常方便。在MCI中使用mciSendC om2 mand或mciSendS tring来执行各种具体的操作。
(3)播放声音文件的高级方法。在VC++中提供了一组对音频设备及多媒体文件直接进行操作的函数。利用这些函数可以灵活地对声音文件进行各种处理。下面主要介绍这种方法。
首先介绍几个要用到的数据结构。W AVEFORM A2 TEX结构定义了W AVE音频数据文件的格式。W AVE2 H DR结构定义了波形音频缓冲区。读出的数据首先要填充此缓冲区才能送音频设备播放,声音的
采集和播放都是在操作这个音频数据块结构。实际上主要用到的就是第一个成员变量lpData,所以只要在分配缓冲区(内存)的同时相应分配W AVEH DR数据块结构,然后将缓冲区的指针赋给对应的数据块结构的成员变量lpData,这样当一个缓冲区填满后,也就是一个音频数据块填满了,通过消息机制就可以在消息函数中进行处理和播放,播放完后又可通过消息函数把缓冲区再送给音频设备输入驱动程序,继续进行采集并播放,当一次性分配多个缓冲区和数据块结构并赋给音频设备输入驱动程序后,至于把哪个缓冲区填满,然后再把哪个空缓冲区赋给设备输入驱动程序,不需人为干预,完全由W indows控制。操作过程如下:
①每台计算机的音频硬件的性能各不相同,在音频数据录制前,先用waveInG etNum Devs()和wave2 OutG etNum Devs()查看当前系统波形音频输入、输出设备的数量;用waveInG etDevsCaps()和waveOutG et2 DevsCaps()查看当前系统波形音频输入、输出设备的能力。
②用waveInOpen(...)和waveOutOpen(...)打开设备。在打开设备时要指定音频格式W AVEFOR2 M ATEX。在本程序中音频格式类型为W AVE-FOR2 M AT-PC M.
③按8000H z,8Bit,单声道,W AVE-FORM AT-
・
5
2
2
・
《机床与液压》2003.N o.5
PC M的格式设置W AVEFORM ATEX结构的成员变量。
④分别给音频数据块和音频数据缓冲区分配、锁定全局内存;在内存需求和处理上,为了少占内存,低层声音服务以块为单位进行处理。当向设备驱动程序传递音频数据块前,需要先对数据块加以准备;设备驱动程序使用完数据块后,需要相应的消除准备,以便释放所占的内存,准备和消除函数为:waveOut2 PrepareHeader(),waveInPrepareHeader(),waveInUn2 prepareHeader(),waveOutUnprepareHeader(),waveIn2 AddBu ffer()。它们不是成对的。
⑤调用waveInS tart(...)函数开始录音。
⑥用waveOutReset(hW aveOut),waveOutC lose (hW aveOut),waveInC lose(hW aveOut)关闭音频输出设备,释放内存。
录音开始后,每当有采样数据填满数据块后,设备驱动程序就会发消息M M-WIM-DAT A给用户窗口,相应的消息回调函数OnAudioInMsg(WPARAM wParam, LPARAM lParam)对数据块中的采样数据进行处理,然后发送给接收方,当一个音频数据块播放完毕,设备驱动程序又会发出消息M M-W OM-DONE,相应的消息回调函数On fullMsg(WPARAM wParam,LPARAM l2 Param)的数据内存被释放,以备接收后续数据。这样,最初为输入设备准备的音频数据块就在消息的控制下,在输入、输出设备间循环使用,无需人为控制,实现了实时采集、处理和播放。当结束通话时要关闭音频输入设备,这时音频设备驱动程序会发送M M-WIM-C LOSE消息,可在相应的消息函数中清除赋给输入、输出设备的音频数据块。
以上的方法各有优缺点:前两者的优点是编程简单,但是后者是对内存进行操作,所以延时非常小。前两者是对磁盘进行操作的所以延时非常大。本程序根据实时性的要求采用了第三种方法。
2 数据的传输
MFC中提供了封装的C AsyncS ocket类,它提供了全面的由事件驱动的S ocket通信能力。事件驱动通信能力是指当有数据要发送时就通过消息机制自动发送,当有数据要接收时就通过消息机制自动接收。这里采用C AsyncS ocket类的派生类C M yS ocket来完成网络通信功能。C M yS ocket类要做的工作就是重载这几个事件处理函数:OnSend(),OnReceive()等。为了提高程序通信的可靠性本程序建了两个套接字。这样可以使通信更加可靠。它们使用不同的端口,分别是3500,3501。
3 音频数据的压缩
音频数据一般具有较高的采样速率,如果不经过压缩的话,它们的数据量将非常大,因此音频数据压缩编码在多媒体技术中占有很重要的地位。目前常用的压缩方法有很多种,不同的方法具有不同的压缩比和还原音质,编码的格式和算法也各不相同,其中某些压缩算法相当复杂,普通程序不可能去实现其编解码算法。所幸的是,W indows95ΠNT4.0为多媒体应用程序提供了更强的支持,引入了AC M(Audio C om2 pression M anager,音频压缩管理器),它负责管理系统中所有音频编解码器,应用程序可以通过AC M提供的编程接口调用这些系统中现成的编解码器来实现音频或视频数据的压缩和解压缩。
(1)搜索驱动的过程
与声音的录制相比,AC M音频压缩接口的编程方法多了搜索驱动的过程。搜索驱动的过程是通过acm DriverEnum()来完成的。函数acm DriverEnum()的功能是枚举所有的音频C ODECs,在acm DriverEnum ()的参数中指定回调函数acm DriverEnumCallback()可以进一步查询每个C ODEC的信息。acmF ormatEnum ()枚举符合格式要求的驱动,它的实质功能是由回调函数acmF ormatEnumCallback()完成的。
(2)实现音频压缩的过程
下面说明使用某一C ODEC实现音频压缩的过程。源信号为8K采样、8bitsPC M编码、单声道。驱动程序采用W indows自带的G S M6.10音频C ODEC。在此例中,G S M6.10支持从源音频格式到目标格式的转换,而在实际应用中,可能某种C ODEC不支持直接将源音频格式转换成目标格式,这时可以采取两步转换法,即先将源格式转换成一种中间格式,再将此中间格式转换成目标格式,
根据查询的结果,设hadID为G S M6.10的ID值,接下来利用获得的ID值打开相应的驱动程序,即调用acm DriverOpen(&had,hadID,0)函数。
压缩和解压缩一样,都是将音频信号从一种音频格式转换成另一种格式,只是把格式转换过程的目标格式和源格式互换就可以了。首先要用acmS tre2 am Open()打开转换流,我们为它指定AC M-STRE2 AM OPE NF-NONRE A LTIME标志,表示转换无需实时进行。在进行转换前,还必须调用acmS treamPre2 pareHeader为转换准备信息头。
语音数据真正的压缩过程是由函数acmS treamC on2 vert()完成的。在调用acmS treamC onvert()时可以指定回调函数。在本例中,未指定回调函数,但是用了线程来检查压缩是否结束。
数据压缩完毕后,应用程序就可以把缓冲区中的数据发到网上去了。最后,必须关闭转换流和驱动程序:
acmS treamReset(HstrIn,0);
acmS treamC lose(HstrIn,0);
acm DriverC lose(Had,0);
・
6
2
2
・《机床与液压》2003.N o.5
总之,要使程序可以在56k 调制解调器上网的情况下也可以运行,那么AC M 压缩是不可少的。如果只在10mbps 的以太网上运行程序的话一般不需要压缩。4 程序的流程
前面主要介绍了程序的主要技术问题,下面给出程序的流程,
如图所示。
程序流程图
5 结论与讨论
(1)数据压缩问题
电话音质需要的流量是8000×8×1=64kbps ,双向时则需要128kbps 。这个带宽对于10Π100M 以太网的宽带来说不算什么。但是对于56k 调制解调器来说128kbps 太大了。所以AC M 的压缩是必要的。G S M 6.10音频C ODEC 可以实现音频数据的5倍压缩,128Π5=25.6kbps.这样就可以在56k 调制解调器实现实时语音通信了。如果使用8k 采样率,单声道,16比特Π位的话质的话,虽然可以用AC M 进行压缩但是
也只能压到51.2kbps ,对于56k 调制解调器来说是一个临界值,很可能达不到要求。所以为了留一些余量还是使用8k 采样率,单声道,8比特Π位音质比较好。
(2)声音操作问题
因为低层声音函数是对内存进行操作的,而音频数据又比较大。所以必须在程序的初始化时就对函数分配内存,并且在程序结束时释放内存。如果是边用边申请,系统因为频繁的内存的操作而性能不佳,可能会出现声音的抖动。为了保证音质,在初始化时分配内存,在录音和放音时直接使用这些内存。
声音文件是很大的。如果是静态的分配内存的话,不管有多大的内存都会被很快的用光。本程序使用了循环数组,为函数申请了50个循环数组。当一个用完就接着用下一个,都用完了,再从头开始。
循环数组的大小是一个重要的问题。不能象一般数学运算时那样每次只申请几十个空间。如果是几十个空间的数组,只要极短的时间就被用完,那样就会是音频函数频繁的切换数组,系统会很忙,进而影响到音质,可以听到有严重的噪音。另外,压缩也是对大量的数据时才有效的。我们是对每一个数组进行压缩的。如果每个数组只有一个数,那是不可能压缩的,所以一般的数组是该大于500的。但是也不是越大越好,控制数组不是要节约内存(现在计算机的内存都很大),而是为了较小的延迟。因为低层声音函
数虽然是对内存进行操作的,但是也是要等一个内存块录制完成后才能发送音频数据的。由此可知低层声音函数也是有延迟的。所以数组的大小要适中,是一个折中量。在本程序中是1920字节。
(3)声音延迟问题
延迟的产生是多方面的问题。其中一个原因是录音函数产生的延迟。但是低层声音函数是对内存进行操作,只要调整好数组空间的大小,由它产生的延迟是不大的。延迟产生的一个重要的原因是音频压缩和解压缩。因为音频压缩的计算量比较大,每一次压缩都要消耗较长的时间。所以压缩延迟是本程序延迟的主要部分,估计会占总延迟的百分之七十。而这部分的延迟是不可能消除的,因为我们不可能自己编写一个好的压缩器(如果有的话W indow 的音频C ODEC 早就加进去了)。只有不用压缩算法才能消除这部分延迟。另外,AC M 的压缩算法的压缩率都不是很大,只有5倍左右(有些让人失望)。网络传输延迟也比较大的。因为本程序是面向连接的,所以延迟比较小,但它对线路的要求比较高。当在10Π100M 以太网上运行此程序时由网络延迟不大。但是在56k 调制解调器运行此程序时由网络延迟将比较大。其实就是其它的通话的软件在56k 调制解调器上的运行效果也不佳。录音的延迟是次要的,因此没有必要太注意录音延迟。所以把声音数组定为1920,这也有利于压缩。
(4)音质问题
音质是本程序的一个重要的要求。因为数据经过压缩和解压往往是有损的,所以音质会有些损失。压缩
损失没法控制。低层声音函数也有音质问题。可以把内存事先都分配好,减小系统的负担。数组的大小也要适当,因为延迟不会因数组的增大而严重增大,可以适当增加数组的大小。数组的个数也是影响音质的一个重要原因。因为压缩要较长的时间,而此时录音还在继续,如果只有一个数组,录音程序只好丢弃音频数据。这样会影响到音质。增大数组的数量使压缩时还有其它的数组可以使用,可以改善音质。但也不能无限的增加数组个数,一方面是内存有限,另一方面是当数组大到一定的程度后,数目对音质的影响已经不大了。当一个音频数据正在回放时,又有一个新的音频数据写入这个数据块就会把没放完的数据冲掉,使声音有抖动。同样在录音和压缩方面也存在这样的问题。所以在程序中设计了一两个变量用来记录剩余缓冲区的数目,当录音开始时先检查变量是否等于零,等于零说明没有缓冲区丢弃录音,不等于零说明有缓冲区开始录音和压缩并把变量减一,当压缩完成时把变量加一。音频回放时的情况大体上相同。这样就可以保证不会出现数据互相覆盖的情况了。但是
(下转第229页)
出了工作区域角的计算式后,就可以进一步分析由于润滑、力及制造安装误差等原因,直接或间接使激波器的工作区域角减小,实际的重合系数变小[1],同时也就分析由于其它几何参数对工作区域角的减小。2 非线性方程的求解方法与计算实例
式(6)、(9)为一非线性方程组,它一般有多个根,对于有多个根的非多项式还没有通用的求解方法,因此,
如何求解选用初始值是一个关键问题。经分析这个根不可能大于理论工作区域角,即使在最大工作区域角内,如果初值选择的不当,也会得到错误的解。经对活齿传动的分析,一般选取最大工作区域角的四分之一,可以保证求出符合要求的解。
例1:已知活齿减速机的参数如下:偏心轮激波器H 和活齿轮的转向相同;滚柱活齿的半径r b =17mm ,偏心距a =8.5mm ,激波系数λ=b Πa =23,中心轮齿数Z k =22,活齿齿数Z g =Z k +1=23,偏心轮激波器半径R =b -r b =178.5mm 。按前述的公式(3)求得理论重合度为ε0=11.5,取工作区域角的迭代初值为θk 0Π4,则求θk =0.08754318080410,代入式(4)求得实际重合度为ε=7.0428。如果取迭代初值0.9θk 0,则得θk =0.14279966731740,从而求得实际重合度为ε=11.5,得到了错误的结果。将(
6)式代入式(9)中,将式(9)
令为y ,则y 与θk 的关系如图3所示,由图3可知在工作区域角最大值前y =0有两个根。初值选取不好会有错误的结果。程序由M AT 2LAB 实现[2,3]。
图3 例1的y 与
θk 的关系图
图4 例2的y 与
θk 的关系图
例2:已知活齿减速机的参数如下:滚柱活齿的
半径r b =3mm ,偏心距a =4mm ,偏心轮激波器半径
R =40mm ,激波系数λ=b Πa =(R +r b )Πa =10.75,
中心轮齿数Z k =11,活齿齿数Z g =Z k -1=10。按前述的公式(3)求得理论重合度为ε0=5,取工作区域角的迭代初值为θk 0Π4,则求θk =θk 0=
0.28559933214453,代入式(4)求得实际重合度为ε=5。而文献[4,5],求得实际重合度为ε=4.675,
得到了错误的结果。且文献[4,5]对应于式(9)
中的r b Πa 写成了r b ,这是该文又一错误。与例1一
样,其对应的y 与θk 的关系如图4所示,由图可知在工作区域角最大值前y =0有一个根。这也说明文献[4,5]的错误。3 结论
本文建立了活齿传动的重合度计算式,给出了正确的求解方法,编制了M AT LABT 计算程序,并用图示
辅助办法证明了本文求解方法的正确性,指出有关文献的错误。为活齿传动的设计与制造打下了基础。参考文献
【1】曲继光.活齿传动理论[M ].北京:机械工业出版
社,1991
【2】薛定宇.科学运算语言—M AT LAB 语言及应用[M].
北京:清华大学出版社,2000
【3】薛定宇,陈阳泉.基于M AT LAB ΠS imulink 的系统仿真技
术与应用[M].北京:清华大学出版社,2002【4】张华弟,王保平.摆动活齿传动重合度计算分析[J ].
enum函数机械传动,2001.25(2):16~17【5】张华弟,张乐乐.OOP 技术在活齿传动重合度计算中
的应用[J ].机械设计,2002.19(8):57~59
作者简介:张龙庭(1961-),男,汉,湖南常德人,副教授,教务处长。主要研究:现代设计方法。
收稿时间:2002-10-08
(上接第227页)
丢弃录音也是一种音质的损失,这是一个矛盾的问题。在所有的方法中,增大数组和增加数组数目是提高音质最好的办法。参考文献
【1】[美]Paul Perry ,陈向等译.多媒体开发指南.清华
大学出版社,1997.
【2】美Peter N orton ,R ob McG reg or ,孙凤英等译.MFC 开发
Windows95ΠNT 4应用程序.清华大学出版社,1998
【3】周敬利.多媒体声卡技术及应用.电子工业出版社,
1998
【4】蒋东兴,林鄂华.Windows S ockets 网络程序设计指南.
清华大学出版社,1995.12
【5】祝小翰.Wins ock 编程初步.微电脑世界,1996(8):
54~60
【6】电脑编程技巧与维护,2001,(11)和2002(3)【7】席 庆,张春林.Visual C ++6.0实用编程技术.中国
水利水电出版社,1999
【8】陈 坚,陈 伟.Visual C ++网络高级编程.人民邮
电出版社,2001.8
作者简介:王晓军,男,1965年出生,江西安福人,井冈山职业技术学院副教授,长期从事机电技术的教学与科研。
收稿时间:2002-10-19
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论