APP下载

Windows平台下VoIP语音引擎的框架设计*

2010-03-22陈亮蒿李炜

电信工程技术与标准化 2010年10期
关键词:调用音频分组

陈亮蒿 李炜

(1 北京邮电大学网络与交换技术国家重点实验室 北京 100876)

(2 东信北邮信息技术有限公司 北京 100191)

VoIP的基本原理是由专门设备或软件将呼叫方的语音信号采样并数字化、压缩、转换为一定长度的数字化语音分组,以数据分组的形式经过分组交换网络传输到对方,对方接收到数据分组后解压缩,还原成语音信号。它提供了低成本、高灵活性、高效率的增强应用。IP在硬件、软件和网络协议等方面的提高和发展,进一步推动了新的集成化基础设施的迅速发展。

随着PC、手机、宽带网越来越普及,VoIP应用越来越广泛,不断影响着人们的工作生活。基于Windows平台的VoIP客户端无疑是应用最广泛的,如QQ、MSN、Skype等。但Windows的版本众多,各个版本提供的 API (Application Programming Interface)也不尽相同,开发时稍不注意就会严重影响语音质量。

本文提出了一套Windows平台下开发语音引擎的解决方案,有效解决了不同版本下引擎的兼容问题,使得引擎在不同Windows版本下能够体现出各自音频处理的优势。利用系统级API,讨论了Windows XP、Vista、Windows 7下录放音技术、回声消除技术的实现,并且通过算法设计有效解决了延迟抖动问题。

1 总体结构

VoIP引擎的总体结构主要包括两个部分:处理音频的核心控制模块和处理本地文件的文件播放模块。基于这两个核心模块,可以在上层封装出各种对外功能接口,例如针对C++的DLL (Dynamic Link Library),或者针对Java、C#等的应用接口。系统总体结构如图1所示。

图1 系统总体结构

(1) 本地文件播放模块:负责对本地的媒体文件进行操作,如播放某文件、停止播放、是否循环播放等;

(2) 引擎核心控制模块:通过调用下层模块的功能接口,进行相关数据逻辑处理,控制数据流以及播放录制缓存等。而且,它对上层也提供了其调用接口及回调接口。下层模块主要包括文件音频模块、会话模块、设备音频模块、音频编解码模块;

(3) 文件音频模块:负责音频文件作为媒体传输源时的相关控制及信息保存,包括数据分发列表(一个音频源在同一时刻对应几个会话)、音频文件的文件路径、文件中存储的音频编解码格式等;

(4) 会话模块:负责一路会话的描述。一路会话对应一个RTP模块,并且保存会话的其它相关信息,如当前数据源是麦克风输入还是某个媒体文件、语音的收发模式(同时收发、只收不发、只发不收、不收不发)、音频编解码格式等;

(5) 设备音频处理模块:包括音频的录制、播放,音频设备的管理,以及音量的控制等。待播放的数据从引擎核心控制模块中获取,已录制的数据也直接交给引擎核心控制模块进行处理;

(6) 音频编解码模块:负责各种音频编码格式的相互转换。将录制的音频转换为指定编码格式进行传输,对接收到的音频数据进行解码等。

2 核心技术

2.1 录放音技术

2.1.1 Windows XP系统

Direct Sound是一套基于COM(Component Object Mode)接口的Windows应用层音频控制API,在Windows XP及其以前版本,作为标准的音频应用程序开发接口提供给开发者。底层直接连接驱动,有些底层接口直接由硬件实现,运行效率很高。

Direct Sound的功能包括音频设备的控制、音量控制、录放音。我们关心的录放音也分为3种:播放、录音和录放双工。如果要实现回声消除(Acoustic Echo Cancellation, AEC),必须采用第3种录放双工的方式进行初始化。对应的初始化函数分别为:Direct Sound Create8、Direct Sound Capture Create8、Direct Sound Full Duplex Create8。从应用效果看,这3个函数的效果就是获得两个接口:IDirect Sound8和IDirect Sound Capture8。其中,Direct Sound Create8获取IDirect Sound8,Direct Sound Capture Create8获取IDirect Sound Capture8,而Direct Sound Full Duplex Create8则是同时获取两个接口。

