MFC錄制音頻和播放音頻


 

一、錄制音頻

  在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函數:表示關閉錄音設備。

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM