CSoundBase实现录音与播放

//==========================================
//TITLE:
//    CSoundBase实现录音与播放
//AUTHOR:
//    norains
//DATE:
//    Wednesday  10-January -2007
//Environment:
//  EVC4.0 + Standard SDK
//==========================================

1.简介
 
  CSoundBase是我封装的一个API类,主要是为了能方便实现声音的录制和播放.目前仅支持WAV的录制和播放.
  完整的代码见本文第四节.
  如果各位朋友发现有BUG需要修正,欢迎和我联系,谢谢!
 
 
2.使用方法
 
  CSoundBase类的使用非常简单,首先声明一个类指针,然后获取类的实例:

CSoundBase *pSoundPlayer; pSoundPlayer->CSoundBase::GetInstance();

假如我们需要在"record"文件夹中录制一个名字为"first"的文件,只需很简单的一条语句:
pSoundPlayer->Record(TEXT("record/first.wav"));

不过这样是采用默认的录制格式,实际录音中我们可以进行更改.不过在进行这一步之前,我们先看一下这个类:
typedef struct { ChannelType channel; SamplesPerSecType samples; BitsPerSampleType bits; }WAVEFORMAT_SETTING,*PWAVEFORMAT_SETTING; 

我们来看看这个类成员代表的意义:
  channel
   声道数,其取值可以为:CHANNEL_SINGLE(单声道),CHANNEL_DOUBLE(双声道).
  samples
   采样率,其取值可以为SAMPLES_11025,SAMPLES_22050,SAMPLES_44100
  bits
   采样位,其取值为 BITS_8,BITS_16
  
  如果我们录制的一个文件有如下要求:单声道,采样率为22050MHZ,采样位为16位,代码非常简单,如下:

WAVEFORMAT_SETTING waveFormat; waveFormat.channel = CHANNEL_SINGLE; waveFormat.samples = SAMPLES_22050; waveFormat.bits = SAMPLES_44100; pSoundPlayer->Record(TEXT("record/first.wav"),&waveFormat); 

当想停止录音时,可调用StopRecording():

pSoundPlayer->StopRecording(); 

 如果想播放刚刚的录制的wav文件,只需简单调用Play()即可: 

pSoundPlayer->Play(); 

 当然,也可以播放储存器上任意一处的wav文件,如:

pSoundPlayer->Play(TEXT("record/second.wav")); 

停止播放同样也很简单:  

pSoundPlayer->StopPlaying(); 

由于录音和播放是分别占用不同的缓冲区,所以我们可以边播放边录音,当然了,前提是录音和播放的不能是同一个文件:

pSoundPlayer->Record(TEXT("record/first.wav")); pSoundPlayer->Play(TEXT("record/second.wav")); 

这时候我们可以调用StopAll()函数同时停止录音和播放: 

pSoundPlayer->StopAll();

3.CSoundBase的实现细节 

 3.1 waveInUnprepareHeader()的死锁
  有的追求完美的朋友可能会觉得,为什么waveInUnprepareHeader()不放在WIM_CLOSE消息的响应函数OnWIM_DATA()中.恩,这个方法我曾经想过,也曾经尝试过,不过很可惜失败了.因为在回调函数中调用waveInUnprepareHeader()会导致死锁,从而使程序崩溃.所以在处理WIM_DATA消息时,很巧妙地没有调用waveInUnprepareHeader()来卸载,而是直接把已经录制完毕并且其数据已经写到文件中的内存作为新录制缓冲区添加:waveInAddBuffer (m_hWaveIn, (PWAVEHDR) wParam, sizeof (WAVEHDR)).这样即可避免了死锁,又减少了分配内存的花销,可谓一箭双雕吧.
 
 3.2 WIM_DATA消息的响应
  细心的朋友可能会发现,在StopRecording()函数里调用waveInClose()之前还调用了waveInReset().因为根据文档,如果在调用waveInClose()之前调用waveInAddBuffer()添加的内存没有返回释放,则waveInClose()将调用失败,从另一个角度来说,此时系统将不会回调WIM_CLOSE消息.故在waveInClose()函数之前调用waveInReset()来释放之前映射的内存.
  不过这时候会有一个小问题,就是调用waveInReset()时系统会发送WIM_DATA消息.所以我们在WIM_DATA消息的响应函数中需要做个小小的判断,就是在响应调用waveInReset()而返回的WIM_DATA消息时,我们不再添加录音缓存区.
  在代码中表现如下:

  if(m_bRecording == TRUE) { waveInAddBuffer (m_hWaveIn, (PWAVEHDR) wParam, sizeof (WAVEHDR)) ; }

 3.3 更多实现细节
  由于代码的总体思路在我的另一篇文章中已经详细说明,在此略为不表.
  有兴趣的朋友可参考《EVC录音详解》一文:http://blog.csdn.net/norains/archive/2006/06/13/795777.aspx
