来源:个人博客
导读:
最近做了一个邮件项目,其中涉及语音的部分,在网上查了很多资料,把其中遇到的一些问题写下来。想必可以对后来人有所帮助。 在网上做语音邮件,不外乎这样的几件事情:录音、回放、压缩、编码传输。
录音是把语音通过录音设备进行 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;
}