获取总体接口后,用Direct Sound Create8函数初始化的程序必须调用Set Cooperative Level函数设置播放的优先级,否则后续步骤会失败。

然后,要初始化播放或录音的Buffer。初始化时,将播放音的属性、音频格式、缓存大小、特效标识等信息作为参数,在初始化函数调用时告诉Direct Sound。具体设置哪些值和如何设置,请参看微软Direct Sound帮助文档。

Direct Sound录放音有两种形式:一种是静态缓存,即一次性把要播放的音频数据导入缓存;另一种是流模式,即播放录制过程中会不断有数据补充交互,缓存会被复用。对于我们的应用,主要关心流模式。基于流模式,Direct Sound提供了buffer通知模型,即通过设置函数,播放或者完成录制多少字节的Buffer后,就发送一个Event事件。外部线程可以拦截响应这个事件,对Buffer的数据进行处理,实现连贯的平滑的录放音。方法是通过IDirect Sound Buffer8或者IDirect Sound Capture Buffer的Query Interface方法获取IDirect Sound Notify8接口,然后调用该接口的Set Notification Positions方法,设置通知事件和事件通知点。

由于CPU是被抢占运行的,因此,通知时一般都有微小时间滞后。所以,事件触发后的处理,如果有对缓存的精确处理,需要重新获取缓存状态信息。之后,就可以调用buffer的Start函数,开始录音或者放音;调用buffer的Stop函数可以立即结束录音或者放音。

2.1.2 Vista与Windows 7系统

Vista和Windows 7在音频播放录制方面提出了一套全新的概念和架构。在设备上,微软取消了声卡的概念,取而代之的是播放设备、麦克风和线路输入设备。Vista和Windows 7包含了多套应用层音频API,有的是老版本Windows的API,如Wave Xxx和Direct Sound,有的是新的,如Media Foundation中的SAR。在这些应用层之下,微软还开放了一套层次较低的公共API——Core Audio API,用于一些实时性较高或者对设备控制有较高需求的应用,如图2所示。

图2 Vista和Windows 7操作系统的音频处理架构

在Vista及以后版本,Direct Sound不再作为微软主流音频应用接口,但微软在Vista和Windows 7上仍然保留了Direct Sound的大多数接口,使得一些老程序可以运行。但是,该接口变成一套软件接口进行维护,因此有些程序在Windows XP下声音很流畅,而在Vista和Windows 7下却不好,因此这里讨论的录放音技术是直接基于Core Audio API。

由于Core Audio API的定位是针对音频设备的操作,而并不包括对音频的处理。因此,我们的应用还需要自己手动加入音频处理相关的模块,如重抽样和AEC等,微软提供了DSP (Digital Signal Processor)系列接口对音视频等进行处理。

录音的总体流程如下。

(1) Core Audio API调用录制音频API,初始化并开始录音;

(2) 通过流操作,定期获取录制的音频数据;

(3) 将数据交给DSP的重抽样接口,变换为我们需要格式的数据;

(4) 将重抽样处理过的数据传递给DSP的AEC回声消除接口;

(5) 将经过回声消除处理过的数据交给应用程序处理。

这个流程是标准的流程,不过DSP的AEC接口还提供了Source Mode类型接口,将前4个步骤合并为一个接口,大大简化了操作。简化后的录音流程如下。

(1) 启用并初始化DSP的AEC接口,模式要选择Source Mode;

(2) 用AEC接口开始录音,并定期取出数据交给应用程序处理。

放音的总体流程如下。

(1) Core Audio API调用播放音频API,初始化并获得接口对象;

(2) 初始化并设置好重抽样DSP;

(3) 获取并锁定播放缓冲,向其中填充通过重抽样处理过之后的数据,并解锁缓冲;

(4) 开始播放 ;

(5) 另起线程,定期重复第3步,补充音频数据,直到终止播放。

2.2 回声消除技术

2.2.1 Windows XP系统