4.CSoundBase 完整源代码  
 
/**/////////////////////////////////////////////////////////////////////// // SoundBase.h: interface for the CSoundBase class. // /**/////////////////////////////////////////////////////////////////////// //AUTHOR: // // norains // //VERSION: // // 1.0.0 // //DATE: // // Wednesday 10-January -2007 // //Environment: // // EVC4.0 + Standard SDK // /**/////////////////////////////////////////////////////////////////////// #ifndef SOUNDBASE_H #define SOUNDBASE_H #include "mmsystem.h" //------------------------------------------------------------------------------ //Macro define #define MAX_SAVEPATH_LENGTH 500 //The length of saved path //------------------------------------------------------------------------------ //Value type enum ChannelType { CHANNEL_SINGLE, CHANNEL_DOUBLE }; enum SamplesPerSecType { SAMPLES_11025, SAMPLES_22050, SAMPLES_44100 }; enum BitsPerSampleType { BITS_8, BITS_16 }; //--------------------------------------------------------------------------- //Struct //Wave format data typedef struct { ChannelType channel; SamplesPerSecType samples; BitsPerSampleType bits; }WAVEFORMAT_SETTING,*PWAVEFORMAT_SETTING; //------------------------------------------------------------------------------ class CSoundBase { public: void StopPlaying(); void StopRecording(); void StopAll(); static CSoundBase* GetInstance(); BOOL Play(const TCHAR *pszPath = NULL); BOOL Record(TCHAR *pszPath,const PWAVEFORMAT_SETTING pWaveFormat = NULL); virtual ~CSoundBase(); protected: void OnWIM_OPEN(WPARAM wParam, LPARAM lParam); void OnWIM_DATA(WPARAM wParam, LPARAM lParam); void OnWIM_CLOSE(WPARAM wParam, LPARAM lParam); BOOL WriteWaveFileHeader(TCHAR *pszFilename, const PWAVEFORMATEX pWFX, DWORD dwBufferSize,BOOL bCover); static void CALLBACK WaveInProc(HWAVEIN hWi,UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); CSoundBase(); void SetRecordWaveFormat(const PWAVEFORMAT_SETTING pWaveFormat); static CSoundBase *m_pInstance; WAVEFORMATEX m_WaveFormatEx; BOOL m_bRecording; HANDLE m_hSaveFile; HWAVEIN m_hWaveIn; PBYTE m_pBuffer1; PBYTE m_pBuffer2; PWAVEHDR m_pWaveHdr1; PWAVEHDR m_pWaveHdr2; DWORD m_dwDataLength; //The length of the data TCHAR m_szSavePath[MAX_SAVEPATH_LENGTH]; //The path to save }; #endif // SOUNDBASE_H 

