1 序言
近几年来传统电信业的发展势头逐渐减弱。 在激烈的市场竞争中,电信业需要不断地去寻找新的收入增长点。VoIP在全球随着技术的成熟及各种人为管制的放松,开始进入一个新的发展时期。低廉的费用、低的带宽、话音和数据应用的集成以及应用性广泛等优点使VoIP必然成为了人们的选择。
IP(Internet Protocol)电话是一种数字电话,是技术创新的一种通信服务业务。它把语音、压缩编码、打包分组、分配路由、存储交换、解包解压等交换处理在IP网或互联网上实现语音通信。
传统的电话网是以电路交换方式传输语音,所要求的传输宽带为64kbit/s。而VoIP是以IP分组交换网络为传输平台,对模拟的语音信号进行压缩、打包等一系列的特殊处理,使之可以采用无连接的UDP协议进行传输。不同编码方案应用于VoIP,使得话音可以以不同的速率进行传输,使得VoIP的传输带宽进一步减小到5-11kbit/s。
传统的电话网络都有自己遵循的协议,在呼叫过程中都要用到这些协议,比如用于电路交换的7号信令等。H.323和sip信令协议被广泛应用于VoIP网络中。H.323是一个框架性协议,这一框架体系结构包括H.323终端、网关、关守和MCU。网关是VoIP的重要组成部分,网关完成了两项功能的转换,媒体信息编码的转换和信令的转换。通过这两种转换,网关将TCP/IP网和PSTN网连接连了起来。
2 VoIP软件的实现
一般IP电话的业务可分为3类: PC-to-PC, PC-to-Phone和Phone-to-Phone。这里主要是针对PC-to-PC的编程过程进行介绍。
IP电话的基本数据流程为:呼叫方经录音设备获取音频流,然后对音频流进行编码、压缩、打包处理。其中计算机对输入的不同类型的数据进行不同的处理(如果是音频数据,采用无连接的UDP协议进行传输;如果是信令和数据则采用面向连接的TCP协议进行传输),然后经网络传输到被叫方。被呼叫方接收到数据以后,再进行一系列的反变换,解包、解压和解码,送入放音缓冲区,经音频设备输出语音。
2.1 通信模块的实现
MFC中提供了封装的Socket类,它提供了全面的由事件驱动的Socket通信能力。程序必须按要求通过此Socket发送和接收数据。Winsock使用的是TCP协议或UDP协议,允许建立并保持一个到远程计算机上的连接,且可以在连接结束之前实时地进行数据交换。用户仅通过设置属性并借助事件处理就能轻而易举地连接到一个远程的计算机上。使用Winsock时,通信的双方需要选定相同的协议。TCP协议适用于传送大容量、需要安全性保证的数据文件; 而UDP协议适用需要分别与很多下属通信,或建立的连接比较多且为时变的情况,如语音通信。
Socket是面向客户/服务器模式设计的,它针对客户和服务器程序提供了不同的系统调用。同时它还分为面向连接和无连接两种类型。对于语音传输,由于语音对实时性要求高,所以采用无连接的形式。而对于信令和数据传输,对准确性要求比较高,通常采用面向连接的形式。
//服务器端
sock=socket(AF_INET,SOCK_STREAM,0); //创建socket
if (bind(sock,(sockaddr*)&serv,addlen)) //绑定
{
m_edit.SetWindowText("bind error");
}
listen(sock,5);
AfxBeginThread(serthread,0); //调用线程
//客户端
clisock=socket(AF_INET,SOCK_STREAM,0);// 创建socket
while(connect(clisock,(sockaddr*)&(cli),sizeof(cli))!=0)
{
dlg->m_edit.SetWindowText("等待.....");
for (int i=0;i<=65000;i++) //空循环
for(int j=0;j<=200;j++);
………
}
2.2 音频模块的实现
IP电话音频功能的实现主要包括语音的录制和播放。因为在IP电话中采集实时的音频数据而不是WAVE文件,而且要把实时的语音数据传输出去,所以编程过程中采用了底层音频处理函数, 这些函数允许应用程序直接与底层驱动程序通信,对录制和播放提供更灵活的控制。对于波形设备来说,不论是录制还是播放波形,系统要处理的数据量都很大,为了少占用内存,底层服务函数以数据块为单位进行处理, 应用程序要自己分配内存,并将内存块的地址、大小等信息告诉底层音频驱动程序。
首先介绍几个要用到的数据结构。WAVEFORMATEX结构定义了WAVE音频数据文件的格式。WAVEHDR结构定义了波形音频缓冲区。读出的数据首先要填充此缓冲区才能送音频设备播放,声音的采集和播放都是在操作这个音频数据块结构。 实际上主要用到的就是第一个成员变量lpData,所以只要在分配缓冲区(内存)的同时相应分配WAVEHDR数据块结构, 然后将缓冲区的指针赋给对应的数据块结构的成员变量lpData,这样当一个缓冲区填满后,也就是一个音频数据块填满了,通过消息机制就可以在消息函数中进行处理和播放,播放完后又可通过消息函数把缓冲区再送给音频设备输入驱动程序,继续进行采集并播放。
当一次性分配多个缓冲区和数据块结构并赋给音频设备输入驱动程序后,至于把哪个缓冲区填满,然后再把哪个空缓冲区赋给设备输入驱动程序,不需人为干预,完全由Windows控制。在程序设计中可以使用两个数据块,交替进行,当正播放第一个缓冲区,就用第二个缓冲区接收。
//检查语音输入/输出设备
if(!waveInGetNumDevs())
{
return FALSE;
}
if(!waveOutGetNumDevs())
{
return FALSE;
}
//打开音频设备之前,需要先设置音频数据格式
……………
//分配缓冲区所需要的内存
pWaveOutHdr=(LPWAVEHDR)GlobalAllocPtr(GHND|GMEM_SHARE,sizeof(WAVEHDR));
pWaveHdr1=(LPWAVEHDR)GlobalAllocPtr(GHND|GMEM_SHARE,sizeof(WAVEHDR));
pWaveHdr2=(LPWAVEHDR)GlobalAllocPtr(GHND|GMEM_SHARE,sizeof(WAVEHDR));
//放音缓冲区
pOutBuffer1=(char*)GlobalAllocPtr(GHND|GMEM_SHARE,PCMBUFFER_SIZE*BLOCK_PER_BUFFER+500);
pOutBuffer2=(char*)GlobalAllocPtr(GHND|GMEM_SHARE,PCMBUFFER_SIZE*BLOCK_PER_BUFFER+500);
rBuffer=(char*)GlobalAllocPtr(GHND|GMEM_SHARE,PCMBUFFER_SIZE); //接收缓冲区
//打开音频设备
result=waveOutOpen((LPHWAVEOUT)&hWaveOut,WAVE_MAPPER,(LPWAVEFORMATEX)&pcm.wf,(DWORD)hwnd,0L, CALLBACK_WINDOW)
2.3 语音压缩模块的实现
VoIP带宽比电路交换更窄的主要原因是对数据进行了压缩处理。VoIP除了可使用最普通的编码技术G.711外,还可以采用G.728, G.729, G.723.1等其它多种编码方案。
G.711有A律和μ律两种形式。G.711通常被称为PCM(脉冲编码调制)。如果在编程中选择PCM编码方式。
G.711提供了良好的语音质量,但是它的主要缺点是需要64Kbit/s的带宽。所以在程序中灵活的选择编码方式是很重要的。
ITU-T推荐的G.723.1标准可以同时支持两种速率的编码。一种是6.3kbit/s另一种是5.3kbit/s。所以,G.723.1在高速率下提供了好的通话质量,是一种好的编码方案。在程序中的实现是通过对G.723.1软件包中相应功能函数的调用来完成的,而且,在进行压缩以前要将G.723.1的链接库导入程序中。
在需要进行压缩时,要调用下面G.723.1的编码和解码函数:
void Coder(FLOAT *DataBuff, char *Vout); //编码
void Decod(FLOAT *DataBuff, char *Vinp, short int Crc); //解码
2.4 RTP封装
因为语音传输对实时性要求非常高, 所以在程序中要采用基于UDP的RTP传输。UDP无法做到避免分组丢失和确保分组有序传输,运行在UDP上的RTP帮助实现了这些功能,RTP含有一个时间戳,可以利用这个时间戳来确保信息同步的传输给了目的用户并计算出了时延和抖动。
在程序中要对语音数据进行RTP格式封装,使用了makertp()函数。而在解析是使用了isrtp()函数。
LONG makertp(databuf *pdata,unsigned long ssrc_i,unsigned long timestamp_i,unsigned short seq_i,int spurt);
BOOL isrtp(unsigned char *pkt,int len);
在进行RTCP 格式的封装时,使用了三个函数:
rtcp_make_sdes();
rtcp_make_sr();
rtcp_make_rr()。
3 结论
本文主要是利用VC++语言来实现PC-to-PC的软件电话,它提供给Internet用户在全球任何地方上网便可以呼叫对方的能力,提高了通信效率,是一种新的通信模式。
来源:中国联通网站