在Windows XP操作系统下,微软提供了基于Direct Sound的AEC解决方案。在Windows XP下启用AEC,必须在DirectSound初始化时,采用DirectSound FullDuplexCreate8函数,及必须以双工的方式进行初始化,将播放缓冲、录制缓冲及音频格式等信息作为参数传进去。注意结构体DSCBUFFERDESC的dwFlags一定要包含DSCBCAPS_CTRLFX标识,并且DSCEFFECTDESC一定要做如下初始化,并把指针传给DSCBUFFERDESC的lpDSCFXDesc。

// 参数:施加于录制缓冲区的效果描述,AEC & NS DSCEFFECTDESC dsced[2];

ZeroMemory( &dsced[0], sizeof(DSCEFFECTDESC ) );

dsced[0].dwSize = sizeof(DSCEFFECTDESC);

dsced[0].dwFlags = DSCFX_LOCSOFTWARE;

dsced[0].guidDSCFXClass = GUID_DSCFX_CLASS_AEC;

dsced[0].guidDSCFXInstance = GUID_DSCFX_MS_AEC;

dsced[0].dwReserved1 = 0;

dsced[0].dwReserved2 = 0;

ZeroMemory( &dsced[1], sizeof( DSCEFFECTDESC) );

dsced[1].dwSize = sizeof(DSCEFFECTDESC);

dsced[1].dwFlags = DSCFX_LOCSOFTWARE;

dsced[1].guidDSCFXClass = GUID_DSCFX_CLASS_NS;

dsced[1].guidDSCFXInstance = GUID_DSCFX_MS_NS;

dsced[1].dwReserved1 = 0;

dsced[1].dwReserved2 = 0;

// 参数:录制缓冲区描述

DSCBUFFERDESC dscbd;

dscbd.dwSize = sizeof(DSCBUFFERDESC);

dscbd.dwFlags = DSCBCAPS_CTRLFX;

dscbd.dwBufferBytes = m_dwBlockNum * m_dwBlockSize ;

dscbd.dwReserved = 0;

dscbd.lpwfxFormat = &m_wfxRecord;

dscbd.dwFXCount = 2;

dscbd.lpDSCFXDesc = dsced;

然后,将DSCFXAec结构体中的dwMode设置为DSCFX_AEC_MODE_FULL_DUPLEX,并通过录音接口的函数进行设置。后面的步骤和一般DirectSound的录放音步骤相同,先分别设置缓存的上报通知事件点,然后初始化并填充播放缓存,开始播放,开始录制等。详情请参看微软DirectSound帮助文档。

2.2.2 Vista与Windows 7系统

在Vista和Windows 7操作系统中,微软提供了DSP系列接口对音视频等进行处理。其中,和音频相关的处理,包括AEC和重抽样Resample。尽管Windows 7较Vista有不少新的功能和应用接口,不过它们共用的接口(Core Audio API)已经能够满足我们的应用需求。

实现AEC的过程如下:首先通过调用COM组件的方法,通过CoCreateInstance函数获取CLSID_CWMAudioAEC的组件接口IMediaObject。再通过该接口的QueryInterface函数,获取IPropertyStore接口。通过IPropertyStore接口的SetValue函数,将MFPKEY_WMAAECMA_SYSTEM_MODE属性的值设为SINGLE_CHANNEL_AEC,即采用单声道AEC模式。其它的属性可根据用户需要,分别设置。之后,通过IMediaObject接口的SetOutputType函数,用DMO_MEDIA_TYPE结构体作为参数传入,设置输出的音频格式。然后调用IMediaObject接口的AllocateStreamingResources函数开始录制。详情请参看微软Windows SDK文档及Demo程序。

2.3 操作系统版本判定

对不同的操作系统版本编写设备音频处理模块,并封装成不同的类,类之间无耦合关系。同时,提取出公共接口,与核心控制模块交互。

通过操作系统提供的系统API获取当前操作系统的版本,并根据相应的版本,初始化对应的设备音频处理模块。辨别操作系统版本的API为GetVersionEx,该API支持的最低版本的操作系统为Windows 2000,具体用法详见微软MSDN帮助文档。常见Windows操作系统版本如表1所示。

