实时语音通信的实现

王朝vc·作者佚名  2006-01-17
宽屏版  字体: |||超大  

实时语音通信的实现

作者:解放军炮兵学院 十四队 孔康

下载源代码

引言

本人虽已学习VC++一年半载,仍觉捉襟见肘,好在有VCKBASE的帮忙,确实学到了不少东西,www.vckbase.com也成了我每次上民网必到之处(阁下有所不知,鄙人接受最为严格的管理,上民网是要申请的)。近日在做一个通信

方面的程序,实时的语音和视频通信当然是大家所喜欢的。本文将向您展示局域网环境下实时语音通信的的一个解决方案(视频这一块正在做,估计很快就能出炉),Winxp环境下测试效果良好,并且具有网络

拥塞处理机制,您不妨一看。

本文以第26期 栾义明 先生的《基于API的录音机程序》为基础的,在此深表感谢。雷同之处将不再赘述,主要做了以下发展:

(1) 利用多线程机制,实现录音、网络传输、放音同时进行。

(2) 网络壅塞处理,保证数据不丢失。

例子程序运行画面:

下面且看我细细道来:

(一)首先定义了一个声音数据“块”

struct CAudioData

{

PBYTE lpdata; //指向语音数据,注意这里内存区域是动态申请释放的

DWORD dwLength;//语音数据长度

}

接下来申明两个循环队列和相关指针。

//InBlocks,OutBlocks非别为两个常数

CAudioData m_AudioDataIn[InBlocks],m_AudioDataOut[OutBlocks];

int nAudioIn, nSend, //录入、发送指针

nAudioOut, nReceive;//接收、播放指针

// 对于录音和放音都存在和网络的同步问题,主要靠这些指针进行协调

讨论:如图所示,几个指针的相互追逐,这种机制在处理网络拥塞上应该有普遍的应用意义

(1)正常网速下:nAudioIn 在 nSend 之前, nReceive 在 nAuioOu t之前,周而复始的走下去。

(2)超快网速下:发送端:-->nSend追上nAudioIn-->“空转”(绕了一圈又回来了)--〉

接收端:因为录、放音的采样频率设置为相等,故不可能出现 nReceive 在n AudioOut 之后,

即收到的声音文件太多,来不及播放的现象。

(3)超慢网速下:(极端情况,网速几乎为0也没关系)

发送端:nAudioIn 绕一圈反追上 nSend,于是将数据接在当前块的尾部,以待发送

接收端:nAudioOut 追上 nReceive 后,发现没有数据可播放了,就“空转”。

综合以上情况,相关实现如下:

(二)声音的录制与播放

(1)录音处理

void CRecTestDlg::OnMM_WIM_DATA(UINT wParam,LONG lParam)

{

int nextBlock = (nAudioIn+1)% InBlocks;

if(m_AudioDataIn[nextBlock].dwLength!=0)//下一“块”没发走

{ //把PWAVEHDR(即pBUfferi)里的数据接到当前“块”的末尾

m_AudioDataIn[nAudioIn].lpdata

= (PBYTE)realloc (m_AudioDataIn[nAudioIn].lpdata ,

(((PWAVEHDR) lParam)->dwBytesRecorded+m_AudioDataIn[nAudioIn].dwLength)) ;

if (m_AudioDataIn[nAudioIn].lpdata == NULL)

{//...出错处理

return ;

}

CopyMemory ((m_AudioDataIn[nAudioIn].lpdata+m_AudioDataIn[nAudioIn].dwLength),

((PWAVEHDR) lParam)->lpData,

((PWAVEHDR) lParam)->dwBytesRecorded) ;//(*destination,*resource,nLen);

m_AudioDataIn[nAudioIn].dwLength +=((PWAVEHDR) lParam)->dwBytesRecorded;

}

else //把PWAVEHDR(即pBUfferi)里的数据拷贝到下一“块”中

{

nAudioIn = (nAudioIn+1)% InBlocks;

m_AudioDataIn[nAudioIn].lpdata = (PBYTE)realloc

(0,((PWAVEHDR) lParam)->dwBytesRecorded);

CopyMemory(m_AudioDataIn[nAudioIn].lpdata,

((PWAVEHDR) lParam)->lpData,

((PWAVEHDR) lParam)->dwBytesRecorded) ;

m_AudioDataIn[nAudioIn].dwLength =((PWAVEHDR) lParam)->dwBytesRecorded;

}

// Send out a new buffer

waveInAddBuffer (hWaveIn, (PWAVEHDR) lParam, sizeof (WAVEHDR)) ;

return ;

}

(2)放音处理

void CRecTestDlg::OnMM_WOM_DONE(UINT wParam,LONG lParam)

{ //释放播放完的缓冲区,并准备新的数据

free(m_AudioDataOut[nAudioOut].lpdata);

m_AudioDataOut[nAudioOut].lpdata = reinterpret_cast<PBYTE>(malloc(1));

m_AudioDataOut[nAudioOut].dwLength = 0;

nAudioOut= (nAudioOut+1)%OutBlocks;

((PWAVEHDR)lParam)->lpData = (LPTSTR)m_AudioDataOut[nAudioOut].lpdata ;

((PWAVEHDR)lParam)->dwBufferLength = m_AudioDataOut[nAudioOut].dwLength ;

waveOutPrepareHeader (hWaveOut,(PWAVEHDR)lParam,sizeof(WAVEHDR));

waveOutWrite(hWaveOut,(PWAVEHDR)lParam,sizeof(WAVEHDR));

return;

}

(三)套接字发送、接收线程

其实,经过刚才的讨论,现在这两个线程的运作很简单---只是循环地操作nReceive和nSend指针。首先发送(接收)声音块的长度,然后发送(接收)声音内容。注意:拿CSocket::Send(buffer,count)为例,其返回值(发送出去的字结数)只是1到count之间的某值,所以要添加检测机制,否则将出现错误,这也是socket编程必须注意的。本文是用一个循环,直到发送出去的字节总数等于“块”的长度才发送第二个数据块的信息。

例外这两个线程稍加改动即可实现多人的语音会议。

UINT Audio_Listen_Thread(LPVOID lParam)

{

CRecTestDlg *pdlg = (CRecTestDlg*)lParam;

CSocket m_Server;

DWORD length;

if(!m_Server.Create(4002))

AfxMessageBox("Listen Socket create error"+pdlg->GetError(GetLastError()));

if(!m_Server.Listen())

AfxMessageBox("m_server.Listen ERROR"+pdlg->GetError(GetLastError()));

CSocket recSo;

if(! m_Server.Accept(recSo))

AfxMessageBox("m_server.Accept() error"+pdlg->GetError(GetLastError()));

m_Server.Close();

int ret ;

while(1)

{ //开始循环接收声音文件,首先接收文件长度

ret = recSo.Receive(&length,sizeof(DWORD));

if(ret== SOCKET_ERROR )

AfxMessageBox("服务器端接收声音文件长度出错,原因: "+pdlg->GetError(GetLastError()));

if(ret!=sizeof(DWORD))

{

AfxMessageBox("接收文件头错误,将关闭该线程");

recSo.Close();

return -1;

}//接下来开辟length长的内存空间

pdlg->m_AudioDataOut[pdlg->nReceive].lpdata =(PBYTE)realloc (0,length);

if (pdlg->m_AudioDataOut[pdlg->nReceive].lpdata == NULL)

{

AfxMessageBox("erro memory_ReceiveAudio");

recSo.Close();

return -1;

}

else//内存申请成功,可以进行循环检测接受

{

DWORD dwReceived = 0,dwret;

while(length>dwReceived)

{

dwret = recSo.Receive((pdlg->m_AudioDataOut[pdlg->nReceive].lpdata+dwReceived),

(length-dwReceived));

dwReceived +=dwret;

if(dwReceived ==length)

{

pdlg->m_AudioDataOut[pdlg->nReceive].dwLength = length;

break;

}

}

}//本轮声音文件接收完毕

pdlg->nReceive=(pdlg->nReceive+1)%OutBlocks;

}

recSo.Close();

return 0;

}

UINT Audio_Send_Thread(LPVOID lParam)

{

CRecTestDlg *pdlg = (CRecTestDlg*)lParam;

CSocket m_Client;

m_Client.Create();

if( m_Client.Connect("127.0.0.1",4002))

{

DWORD ret, length;

int count=0;

while(1)//循环使用指针nSend

{

length =pdlg->m_AudioDataIn[pdlg->nSend].dwLength;

if(length !=0)

{ //首先发送块的长度

if(((ret = m_Client.Send(&length,sizeof(DWORD)))

!= sizeof(DWORD))||(ret==SOCKET_ERROR))

{

AfxMessageBox("声音文件头传输错误!"+pdlg->GetError(GetLastError()));

pdlg->OnOK();

break;

}//其次发送块的内容,循环检测是否发送完毕

DWORD dwSent = 0;//已经发送掉的字节数

while(1)//==============================发送声音数据开始

{

ret = m_Client.Send((pdlg->m_AudioDataIn[pdlg->nSend].lpdata+dwSent),

(length-dwSent));

if(ret==SOCKET_ERROR)//检错

{

AfxMessageBox("声音文件传输错误!"+pdlg->GetError(GetLastError()));

break;

}

else //发送未发送完的

{

dwSent += ret;

if(dwSent ==length)//发送完毕,则释放当前“块”

{

free(pdlg->m_AudioDataIn[pdlg->nSend].lpdata);

pdlg->m_AudioDataIn[pdlg->nSend].dwLength = 0;

break;

}

}

} //======================================发送声音数据结束

}

pdlg->nSend = (pdlg->nSend +1)% InBlocks;

}

}

else

AfxMessageBox("Socket连接失败"+pdlg->GetError(GetLastError()));

m_Client.Close();

return 0;

}

存在的问题

(1) 一旦添加声音控制waveSetGetVolume(),耳机就变成单声的,打开系统的音量控制,发现“波形”选项完全不平衡。

(2) 声音的录入运用双缓冲技术,使得无懈可击,但是在播放时,采用双缓冲调试时未能取得成功,相反使用单缓冲却基本上能够满足一般的音效。

(3) 可能还有尚未暴露的错误,恳请广大朋友不吝赐教。E-mail:

candy0624@163.com

Finally,Thank Candy Lee(my special friend) for her help.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
© 2005- 王朝网络 版权所有