分类:
2008-10-13 16:06:42
实时语音通信的实现
作者:解放军炮兵学院 十四队
引言
本人虽已学习VC++一年半载,仍觉捉襟见肘,好在有VCKBASE的帮忙,确实学到了不少东西,也成了我每次上民网必到之处(阁下有所不知,鄙人接受最为严格的管理,上民网是要申请的)。近日在做一个通信
方面的程序,实时的语音和视频通信当然是大家所喜欢的。本文将向您展示局域网环境下实时语音通信的的一个解决方案(视频这一块正在做,估计很快就能出炉),Winxp环境下测试效果良好,并且具有网络
拥塞处理机制,您不妨一看。
本文以第26期 栾义明 先生的为基础的,在此深表感谢。雷同之处将不再赘述,主要做了以下发展:
例子程序运行画面:
下面且看我细细道来:
(一)首先定义了一个声音数据“块”
struct CAudioData { PBYTE lpdata; //指向语音数据,注意这里内存区域是动态申请释放的 DWORD dwLength;//语音数据长度 }接下来申明两个循环队列和相关指针。
//InBlocks,OutBlocks非别为两个常数 CAudioData m_AudioDataIn[InBlocks],m_AudioDataOut[OutBlocks]; int 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(三)套接字发送、接收线程(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; }
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; }存在的问题
Finally,Thank Candy Lee(my special friend) for her help.
--------------------next---------------------
首先,CPU占用100%,不知道什么原因
第二,我怀疑你的拥塞控制没有作用,因为每录完一块,nAudioIn+1,假设已录到第4块,而第1块还没有发完,你的处理办法是仍然录在当前块
if(m_AudioDataIn[nextBlock].dwLength!=0)//下一“块”没发走
{ //把PWAVEHDR(即pBUfferi)里的数据拷贝到当前“块”中
.....
}
,那么就会将第4块的录音给冲掉 ( talent529 发表于 2008-5-8 20:59:00)
TCP一样的可以做到延时很小.0.5秒以内.BUFFER 应该是一个ARRAY 或者内存块.发的时间去Get()然后再discard.
while (TRUE)
{
if(WAIT_OBJECT_0!=::WaitForSingleObject(=_SEND_BUFFER.m_hBufEnough,INFINITE))
{
return 1;
} packet=_SEND_BUFFER_.Get();
if (packet==NULL)
{
continue;
}
if(!SendPack(*packet,this))
{
break;
}
_SEND_BUFFER_.Discard();
} ( heyunet 发表于 2007-11-21 14:55:00)
"例外这两个线程稍加改动即可实现多人的语音会议"
我想实现两台机器的语音通话 我将m_Client.Connect("192.168.1.100",4002) 改成了我同学机器的IP
但是一直报错“SOCKET连接失败” “一般错误” 不知道是怎么回事 请指点 ( karl412 发表于 2007-5-3 15:14:00)
声音有延迟主要是因为作者的缓冲区设的很大(#define INP_BUFFER_SIZE 16384)
大约采样需要2秒,才第一次触发OnMM_WIM_DATA,所以感觉延迟大,可以试着改小INP_BUFFER_SIZE。和tcp无关,用udp实现,主要难点是如何协调数据包的到达顺序,而不窜声。 ( bifei 发表于 2007-1-18 20:16:00)
CPU资源占用居高不下。 ( magicdigua 发表于 2006-10-30 8:30:00)
我的发送线程是在else语句处return 的-1
到了CSocket::send内部后,判断如果是-1就直接返回-1
( ydbcsdn 发表于 2006-6-16 9:36:00)
我想请问一句:
CSocket的Send 函数的确经常返回-1,我跟踪到send函数的内部后发现,在他的发送函数里已经有了发送数据数据长度的检测,显然,我们不需要对Send函数进行额外的处理!!
我的新问题,我的Send函数返回的并不是一个长度,而是返回-1,很明显是CSysnSocket::Send 就返回了-1,当为-1时他就不检测长度了,而是直接返回了。
while ((nResult = CAsyncSocket::Send(lpBuf, nBufLen, nFlags)) == SOCKET_ERROR)
{
if (GetLastError() == WSAEWOULDBLOCK)
{
if (!PumpMessages(FD_WRITE))
return SOCKET_ERROR;
}
else
return SOCKET_ERROR;
} ( ydbcsdn 发表于 2006-6-16 9:34:00)
双/多缓冲播放文件要使用定时机制,即采样率/缓冲长度为调用waveOutWrite的周期,如果是网络接收播放,每收到一个包填入当前缓冲区,填满后调用waveOutWrite加入播放队列就行了,这个播放队列是waveOutOpen之后默认建立的,不能通过放完一个缓冲后的回调函数中再调用waveOutWrite的方式来实现流畅播放 ( supercjj 发表于 2006-1-18 17:29:00)
这是什么水平??太初手了
一点也不PF,但是<<基于API的录音机程序>>作者栾义明写文档的功夫和分享精神个人还是很欣赏的 ( 逍遥剑侠 发表于 2006-1-4 14:10:00)
您好,我想请教一下,用UDP方式和TCP方式怎么没有变化
因为TCP方式延迟大约3到5秒钟,我想用UDP方式来改变延迟问题。希望能得到指教! ( ziying1211 发表于 2005-11-24 11:34:00)
.......................................................
--------------------next---------------------