3 延迟抖动消除算法

IP网络的一个特征就是网络延迟与抖动,这将导致IP电话音质下降。网络延迟是指1个IP分组在网络上传输所需的时间,抖动是指IP分组传输时间的长短变化。如果抖动较严重,那么有的语音分组会因迟到而被丢弃,产生语音的断续及部分失真,严重影响音质。为了降低或者消除抖动的影响,我们采用缓冲技术,即在接收方设定1个缓冲区,语音分组到达时首先进入缓冲池暂存,系统以稳定平滑的速率将语音分组从缓冲池中取出、解压、播放给收话者。这种缓冲技术可以在一定限度内有效处理语音抖动,提高音质。接下来,我们将以消除或者降低延迟抖动的影响从而平滑语音流、提高语音质量为目的,分别针对发送端与接收端进行探讨。

表1 常见Windows操作系统版本

3.1 发送端缓存控制

在发送端,需要稳定地发送数据分组。由于发送端的声卡自身的定时器不一定准确,可能偏快或偏慢:若偏快,将导致在单位时间内发送的数据分组比预期的多,时间一长,接收端缓存而不能及时处理的数据分组越来越多,最终导致播放延迟越来越大,即延迟扩大。如PC终端呼叫手机或固定电话时,即会发生如此情况,因此,我们必须在发送端控制数据分组的平稳发送,避免延迟扩大。

首先,采用比较准确的定时器(如CPU定时器);然后设置一个相对较长的计数周期t(时间较短则相对误差较大,无可信性),如10s,计算出在该时间段内理论上应发出的数据分组个数a。(其中,若8kHz的采样率,1个数据分组含160个采样,表明1s发送50个数据分组,每20ms发送1个数据分组)

在发分组过程中,同时做如下处理。

Step1:在第n个计数周期内,通过计数器计算出实际向外发送的数据分组个数bn。若n=1,转Step2;否则转Step3。

Step2:若bn>a,即发分组速率偏快,记录cn=bn-a;否则记录cn=0。转Step5。

Step3:若cn-1>0,则在当前计数周期内均匀丢弃cn-1个数据分组,即每隔t/cn-1时间丢弃1个数据分组。转Step4。

Step4:若bn-a+cn-1>0,记录cn=bn-a+cn-1;否则记录cn=0。转Step5。

Step5:n++。转Step1。

流程图如图3所示。

这样就能控制发送端的数据分组实际速率与理论速率达到动态平衡,防止传输过多的数据分组,避免延迟扩大。因丢弃分组是均匀的,用户感觉不到这微小的差异,故此方案可行。

3.2 接收端缓存控制

图3 发送端数据分组的缓存控制

图4 接收端数据分组的缓存控制

在接收端,需要平滑地读取数据分组。这就需要一个缓存,来存放并整理刚来到的数据分组,在其达到一定数量时,再取出相应数据流畅地播放。如若没有缓存,来一数据就开始播放,极有可能出现语音播放不流畅,先发送的数据分组也有可能后到达,这样的播放就会出现逻辑混乱的错误。

在收包的过程中,需要做如下处理。

Step1:申请m大小的缓存buffer,设定阈值a与b(0

Step2:对接收到的数据分组按照其发送的原始序列进行排序处理,并存放在buffer中,若buffer满,则拒收当前数据分组。记录buffer中实际的数据分组个数c。

Step3:若c≤a,送全0数据给声卡;否则若a

流程图如图4所示。

上述过程中的a、b、m的大小设定比较重要,这需要依据具体的应用场合来进行最佳的设置。上述方案可以保证数据的有序以及较为流畅地播放。

猜你喜欢

调用音频分组
核电项目物项调用管理的应用研究
分组搭配
必须了解的音频基础知识 家庭影院入门攻略:音频认证与推荐标准篇
LabWindows/CVI下基于ActiveX技术的Excel调用
基于Daubechies(dbN)的飞行器音频特征提取
怎么分组
分组
音频分析仪中低失真音频信号的发生方法
基于系统调用的恶意软件检测技术研究
Pro Tools音频剪辑及修正