norains的专栏

只专注于WINCE开发

原创 CSoundBase实现录音与播放收藏

新一篇: 我为什么写《C语言已死》 | 旧一篇: CD转换高品质MP3,其实也可以很简单

 //========================================================================
//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)