网上有很多方法从麦克风读取PCM数据,不想一一举例。只是在这里发布一个我自己写的directsound的麦克风PCM数据采集类,通过它,可以很方便的利用directsound技术把麦克风的数据采集到,而且本身,开发者不必太在意自己会不会directsound编程,可以很方便的让开发者的主要精力集中于程序本身,而不是细节。就是需要安装directx9b的sdk,然后在编译的时候加入以下lib: strmiids.lib Quartz.lib winmm.lib dsound.lib dxguid.lib
这个是头文件:
#pragma once #ifndef _CAPTURE_SOUND_H_ #define _CAPTURE_SOUND_H_ #include <mmsystem.h> #include <dsound.h> #define NUM_REC_NOTIFICATIONS 16 class CAdoFrameHandler { public: virtual void AdoFrameData(BYTE* pBuffer, long lBufferSize) = 0 ; }; class CCaptureAudio { public: BOOL m_bRecording ; //recording now ? also used by event recv thread protected: LPDIRECTSOUNDCAPTURE8 m_pCapDev ; //capture device ptr LPDIRECTSOUNDCAPTUREBUFFER m_pCapBuf ; //capture loop buffer ptr LPDIRECTSOUNDNOTIFY8 m_pNotify ; //capture auto-notify event callback handler ptr GUID m_guidCapDevId ; //capture device id WAVEFORMATEX m_wfxInput; //input wave format description struct DSBPOSITIONNOTIFY m_aPosNotify[NUM_REC_NOTIFICATIONS + 1]; //notify flag array HANDLE m_hNotifyEvent; //notify event BOOL m_abInputFmtSupported[20]; DWORD m_dwCapBufSize; //capture loop buffer size DWORD m_dwNextCapOffset;//offset in loop buffer DWORD m_dwNotifySize; //notify pos when loop buffer need to emit the event CAdoFrameHandler* m_frame_handler ; // outer frame data dealer ptr public: // callback func to add enum devices string name static BOOL CALLBACK enum_dev_proc(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext ) ; static UINT notify_capture_thd(LPVOID data) ; protected: HRESULT InitDirectSound(GUID dev_id = GUID_NULL) ; HRESULT FreeDirectSound() ; HRESULT InitNotifications() ; HRESULT CreateCaptureBuffer(WAVEFORMATEX * wfx) ; HRESULT StartOrStopRecord(BOOL bStartRec) ; HRESULT RecordCapturedData() ; void SetWavFormat(WAVEFORMATEX * wfx) ; public: CCaptureAudio(void); ~CCaptureAudio(void); BOOL EnumDevices(HWND hList) ; BOOL Open(void) ; BOOL Close() ; void GrabAudioFrames(BOOL bGrabAudioFrames, CAdoFrameHandler* frame_handler) ; }; #endif
下面这个是cpp文件:
#include "StdAfx.h" #include ".captureaudio.h" #include <mmsystem.h> #include <dsound.h> #ifndef SAFE_DELETE #define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } #endif #ifndef SAFE_RELEASE #define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } #endif #ifndef MAX #define MAX(a,b) ( (a) > (b) ? (a) : (b) ) #endif CCaptureAudio::CCaptureAudio(void) { if(FAILED(CoInitialize(NULL))) /*, COINIT_APARTMENTTHREADED)))*/ { AfxMessageBox("CCaptureAudio CoInitialize Failed! "); return; } m_pCapDev = NULL ; m_pCapBuf = NULL ; m_pNotify = NULL ; // set default wave format PCM ZeroMemory( &m_wfxInput, sizeof(m_wfxInput)); m_wfxInput.wFormatTag = WAVE_FORMAT_PCM; m_guidCapDevId = GUID_NULL ; m_bRecording = FALSE ; m_hNotifyEvent = NULL ; } CCaptureAudio::~CCaptureAudio(void) { CoUninitialize() ; } BOOL CALLBACK CCaptureAudio::enum_dev_proc(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext) { HWND hList = (HWND)lpContext; if(!hList) return FALSE ; LPGUID lpTemp = NULL; if (lpGUID != NULL) { // NULL only for "Primary Sound Driver". if ((lpTemp = (LPGUID)malloc(sizeof(GUID))) == NULL) return(TRUE); memcpy(lpTemp, lpGUID, sizeof(GUID)); } ::SendMessage(hList, CB_ADDSTRING, 0,(LPARAM)lpszDesc); ::SendMessage(hList, LB_SETITEMDATA, 0, (LPARAM)lpTemp) ; free(lpTemp); return(TRUE); } UINT CCaptureAudio::notify_capture_thd(LPVOID data) { CCaptureAudio * pado = static_cast<CCaptureAudio *>(data) ; MSG msg; HRESULT hr ; DWORD dwResult ; while(pado->m_bRecording) { dwResult = MsgWaitForMultipleObjects( 1, &(pado->m_hNotifyEvent), FALSE, INFINITE, QS_ALLEVENTS ); switch( dwResult ) { case WAIT_OBJECT_0 + 0: // g_hNotificationEvents[0] is signaled // This means that DirectSound just finished playing // a piece of the buffer, so we need to fill the circular // buffer with new sound from the wav file if( FAILED( hr = pado->RecordCapturedData() ) ) { AfxMessageBox("Error handling DirectSound notifications.") ; pado->m_bRecording = FALSE ; } break; case WAIT_OBJECT_0 + 1: // Windows messages are available while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); if( msg.message == WM_QUIT ) pado->m_bRecording = FALSE ; } break; } } AfxEndThread(0, TRUE) ; return 0 ; } BOOL CCaptureAudio::EnumDevices(HWND hList) { if (FAILED(DirectSoundCaptureEnumerate ( (LPDSENUMCALLBACK)(CCaptureAudio::enum_dev_proc), (VOID*)&hList))) { return(FALSE); } return (TRUE) ; } BOOL CCaptureAudio::Open(void) { HRESULT hr ; if(!m_bRecording) { hr = InitDirectSound() ; } return (FAILED(hr)) ? FALSE : TRUE ; } BOOL CCaptureAudio::Close() { HRESULT hr ; hr = FreeDirectSound() ; CloseHandle(m_hNotifyEvent) ; return (FAILED(hr)) ? FALSE : TRUE ; } HRESULT CCaptureAudio::InitDirectSound(GUID dev_id) { HRESULT hr ; m_guidCapDevId = dev_id ; ZeroMemory( &m_aPosNotify, sizeof(DSBPOSITIONNOTIFY) * (NUM_REC_NOTIFICATIONS + 1) ) ; m_dwCapBufSize = 0 ; m_dwNotifySize = 0 ; // Create IDirectSoundCapture using the preferred capture device hr = DirectSoundCaptureCreate(&m_guidCapDevId, &m_pCapDev, NULL ) ; // init wave format SetWavFormat(&m_wfxInput) ; return (FAILED(hr)) ? S_FALSE : S_OK ; } HRESULT CCaptureAudio::FreeDirectSound() { // Release DirectSound interfaces SAFE_RELEASE( m_pNotify ) ; SAFE_RELEASE( m_pCapBuf ) ; SAFE_RELEASE( m_pCapDev ) ; return S_OK; } HRESULT CCaptureAudio::CreateCaptureBuffer(WAVEFORMATEX * wfx) { HRESULT hr; DSCBUFFERDESC dscbd; SAFE_RELEASE( m_pNotify ); SAFE_RELEASE( m_pCapBuf ); // Set the notification size m_dwNotifySize = MAX( 1024, wfx->nAvgBytesPerSec / 8 ) ; m_dwNotifySize -= m_dwNotifySize % wfx->nBlockAlign ; // Set the buffer sizes m_dwCapBufSize = m_dwNotifySize * NUM_REC_NOTIFICATIONS; SAFE_RELEASE( m_pNotify ); SAFE_RELEASE( m_pCapBuf ); // Create the capture buffer ZeroMemory( &dscbd, sizeof(dscbd) ); dscbd.dwSize = sizeof(dscbd); dscbd.dwBufferBytes = m_dwCapBufSize; dscbd.lpwfxFormat = wfx ; // Set the format during creatation if( FAILED( hr = m_pCapDev->CreateCaptureBuffer( &dscbd, &m_pCapBuf, NULL ) ) ) return S_FALSE ; m_dwNextCapOffset = 0; if( FAILED( hr = InitNotifications() ) ) return S_FALSE ; return S_OK; } HRESULT CCaptureAudio::InitNotifications() { HRESULT hr; int i ; if( NULL == m_pCapBuf ) return S_FALSE; // create auto notify event m_hNotifyEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); // Create a notification event, for when the sound stops playing if( FAILED( hr = m_pCapBuf->QueryInterface( IID_IDirectSoundNotify, (VOID**)&m_pNotify ) ) ) return S_FALSE ; // Setup the notification positions for( i = 0; i < NUM_REC_NOTIFICATIONS; i++ ) { m_aPosNotify[i].dwOffset = (m_dwNotifySize * i) + m_dwNotifySize - 1; m_aPosNotify[i].hEventNotify = m_hNotifyEvent; } // Tell DirectSound when to notify us. the notification will come in the from // of signaled events that are handled in WinMain() if( FAILED( hr = m_pNotify->SetNotificationPositions( NUM_REC_NOTIFICATIONS, m_aPosNotify ) ) ) return S_FALSE ; return S_OK; } HRESULT CCaptureAudio::StartOrStopRecord(BOOL bStartRec) { HRESULT hr; if( bStartRec ) { // Create a capture buffer, and tell the capture // buffer to start recording if( FAILED( hr = CreateCaptureBuffer( &m_wfxInput ) ) ) return S_FALSE ; if( FAILED( hr = m_pCapBuf->Start( DSCBSTART_LOOPING ) ) ) return S_FALSE ; // create notify event recv thread AfxBeginThread(CCaptureAudio::notify_capture_thd, (LPVOID)(this)) ; } else { // Stop the capture and read any data that // was not caught by a notification if( NULL == m_pCapBuf ) return S_OK; // wait until the notify_event_thd thread exit and release the resources. Sleep(500) ; // Stop the buffer, and read any data that was not // caught by a notification if( FAILED( hr = m_pCapBuf->Stop() ) ) return S_OK ; if( FAILED( hr = RecordCapturedData() ) ) return S_FALSE ; } return S_OK; } HRESULT CCaptureAudio::RecordCapturedData() { HRESULT hr; VOID* pbCaptureData = NULL; DWORD dwCaptureLength; VOID* pbCaptureData2 = NULL; DWORD dwCaptureLength2; DWORD dwReadPos; DWORD dwCapturePos; LONG lLockSize; if( NULL == m_pCapBuf ) return S_FALSE; if( FAILED( hr = m_pCapBuf->GetCurrentPosition( &dwCapturePos, &dwReadPos ) ) ) return S_FALSE; lLockSize = dwReadPos - m_dwNextCapOffset; if( lLockSize < 0 ) lLockSize += m_dwCapBufSize; // Block align lock size so that we are always write on a boundary lLockSize -= (lLockSize % m_dwNotifySize); if( lLockSize == 0 ) return S_FALSE; // Lock the capture buffer down if( FAILED( hr = m_pCapBuf->Lock( m_dwNextCapOffset, lLockSize, &pbCaptureData, &dwCaptureLength, &pbCaptureData2, &dwCaptureLength2, 0L ) ) ) return S_FALSE ; // call the outer data handler if(m_frame_handler) { m_frame_handler->AdoFrameData((BYTE*)pbCaptureData, dwCaptureLength) ; } // Move the capture offset along m_dwNextCapOffset += dwCaptureLength; m_dwNextCapOffset %= m_dwCapBufSize; // Circular buffer if( pbCaptureData2 != NULL ) { // call the outer data handler if(m_frame_handler) { m_frame_handler->AdoFrameData((BYTE*)pbCaptureData, dwCaptureLength) ; } // Move the capture offset along m_dwNextCapOffset += dwCaptureLength2; m_dwNextCapOffset %= m_dwCapBufSize; // Circular buffer } // Unlock the capture buffer m_pCapBuf->Unlock( pbCaptureData, dwCaptureLength, pbCaptureData2, dwCaptureLength2 ); return S_OK; } void CCaptureAudio::SetWavFormat(WAVEFORMATEX * wfx) { // get the default capture wave formate ZeroMemory(wfx, sizeof(WAVEFORMATEX)) ; wfx->wFormatTag = WAVE_FORMAT_PCM; // 8KHz, 16 bits PCM, Mono wfx->nSamplesPerSec = 8000 ; wfx->wBitsPerSample = 16 ; wfx->nChannels = 1 ; wfx->nBlockAlign = wfx->nChannels * ( wfx->wBitsPerSample / 8 ) ; wfx->nAvgBytesPerSec = wfx->nBlockAlign * wfx->nSamplesPerSec; } void CCaptureAudio::GrabAudioFrames(BOOL bGrabAudioFrames, CAdoFrameHandler* frame_handler) { m_frame_handler = frame_handler ; m_bRecording = bGrabAudioFrames ; StartOrStopRecord(m_bRecording) ; }
使用的时候,也很简单,我这里声明了一个纯虚类CAdoFrameHandler,这个类专门是用来让使用者重载它的纯虚函数的,只要重载了以后,设置正确,就可以自动开始采集音频数据了。注意,在这个类里面,我用的是8KHz,16Bits,Mono单声道的PCM数据采集。
使用的时候,首先:
#include "CaptureAudio.h"
然后:
class CMyClass : public CAdoFrameHandler { public: // override the CAdoFrameHandler void AdoFrameData(BYTE* pBuffer, long lBufferSize) ; // 这个类重载一下,就可以采集了 protected: CCaptureAudio m_cap_ado ; // 这个对象就是用来采集音频数据的 } ;
在OnInitDialog类中,我们可以使用如下初始化方法:
打开mic,同时初始化directsound:
m_cap_ado.Open() ;
开始采集声音就是:
m_cap_ado.GrabAudioFrames(TRUE, this) ;
调用它以后,只要你重载了上面的那个函数,directsound就会周期性的从麦克采集数据,然后调用该函数。
停止声音采集是:
m_cap_ado.GrabAudioFrames(FALSE, NULL) ;
关闭mic,同时释放directsound:
m_cap_ado.Close() ;
就这么简单的几步,就可以完成麦克风的音频数据采集。
转自:http://blog.chinaunix.net/uid-8272118-id-2033248.html