zoukankan      html  css  js  c++  java
  • 实时语音通信的实现

    仍觉捉襟见肘,好在有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;//接收、播放指针
    // 对于录音和放音都存在和网络的同步问题,主要靠这些指针进行协调
    
    讨论:如图所示,几个指针的相互追逐,这种机制在处理网络拥塞上应该有普遍的应用意义
      
     
     
        
    
    (1)正常网速下:nAudioIn 在 nSend 之前, nReceive 在 nAuioOu t之前,周而复始的走下去。 
    (2)超快网速下:发送端:-->nSend追上nAudioIn-->“空转”(绕了一圈又回来了)--〉
    接收端:因为录、放音的采样频率设置为相等,故不可能出现 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,
           ((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<PBYTE>(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;
    }
    (三)套接字发送、接收线程
      其实,经过刚才的讨论,现在这两个线程的运作很简单---只是循环地操作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(&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;
    }
    
    
  • 相关阅读:
    leetCode 24. Swap Nodes in Pairs (双数交换节点) 解题思路和方法
    【jQuery 区别】.click()和$(document).on("click","指定的元素",function(){});的区别
    【前台 submit的重复提交 错误】submit的重复提交
    【spring 注解 错误】使用controller 作为后台给前台ajax交互数据出错
    【Filter 不登陆无法访问】web项目中写一个过滤器实现用户不登陆,直接给链接,无法进入页面的功能
    【Filter 页面重定向循环】写一个过滤器造成的页面重定向循环的问题
    【前台页面 BUG】回车按钮后,页面自动跳转
    【hibernate 执行方法未插入数据库】hibernate的save方法成功执行,但是未插入到数据库
    【maven 报错】maven项目执行maven install时报错Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode)
    【hibernate criteria】hibernate中criteria的完整用法 转
  • 原文地址:https://www.cnblogs.com/beeone/p/1812374.html
Copyright © 2011-2022 走看看