VC驿站

 找回密码
 加入驿站

QQ登录

只需一步,快速开始

搜索
查看: 22314|回复: 194

[交流] 实时语音通信的实现

  [复制链接]
51_avatar_middle
最佳答案
78 
online_admins 发表于 2011-5-11 00:03:40 | 显示全部楼层 |阅读模式
示例代码:
游客,如果您要查看本帖隐藏内容请回复


引言
  近日在做一个通信 方面的程序,实时的语音和视频通信当然是大家所喜欢的。本文将向您展示局域网环境下实时语音通信的的一个解决方案(视频这一块正在做,估计很快就能出炉),Winxp环境下测试效果良好,并且具有网络 拥塞处理机制,您不妨一看。
  本文以栾义明 先生的《基于API的录音机程序》为基础的(地址:https://www.cctry.com/thread-25145-1-1.html),在此深表感谢。雷同之处将不再赘述,主要做了以下发展:

(1) 利用多线程机制,实现录音、网络传输、放音同时进行。
(2) 网络壅塞处理,保证数据不丢失。

例子程序运行画面:
实时语音通信的实现

下面且看我细细道来:

(一)首先定义了一个声音数据“块”
  1. struct CAudioData
  2. {
  3.         PBYTE lpdata; //指向语音数据,注意这里内存区域是动态申请释放的
  4.         DWORD dwLength;//语音数据长度
  5. }
复制代码
接下来申明两个循环队列和相关指针。
  1. //InBlocks,OutBlocks非别为两个常数
  2. CAudioData m_AudioDataIn[InBlocks],m_AudioDataOut[OutBlocks];
  3. int   nAudioIn, nSend, //录入、发送指针
  4.      nAudioOut, nReceive;//接收、播放指针
复制代码
// 对于录音和放音都存在和网络的同步问题,主要靠这些指针进行协调

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

(1)正常网速下:nAudioIn 在 nSend 之前, nReceive 在 nAuioOu t之前,周而复始的走下去。
(2)超快网速下:发送端:-->nSend追上nAudioIn-->“空转”(绕了一圈又回来了)--〉
接收端:因为录、放音的采样频率设置为相等,故不可能出现 nReceive 在n AudioOut 之后,
即收到的声音文件太多,来不及播放的现象。
(3)超慢网速下:(极端情况,网速几乎为0也没关系)
发送端:nAudioIn 绕一圈反追上 nSend,于是将数据接在当前块的尾部,以待发送
接收端:nAudioOut 追上 nReceive 后,发现没有数据可播放了,就“空转”。
综合以上情况,相关实现如下:

(二)声音的录制与播放

(1)录音处理
  1. void CRecTestDlg::OnMM_WIM_DATA(UINT wParam,LONG lParam)
  2. {
  3.       int nextBlock = (nAudioIn+1)% InBlocks;       
  4.         if(m_AudioDataIn[nextBlock].dwLength!=0)//下一“块”没发走
  5.         {  //把PWAVEHDR(即pBUfferi)里的数据接到当前“块”的末尾
  6.            m_AudioDataIn[nAudioIn].lpdata  
  7.                 = (PBYTE)realloc (m_AudioDataIn[nAudioIn].lpdata ,
  8.                  (((PWAVEHDR) lParam)->dwBytesRecorded+m_AudioDataIn[nAudioIn].dwLength)) ;
  9.                 if (m_AudioDataIn[nAudioIn].lpdata == NULL)
  10.                 {//...出错处理
  11.                         return ;
  12.                 }
  13.                 CopyMemory ((m_AudioDataIn[nAudioIn].lpdata+m_AudioDataIn[nAudioIn].dwLength),
  14.                                    ((PWAVEHDR) lParam)->lpData,
  15.                                    ((PWAVEHDR) lParam)->dwBytesRecorded) ;//(*destination,*resource,nLen);       
  16.                 m_AudioDataIn[nAudioIn].dwLength +=((PWAVEHDR) lParam)->dwBytesRecorded;        
  17.         }
  18.         else //把PWAVEHDR(即pBUfferi)里的数据拷贝到下一“块”中
  19.         {
  20.                 nAudioIn = (nAudioIn+1)% InBlocks;
  21.                 m_AudioDataIn[nAudioIn].lpdata = (PBYTE)realloc
  22.                         (0,((PWAVEHDR) lParam)->dwBytesRecorded);
  23.                 CopyMemory(m_AudioDataIn[nAudioIn].lpdata,
  24.                             ((PWAVEHDR) lParam)->lpData,
  25.                                 ((PWAVEHDR) lParam)->dwBytesRecorded) ;
  26.            m_AudioDataIn[nAudioIn].dwLength =((PWAVEHDR) lParam)->dwBytesRecorded;

  27.         }
  28.         // Send out a new buffer       
  29.         waveInAddBuffer (hWaveIn, (PWAVEHDR) lParam, sizeof (WAVEHDR)) ;
  30.         return ;       
  31. }
复制代码
(2)放音处理
  1. void CRecTestDlg::OnMM_WOM_DONE(UINT wParam,LONG lParam)
  2. { //释放播放完的缓冲区,并准备新的数据  
  3.         free(m_AudioDataOut[nAudioOut].lpdata);
  4.         m_AudioDataOut[nAudioOut].lpdata = reinterpret_cast<PBYTE>(malloc(1));
  5.         m_AudioDataOut[nAudioOut].dwLength = 0;

  6.        nAudioOut= (nAudioOut+1)%OutBlocks;
  7.         ((PWAVEHDR)lParam)->lpData          = (LPTSTR)m_AudioDataOut[nAudioOut].lpdata ;
  8.         ((PWAVEHDR)lParam)->dwBufferLength  = m_AudioDataOut[nAudioOut].dwLength ;
  9.            waveOutPrepareHeader (hWaveOut,(PWAVEHDR)lParam,sizeof(WAVEHDR));
  10.        waveOutWrite(hWaveOut,(PWAVEHDR)lParam,sizeof(WAVEHDR));
  11.    return;
  12. }
复制代码
(三)套接字发送、接收线程
  其实,经过刚才的讨论,现在这两个线程的运作很简单---只是循环地操作nReceive和nSend指针。首先发送(接收)声音块的长度,然后发送(接收)声音内容。注意:拿CSocket::Send(buffer,count)为例,其返回值(发送出去的字结数)只是1到count之间的某值,所以要添加检测机制,否则将出现错误,这也是socket编程必须注意的。本文是用一个循环,直到发送出去的字节总数等于“块”的长度才发送第二个数据块的信息。
例外这两个线程稍加改动即可实现多人的语音会议。
  1. UINT Audio_Listen_Thread(LPVOID lParam)
  2. {
  3.         CRecTestDlg *pdlg = (CRecTestDlg*)lParam;
  4.         CSocket m_Server;
  5.         DWORD         length;
  6.         if(!m_Server.Create(4002))
  7.                 AfxMessageBox("Listen Socket create error"+pdlg->GetError(GetLastError()));
  8.         if(!m_Server.Listen())
  9.                 AfxMessageBox("m_server.Listen ERROR"+pdlg->GetError(GetLastError()));
  10.         CSocket recSo;
  11.         if(! m_Server.Accept(recSo))
  12.                 AfxMessageBox("m_server.Accept() error"+pdlg->GetError(GetLastError()));
  13.         m_Server.Close();       
  14.         int ret ;
  15.         while(1)
  16.         {   //开始循环接收声音文件,首先接收文件长度
  17.                 ret = recSo.Receive(&length,sizeof(DWORD));               
  18.                 if(ret== SOCKET_ERROR )
  19.                         AfxMessageBox("服务器端接收声音文件长度出错,原因: "+pdlg->GetError(GetLastError()));
  20.                 if(ret!=sizeof(DWORD))
  21.                 {
  22.                         AfxMessageBox("接收文件头错误,将关闭该线程");
  23.                         recSo.Close();
  24.                         return -1;
  25.                 }//接下来开辟length长的内存空间
  26.                 pdlg->m_AudioDataOut[pdlg->nReceive].lpdata =(PBYTE)realloc (0,length);
  27.                 if (pdlg->m_AudioDataOut[pdlg->nReceive].lpdata == NULL)
  28.                 {
  29.                         AfxMessageBox("erro memory_ReceiveAudio");
  30.                         recSo.Close();
  31.                         return -1;
  32.                 }
  33.                 else//内存申请成功,可以进行循环检测接受
  34.                 {
  35.                         DWORD dwReceived = 0,dwret;
  36.                         while(length>dwReceived)
  37.                         {
  38.                                 dwret = recSo.Receive((pdlg->m_AudioDataOut[pdlg->nReceive].lpdata+dwReceived),
  39.                                         (length-dwReceived));
  40.                                 dwReceived +=dwret;
  41.                                 if(dwReceived ==length)
  42.                                 {
  43.                                         pdlg->m_AudioDataOut[pdlg->nReceive].dwLength = length;
  44.                                         break;
  45.                                 }
  46.                         }
  47.                 }//本轮声音文件接收完毕       
  48.                 pdlg->nReceive=(pdlg->nReceive+1)%OutBlocks;
  49.         }
  50.         recSo.Close();
  51.         return 0;
  52. }

  53. UINT Audio_Send_Thread(LPVOID lParam)
  54. {                                    
  55.         CRecTestDlg *pdlg = (CRecTestDlg*)lParam;
  56.         CSocket m_Client;
  57.         m_Client.Create();
  58.         if( m_Client.Connect("127.0.0.1",4002))
  59.         {               
  60.                 DWORD ret, length;
  61.                 int count=0;
  62.                 while(1)//循环使用指针nSend
  63.                 {
  64.                         length =pdlg->m_AudioDataIn[pdlg->nSend].dwLength;                       
  65.                         if(length !=0)
  66.                         {   //首先发送块的长度
  67.                                 if(((ret = m_Client.Send(&length,sizeof(DWORD)))
  68.                                      != sizeof(DWORD))||(ret==SOCKET_ERROR))
  69.                                 {   
  70.                                         AfxMessageBox("声音文件头传输错误!"+pdlg->GetError(GetLastError()));
  71.                                         pdlg->OnOK();
  72.                                         break;       
  73.                                 }//其次发送块的内容,循环检测是否发送完毕
  74.                                 DWORD dwSent = 0;//已经发送掉的字节数
  75.                                 while(1)//==============================发送声音数据开始
  76.                                 {
  77.                                         ret = m_Client.Send((pdlg->m_AudioDataIn[pdlg->nSend].lpdata+dwSent),
  78.                                                              (length-dwSent));
  79.                                         if(ret==SOCKET_ERROR)//检错
  80.                                         {
  81.                                                 AfxMessageBox("声音文件传输错误!"+pdlg->GetError(GetLastError()));
  82.                                                 break;                       
  83.                                         }
  84.                                         else //发送未发送完的
  85.                                         {
  86.                                                 dwSent += ret;
  87.                                                 if(dwSent ==length)//发送完毕,则释放当前“块”
  88.                                                 {   
  89.                                                         free(pdlg->m_AudioDataIn[pdlg->nSend].lpdata);
  90.                                                         pdlg->m_AudioDataIn[pdlg->nSend].dwLength = 0;
  91.                                                         break;
  92.                                                 }
  93.                                         }       
  94.                                 }  //======================================发送声音数据结束
  95.                         }
  96.                         pdlg->nSend = (pdlg->nSend +1)% InBlocks;
  97.                 }
  98.                
  99.         }
  100.         else
  101.                 AfxMessageBox("Socket连接失败"+pdlg->GetError(GetLastError()));
  102.         m_Client.Close();
  103.         return 0;
  104. }
复制代码
存在的问题:
(1) 一旦添加声音控制waveSetGetVolume(),耳机就变成单声的,打开系统的音量控制,发现“波形”选项完全不平衡。
(2) 声音的录入运用双缓冲技术,使得无懈可击,但是在播放时,采用双缓冲调试时未能取得成功,相反使用单缓冲却基本上能够满足一般的音效。
(3) 可能还有尚未暴露的错误,恳请广大朋友不吝赐教。E-mail: candy0624@163.com  




上一篇:基于API的录音机程序
下一篇:VC6.0局域网五子棋游戏
05_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-11 00:11:22 | 显示全部楼层
好东东啊,标记一个
32_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-11 00:11:49 | 显示全部楼层
哇!!支持!
抢个沙发!!!!!实时语音通信的实现
94_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-11 09:15:59 | 显示全部楼层
这个肯定要学习下。Mask
谢谢老大。实时语音通信的实现
07_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-11 09:18:25 | 显示全部楼层
好东西,支持一下实时语音通信的实现
75_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-11 09:19:13 | 显示全部楼层
好东西啊,感谢楼主~
12_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-11 09:24:39 | 显示全部楼层
够丰富 详细了啊
72_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-11 09:30:31 | 显示全部楼层
谢谢老大分享!·
89_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-11 10:55:53 | 显示全部楼层
yyyyyyyy我想看看
98_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-11 11:59:09 | 显示全部楼层
代码可以上传吗
86_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-12 09:52:19 | 显示全部楼层
谢谢老大分享!·
53_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-13 10:21:07 | 显示全部楼层
很好 很强大 下来试试
98_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-14 11:28:29 | 显示全部楼层
看看。。。
95_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-14 12:46:13 | 显示全部楼层
老大的东西就是学不完
辛苦了
83_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-15 15:49:36 | 显示全部楼层
好东西 标记一下 正想做这个东东
61_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-15 15:54:32 | 显示全部楼层
好东西,学习学习!
82_avatar_middle
最佳答案
0 
online_vip 发表于 2011-5-16 08:32:14 | 显示全部楼层
老大又有新的东西了啊,哈哈
56_avatar_middle
最佳答案
0 
在线会员 发表于 2011-5-16 08:53:58 | 显示全部楼层
老大的好东西,收藏。。
18_avatar_middle
最佳答案
0 
online_vip 发表于 2011-5-16 09:08:11 | 显示全部楼层
老大的东西,呵呵
12_avatar_middle
最佳答案
0 
online_moderator 发表于 2011-5-16 09:31:39 | 显示全部楼层
实时语音通信的实现实时语音通信的实现实时语音通信的实现实时语音通信的实现
您需要登录后才可以回帖 登录 | 加入驿站 qq_login

本版积分规则

×【发帖 友情提示】
1、请回复有意义的内容,请勿恶意灌水;
2、纯数字、字母、表情等无意义的内容系统将自动删除;
3、若正常回复后帖子被自动删除,为系统误删的情况,请重新回复其他正常内容或等待管理员审核通过后会自动发布;
4、感谢您对VC驿站一如既往的支持,谢谢合作!

关闭

站长提醒上一条 /2 下一条

QQ|小黑屋|手机版|VC驿站 ( 辽ICP备09019393号 )|网站地图wx_jqr

GMT+8, 2020-7-3 05:10

Powered by CcTry.CoM

© 2009-2020 cctry.com

快速回复 返回顶部 返回列表