语音邮件控件的实现

导读:
  最近做了一个邮件项目,其中涉及语音的部分,在网上查了很多资料,把其中遇到的一些问题写下来。想必可以对后来人有所帮助。 在网上做语音邮件,不外乎这样的几件事情:录音、回放、压缩、编码传输。
  录音是把语音通过录音设备进行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;
  }
  
  如果您是学习之用,有充裕的时间,参考上面的代码再结合MSDN,相信您一定可以写出更灵活的代码。
  如果你时间较紧,需要现成的语音邮件控件,不妨联系我,只要花费20元便可以得到。
  如果你需要完整的源代码和参考资料,也可以联系我,只要50元左右。
  如果你需要定制语音控件之类的东西,同样可以联系我,价格面议。
  所有的这些都是一些成本费,毕竟也是花了很多时间开发的,也相信一定可以给你的开发节省许多宝贵的时间。
  
  联系电话 13852946750 或者QQ联系 15236281
 
本文转自
http://blog.csdn.net/cz_hyf/archive/2006/08/27/1127419.aspx
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值