注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

面包会有的

php asp 高端网站开发 微信网站开发 视频会议软硬件

 
 
 

日志

 
 

语音邮件控件的实现  

2011-11-03 16:33:18|  分类: 音频采集 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
导读: 
  最近做了一个邮件项目,其中涉及语音的部分,在网上查了很多资料,把其中遇到的一些问题写下来。想必可以对后来人有所帮助。 在网上做语音邮件,不外乎这样的几件事情:录音、回放、压缩、编码传输。 
  录音是把语音通过录音设备进行 PCM 编码调制变成二进制数据放至内存,在录音停止后,应该可以通过回放来检查录音的效果。录音可以调用 WINDOWS API 来实现,windows 系统中自带的录音机就是通过这种方法的。 
   
  录音完的数据是否就可以直接用了呢? 当然不是,录音机一分钟通过 PCM 编码调制录下的数据大概有1M 左右,这个数据量对于带宽有限的网络传输是无法接受的,所以还必须对原有的 PCM 格式数据进行压缩,windows 系统中一般自带有许多种压缩算法,但有
一种叫做 DSP Group TrueSPeech(TM)的压缩算法的压缩比非常高(大概可以达到15:1左右,而且基本不失真,据说这是针对人的语音的特别压缩,它丢弃了一般人的声音不可以达到的频段的数据). 
   
  在进行压缩以后,是否这些二进制数据就可以用了?如果是语音聊天,通过 socket 的方式传输,应该是可以了,但是对于语音邮件,需要以网页(post 或者 get)的方式传输数据到服务器端去,还必须对这些二进制数据进行编码,最常用的方式当然是 base64编码,这一举两得,不仅使得编码后的数据可以直接传输,而且如果语音数据是作为附件的形式存放在服务器端的,则服务器端就省去了解码和重新编码的工作。 
   
  至此为止,整个语音邮件要做的事情就全部做完了,下面就具体来介绍一下主要实现代码。 
   
  一 录音部分 
   
  录音处理函数:StarRec 
   
  //分配录音缓存空间 
  pBuffer1=(PBYTE)malloc(INP_BUFFER_SIZE); 
  pBuffer2=(PBYTE)malloc(INP_BUFFER_SIZE); 
  if (!pBuffer1 || !pBuffer2) { 
  if (pBuffer1) free(pBuffer1); 
  if (pBuffer2) free(pBuffer2); 
  MessageBeep(MB_ICONEXCLAMATION); 
  MessageBox("Memory erro!"); 
  return ; 
  } 
   
  //设置录音的格式 
  waveform.wFormatTag=WAVE_FORMAT_PCM; 
  waveform.nChannels=1; 
  waveform.nSamplesPerSec= 8000; 
  waveform.wBitsPerSample= 16; 
  waveform.nBlockAlign= waveform.wBitsPerSample/8; 
  waveform.nAvgBytesPerSec= waveform.nSamplesPerSec*waveform.nBlockAlign; 
  waveform.cbSize=0; 
   
  //打开录音设备 
  if (waveInOpen(&hWaveIn,WAVE_MAPPER,&waveform,(DWORD)this->m_hWnd,NULL,CALLBACK_WINDOW)) { 
  free(pBuffer1); 
  free(pBuffer2); 
  MessageBeep(MB_ICONEXCLAMATION); 
  MessageBox("Audio can not be open!"); 
  } 
   
  //为录音设备准备缓存,最好准备两个缓存,否则在缓存满时来不及清除切换可能会导致录音失真 
  pWaveHdr1->lpData=(LPTSTR)pBuffer1; 
  pWaveHdr1->dwBufferLength=INP_BUFFER_SIZE; 
  pWaveHdr1->dwBytesRecorded=0; 
  pWaveHdr1->dwUser=0; 
  pWaveHdr1->dwFlags=0; 
  pWaveHdr1->dwLoops=1; 
  pWaveHdr1->lpNext=NULL; 
  pWaveHdr1->reserved=0; 
  waveInPrepareHeader(hWaveIn,pWaveHdr1,sizeof(WAVEHDR)); 
  pWaveHdr2->lpData=(LPTSTR)pBuffer2; 
  pWaveHdr2->dwBufferLength=INP_BUFFER_SIZE; 
  pWaveHdr2->dwBytesRecorded=0; 
  pWaveHdr2->dwUser=0; 
  pWaveHdr2->dwFlags=0; 
  pWaveHdr2->dwLoops=1; 
  pWaveHdr2->lpNext=NULL; 
  pWaveHdr2->reserved=0; 
  waveInPrepareHeader(hWaveIn,pWaveHdr2,sizeof(WAVEHDR)); 
   
  //为录音设备增加缓存 
  waveInAddBuffer (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ; 
  waveInAddBuffer (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ; 
   
  //开始录音 
  rState = RECORDING_STATE; 
  waveInStart (hWaveIn); 
   
  相关消息1:MM_WIM_DATA 
  工作:当缓存已满或者停止录音时的消息,处理这个消息可以对缓存进行重新分配,实现不限长度录音 
  消息处理函数: OnMM_WIM_DATA 
  // 重新分配缓存 
  pNewBuffer = (PBYTE)realloc (pSaveBuffer, dwDataLength + 
  ((PWAVEHDR) lParam)->dwBytesRecorded) ; 
   
  if (pNewBuffer == NULL) 
  { 
  waveInClose (hWaveIn) ; 
  MessageBeep (MB_ICONEXCLAMATION) ; 
  MessageBox("erro memory"); 
  return ; 
  } 
   
  pSaveBuffer = pNewBuffer ; 
   
  // 拷贝刚录制的缓存中的内容进入保存数据区,dwDataLength 为数据区中已有内容 
  CopyMemory (pSaveBuffer + dwDataLength, ((PWAVEHDR) lParam)->lpData, 
  ((PWAVEHDR) lParam)->dwBytesRecorded) ; 
   
  dwDataLength += ((PWAVEHDR) lParam)->dwBytesRecorded ; 
   
  if (stopRecByHand || timeOut) //stop record by yourselve 
  { 
  waveInClose (hWaveIn) ; 
  return ; 
  } 
  //重新加入新的缓存 
  waveInAddBuffer (hWaveIn, (PWAVEHDR) lParam, sizeof (WAVEHDR)) ; 
  return ; 
   
  相关消息2:MM_WIM_CLOSE 
  工作:调用 waveInReset 后停止录音,当手动停止录音或者最长录音时间已到则调用该函数 
  消息处理函数:OnMM_WIM_CLOSE 
  KillTimer(1); 
  if (0==dwDataLength) { 
  return; 
  } 
  waveInUnprepareHeader (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ; 
  waveInUnprepareHeader (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ; 
  free (pBuffer1) ; 
  free (pBuffer2) ; 
  rState= RECORD_PLAY_STOPED; 
  if (timeOut){ 
  char recordinfo[30]; 
  sprintf(recordinfo,"录音达到最长时限%d秒,已经终止!",m_maxTimeLength); 
  MessageBox(recordinfo); 
  }else{ 
  //MessageBox("录音结束!"); 
  } 
  return ; 
   
  二 回放部分 
  回放处理函数:StartPlay 
   
  if (rState==PLAYING_STATE || rState==RECORDING_STATE || rState==NO_RECORDING_STATE) { 
  return; 
  } 
  //设置回放的格式 
  waveform.wFormatTag=WAVE_FORMAT_PCM; 
  waveform.nChannels=1; 
  waveform.nSamplesPerSec= 8000; 
  waveform.wBitsPerSample= 16; 
  waveform.nBlockAlign= waveform.wBitsPerSample/8; 
  waveform.nAvgBytesPerSec= waveform.nSamplesPerSec*waveform.nBlockAlign; 
  waveform.cbSize=0; 
   
  //打开回放设备 
  if (waveOutOpen(&hWaveOut,WAVE_MAPPER,&waveform,(DWORD)this->m_hWnd,NULL,CALLBACK_WINDOW)) { 
  MessageBeep(MB_ICONEXCLAMATION); 
  MessageBox("Audio output erro"); 
  } 
  return ; 
   
  相关消息1:MM_WOM_OPEN 
  工作:准备并开始回放 
  消息处理函数:OnMM_WIM_CLOSE 
  //准备回放 
  pWaveHdr1->lpData = (LPTSTR)pSaveBuffer ; 
  pWaveHdr1->dwBufferLength = dwDataLength ; 
  pWaveHdr1->dwBytesRecorded = 0 ; 
  pWaveHdr1->dwUser = 0 ; 
  pWaveHdr1->dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP ; 
  pWaveHdr1->dwLoops = dwRepetitions ; 
  pWaveHdr1->lpNext = NULL ; 
  pWaveHdr1->reserved = 0 ; 
  waveOutPrepareHeader (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ; 
   
  //回放开始 
  waveOutWrite (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ; 
  rState = PLAYING_STATE; 
  二 压缩部分 
  压缩转换处理函数:Convert 
  //寻找DSPGROUP_TRUESPEECH 驱动 
  if (rState!=RECORD_PLAY_STOPED) return; 
  convertState = false; 
  findsuccess=0; 
  WORD wformattag = WAVE_FORMAT_DSPGROUP_TRUESPEECH; 
  HACMDRIVERID hadid = find_driver(wformattag); 
  if (!(findsuccess &&hadid)) { 
  MessageBox("找不到 DSPGROUP 驱动"); 
  return; 
  } 
   
  //打开 DISP 驱动 
  HACMDRIVER had = NULL; 
  MMRESULT mmr = acmDriverOpen(&had, hadid, 0); 
  if (mmr) 
  { 
  MessageBox("open driver error!"); 
  return; 
  } 
   
  //准备打开流 
  WAVEFORMATEX *pwfSrc = get_driver_format(hadid,WAVE_FORMAT_PCM); 
  WAVEFORMATEX *pwfDrv= get_driver_format(hadid,wformattag); 
  pwaveformdsp = get_driver_format(hadid,wformattag); 
   
  //根据源和目标的格式创建转换流 
  HACMSTREAM hstr = NULL; 
  mmr = acmStreamOpen(&hstr,had, pwfSrc, pwfDrv, NULL, NULL, 0, ACM_STREAMOPENF_NONREALTIME); 
  if (mmr) { 
  MessageBox("can't open"); 
  return; 
  } 
  //分配目标数据 
  DWORD dwDstBytes=pwfDrv->nAvgBytesPerSec*dwSrcSamples/pwfSrc->nSamplesPerSec; 
  dwDstSamples = dwSrcSamples; 
  dwDstBytes = dwDstBytes*3/2; 
  DWORD fileBytes = dwDstBytes+12+58+12+8; //fileBytes equal dwBstBytes add head size 
  pSaveConvertData = new BYTE[fileBytes]; 
  BYTE* pDstData = pSaveConvertData+12+58+12+8;//这是根据压缩格式算出来的 
  //创建转换流的头 
  ACMSTREAMHEADER shdr; 
  memset(&shdr, 0, sizeof(shdr)); 
  shdr.cbStruct = sizeof(shdr); 
  shdr.pbSrc = pSaveBuffer; //original data area 
  shdr.cbSrcLength = dwDataLength; 
  shdr.pbDst = pDstData; 
  shdr.cbDstLength = dwDstBytes; ////dst data area 
  //安装转换流的头 
  mmr = acmStreamPrepareHeader(hstr, &shdr, 0); 
  if (mmr) { 
  MessageBox("error create covert header!"); 
  return; 
  } 
  //转换 
  mmr = acmStreamConvert(hstr, &shdr, 0); 
  if (mmr) { 
  MessageBox("error convert!"); 
  return; 
  } 
   
  //卸载转换流头 
  mmr = acmStreamUnprepareHeader(hstr, &shdr, 0); 
  if (mmr) { 
  MessageBox("error unprepareheader!"); 
  return; 
  } 
   
  //关闭转换流 
  mmr = acmStreamClose(hstr,0); 
  if (mmr) { 
  MessageBox("error close"); 
  return; 
  } 
  //关闭 DISP 驱动 
  mmr = acmDriverClose(had, 0); 
  if (mmr) { 
  MessageBox("error close had!"); 
  return; 
  } 
   
  //创建 DISP 数据的 WAVE 格式数据 
  //convert datalength 
  convertDataLength = shdr.cbDstLengthUsed+12+58+12+8; 
  //write header 
  //RIFF head area 
  BYTE* pstr= pSaveConvertData; 
  DWORD dwNumber = FCC("RIFF"); 
  memcpy(pstr,&dwNumber,4); 
  pstr=pstr+4; 
  //RIFF size area 
  dwNumber = convertDataLength-8; //the filelength sub RIFF header and size 
  memcpy(pstr,&dwNumber,4); 
  pstr=pstr+4; 
  //RIFF TYPE area 
  dwNumber = FCC("WAVE"); 
  memcpy(pstr,&dwNumber,4); 
  pstr=pstr+4; 
  //fmt head area 
  dwNumber = FCC("fmt "); 
  memcpy(pstr, &dwNumber, 4); 
  pstr=pstr+4; 
  //fmt size area 
  dwNumber = 50L; 
  memcpy(pstr, &dwNumber,4); 
  pstr=pstr+4; 
  //fmt format 
  memcpy(pstr, pwaveformdsp, dwNumber); 
  pstr=pstr+dwNumber; 
  //fact head area 
  dwNumber = FCC("fact"); 
  memcpy(pstr, &dwNumber, 4); 
  pstr=pstr+4; 
  //fact data size 
  dwNumber = 4L; 
  memcpy(pstr, &dwNumber, 4); 
  pstr=pstr+4; 
  //fact data area -samples 
  dwNumber = dwDstSamples; 
  memcpy(pstr, &dwNumber, 4); 
  pstr=pstr+4; 
  //data head area 
  dwNumber = FCC("data"); 
  memcpy(pstr, &dwNumber, 4); 
  pstr=pstr+4; 
  //data length area 
  dwNumber = convertDataLength-90; 
  memcpy(pstr, &dwNumber, 4); 
  pstr=pstr+4; 
  convertState = true; 
  canGetData = true; 
  return; 
  三 编码部分 
  得到编码函数:GetData 
  if (rState!=RECORD_PLAY_STOPED || !convertState) return NULL; 
  if (canGetData) //转换成功了,可以直接编码返回数据 
  { 
  Coder.Encode(pSaveConvertData,convertDataLength); 
  LPCTSTR encodedmsg = (LPCTSTR)Coder.EncodedMessage();//对二进制数据进行 base64编码 
  BSTR base64msg=GetBSTR(encodedmsg);//非常重要,实现 char * to bstr 的转换 
  return base64msg; 
  } 
  Convert(); 
  if (convertState) 
  { 
  Coder.Encode(pSaveConvertData,convertDataLength); 
  LPCTSTR encodedmsg = (LPCTSTR)Coder.EncodedMessage();//对二进制数据进行 base64编码 
  BSTR base64msg=GetBSTR(encodedmsg);//非常重要,实现 char * to bstr 的转换 
  return base64msg; 
  }else 
  { 
  MessageBox("转换录音数据过程中发生错误!"); 
  return NULL; 
  }
  评论这张
 
阅读(952)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017