EVC录音详解
//=====================================================================================================
//TITLE:
// EVC录音详解
//AUTHOR:
// norains
//DATE:
// Friday 9-June-2006
//=====================================================================================================
借助evc在wince下实现录音不是一件难事.恩,的确不是一件难事.本文主要解释如何使用wavein,并且把声音以wave文件形式保存到储存器中.
最先,我们要分配两个缓冲区.因为数据首先要保存到内存中,两个内存缓存区间可以较快进行切换,可以避免录音有断断续续的现象.
#define INP_BUFFER_SIZE 16*1024 //输入的缓冲区长度
PBYTE pBuffer1,pBuffer2; //保存输入数据的两个缓冲区
pBuffer1=(PBYTE)malloc(INP_BUFFER_SIZE);
pBuffer2=(PBYTE)malloc(INP_BUFFER_SIZE);
if (!pBuffer1 || !pBuffer2)
{
if (pBuffer1) free(pBuffer1);
if (pBuffer2) free(pBuffer2);
AfxMessageBox(L"Memory erro!");
return ;
}
接下来需要设置录音的方式,需要用到WAVEFORMATEX结构.声道数,采样位和采样率都可以在这结构中设置.
WAVEFORMATEX waveform;
waveform.wFormatTag=WAVE_FORMAT_PCM; //录音的格式
waveform.cbSize=0; //方式为WAVE_FORMAT_PCM时此参数可以忽略
waveform.nChannels=1; //声道数,数值可为1或2
waveform.nSamplesPerSec=11025; //采样率,数值有:11025,22050,44100
waveform.wBitsPerSample=8; //采样位,数值有:8,16
waveform.nBlockAlign=waveform.nChannels * waveform.wBitsPerSample / 8;
waveform.nAvgBytesPerSec=waveform.nBlockAlign * waveform.nSamplesPerSec;
设置完毕之后,就可以用waveInOpen函数打开输入设备.
HWAVEIN hWaveIn; //输入设备句柄
if (waveInOpen(&hWaveIn,WAVE_MAPPER,&waveform,(DWORD)this->m_hWnd,NULL,CALLBACK_WINDOW))
{
free(pBuffer1);
free(pBuffer2);
AfxMessageBox(L"无法打开录音设备");
return;
}
设备可以打开后,就需要初始化两个输入缓存区的声音文件头了.声音文件头主要是在录音时,记录相关的数据,以方便后期的处理.
PWAVEHDR pWaveHdr1,pWaveHdr2;
pWaveHdr1->lpData=(LPSTR)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)); //将缓冲区信息和输入设备相关联
waveInAddBuffer (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ; //将缓冲区地址和输入设备相关联
在对PWAVEHDR进行赋值时,本程序中需要设置的其实只有lpData和dwBufferLength.接下来将pWaveHdr2同pWaveHdr1进行相关处理(略).
由于我们是要将录音数据以文件形式保存到非易失性存储器上,所以在开始录音之前我们需要先建立文件,并且把相关的文件头信息写入(WriteWaveFileHeader是自写函数,代码附在文章最后).
//先写文件头
MMRESULT mr;
mr=WriteWaveFileHeader(strSavePath,&waveform,0,TRUE);
if(mr != MMSYSERR_NOERROR)
{
AfxMessageBox(L"文件保存失败!");
//停止录音,关闭设备
waveInReset(hWaveIn);
return;
}
//获取文件句柄,方便之后对其添加数据.
m_fh = CreateFile(strSavePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if( m_fh == INVALID_HANDLE_VALUE )
{
AfxMessageBox(L"添加数据音频数据错误");
return ;
}
一切准备就绪之后,就可以调用函数waveInStart()来进行真正的录音了:
waveInStart(hWaveIn);
在录音过程中,有三个回调函数系统会自动调用,分别是:OnMM_WIM_OPEN(),OnMM_WIM_DATA()和OnMM_WIM_CLOSE().顾名思义,这三个函数分别在这三种情况下调用:开始录音时;缓冲区用完时;录音关闭时.其中OnMM_WIM_OPEN()和OnMM_WIM_CLOSE()只调用一次.本程序最重要是对OnMM_WIM_DATA()函数进行处理.
相关代码如下:
void OnMM_WIM_DATA(UINT wParam, LONG lParam)
{
//bEnding是一个外部定义的BOOL变量,用来判断外部是否按下"停止"按钮;是则不分配内存,直接返回.
if (bEnding)
{
//关闭录音
waveInClose (hWaveIn) ;
return ;
}
//dwDataLength是一个外部定义的DWORD变量,用来记录录音数据的长度.
dwDataLength += ((PWAVEHDR) lParam)->dwBytesRecorded ;
//将内存数据写到文件中
//pSaveBuffer是外部定义的一个临时缓存
pSaveBuffer=(PBYTE)realloc (pSaveBuffer, ((PWAVEHDR) lParam)->dwBytesRecorded);
CopyMemory (pSaveBuffer, ((PWAVEHDR) lParam)->lpData,((PWAVEHDR) lParam)->dwBytesRecorded) ;
m_bAddSuc=AddWaveFileDate(m_fh,pSaveBuffer,((PWAVEHDR) lParam)->dwBytesRecorded);
if(m_bAddSuc==FALSE)
{
//加入不成功
waveInClose (hWaveIn) ;
return ;
}
//加入新的内存
waveInAddBuffer (hWaveIn, (PWAVEHDR) lParam, sizeof (WAVEHDR)) ;
}
录音完毕则调用OnMM_WIM_CLOSE(),我们在此函数体里进行相关的收尾清除工作
void CRecordDlg::OnMM_WIM_CLOSE(UINT wParam, LONG lParam)
{
//关闭文件句柄
CloseHandle(m_fh);
if (0==dwDataLength)
{
//长度为0,可能录音失败
return;
}
//重写一次文件头,将文件长度写入文件中
MMRESULT mr;
mr=WriteWaveFileHeader(strSavePath,&waveform,dwDataLength,FALSE);
if(mr != MMSYSERR_NOERROR)
{
AfxMessageBox(L"重写文件头失败!");
return;
}
waveInUnprepareHeader (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ;
waveInUnprepareHeader (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ;
free (pBuffer1) ;
free (pBuffer2) ;
}
至此,整个录音程序结束.
附录:相关文件函数
//******************************************************************************************
//写wav文件头
//---------------------------------------------------------------
//pszFilename:保存的路径
//pWFX:保存的格式信息
//dwBufferSize:保存WAV的长度
//bCover:创建文件时如果原文件存在,是否截断(在此函数意义是:是新建文件写文件头,还是改写文件头)
//*******************************************************************************************
//----------------------------------------------------------------
MMRESULT CRecordDlg::WriteWaveFileHeader(LPCTSTR pszFilename, PWAVEFORMATEX pWFX, DWORD dwBufferSize,BOOL bCover)
{
RIFF_FILEHEADER FileHeader;
RIFF_CHUNKHEADER WaveHeader;
RIFF_CHUNKHEADER DataHeader;
DWORD dwBytesWritten;
HANDLE fh;
MMRESULT mmRet = MMSYSERR_ERROR;
// Fill in the file, wave and data headers
WaveHeader.dwCKID = RIFF_FORMAT;
WaveHeader.dwSize = sizeof(WAVEFORMATEX) + pWFX->cbSize;
// the DataHeader chunk contains the audio data
DataHeader.dwCKID = RIFF_CHANNEL;
DataHeader.dwSize = dwBufferSize;
// The FileHeader
FileHeader.dwRiff = RIFF_FILE;
FileHeader.dwSize = sizeof(WaveHeader) + WaveHeader.dwSize + sizeof(DataHeader) + DataHeader.dwSize;
FileHeader.dwWave = RIFF_WAVE;
//-------------------------------------------------
//追踪一下
DWORD i=sizeof(WaveHeader);
i=sizeof(WaveHeader) + WaveHeader.dwSize;
i=sizeof(WaveHeader) + WaveHeader.dwSize + sizeof(DataHeader);
i=sizeof(WaveHeader) + WaveHeader.dwSize + sizeof(DataHeader) + DataHeader.dwSize;
//--------------------------------------------------
// Open wave file
if(bCover==TRUE)
{
//如果原文件已存在,则把原文件截断(覆盖)
fh = CreateFile(pszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
}
else
{
//打开已存在的原文件
fh = CreateFile(pszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
//将文件指针移到文件头
SetFilePointer(fh,0,NULL,FILE_BEGIN);
}
if( fh == INVALID_HANDLE_VALUE ) {
RETAILMSG(1, (TEXT("Error opening %s. Error code = 0x%08x/n"), pszFilename, GetLastError()));
//AfxMessageBox(L"error open file in writing header");
return mmRet;
}
// write the riff file
if (! WriteFile(fh, &FileHeader, sizeof(FileHeader), &dwBytesWritten, NULL)) {
RETAILMSG(1, (TEXT("Error writing file header/r/n")));
//AfxMessageBox(L"erro writing file header");
goto ERROR_EXIT;
}
// write the wave header
if (! WriteFile(fh, &WaveHeader, sizeof(WaveHeader), &dwBytesWritten, NULL)) {
RETAILMSG(1, (TEXT("Error writing wave header/r/n")));
//AfxMessageBox(L"erroer writing wave header");
goto ERROR_EXIT;
}
// write the wave format
if (! WriteFile(fh, pWFX, WaveHeader.dwSize, &dwBytesWritten, NULL)) {
RETAILMSG(1, (TEXT("Error writing wave format/r/n")));
//AfxMessageBox(L"error writing wave formate");
goto ERROR_EXIT;
}
// write the data header
if (! WriteFile(fh, &DataHeader, sizeof(DataHeader), &dwBytesWritten, NULL)) {
RETAILMSG(1, (TEXT("Error writing PCM data header/r/n")));
//AfxMessageBox(L"error wrting pcm data header");
goto ERROR_EXIT;
}
/*-----------------------------------------------------------------------------
//此函数只是为了写文件头,不写录音数据
// write the PCM data
if (! WriteFile(fh, pBufferBits, DataHeader.dwSize, &dwBytesWritten, NULL)) {
RETAILMSG(1, (TEXT("Error writing PCM data/r/n")));
AfxMessageBox(L"error wrting pcm data");
goto ERROR_EXIT;
}
---------------------------------------------------------------------------------*/
// Success
mmRet = MMSYSERR_NOERROR;
ERROR_EXIT:
CloseHandle(fh);
return mmRet;
}
//****************************************************************************************
//将wav的数据加到现存的一个文件中
//-----------------------------------------------------------------
//LPCTSTR pszFilename 要追加的文件名
//PBYTE pBufferBits 要写入的数据
//DWORD dwBufferSize 要写入数据的长度
//-----------------------------------------------------------------
//*****************************************************************************************
BOOL CRecordDlg::AddWaveFileDate(HANDLE fh, PBYTE pBufferBits, DWORD dwBufferSize)
{
//HANDLE fh;
DWORD dwBytesWritten;
/*
// Open the existing wave file
fh = CreateFile(pszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if( fh == INVALID_HANDLE_VALUE )
{
RETAILMSG(1, (TEXT("Error opening %s. Error code = 0x%08x/n"), pszFilename, GetLastError()));
AfxMessageBox(L"error open file in the adding");
return FALSE;
}
//将文件指针移到文件尾
SetFilePointer(fh,0,NULL,FILE_END);
*/
// write the PCM data
if (! WriteFile(fh, pBufferBits, dwBufferSize, &dwBytesWritten, NULL)) {
RETAILMSG(1, (TEXT("Error writing PCM data/r/n")));
//AfxMessageBox(L"当前存储器已满");
goto ERROR_EXIT;
}
//CloseHandle(fh);
return TRUE;
ERROR_EXIT:
//CloseHandle(fh);
return FALSE;
}