一、錄制音頻
在windows中提供了相應的API函數(waveIn這個族的函數)實現錄音功能;在使用這些函數時,一定要引入相應的頭文件
#include <windows.h>
#include <Mmsystem.h>
#pragram comment(lib, "Winmm.lib")
1、在開始錄音之前,需要首先定義音頻的相關信息:使用WAVEFORMATEX結構,設置相關的音頻流信息。以下是MSDN中的定義:
typedef struct { WORD wFormatTag; // 波形音頻的格式,一般情況下設置為WAVE_FORMAT_PCM WORD nChannels; // 音頻聲道的數量。可以是1或者2(現在電腦基本上都是左右兩個聲道,因此一般設置為2) DWORD nSamplesPerSec; // 每個聲道播放和接收的音頻的樣本頻率(一般的頻率為8khz, 11.025khz, 22.05khz,44.1khz) DWORD nAvgBytesPerSec; // 平均的數據傳輸率,單位為byte/s WORD nBlockAlign; // 以字節為單位的塊對齊的大小,一般為:(nChannels * wBitsPerSample)/8 WORD wBitsPerSample; // 根據wFormatTag設置的類型,設置采樣率的大小,如果設置為WAVE_FORMAT_PCM,則大小為8的整倍數 WORD cbSize; // 額外的空間,一般不需要,設置為0 }WAVEFORMATEX, *PWAVEFORMATEX;
定義一個WAVEFORMATEX對象,根據自己的要求設置音頻流的信息,如下:
WAVEFORMATEX waveFormat; waveFormat.nSamplesPerSec = 44100; waveFormat.wBitsPerSample = 16; waveFormat.nChannels = 2; waveFormat.cbSize = 0; waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nBlockAlign = (waveFormat.wBitsPerSample * waveFormat.nChannels)/8; waveFormat.nAvgBytesPerSec = waveFormat.nBlockAlign * waveFormat.nSamplesPersec;
2、當音頻流信息設置完成后,接下來需要啟動錄音設備:使用waveInOpen函數。
waveInOpen函數原型為:
WINMMAPI MMRESULT WINAPI waveInOpen( _Out_opt_ LPHWAVEIN phwi, // 一個特定的錄音設備指針,如果設備啟動成功,該參數的值將會被賦值為啟動的設備 _In_ UINT uDeviceID, // 需要啟動的設備ID。一般不會手動指定某個設備,而是通過設置WAVE_MAPPER,通過系統查找可用設備 _In_ LPCWAVEFORMATEX pwfx, // 音頻流信息對象的指針。這個參數就是我們第一步設置的對象 _In_opt_ DWORD_PTR dwCallback, // 錄音消息的處理程序,可以設置一個函數、事件句柄、窗口句柄、一個特定的線程。也就是說錄音消息產生后,由這個參數對應的值來處理該消息。包括關閉錄音、緩沖區已滿、開啟設備 _In_opt_ DWORD_PTR dwInstance, // dwCallback參數的參數列表 _In_ DWORD fdwOpen // 打開設備的標識符。對應dwCallback,如果第四個參數設置為函數,則這個參數的值為CALLBACK_FUNCTION;如果為線程,則為CALLBACK_THREAD );
注意:要想該函數成功執行,必須在開始之前,有錄音設備的存在(台式電腦一定要插入麥克風才可以被檢測到)。
3、當錄音設備啟動后,接下來需要聲明兩個緩沖區和兩個緩沖區頭部結構體WAVEHDR對象,緩沖區用來存放錄音音頻,並用緩沖區初始化頭部對象:
INT bufSize = 512; BYTE *pBuffer1 = new BYTE[bufSize]; if (pBuffer1 == NULL) return; memset(pBuffer1, 0, bufSize); WAVEHDR wHdr1; wHdr1.lpData = (LPSTR)pBuffer1; wHdr1.dwBufferLength = bufSize; wHdr1.dwBytesRecorded = 0; wHdr1.dwUser = 0; wHdr1.dwFlags = 0; wHdr1.dwLoops = 1; BYTE *pBuffer2 = new BYTE[bufSize]; if (pBuffer2 == NULL) return; memset(pBuffer2,0, bufSize); WAVEHDR wHdr2; wHdr2.lpData = (LPSTR)pBuffer2; wHdr2.dwBufferLength = bufSize; wHdr2.dwBytesRecorded = 0; wHdr2.dwUser = 0; wHdr2.dwFlags = 0; wHdr2.dwLoops = 1;
WAVEHDR對象定義如下:
typedef struct { LPSTR lpData; // 緩沖區存放的內容 DWORD dwBufferLength; // 緩沖區的大小 DWORD dwButesRecorded; // 緩沖區中存放的字節數 DWORD_PTR dwUser; DWORD dwFlags; DWORD dwLoops; struct wavehdr_tag *lpNext; DWORD_PTR reserved; } WAVEHDR;
4、接下來將這兩個頭部對象,加入到准備的錄音緩沖區中。該過程使用waveInPrepareHeader函數。
waveInPrepareHeader(hWaveIn, &wHdr1, sizeof(WAVEHDR)); // 准備第一個波形數據塊用於錄音 waveInPrepareHeader(hWaveIn, &wHdr2, sizeof(WAVEHDR)); // 准備第二個數據塊用於錄音
waveInPrepareHeader的第一個參數表示:錄音設備句柄;第二個參數表示:錄音的緩沖區對象;第三個參數表示:錄音緩沖區結構體的大小。
5、當准備好錄音緩沖區,就可以將錄音緩沖區加入到指定的錄音設備中。該步驟使用waveInAddBuffer函數:
waveInAddBuffer(hWaveIn, &wHdr1, sizeof(WAVEHDR)); // 指定波形數據塊為錄音輸入緩存 waveInAddBuffer(hWaveIn, &wHdr2, sizeof(WAVEHDR)); // 指定波形數據塊為錄音緩存
分別將緩沖區1和2設置為錄音緩沖區。這些緩沖區將被加入到錄音緩沖隊列中,緩沖區循環執行。
6、開始錄音,使用waveInStart函數
waveInStart(hWaveIn); // 開始錄音
這個函數的意思就是,通過hWaveIn錄音設備,將波形音頻放入錄音緩沖區(前面已經指定了緩沖區)
7、當緩沖區滿時,waveInStart函數,就會自動的調用waveInOpen函數中指定的函數/窗體/事件;通過該函數,用戶可以將緩沖區的波形文件發給其它的用戶,也可以將緩沖區的文件保存起來,即就是用戶對緩沖區的拷貝。聲卡自動將音頻緩沖區從緩沖隊列中刪除。拷貝完成后,就將該緩沖區以及對應的音頻頭文件初始化,並通過waveInAddBuffer函數重新加入錄音緩沖隊列中。
DWORD CIP_PHONEDlg::MicCallBack(HWAVEIN hWaveIn, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) { // 所有的這些錄音緩沖區都是由錄音函數自動觸發的,開發這不需要自己觸發 CIP_PHONEDlg *pwnd = (CIP_PHONEDlg*)dwInstace; // 表示錄音的窗體 PWAVEHDR whd = (PWAVEHDR)dwParam1; // 錄音的頭結構體對象 switch(uMsg) { case WIM_OPEN: // 打開錄音設備,這里不做處理 break; case WIM_DATA: // 表示緩沖區已滿,我們將信息寫入一個pcm文件 { // 保存數據 pwnd->pf = fopen(pwnd->soundName, "ab+"); // 一定要以二進制數據寫入,否則錄音的音頻會出現雜音 Sleep(1000); // 等待聲音錄制1s fwrite(whd->lpData, 1, whd->dwBufferLength, pwnd->pf); if (pwnd->isGetSound) { waveInAddBuffer(hWaveIn, whd, sizeof(WAVEHDR)); } fclose(pwnd->pf); } break; case WIM_CLOSE: // 停止錄音 { waveInStop(hWaveIn); waveInReset(hWaveIn); waveInClose(hWaveIn); } break; default: break; } return 0; }
8、停止錄音,使用waveInClose函數執行該操作
delete [] pBuffer1->lpData; delete [] pBuffer2->lpData; waveInClose(hWaveIn); // 停止錄音
停止錄音時,將會觸發WIM_CLOSE消息。
在這個過程中首先執行waveInStop函數:表示禁止向輸入緩沖區中輸入波形數據;
然后執行waveInReset函數:表示停止波形數據的輸入並且將當前的位置位0,將所有掛起的輸入緩沖區設置為完成,並返回給應用程序(其實就是一個復位操作)
最后執行waveInClose函數:表示關閉錄音設備。