引言
本人虽已学习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;//接收、播放指针
讨论:如图所示,几个指针的相互追逐,这种机制在处理网络拥塞上应该有普遍的应用意义
接收端:因为录、放音的采样频率设置为相等,故不可能出现 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,
void CRecTestDlg::OnMM_WOM_DONE(UINT wParam,LONG lParam)
(三)套接字发送、接收线程
{ //释放播放完的缓冲区,并准备新的数据
free(m_AudioDataOut[nAudioOut].lpdata);
m_AudioDataOut[nAudioOut].lpdata = reintERPret_cast
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(&le