📄 实时语音通信的实现 - 语音通信 - 技术应用 - 豆豆网.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd">
<!-- saved from url=(0049)http://tech.ddvip.com/2006-04/11443564804073.html -->
<HTML xmlns="http://www.w3.org/1999/xhtml"><HEAD><TITLE>实时语音通信的实现 - 语音通信 - 技术应用 - 豆豆网</TITLE><!-- 本模板由 legmail 最后于 2007-09-19 13:47 修改 -->
<META http-equiv=Content-Type content="text/html; charset=gb2312"><LINK
href="实时语音通信的实现 - 语音通信 - 技术应用 - 豆豆网.files/tech_content.css" type=text/css
rel=stylesheet>
<STYLE>.nav {
BACKGROUND: #f2f6fb; WORD-SPACING: 20px; TEXT-INDENT: 5px; LINE-HEIGHT: 28px; BORDER-BOTTOM: #aaa 1px solid; HEIGHT: 28px; TEXT-ALIGN: left
}
</STYLE>
<SCRIPT><!--document.domain = 'ddvip.com';var node = 'tech';var subid = "2006-04-07_4073";--></SCRIPT>
<META content="MSHTML 6.00.2900.3268" name=GENERATOR></HEAD>
<BODY>
<DIV id=main><!-- 页面头部 -->
<DIV class=header>
<DIV class=top_nav><A href="http://www.ddvip.com/">豆豆首页</A> - <A
href="http://down.ddvip.com/">软件下载</A> - <A
href="http://code.ddvip.com/">源码下载</A> - <A
href="http://down.ddvip.com/news">软件资讯</A> - <A
href="http://book.ddvip.com/">小说阅读</A> - <A
href="http://club.ddvip.com/">技术论坛</A> - <A
href="http://man.ddvip.com/">技术手册</A> - <A
href="http://www.ddvip.com/link">网址导航</A> - <A
href="http://law.ddvip.com/">法律资讯</A> - <A
href="http://blog.ddvip.com/">博客人生</A> </DIV>
<DIV class=sitelogo><IMG alt=网站LOGO
src="实时语音通信的实现 - 语音通信 - 技术应用 - 豆豆网.files/logo.gif">
<DIV class=top_ad_580x60 id=top_ad_580x60></DIV></DIV>
<DIV class=clear></DIV>
<UL class=global_nav>
<LI><A href="http://tech.ddvip.com/index.html">技术首页</A> </LI>
<LI><A href="http://tech.ddvip.com/os/index.html">操作系统</A> </LI>
<LI class=global_nav_c><A
href="http://tech.ddvip.com/program/index.html">程序设计</A> </LI>
<LI><A href="http://tech.ddvip.com/network/index.html">网络技术</A> </LI>
<LI><A href="http://tech.ddvip.com/network/server/index.html">服务器</A> </LI>
<LI><A href="http://tech.ddvip.com/safe/index.html">安全技术</A> </LI>
<LI><A href="http://tech.ddvip.com/web/index.html">Web开发</A> </LI>
<LI><A href="http://tech.ddvip.com/db/index.html">数据库</A> </LI>
<LI><A href="http://tech.ddvip.com/pic/index.html">平面设计</A> </LI>
<LI><A href="http://tech.ddvip.com/mediamovice/index.html">三维动画</A> </LI>
<LI><A href="http://tech.ddvip.com/cert/index.html">认证考试</A> </LI>
<LI><A href="http://tech.ddvip.com/soft/index.html">软件应用</A> </LI>
<LI><A href="http://tech.ddvip.com/machine/index.html">机械电子</A> </LI></UL>
<DIV class=nav><A
href="http://tech.ddvip.com/program/vc/visualstudio/index.html">VisualStudio</A>
<A href="http://tech.ddvip.com/program/vc/interface/index.html">界面</A> <A
href="http://tech.ddvip.com/program/vc/view/index.html">视图</A> <A
href="http://tech.ddvip.com/program/vc/pic/index.html">图像</A> <A
href="http://tech.ddvip.com/program/vc/multimedia/index.html">多媒体</A> <A
href="http://tech.ddvip.com/program/vc/database/index.html">数据库</A> <A
href="http://tech.ddvip.com/program/vc/windows/index.html">Windows系统</A> <A
href="http://tech.ddvip.com/program/vc/file/index.html">文件</A> <A
href="http://tech.ddvip.com/program/vc/comnet/index.html">COM/.NET</A> <A
href="http://tech.ddvip.com/program/vc/network/index.html">网络通信</A> <A
href="http://tech.ddvip.com/program/vc/integrated/index.html">综合</A> <A
href="http://tech.ddvip.com/program/softjeer/index.html">软件工程</A> </DIV></DIV>
<DIV class=tlad id=tech_content_tl></DIV><!-- 内容部分 -->
<DIV class=content>
<DIV class=location><A href="http://www.ddvip.com/" target=_balnk>豆豆网</A> >
<A href="http://tech.ddvip.com/index.html">技术应用</A> > <A
href="http://tech.ddvip.com/program/index.html">程序设计</A> > <A
href="http://tech.ddvip.com/program/vc/index.html">VC</A> > <A
href="http://tech.ddvip.com/program/vc/network/index.html">VC网络通信编程</A> > 正文
</DIV>
<DIV class=content_left>
<DIV class=article>
<DIV class=article_title>
<H1>实时语音通信的实现</H1>
<DIV class=title_bottom>
<SCRIPT language=javascript
src="实时语音通信的实现 - 语音通信 - 技术应用 - 豆豆网.files/title_bottom.js"></SCRIPT>
</DIV>
<P><A href="http://www.ddvip.com/">豆豆网</A> <A
href="http://tech.ddvip.com/">技术应用频道</A> 2006年07月23日
<A href="http://club.ddvip.com/">社区交流</A></P></DIV>
<DIV class=article_content>
<DIV class=title_bottom_cpr>
<SCRIPT src="实时语音通信的实现 - 语音通信 - 技术应用 - 豆豆网.files/ad_title_bottom.js"></SCRIPT>
</DIV><!--ID="929"-->
<DIV class=searchhot>关键字: <A href="http://tech.ddvip.com/search.php?key=TCP"
target=_blank>TCP</A> <A href="http://tech.ddvip.com/search.php?key=模拟器"
target=_blank>模拟器</A> <A href="http://tech.ddvip.com/search.php?key=串口"
target=_blank>串口</A> <A href="http://tech.ddvip.com/search.php?key=Web应用程序"
target=_blank>Web应用程序</A> <A href="http://tech.ddvip.com/search.php?key=.NET"
target=_blank>.NET</A> <A href="http://tech.ddvip.com/search.php?key=串口编程"
target=_blank>串口编程</A> </DIV>
<DIV class=article_z>
<H2>本文详细介绍实时语音通信的实现 </H2></DIV>
<DIV class=article_ad>
<SCRIPT language=javascript src=""></SCRIPT>
</DIV>
<DIV class=article_c id=fontsize>
<P> <A
href="http://file.ddvip.com/2006_04/1144356458_ddvip_1009.rar">本文示例源代码或素材下载</A></P>
<P> 引言</P>
<P> 本人虽已学习VC++一年半载,仍觉捉襟见肘,好在有VCKBASE的帮忙,确实学到了不少东西,www.vckbase.com也成了我每次上民网必到之处(阁下有所不知,鄙人接受最为严格的管理,上民网是要申请的)。近日在做一个通信
方面的程序,实时的语音和视频通信当然是大家所喜欢的。本文将向您展示局域网环境下实时语音通信的的一个解决方案(视频这一块正在做,估计很快就能出炉),Winxp环境下测试效果良好,并且具有网络
拥塞处理机制,您不妨一看。</P>
<P> 本文以第26期 栾义明 先生的《基于API的录音机程序》为基础的,在此深表感谢。雷同之处将不再赘述,主要做了以下发展:
<LI>(1) 利用多线程机制,实现录音、网络传输、放音同时进行。
<LI>(2) 网络壅塞处理,保证数据不丢失。
<P> 例子程序运行画面:</P><IMG onclick=get_larger(this)
src="实时语音通信的实现 - 语音通信 - 技术应用 - 豆豆网.files/1153632373_ddvip_5739.jpg">
<P> 下面且看我细细道来:</P>
<P> (一)首先定义了一个声音数据“块”</P>
<P> <CODE>struct CAudioData<BR>{<BR> PBYTE lpdata;
//指向语音数据,注意这里内存区域是动态申请释放的<BR> DWORD
dwLength;//语音数据长度<BR>}</CODE>接下来申明两个循环队列和相关指针。</P>
<P> <CODE>//InBlocks,OutBlocks非别为两个常数<BR>CAudioData
m_AudioDataIn[InBlocks],m_AudioDataOut[OutBlocks];<BR>int nAudioIn, nSend,
//录入、发送指针<BR> nAudioOut, nReceive;//接收、播放指针</CODE>//
对于录音和放音都存在和网络的同步问题,主要靠这些指针进行协调</P>
<P> 讨论:如图所示,几个指针的相互追逐,这种机制在处理网络拥塞上应该有普遍的应用意义</P>
<P>
<TABLE cellSpacing=1 cellPadding=0>
<TBODY>
<TR>
<TD><IMG onclick=get_larger(this)
src="实时语音通信的实现 - 语音通信 - 技术应用 - 豆豆网.files/1153632383_ddvip_8387.jpg"></TD>
<TD><IMG onclick=get_larger(this)
src="实时语音通信的实现 - 语音通信 - 技术应用 - 豆豆网.files/1153632390_ddvip_5972.jpg"></TD></TR>
<TR>
<TD> </TD>
<TD> </TD></TR></TBODY></TABLE></P>
<LI>(1)正常网速下:nAudioIn 在 nSend 之前, nReceive 在 nAuioOu t之前,周而复始的走下去。
<LI>(2)超快网速下:发送端:-->nSend追上nAudioIn-->“空转”(绕了一圈又回来了)--〉
<P></P>
<P> 接收端:因为录、放音的采样频率设置为相等,故不可能出现 nReceive 在n AudioOut 之后,</P>
<P> 即收到的声音文件太多,来不及播放的现象。 </P>
<LI>(3)超慢网速下:(极端情况,网速几乎为0也没关系)
<P></P>
<P> 发送端:nAudioIn 绕一圈反追上 nSend,于是将数据接在当前块的尾部,以待发送</P>
<P> 接收端:nAudioOut 追上 nReceive 后,发现没有数据可播放了,就“空转”。
<P> 综合以上情况,相关实现如下:</P>
<P> (二)声音的录制与播放</P>
<P> (1)录音处理</P>
<P> <CODE>void CRecTestDlg::OnMM_WIM_DATA(UINT wParam,LONG
lParam)<BR>{<BR> int nextBlock = (nAudioIn+1)%
InBlocks; <BR> if(m_AudioDataIn[nextBlock].dwLength!=0)//下一“块”没发走<BR> { //把PWAVEHDR(即pBUfferi)里的数据接到当前“块”的末尾<BR>
m_AudioDataIn[nAudioIn].lpdata <BR> = (PBYTE)realloc
(m_AudioDataIn[nAudioIn].lpdata ,<BR> (((PWAVEHDR)
lParam)->dwBytesRecorded+m_AudioDataIn[nAudioIn].dwLength)) ;<BR> if
(m_AudioDataIn[nAudioIn].lpdata == NULL)<BR> {//...出错处理<BR> return
;<BR> }<BR> CopyMemory
((m_AudioDataIn[nAudioIn].lpdata+m_AudioDataIn[nAudioIn].dwLength),<BR>
((PWAVEHDR) lParam)->lpData,<BR> ((PWAVEHDR)
lParam)->dwBytesRecorded)
;//(*destination,*resource,nLen); <BR> m_AudioDataIn[nAudioIn].dwLength
+=((PWAVEHDR) lParam)->dwBytesRecorded; <BR> }<BR> else
//把PWAVEHDR(即pBUfferi)里的数据拷贝到下一“块”中<BR> {<BR> nAudioIn = (nAudioIn+1)%
InBlocks;<BR> m_AudioDataIn[nAudioIn].lpdata =
(PBYTE)realloc<BR> (0,((PWAVEHDR)
lParam)->dwBytesRecorded);<BR> CopyMemory(m_AudioDataIn[nAudioIn].lpdata,<BR> ((PWAVEHDR)
lParam)->lpData,<BR> ((PWAVEHDR) lParam)->dwBytesRecorded) ;<BR>
m_AudioDataIn[nAudioIn].dwLength =((PWAVEHDR)
lParam)->dwBytesRecorded;<BR> }<BR> // Send out a new
buffer <BR> waveInAddBuffer (hWaveIn, (PWAVEHDR) lParam, sizeof (WAVEHDR))
;<BR> return ; <BR>}</CODE>(2)放音处理</P>
<P> <CODE>void CRecTestDlg::OnMM_WOM_DONE(UINT wParam,LONG lParam)<BR>{
//释放播放完的缓冲区,并准备新的数据 <BR> free(m_AudioDataOut[nAudioOut].lpdata);<BR> m_AudioDataOut[nAudioOut].lpdata
=
reinterpret_cast<PBYTE>(malloc(1));<BR> m_AudioDataOut[nAudioOut].dwLength
= 0;<BR> nAudioOut=
(nAudioOut+1)%OutBlocks;<BR> ((PWAVEHDR)lParam)->lpData =
(LPTSTR)m_AudioDataOut[nAudioOut].lpdata
;<BR> ((PWAVEHDR)lParam)->dwBufferLength =
m_AudioDataOut[nAudioOut].dwLength ;<BR> waveOutPrepareHeader
(hWaveOut,(PWAVEHDR)lParam,sizeof(WAVEHDR));<BR>
waveOutWrite(hWaveOut,(PWAVEHDR)lParam,sizeof(WAVEHDR));<BR>
return;<BR>}</CODE>(三)套接字发送、接收线程</P>
<P> 其实,经过刚才的讨论,现在这两个线程的运作很简单---只是循环地操作nReceive和nSend指针。首先发送(接收)声音块的长度,然后发送(接收)声音内容。注意:拿CSocket::Send(buffer,count)为例,其返回值(发送出去的字结数)只是1到count之间的某值,所以要添加检测机制,否则将出现错误,这也是socket编程必须注意的。本文是用一个循环,直到发送出去的字节总数等于“块”的长度才发送第二个数据块的信息。</P>
<P> 例外这两个线程稍加改动即可实现多人的语音会议。 <CODE>UINT Audio_Listen_Thread(LPVOID
lParam)<BR>{<BR> CRecTestDlg *pdlg = (CRecTestDlg*)lParam;<BR> CSocket
m_Server;<BR> DWORD
length;<BR> if(!m_Server.Create(4002))<BR> AfxMessageBox("Listen Socket
create
error"+pdlg->GetError(GetLastError()));<BR> if(!m_Server.Listen())<BR> AfxMessageBox("m_server.Listen
ERROR"+pdlg->GetError(GetLastError()));<BR> CSocket recSo;<BR> if(!
m_Server.Accept(recSo))<BR> AfxMessageBox("m_server.Accept()
error"+pdlg->GetError(GetLastError()));<BR> m_Server.Close(); <BR> int ret
;<BR> while(1)<BR> { //开始循环接收声音文件,首先接收文件长度<BR> ret =
recSo.Receive(&length,sizeof(DWORD)); <BR> if(ret== SOCKET_ERROR
)<BR> AfxMessageBox("服务器端接收声音文件长度出错,原因:
"+pdlg->GetError(GetLastError()));<BR> if(ret!=sizeof(DWORD))<BR> {<BR> AfxMessageBox("接收文件头错误,将关闭该线程");<BR> recSo.Close();<BR> return
-1;<BR> }//接下来开辟length长的内存空间<BR> pdlg->m_AudioDataOut[pdlg->nReceive].lpdata
=(PBYTE)realloc (0,length);<BR> if
(pdlg->m_AudioDataOut[pdlg->nReceive].lpdata ==
NULL)<BR> {<BR> AfxMessageBox("erro
memory_ReceiveAudio");<BR> recSo.Close();<BR> return
-1;<BR> }<BR> else//内存申请成功,可以进行循环检测接受<BR> {<BR> DWORD dwReceived =
0,dwret;<BR> while(length>dwReceived)<BR> {<BR> dwret =
recSo.Receive((pdlg->m_AudioDataOut[pdlg->nReceive].lpdata+dwReceived),<BR> (length-dwReceived));<BR> dwReceived
+=dwret;<BR> if(dwReceived
==length)<BR> {<BR> pdlg->m_AudioDataOut[pdlg->nReceive].dwLength
=
length;<BR> break;<BR> }<BR> }<BR> }//本轮声音文件接收完毕 <BR> pdlg->nReceive=(pdlg->nReceive+1)%OutBlocks;<BR> }<BR> recSo.Close();<BR> return
0;<BR>}<BR>UINT Audio_Send_Thread(LPVOID
lParam)<BR>{ <BR> CRecTestDlg *pdlg =
(CRecTestDlg*)lParam;<BR> CSocket m_Client;<BR> m_Client.Create();<BR> if(
m_Client.Connect("127.0.0.1",4002))<BR> { <BR> DWORD ret,
length;<BR> int
count=0;<BR> while(1)//循环使用指针nSend<BR> {<BR> length
=pdlg->m_AudioDataIn[pdlg->nSend].dwLength; <BR> if(length
!=0)<BR> { //首先发送块的长度<BR> if(((ret =
m_Client.Send(&length,sizeof(DWORD)))<BR> !=
sizeof(DWORD))||(ret==SOCKET_ERROR))<BR> { <BR> AfxMessageBox("声音文件头传输错误!"+pdlg->GetError(GetLastError()));<BR> pdlg->OnOK();<BR> break; <BR> }//其次发送块的内容,循环检测是否发送完毕<BR> DWORD
dwSent =
0;//已经发送掉的字节数<BR> while(1)//==============================发送声音数据开始<BR> {<BR> ret
=
m_Client.Send((pdlg->m_AudioDataIn[pdlg->nSend].lpdata+dwSent),<BR>
(length-dwSent));<BR> if(ret==SOCKET_ERROR)//检错<BR> {<BR> AfxMessageBox("声音文件传输错误!"+pdlg->GetError(GetLastError()));<BR> break; <BR> }<BR> else
//发送未发送完的<BR> {<BR> dwSent += ret;<BR> if(dwSent
==length)//发送完毕,则释放当前“块”<BR> { <BR> free(pdlg->m_AudioDataIn[pdlg->nSend].lpdata);<BR> pdlg->m_AudioDataIn[pdlg->nSend].dwLength
=
0;<BR> break;<BR> }<BR> } <BR> } //======================================发送声音数据结束<BR> }<BR> pdlg->nSend
= (pdlg->nSend +1)%
InBlocks;<BR> }<BR> <BR> }<BR> else<BR> AfxMessageBox("Socket连接失败"+pdlg->GetError(GetLastError()));<BR> m_Client.Close();<BR> return
0;<BR>} </CODE>存在的问题 </P>
<LI>(1) 一旦添加声音控制waveSetGetVolume(),耳机就变成单声的,打开系统的音量控制,发现“波形”选项完全不平衡。
<LI>(2) 声音的录入运用双缓冲技术,使得无懈可击,但是在播放时,采用双缓冲调试时未能取得成功,相反使用单缓冲却基本上能够满足一般的音效。
<LI>(3) 可能还有尚未暴露的错误,恳请广大朋友不吝赐教。E-mail: candy0624@163.com
<P> Finally,Thank Candy Lee(my special friend) for her help.</P></LI></DIV><!-- 分页 --><!-- 分页end -->
<P
style="PADDING-RIGHT: 4px; FONT-SIZE: 12px; COLOR: #000000; TEXT-ALIGN: right">作者:孔康
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -