PC到PC的IP电话实现作者:阮帮秋发布时间:2001/04/09
文章摘要:
IP电话,也称为网络电话,它的发展非常之迅速。本文设计并实现了一个计算机到计算机的IP电话的软件模型,详细讲解了软件设计中的重点和难点,分析了软件中语音的采集和播放,语音的网络传输等一些关键技术的实现方法和注意事项,并指出了软件的不足和进一步工作。在局域网上用此软件来做通话实验,音质和时延都达到了电话的效果,说明此软件达到了IP电话的基本要求。
关键词:IP电话 语音 网络
正文:
PC到PC的IP电话实现
Internet是当今应用最广泛、发展最迅速的通信网络。这是基于数据包方式的数据分组交换方式,用户数据被封装在分组中,而分组还包含一些附加信息用于网络中的路由选择、差错纠正、流量控制等。数据包各自独立地在网络中传递,由于网络状况的变化和经历路径的不同,数据包到达目的地的时间是不固定的、非实时的。故此,一般来说互联网较适用于数据的传输。但是,我们知道视频、音频信号经过模数转换后也可以作为数据在互联网上传递,因此将语音采样,量化变为数字信号,然后打包从网络上传输,双方也可以进行通话,这就是IP电话。
IP电话是对模拟语音信号经过模数转换,进行编码压缩后,按一定的打包规则将压缩帧转换成IP数据包通过数据网进行传输,在目的地经过数据解压、数模转换复原成话音,从而达到语音通信的目的。由于数据网是采用统计时分的方式分配、使用网络资源,任何通信实体都不可能独占某一信道,所以IP电话可以大大提高网络资源的利用率,降低运营成本。
IP电话的初次亮相是在1995年初,VocalTec公司推出了一种Internet Phone客户软件。虽然当时该公司还没有提出在IP上传输话音的概念,但这确实是IP电话第一次成功的商业化和市场化。在此之前,IP上的话音传输极为困难,VocalTec的第一个产品用于LAN上的两台PC相互通话。现已有多家网络公司开始利用因特网正式提供国际国内的长途电话服务。特别在美国,此类业务开展得更为广泛。类似的技术也可用于长途传真(E-Fax)等电信服务项目。由于全球范围内的因特网通信费用大大低于通常的电信长途费用,众多的用户已开始使用通过因特网的长途电话服务。
基于实验和研究的目的,本文实现了一个从计算机到计算机的IP电话的软件。软件的开发环境为Windows 98平台,开发工具采用Visual C++ 5.0。本软件能够在网络上实现多人之间的文本传输和两人之间的语音对话。下文将对本软件展开分析。
一 软件设计
实现此软件的重点和难点如下:
(1)实现语音的采集和播放,就是在发话方将声音通过麦克风和声卡采集成语音数据,在接收方通过声卡和耳机播放出来。
(2)实现语音数据的压缩和解压缩,在发话方压缩采集的语音数据,在接收方解压缩。
(3)实现语音数据在网络上的传输。
(4)多用户之间的连接方式和控制。
(5)软件整体结构和交互界面的设计。
考虑到软件的核心功能是传输语音,所以整个软件的设计应该围绕着语音传输来设计。首先分析语音传输的过程,如图一是一个单向的语音传输过程。发话方首先由声卡采集语音,将语音转换为数据,存入内存中,再通过CPU进行语音压缩算法的运算,将语音数据进行压缩,最后通过网卡将语音数据发送出去。接收方通过网卡接收到语音数据,首先由CPU采用解压缩算法将语音数据进行解压缩,然后通过声卡将数据转换为语音,通过耳机播放。此流程图实际上已经包含了上述问题中前三个问题的解答,当然这只是从理论上肯定了可行性,具体的实现后面还会进一步讨论。
图一 语音传输流程
多个用户之间的互相连接通常可以采用两种方法。第一种方法是设置一个服务器,所有的客户机都连接到服务器上,通过服务器互相连接。这种方法的好处是软件控制比较简单,用户的连接也比较方便,用户只需要知道服务器的IP地址就行了。但缺点也很明显,它较适合于公司的商业性质的软件,普通人员没有资金去专门购买和设置一个服务器,也没有时间和精力去管理和维护服务器。第二种方法是不要服务器,每两个用户之间都可以互相连接,只要知道对方的IP地址就可以去连接对方,同时别人只要知道你的IP地址,也可以与你连接,即每台计算机都既是服务器,又是客户机。采用这种方法的优缺点与第一种方法可以说是正好相反,不再赘述。也有某些比较完善的软件对这两种方法都予以采用,用户可以自行选择其中一种方法与其它用户进行连接。
由以上分析,作者决定采用第二种方式进行计算机之间的连接,这样虽然增加了软件的复杂度,但符合我们的实际情况。在实际程序中,采用了一个链表来保存与之相连的用户的信息,充分利用了链表的无序性和动态的增加,删除特性来表示无序的用户的动态的连接和离开。这样也从理论上解决了第四个问题。
软件的整体结构和数据流程如图二,用户交互界面用来响应用户的操作,提示用户重要的信息,显示文本,播放语音等,实现软件与用户的交互。用户可以通过键盘来输入文本,通过麦克风来输入语音。
语音采集模块用来采集语音数据,将模拟语音信号转换为数字信号,在程序中为了减少通话的延迟时间,直接分配内存块,将采集的数据放在内存块中,以便压缩或传输。对语音的采集是通过调用声卡的API函数来完成。在本软件中语音的采样格式为WAVE_FORMAT_PCM,单声道,采样率为8K,样本量化率为16位,即每秒的语音数据有16K字节。
数据传输模块采用VC的CSocket类,通过TCP/IP协议将放在内存块中的语音采集数据传输到接收方。
语音播放模块将接收到的语音数据直接通过声卡把数字信号转换为模拟信号,进行播放。
语音压缩模块首先对输入的语音帧进行高通滤波,去除信号中的直流分量和50Hz的成分,同时进行增益控制。再进行线性预测分析,提取线谱对参数,对线谱对进行预测和量化,利用线谱对参数构造共振峰感觉加权滤波器,对高通信号进行开环基音估计,在开环基音估计的基础上进行闭环基音估计,在已经取得的参数的基础上代入语音生成模型对激励码本进行搜索,提取激励参数。对各参数进行量化,编帧。本软件的压缩率达到了1/20的效果,即压缩后每秒的语音数据为0.8K字节。
语音解压模块从语音帧中提取各参数代入语音生成模型,输出语音。
图二软件的整体结构和数据流程
至此我们已经搭起了整个软件的基本框架,对于软件的数据流程也已分析得比较透彻,下一步就是具体的实现了。
二 软件实现
下面具体分析软件实现中的一些主要难点。
1.语音的采集和播放。
本软件中要把语音直接转换为数据,放在内存中,而不是存为语音文件,而且播放语音时,也是直接播放语音数据,而不是播放语音文件。这样的好处前面已经提到,即省略了读写硬盘的费时操作,提高了语音通话的实时性。
要完成上述语音操作,编程语言中提供的容易使用的高级的多媒体语音函数是无法胜任的,只能用一些底层的语音函数来实现,这一类函数和结构的名字都以"wave"作为前缀。
下面简要分析录音和放音的流程,考虑到两着的处理和流程基本上是类似的,本文就只以录音为例来分析,如图三为录音流程。录音的准备工作主要是三点,打开录音设备,获得录音句柄,指定录音格式,分配若干用于录音的内存,内存的大小和数量下文将进一步分析。开始录音时,先将所有内存块都提供给录音设备用来录音,录音设备就会依次将语音数据写入内存,当一块内存写满,录音设备就会发一个Window 消息MM_WIM_DATA给相应的窗口,通知程序作相关的处理,这时程序通常的处理是把内存中的数据进行复制,如写入文件等,在此我们的处理是把数据进行压缩和网络发送,然后把内存置空,返还给录音设备进行录音,这样就形成一个循环不息的录音过程。结束录音时就释放所有内存块,关闭录音设备。
图三录音流程
以录音为例,关键的录音函数和顺序如下:
WAVEFORMATEX waveformat;
waveformat.wFormatTag=WAVE_FORMAT_PCM;
waveformat.nChannels=1;
waveformat.nSamplesPerSec=8000;
waveformat.nAvgBytesPerSec=16000;
waveformat.nBlockAlign=2;
waveformat.cbSize=0;
waveformat.wBitsPerSample=16; //指定录音格式
int res=waveInOpen(&m_hWaveIn,WAVE_MAPPER, &waveformat, (DWORD)m_hWnd,0L,CALLBACK_WINDOW); //打开录音设备
waveInPrepareHeader(m_hWaveIn,m_pWaveHdr[i],sizeof(WAVEHDR)); //准备内存块录音
waveInAddBuffer(m_hWaveIn,m_pWaveHdr[i],sizeof(WAVEHDR)); //增加内存块
waveInStart(m_hWaveIn);//开始录音
waveInStop(m_hWaveIn); //停止录音
waveInReset(m_hWaveIn); //清空内存块
waveInClose(m_hWaveIn); //关闭录音设备
在录音和放音的程序处理中,要特别注意两点。一个是分配的内存的大小和数量。内存的大小与IP电话中语音的连续性和延时性有直接关系,内存越大,则语音的连续性越好,但延时性越差,反之,内存越小,则语音的延时性越小,但连续性越差,所以要权衡利弊,取一个折衷量,这就需要反复实验。根据作者的实验,大约每个内存块录音的时间长度取0.1秒比较合适,具体的大小与采用的语音格式有关。内存的数量则与内存的大小和对每个内存的录音数据的处理时间长短有关,一定要保证在录音过程中,录音设备至少有一块内存可供录音,也就是说录满的内存要及时返回,使得循环能够顺利进行。由此可知,内存大,分配的数量就可以少,对每个内存的录音数据的处理时间短,分配的数量就可以少,反之,分配的数量就要多。当然可以分配足够多的数量,但是太多则耗费资源,降低了程序的效率。根据作者的实验,大约所有的内存块录音的时间长度和有0.5秒就足够了,也就是说,如果每个内存块录音时间长度为0.1秒,则可以分配5个内存块。
程序中还有特别重要的一点是语音设备的Window消息的相应,通过语音设备发送的一系列消息,如MM_WIN_OPEN录音设备打开消息,MM_WIM_CLOSE录音设备关闭消息等,我们可以响应语音设备的打开,关闭,开始录音和放音,停止录音和放音,录音时一个内存块录满,放音时一个内存块放完等各种事件,来进行相关的处理。需要注意的是这些消息在VC的ClassWizard类工具的消息序列中是看不到的,需要手工编辑消息响应宏和代码。
2.语音数据的网络传输
TCP/IP协议是网络上最通用的协议,本软件采用TCP/IP协议来进行数据语音的网络传输。CSocket类是Visual C++ 4.0新增加的一个类,它是对原来的Windows Socket API的封装,用它进行网络编程比用Windows Socket API要方便得多。
建立网络的连接主要有如下三个步骤,一是在程序中加入Windows Sockets支持,二是以CSocket为基类构造两个新类CServerSocket和CMsgSocket。CServerSocket用来接收请求连接的申请,CMsgSocket用来传输数据。三是建立服务器方和客户机方的连接。下表显示了在服务器与客户机之间建立通信所需做的工作的顺序,具体的函数参数可查阅连机文档。服务器方首先构造一个CServerSocket类的对象,用来接收请求连接的申请,调用此对象的Listen成员函数,表示处于等待连接状态,等待客户机方发出申请连接(Connect)的消息,当接收到此消息后,CServerSocket类的OnAccept消息响应函数即会响应,此时再构造一个CMsgSocket类的对象(用来传输数据),然后调用CServerSocket类的Accept成员函数表示接受连接申请,若此函数返回真值,则表示连接成功。客户机方则只需构造一个CMsgSocket类的对象,调用此对象的Connect成员函数,申请连接即可。
按上述顺序建立连接后,服务器方和客户机方都调用CMsgSocket对象的Send函数来发送数据,当接收到数据时,CMsgSocket类的OnReceive消息响应函数即会响应,再调用Receive函数来接收数据,这样服务器方和客户机方就可以进行数据通信了。
在程序中要注意网络消息的响应,如接收到数据,有客户申请连接,已连接上,对方已断开等等,与一般的消息响应函数不同,它已经集成在CSocket类的成员函数中,而不用消息响应宏。如在CServerSocket类中重载CSocket类的成员函数OnAccept,即可以处理申请连接的消息,在CMsgSocket类中重载CSocket类的成员函数OnReceive,即可以处理接收到数据的消息。重载CSocket类的成员函数OnClose函数,即可以处理对方网络已断开的消息。另外用CSocket类的成员函数GetPeerName可以得到对方的IP地址,在程序中可以保存下来,供以后连接使用,用户不必再重新输入。
3.对单工声卡的支持
声卡有单工声卡和双工声卡之分,双工声卡可以同时录音和放音,而单工声卡一个时间段只能从事一种工作,要么录音,要么放音。
对IP电话来说,当然是要用双工声卡,才能达到电话的效果。但考虑到当前广大用户的计算机配置的都是单工声卡,本软件也加入了对单工声卡的支持。软件可以自动检测计算机的声卡,针对不同的声卡采用不同的工作方式。当声卡为双工声卡时,可以同时听和说,当声卡为单工方式时则用户可以控制,在听和说之间进行切换。
具体的实现方法是,当一方为单工声卡时,首先通过网络通知对方,让对方有心理准备。然后在实际的通话过程中,一方进行切换时,就通知另一方,另一方也自动进行切换,进入对应的方式。即一方为说时,另一方就自动为听,反之亦然,这样双方就不会发生冲突。具体的编程实现比较复杂,但只要考虑周到,也并不困难,这里不再赘述。
三 软件界面和使用方法
软件界面如图四,界面上方的IP地址栏可输入欲连接的计算机的IP地址,单击右边的代表连接含义的图标即可与之连接,连通或未连通在界面上方的文本框内都有提示。用户也可以按连接按纽旁边的断开按纽断开与任意已连通的对象的网络连接。
网上成员的下拉框内有当前与用户连通的所有用户的姓名。
界面下方的文本框可以输入多行文本,在界面下方的下拉框内选择发送对象,发送对象就是所有与用户连通的其他人,单击左边的按纽,就可以将文本发送给所选择的对象。如下图:接收到的文本和发送对象会在界面上方的文本框内显示出来。
单击电话按纽,类似与电话拨号,用户可以选择一个与他连通的人进行语音通话,对方的计算机就会有振铃声,对方可以选择是否接通进行通话,类似与摘机。若对方同意通话,双方就可以用麦克风和耳机进行通话。若声卡具备全双工功能,即声卡可以同时录音和放音,双方就可以象打电话一样进行交谈。若声卡不具备全双工功能,事实上当前市场上的大多数声卡都不具备此性能,则听和说同时只能选择其中一种,考虑到实际情况,本软件提供了对单工声卡的支持,可以自动识别声卡的单双工特性,分别予以处理,如果是单工声卡,用户可以在说和听时单击电话图标按纽(当通话时,电话图标会改变为麦克风图标或喇叭图标,麦克风图标代表说,喇叭图标代表听),进行功能切换。当然最好是双方都具备全双工声卡,都可以同时说和听,这样才有电话的效果。
通话完毕,双方都可以单击电话图标旁边的停止图标按纽停止交谈,类似与挂断电话。
用户依据网络状况的好坏,可以采用压缩和不压缩的方式来传输语音。在局域网内用不压缩的方式就可以达到很好的效果。
图四软件界面
实践证明,本软件在实际运行中稳定可靠,在局域网上音质和延迟都基本上达到了电话的效果。
四 软件的不足和进一步工作
本软件从总体上已经达到了IP电话的基本要求,但是由于作者的时间和条件有限,软件也存在一些不足和需要进一步完善的地方。
最大的不足是每秒0.8K的语音数据在我们国家当前的Internet上还是显得过高,本软件中语音压缩率还有待继续提高。
其次在本软件中从通用性的目的出发,采用TCP/IP协议进行网络传输。实际上IP电话的语音传输和视频传输以及语音和视频的压缩方式等都有相应的国际标准,如果作为一个严格的商业软件,则必须采用这些标准。
另外在软件的整体功能上还可以进一步加强。例如语音通话中可以加入录音功能,实现起来也并不困难,将内存中的语音数据写入文件即可,同理也可以实现语音信箱的功能。其它一些重要的功能,如文件传输,多方通话等等,都可以加入软件中。
作者会员名:ruan_bangqiu