⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 实时语音通信的实现 - 语音通信 - 技术应用 - 豆豆网.htm

📁 可以实现实时语音通信
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<!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> &gt; 
<A href="http://tech.ddvip.com/index.html">技术应用</A> &gt; <A 
href="http://tech.ddvip.com/program/index.html">程序设计</A> &gt; <A 
href="http://tech.ddvip.com/program/vc/index.html">VC</A> &gt; <A 
href="http://tech.ddvip.com/program/vc/network/index.html">VC网络通信编程</A> &gt; 正文 
</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> &nbsp;&nbsp;<A 
href="http://tech.ddvip.com/">技术应用频道</A>&nbsp;&nbsp;&nbsp;2006年07月23日 
&nbsp;&nbsp;&nbsp;<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)超快网速下:发送端:--&gt;nSend追上nAudioIn--&gt;“空转”(绕了一圈又回来了)--〉
<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)-&gt;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)-&gt;lpData,<BR>          ((PWAVEHDR) 
lParam)-&gt;dwBytesRecorded) 
;//(*destination,*resource,nLen);  <BR>    m_AudioDataIn[nAudioIn].dwLength 
+=((PWAVEHDR) lParam)-&gt;dwBytesRecorded;    <BR>  }<BR>  else 
//把PWAVEHDR(即pBUfferi)里的数据拷贝到下一“块”中<BR>  {<BR>    nAudioIn = (nAudioIn+1)% 
InBlocks;<BR>    m_AudioDataIn[nAudioIn].lpdata = 
(PBYTE)realloc<BR>      (0,((PWAVEHDR) 
lParam)-&gt;dwBytesRecorded);<BR>    CopyMemory(m_AudioDataIn[nAudioIn].lpdata,<BR>        ((PWAVEHDR) 
lParam)-&gt;lpData,<BR>        ((PWAVEHDR) lParam)-&gt;dwBytesRecorded) ;<BR>    
m_AudioDataIn[nAudioIn].dwLength =((PWAVEHDR) 
lParam)-&gt;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&lt;PBYTE&gt;(malloc(1));<BR>  m_AudioDataOut[nAudioOut].dwLength 
= 0;<BR>    nAudioOut= 
(nAudioOut+1)%OutBlocks;<BR>  ((PWAVEHDR)lParam)-&gt;lpData     = 
(LPTSTR)m_AudioDataOut[nAudioOut].lpdata 
;<BR>  ((PWAVEHDR)lParam)-&gt;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-&gt;GetError(GetLastError()));<BR>  if(!m_Server.Listen())<BR>    AfxMessageBox("m_server.Listen 
ERROR"+pdlg-&gt;GetError(GetLastError()));<BR>  CSocket recSo;<BR>  if(! 
m_Server.Accept(recSo))<BR>    AfxMessageBox("m_server.Accept() 
error"+pdlg-&gt;GetError(GetLastError()));<BR>  m_Server.Close();  <BR>  int ret 
;<BR>  while(1)<BR>  {  //开始循环接收声音文件,首先接收文件长度<BR>    ret = 
recSo.Receive(&amp;length,sizeof(DWORD));    <BR>    if(ret== SOCKET_ERROR 
)<BR>      AfxMessageBox("服务器端接收声音文件长度出错,原因: 
"+pdlg-&gt;GetError(GetLastError()));<BR>    if(ret!=sizeof(DWORD))<BR>    {<BR>      AfxMessageBox("接收文件头错误,将关闭该线程");<BR>      recSo.Close();<BR>      return 
-1;<BR>    }//接下来开辟length长的内存空间<BR>    pdlg-&gt;m_AudioDataOut[pdlg-&gt;nReceive].lpdata 
=(PBYTE)realloc (0,length);<BR>    if 
(pdlg-&gt;m_AudioDataOut[pdlg-&gt;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&gt;dwReceived)<BR>      {<BR>        dwret = 
recSo.Receive((pdlg-&gt;m_AudioDataOut[pdlg-&gt;nReceive].lpdata+dwReceived),<BR>          (length-dwReceived));<BR>        dwReceived 
+=dwret;<BR>        if(dwReceived 
==length)<BR>        {<BR>          pdlg-&gt;m_AudioDataOut[pdlg-&gt;nReceive].dwLength 
= 
length;<BR>          break;<BR>        }<BR>      }<BR>    }//本轮声音文件接收完毕  <BR>    pdlg-&gt;nReceive=(pdlg-&gt;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-&gt;m_AudioDataIn[pdlg-&gt;nSend].dwLength;      <BR>      if(length 
!=0)<BR>      {  //首先发送块的长度<BR>        if(((ret = 
m_Client.Send(&amp;length,sizeof(DWORD)))<BR>           != 
sizeof(DWORD))||(ret==SOCKET_ERROR))<BR>        { <BR>          AfxMessageBox("声音文件头传输错误!"+pdlg-&gt;GetError(GetLastError()));<BR>          pdlg-&gt;OnOK();<BR>          break;  <BR>        }//其次发送块的内容,循环检测是否发送完毕<BR>        DWORD 
dwSent = 
0;//已经发送掉的字节数<BR>        while(1)//==============================发送声音数据开始<BR>        {<BR>          ret 
= 
m_Client.Send((pdlg-&gt;m_AudioDataIn[pdlg-&gt;nSend].lpdata+dwSent),<BR>                     
(length-dwSent));<BR>          if(ret==SOCKET_ERROR)//检错<BR>          {<BR>            AfxMessageBox("声音文件传输错误!"+pdlg-&gt;GetError(GetLastError()));<BR>            break;      <BR>          }<BR>          else 
//发送未发送完的<BR>          {<BR>            dwSent += ret;<BR>            if(dwSent 
==length)//发送完毕,则释放当前“块”<BR>            { <BR>              free(pdlg-&gt;m_AudioDataIn[pdlg-&gt;nSend].lpdata);<BR>              pdlg-&gt;m_AudioDataIn[pdlg-&gt;nSend].dwLength 
= 
0;<BR>              break;<BR>            }<BR>          }  <BR>        } //======================================发送声音数据结束<BR>      }<BR>      pdlg-&gt;nSend 
= (pdlg-&gt;nSend +1)% 
InBlocks;<BR>    }<BR>    <BR>  }<BR>  else<BR>    AfxMessageBox("Socket连接失败"+pdlg-&gt;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 + -