1. 程式人生 > >用VC自己動手做個錄音機

用VC自己動手做個錄音機

搞了很久的程式,都是做業務系統之類的,還沒有搞過多媒體方面的程式設計,今天動手做個錄音機,瞭解一下聲音相關的API,

window下面聲音的程式設計主要有三種方式,

  • MCI,這種方式很簡單,但是不夠靈活
  • waveXXXX等低階的聲音API
  • 還有就是DirectSound

個人感覺使用waveXXX函式應該是最方便和最靈活的,也是比較簡單的,

隨便建立一個MFC基於對話方塊的工程,在視窗類裡面增加幾個成員

複製程式碼 DWORD m_dwDataLength;        //資料長度
WAVEFORMATEX m_waveform;    //聲音格式
HWAVEIN m_hWaveIn;            
//音訊輸入控制代碼
HWAVEOUT m_hWaveOut;        //音訊輸出控制代碼
PBYTE m_pSaveBuffer;        //音訊儲存記憶體

WAVEHDR m_WAVEHDR1;
WAVEHDR m_WAVEHDR2;

char m_cbBuffer1[INP_BUFFER_SIZE];    //聲音臨時快取1
char m_cbBuffer2[INP_BUFFER_SIZE];    //聲音臨時快取2 複製程式碼

 在建構函式對成員進行初始化

複製程式碼 //錄音機狀態初始化m_nRecordState = RECORD_STATE_INIT;
m_dwDataLength 
=0;

//open waveform audo for input
//為聲音輸入設定格式m_waveform.wFormatTag=WAVE_FORMAT_PCM;
m_waveform.nChannels
=1;
m_waveform.nSamplesPerSec
=11025;
m_waveform.nAvgBytesPerSec
=11025;
m_waveform.nBlockAlign
=1;
m_waveform.wBitsPerSample
=8;
m_waveform.cbSize
=0;    
    
m_pSaveBuffer 
= NULL; 複製程式碼

 看看我的窗體

 

錄音的處理函式

複製程式碼
void CWaveRecordDlg::OnBnClickedBtnRecord()
{
    
//開啟聲音裝置
if(waveInOpen(&m_hWaveIn,WAVE_MAPPER,&m_waveform,
            (DWORD)m_hWnd,NULL,CALLBACK_WINDOW))
{
        MessageBeep(MB_ICONEXCLAMATION);
        MessageBox(_T(
"錄製聲音失敗!"),
                    _T(
"錯誤"),MB_ICONEXCLAMATION|MB_OK);
        
return;
    }


    
//設定緩衝區
    m_WAVEHDR1.lpData = (LPTSTR)m_cbBuffer1;
    m_WAVEHDR1.dwBufferLength 
= INP_BUFFER_SIZE;
    m_WAVEHDR1.dwBytesRecorded 
=0;
    m_WAVEHDR1.dwUser
=0;
    m_WAVEHDR1.dwFlags
=0;
    m_WAVEHDR1.dwLoops
=1;
    m_WAVEHDR1.lpNext
=NULL;
    m_WAVEHDR1.reserved
=0;

    m_WAVEHDR2.lpData
=(LPTSTR)m_cbBuffer2;
    m_WAVEHDR2.dwBufferLength 
= INP_BUFFER_SIZE;
    m_WAVEHDR2.dwBytesRecorded 
=0;
    m_WAVEHDR2.dwUser
=0;
    m_WAVEHDR2.dwFlags
=0;
    m_WAVEHDR2.dwLoops
=1;
    m_WAVEHDR2.lpNext
=NULL;
    m_WAVEHDR2.reserved
=0;

    
//設定雙緩衝
    waveInPrepareHeader(m_hWaveIn,&m_WAVEHDR1,sizeof(WAVEHDR));
    waveInPrepareHeader(m_hWaveIn,
&m_WAVEHDR2,sizeof(WAVEHDR));

    waveInAddBuffer(m_hWaveIn,
&m_WAVEHDR1,sizeof(WAVEHDR));
    waveInAddBuffer(m_hWaveIn,
&m_WAVEHDR2,sizeof(WAVEHDR));

    
//先隨便分配個記憶體
if(m_pSaveBuffer == NULL){
        free(m_pSaveBuffer);
        m_pSaveBuffer 
= NULL;
    }

    m_pSaveBuffer 
= (PBYTE)malloc(1);
    m_dwDataLength 
=0;

    
//開始錄音
    
// Begin sampling    
    waveInStart(m_hWaveIn);
}
複製程式碼

 然後增加三個訊息進行處理

複製程式碼
LRESULT CWaveRecordDlg::OnMM_WIM_CLOSE(UINT wParam, LONG lParam)
{
    waveInUnprepareHeader(m_hWaveIn, 
&m_WAVEHDR1, sizeof (WAVEHDR)) ;
    waveInUnprepareHeader(m_hWaveIn, 
&m_WAVEHDR2, sizeof (WAVEHDR)) ;
    
    m_nRecordState 
= RECORD_STATE_STOP;
    SetButtonState();

    
return NULL;
}


LRESULT CWaveRecordDlg::OnMM_WIM_DATA(UINT wParam, LONG lParam)
{
    PWAVEHDR pWaveHdr 
= (PWAVEHDR)lParam;
    
    
if(pWaveHdr->dwBytesRecorded >0){
        m_pSaveBuffer 
= (PBYTE)realloc(m_pSaveBuffer,m_dwDataLength + pWaveHdr->dwBytesRecorded);
        
if(m_pSaveBuffer == NULL){
            waveInClose (m_hWaveIn);
            MessageBeep (MB_ICONEXCLAMATION) ;
            AfxMessageBox(
"erro memory");
            
return NULL;
        }


        memcpy(m_pSaveBuffer
+m_dwDataLength , pWaveHdr->lpData,pWaveHdr->dwBytesRecorded);

        m_dwDataLength 
+= pWaveHdr->dwBytesRecorded;
    }


    
if(m_nRecordState == RECORD_STATE_STOPING){
        waveInClose(m_hWaveIn);        
    }


    waveInAddBuffer(m_hWaveIn, pWaveHdr, 
sizeof (WAVEHDR));

    
return NULL;
}


LRESULT CWaveRecordDlg::OnMM_WIM_OPEN(UINT wParam, LONG lParam)
{
    m_nRecordState 
= RECORD_STATE_RECING;
    SetButtonState();
    
return NULL;
}
複製程式碼

 對錄音處理的基本原理簡單的談一下,

先呼叫waveInOpen開啟聲音裝置,設定WAVEHDR型別的緩衝區,其中lpData成員指向緩衝區的記憶體塊,

可以分配動態記憶體也可以像我這裡一樣,分配在棧區的記憶體。接著就可以呼叫waveInPrepareHeader以及waveInAddBuffer,

呼叫 waveInStart開始錄音,然後會發出訊息MM_WIM_OPEN,如果一人緩衝區滿了以後就會發出訊息MM_WIM_DATA,

我們就可以在這個訊息處理函式將聲音內容拷到我們自己記憶體塊儲存起來,以及增加m_dwDataLength的長度,如果要停止錄音,

只要呼叫一下waveInReset,然後會發出訊息MM_WIM_DATA作最後處理,並且發出訊息OnMM_WIM_CLOSE作點善後工作,

如在這裡呼叫waveInUnprepareHeader去掉緩衝區,並且設定按鈕的狀態,lpData指向的是動態記憶體也可以在這裡釋放。

注:這裡用了兩個緩衝區,可以使聲音連續,如果只使用一個緩衝區會導致聲音有頓

現在看看聲音播放,先看程式碼

複製程式碼 void CWaveRecordDlg::OnBnClickedBtnPlay()
{
    
//開啟聲音播放控制代碼
if(waveOutOpen(&m_hWaveOut,WAVE_MAPPER,&m_waveform,(DWORD)m_hWnd, NULL, CALLBACK_WINDOW)){
        MessageBeep(MB_ICONEXCLAMATION);
        MessageBox(_T(
"錄製聲音失敗!"),_T("錯誤"),MB_ICONEXCLAMATION|MB_OK);
        
return;
    }
    
}


LRESULT CWaveRecordDlg::OnMM_WON_OPEN(UINT wParam,LONG lParam)
{
    memset(
&m_WAVEHDR1,0,sizeof(WAVEHDR));
    m_WAVEHDR1.lpData 
= (char*)m_pSaveBuffer;
//指向要播放的記憶體
    m_WAVEHDR1.dwBufferLength 
= m_dwDataLength;
//播放的長度
    m_WAVEHDR1.dwFlags 
= WHDR_BEGINLOOP | WHDR_ENDLOOP;
    m_WAVEHDR1.dwLoops 
=1;

    waveOutPrepareHeader(m_hWaveOut,
&m_WAVEHDR1,sizeof(WAVEHDR));

    waveOutWrite(m_hWaveOut,
&m_WAVEHDR1,sizeof(WAVEHDR));

    m_nRecordState 
= RECORD_STATE_PLAYING;
    SetButtonState();
    
return NULL;
}


LRESULT CWaveRecordDlg::OnMM_WOM_DONE(UINT wParam,LONG lParam)
{
    PWAVEHDR pWaveHdr 
= (PWAVEHDR)lParam;
    waveOutUnprepareHeader (m_hWaveOut, pWaveHdr, 
sizeof (WAVEHDR)) ;
    waveOutClose (m_hWaveOut);
    
return NULL;
}


LRESULT CWaveRecordDlg::OnMM_WOM_CLOSE(UINT wParam,LONG lParam)
{
    m_nRecordState 
= RECORD_STATE_STOP;
    SetButtonState();
    
return NULL;
}
複製程式碼

 聲音的播放相對簡單一點,在這裡這種情況不需要用到雙快取技術,只要呼叫 waveOutOpen開啟聲音播放控制代碼,

然後響應 MM_WON_OPEN訊息,設定緩衝區,就可以了

當然,如果是網路聊天的時候發出來的聲音包應該不是這樣一大塊的記憶體,而是一小塊一小塊就得用到雙快取技術,

需要處理MM_WOM_DONE,以便重新加入聲音包,可以順暢的播放;在這裡只是簡單的去掉緩衝區 

 然後現在就剩下儲存成wav,以後讀取wav檔案了

首先看一下wav檔案的格式,以下內容部分摘自window程式設計

表22-1 .WAV檔案格式
偏移量 位元組 資料
0000 4 「RIFF」
0004 4 波形塊的大小(檔案大小減8)
0008 4 「WAVE」
000C 4 「fmt 」
0010 4 格式塊的大小(16位元組)
0014 2 wf.wFormatTag = WAVE_FORMAT_PCM = 1
0016 2 wf.nChannels
0018 4 wf.nSamplesPerSec
001C 4 wf.nAvgBytesPerSec
0020 2 wf.nBlockAlign
0022 2 wf.wBitsPerSample
0024 4 「data」
0028 4 波形資料的大小
002C 波形資料

--------------------------------

基於上面的瞭解,就可以寫一個讀取wav包頭的函數了

複製程式碼 #define WAVE_HEADER_SIZE 44