///////////////////////////////////////////////////////////////////// // SoundBase.cpp: implementation of the CSoundBase class. // /**////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "SoundBase.h" //------------------------------------------------------------------------------ //Macro define #define INP_BUFFER_SIZE 16*1024 //The input buffer size #define RIFF_FILE mmioFOURCC('R','I','F','F') #define RIFF_WAVE mmioFOURCC('W','A','V','E') #define RIFF_FORMAT mmioFOURCC('f','m','t',' ') #define RIFF_CHANNEL mmioFOURCC('d','a','t','a') //Default values #define DEFAULT_CHANNEL CHANNEL_SINGLE #define DEFAULT_SAMPLES SAMPLES_11025 #define DEFAULT_BITS BITS_8 //------------------------------------------------------------------------------ //The struct //FileHeader typedef struct { DWORD dwRiff; // Type of file header. DWORD dwSize; // Size of file header. DWORD dwWave; // Type of wave. } RIFF_FILEHEADER, *PRIFF_FILEHEADER; //ChunkHeader typedef struct { DWORD dwCKID; // Type Identification for current chunk header. DWORD dwSize; // Size of current chunk header. } RIFF_CHUNKHEADER, *PRIFF_CHUNKHEADER; //--------------------------------------------------------------------------------- //------------------------------------------------------------------- //Static member initialize CSoundBase *CSoundBase::m_pInstance = NULL; //If you don't initialize,the GetInstance() will link erro. /**/////////////////////////////////////////////////////////////////////// // Construction/Destruction /**/////////////////////////////////////////////////////////////////////// CSoundBase::CSoundBase() { memset(m_szSavePath,0,sizeof(m_szSavePath)); memset(&m_WaveFormatEx,0,sizeof(m_WaveFormatEx)); m_pBuffer1 = NULL; m_pBuffer2 = NULL; m_pWaveHdr1 = NULL; m_pWaveHdr2 = NULL; m_hWaveIn = NULL; m_hSaveFile = NULL; m_bRecording = FALSE; m_dwDataLength = 0; } CSoundBase::~CSoundBase() { if(m_pWaveHdr1 != NULL) { free(m_pWaveHdr1); m_pWaveHdr1 = NULL; } if(m_pWaveHdr2 != NULL) { free(m_pWaveHdr2); m_pWaveHdr2 = NULL; } if(m_pBuffer1 != NULL) { free(m_pBuffer1); m_pBuffer1 = NULL; } if(m_pBuffer2 != NULL) { free(m_pBuffer2); m_pBuffer2 = NULL; } } //---------------------------------------------------------------------- //Decription: // Start to record. // //Parameter: // pszPath: [in] The record path // //Retrun Values: // TRUE: Succeed. // FALSE: Failed. //---------------------------------------------------------------------- BOOL CSoundBase::Record(TCHAR *pszPath,const PWAVEFORMAT_SETTING pWaveFormat) { BOOL bResult = FALSE; _tcscpy(m_szSavePath,pszPath); SetRecordWaveFormat(pWaveFormat); if (waveInOpen(&m_hWaveIn,WAVE_MAPPER,&m_WaveFormatEx,(DWORD)WaveInProc,NULL,CALLBACK_FUNCTION) != MMSYSERR_NOERROR ) { goto END; } m_pBuffer1=(PBYTE)malloc(INP_BUFFER_SIZE); m_pBuffer2=(PBYTE)malloc(INP_BUFFER_SIZE); if(m_pBuffer1 == NULL || m_pBuffer2 == NULL) { goto END; } //allocate memory for wave header m_pWaveHdr1=reinterpret_cast<PWAVEHDR>(malloc(sizeof(WAVEHDR))); m_pWaveHdr2=reinterpret_cast<PWAVEHDR>(malloc(sizeof(WAVEHDR))); if(m_pWaveHdr1 == NULL || m_pWaveHdr2 == NULL ) { goto END; } m_pWaveHdr1->lpData = (LPSTR)m_pBuffer1; m_pWaveHdr1->dwBufferLength = INP_BUFFER_SIZE; m_pWaveHdr1->dwBytesRecorded = 0; m_pWaveHdr1->dwUser = 0; m_pWaveHdr1->dwFlags = 0; m_pWaveHdr1->dwLoops = 1; m_pWaveHdr1->lpNext = NULL; m_pWaveHdr1->reserved = 0; waveInPrepareHeader(m_hWaveIn,m_pWaveHdr1,sizeof(WAVEHDR)); m_pWaveHdr2->lpData = (LPSTR)m_pBuffer2; m_pWaveHdr2->dwBufferLength = INP_BUFFER_SIZE; m_pWaveHdr2->dwBytesRecorded = 0; m_pWaveHdr2->dwUser = 0; m_pWaveHdr2->dwFlags = 0; m_pWaveHdr2->dwLoops = 1; m_pWaveHdr2->lpNext = NULL; m_pWaveHdr2->reserved = 0; waveInPrepareHeader(m_hWaveIn,m_pWaveHdr2,sizeof(WAVEHDR)); // Add the buffers waveInAddBuffer (m_hWaveIn, m_pWaveHdr1, sizeof (WAVEHDR)) ; waveInAddBuffer (m_hWaveIn, m_pWaveHdr2, sizeof (WAVEHDR)) ; if (WriteWaveFileHeader(m_szSavePath,&m_WaveFormatEx,0,TRUE) == FALSE) { goto END; } //norains: Open the existing wave file incording to add wave data m_hSaveFile = CreateFile(m_szSavePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if( m_hSaveFile == INVALID_HANDLE_VALUE ) { goto END; } //Set the file pointer to the end. SetFilePointer(m_hSaveFile,0,NULL,FILE_END); //Begin recording waveInStart (m_hWaveIn) ; bResult = TRUE; END: if(bResult == FALSE) { if(m_pWaveHdr1 != NULL) { free(m_pWaveHdr1); m_pWaveHdr1 = NULL; } if(m_pWaveHdr2 != NULL) { free(m_pWaveHdr2); m_pWaveHdr2 = NULL; } if(m_pBuffer1 != NULL) { free(m_pBuffer1); m_pBuffer1 = NULL; } if(m_pBuffer2 != NULL) { free(m_pBuffer2); m_pBuffer2 = NULL; } if(m_hWaveIn != NULL) { //Stop recording, close the device waveInReset(m_hWaveIn); waveInClose(m_hWaveIn); m_hWaveIn = NULL; } } return bResult; } //---------------------------------------------------------------------- //Decription: // Stop recording and playing // //Parameter: // NULL // //Retrun Values: // NULL //---------------------------------------------------------------------- void CSoundBase::StopAll() { StopRecording(); StopPlaying(); } //---------------------------------------------------------------------- //Decription: // Stop playing // //Parameter: // NULL // //Retrun Values: // NULL //---------------------------------------------------------------------- void CSoundBase::StopPlaying() { sndPlaySound(NULL,SND_ASYNC); } //---------------------------------------------------------------------- //Decription: // Stop recording // //Parameter: // NULL // //Retrun Values: // NULL //---------------------------------------------------------------------- void CSoundBase::StopRecording() { if(m_bRecording == FALSE) { return; } m_bRecording = FALSE; waveInReset(m_hWaveIn); waveInClose(m_hWaveIn); //Please don't call waveInUnprepareHeader() in the WIM_CLOSE, //or it will work badly ! waveInUnprepareHeader (m_hWaveIn, m_pWaveHdr1, sizeof (WAVEHDR)) ; waveInUnprepareHeader (m_hWaveIn, m_pWaveHdr2, sizeof (WAVEHDR)) ; if(m_pWaveHdr1 != NULL) { free(m_pWaveHdr1); m_pWaveHdr1 = NULL; } if(m_pWaveHdr2 != NULL) { free(m_pWaveHdr2); m_pWaveHdr2 = NULL; } if(m_pBuffer1 != NULL) { free(m_pBuffer1); m_pBuffer1 = NULL; } if(m_pBuffer2 != NULL) { free(m_pBuffer2); m_pBuffer2 = NULL; } } //---------------------------------------------------------------------- //Decription: // Start to play. // //Parameter: // pszPath: [in] The playing path // //Retrun Values: // TRUE: Succeed. // FALSE: Failed. //---------------------------------------------------------------------- BOOL CSoundBase::Play(const TCHAR *pszPath) { TCHAR szPath[MAX_SAVEPATH_LENGTH] = {0}; if(pszPath != NULL) { _tcscpy(szPath,pszPath); } else { _tcscpy(szPath,m_szSavePath); } if(sndPlaySound(szPath,SND_ASYNC) == FALSE) { return FALSE; } return TRUE; } //---------------------------------------------------------------------- //Decription: // Set the wave format for recording. // //Parameter: // pWaveFormat: [in] The wave format to set. // //Retrun Values: // TRUE: Succeed. // FALSE: Failed. //---------------------------------------------------------------------- void CSoundBase::SetRecordWaveFormat(const PWAVEFORMAT_SETTING pWaveFormat) { WAVEFORMAT_SETTING waveFormatSetting; if(pWaveFormat == NULL) { waveFormatSetting.channel = DEFAULT_CHANNEL; waveFormatSetting.samples = DEFAULT_SAMPLES; waveFormatSetting.bits = DEFAULT_BITS; } else { waveFormatSetting = *pWaveFormat; } m_WaveFormatEx.wFormatTag=WAVE_FORMAT_PCM; m_WaveFormatEx.cbSize=0; //When the wFormatTag is PCM, the parameter is abort. switch(waveFormatSetting.channel) { case CHANNEL_SINGLE: m_WaveFormatEx.nChannels = 1; break; case CHANNEL_DOUBLE: m_WaveFormatEx.nChannels = 2; break; } switch(waveFormatSetting.samples) { case SAMPLES_11025: m_WaveFormatEx.nSamplesPerSec = 11025; break; case SAMPLES_22050: m_WaveFormatEx.nSamplesPerSec = 22050; break; case SAMPLES_44100: m_WaveFormatEx.nSamplesPerSec = 44100; break; } switch(waveFormatSetting.bits) { case BITS_8: m_WaveFormatEx.wBitsPerSample = 8; break; case BITS_16: m_WaveFormatEx.wBitsPerSample = 16; break; } m_WaveFormatEx.nBlockAlign=m_WaveFormatEx.nChannels * m_WaveFormatEx.wBitsPerSample / 8; m_WaveFormatEx.nAvgBytesPerSec=m_WaveFormatEx.nBlockAlign * m_WaveFormatEx.nSamplesPerSec; } //---------------------------------------------------------------------- //Decription: // Get instance // //Parameter: // Null // //Retrun Values: // The pointer to object //---------------------------------------------------------------------- CSoundBase* CSoundBase::GetInstance() { if(m_pInstance == NULL) { m_pInstance = new CSoundBase; } return m_pInstance; } //---------------------------------------------------------------------- //Decription: // WaveIn Process // //Parameter: // hwi: [in] Handle to the waveform-audio device associated with the callback function // uMsg: [in] Waveform-audio input message. It can be one of the messages shown :WIM_CLOSE,WIM_DATA,WIM_OPEN // dwInstance: [in] User instance data specified with waveInOpen. // dwParam1: [in] Message parameter. // dwParam2: [in] Message parameter. // //Retrun Values: // DWORD type. //---------------------------------------------------------------------- void CALLBACK CSoundBase::WaveInProc(HWAVEIN hWi, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) { switch(uMsg) { case WIM_CLOSE: m_pInstance->OnWIM_CLOSE(dwParam1,dwParam2); break; case WIM_DATA: m_pInstance->OnWIM_DATA(dwParam1,dwParam2); break; case WIM_OPEN: m_pInstance->OnWIM_OPEN(dwParam1,dwParam2); break; } } //---------------------------------------------------------------------- //Decription: // Write the wave file header. // //Parameter: // pszFilename: [in] The path to save // pWFX: [in] The information to write // dwBufferSize: [in] The size of wave buffer // bCover: [in] Cover writing or not // //Retrun Values: // TRUE: Succeed. // FASLE: Failed. //---------------------------------------------------------------------- BOOL CSoundBase::WriteWaveFileHeader(TCHAR *pszFilename, const PWAVEFORMATEX pWFX, DWORD dwBufferSize, BOOL bCover) { RIFF_FILEHEADER FileHeader; RIFF_CHUNKHEADER WaveHeader; RIFF_CHUNKHEADER DataHeader; DWORD dwBytesWritten; HANDLE fh; BOOL bResult = FALSE; // 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; // Open wave file if(bCover==TRUE) { //If the file existed , cover writng fh = CreateFile(pszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL); } else { //Open the file existed fh = CreateFile(pszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); //Move the pointer to the begin SetFilePointer(fh,0,NULL,FILE_BEGIN); } if( fh == INVALID_HANDLE_VALUE ) { DEBUGMSG(1, (TEXT("Error opening %s. Error code = 0x%08x "), pszFilename, GetLastError())); goto ERROR_EXIT; } // write the riff file if (! WriteFile(fh, &FileHeader, sizeof(FileHeader), &dwBytesWritten, NULL)) { DEBUGMSG(1, (TEXT("Error writing file header "))); goto ERROR_EXIT; } // write the wave header if (! WriteFile(fh, &WaveHeader, sizeof(WaveHeader), &dwBytesWritten, NULL)) { DEBUGMSG(1, (TEXT("Error writing wave header "))); goto ERROR_EXIT; } // write the wave format if (! WriteFile(fh, pWFX, WaveHeader.dwSize, &dwBytesWritten, NULL)) { DEBUGMSG(1, (TEXT("Error writing wave format "))); goto ERROR_EXIT; } // write the data header if (! WriteFile(fh, &DataHeader, sizeof(DataHeader), &dwBytesWritten, NULL)) { DEBUGMSG(1, (TEXT("Error writing PCM data header "))); goto ERROR_EXIT; } // Success bResult = TRUE; ERROR_EXIT: if(fh != INVALID_HANDLE_VALUE) { CloseHandle(fh); } return bResult; } //---------------------------------------------------------------------- //Decription: // On WIM_CLOSE //------------------------------------------------------------------------ void CSoundBase::OnWIM_CLOSE(WPARAM wParam, LPARAM lParam) { if(m_hSaveFile != NULL) { CloseHandle(m_hSaveFile); m_hSaveFile = NULL; } if (0 != m_dwDataLength) { //Write the data length to the file. WriteWaveFileHeader(m_szSavePath,&m_WaveFormatEx,m_dwDataLength,FALSE); } } //---------------------------------------------------------------------- //Decription: // On WIM_DATA //------------------------------------------------------------------------ void CSoundBase::OnWIM_DATA(WPARAM wParam, LPARAM lParam) { DWORD dwBytesRecorded = ((PWAVEHDR)wParam)->dwBytesRecorded; PBYTE pSaveBuffer; //allocate memory for save buffer pSaveBuffer = reinterpret_cast<PBYTE>(malloc(dwBytesRecorded)); if(pSaveBuffer == NULL) { waveInClose (m_hWaveIn) ; return; } //Copy the data to the save buffer. CopyMemory (pSaveBuffer, ((PWAVEHDR)wParam)->lpData, dwBytesRecorded) ; //Write the memory data to the file DWORD dwBytesWritten; if(WriteFile(m_hSaveFile, pSaveBuffer, dwBytesRecorded, &dwBytesWritten, NULL) != TRUE) { if(m_bRecording == TRUE) { waveInClose (m_hWaveIn) ; } } m_dwDataLength += dwBytesWritten ; free(pSaveBuffer); //If m_bRecording is FALSE, it may call the function waveInReset().So don't add buffer. if(m_bRecording == TRUE) { // Send out a new buffer.The new buffer is the original full buffer, used again. waveInAddBuffer (m_hWaveIn, (PWAVEHDR) wParam, sizeof (WAVEHDR)) ; } } //---------------------------------------------------------------------- //Decription: // On WIM_OPEN //------------------------------------------------------------------------ void CSoundBase::OnWIM_OPEN(WPARAM wParam, LPARAM lParam) { m_bRecording = TRUE; m_dwDataLength = 0; }  

你可能感兴趣的:(CSoundBase实现录音与播放)