VC錄音機程序


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);
}

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

ON_MESSAGE(MM_WIM_OPEN,OnMM_WIM_OPEN)
ON_MESSAGE(MM_WIM_DATA,OnMM_WIM_DATA)
ON_MESSAGE(MM_WIM_CLOSE,OnMM_WIM_CLOSE)

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 波形資料

 


這是一種擴充自RIFF(Resource Interchange File Format:資源交換檔案格式)的格式。RIFF是用於多媒體資料檔案的萬用格式,它是一種標記檔案格式。在這種格式下,檔案由資料「塊」組成,而這些資料塊則由前面4個字元的ASCII名稱和4位元組(32位元)的資料塊大小來確認。資料塊大小值不包括名稱和大小所需要的8位元組。

波形聲音檔案以文字字串「RIFF」開始,用來標識這是一個RIFF檔案。字串後面是一個32位元的資料塊大小,表示檔案其餘部分的大小,或者是小於8位元組的檔案大小。

資料塊以文字字串「WAVE」開始,用來標識這是一個波形聲音塊,後面是文字字串「fmt」-注意用空白使之成爲4字元的字串-用來標識包含波形聲音資料格式的子資料塊。「fmt」字串的後面是格式資訊大小,這裏是16位元組。格式資訊是WAVEFORMATEX結構的前16個位元組,或者,像最初定義時一樣,是包含WAVEFORMAT結構的PCMWAVEFORMAT結構。

格式資訊的後面是文字字串「data」,然後是32位元的資料大小,最後是波形資料本身。這些資料是按相同格式進行簡單連結的樣本,這與低階波形聲音設備上所使用的格式相同。如果樣本大小是8位元,或者更少,那麼每個樣本有1位元組用於單聲道,或者有2位元組用於立體聲。如果樣本大小在9到16位元之間,則每個樣本就有2位元組用於單聲道,或者4位元組用於立體聲。對於立體聲波形資料,每個樣本都由左值及其後面的右值組成。

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

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

int CWaveRecordDlg::read_wav_head(WAVEFORMATEX *wf,char **out_buffer,int *out_len,char *in_buffer,int in_len)
{
char *lp_pos;
int itmp;

lp_pos = in_buffer;
if(in_buffer == NULL || in_len == 0 || in_len < WAVE_HEADER_SIZE || wf==NULL)
return 1;

if(strncmp(lp_pos,"RIFF",4)!=0)
return -1;
lp_pos += 4;

itmp = *((int*)lp_pos);
if(itmp != (in_len-8))
return -1;
lp_pos += 4;

if(strncmp(lp_pos,"WAVEfmt ",8)!=0)
return -1;
lp_pos += 8;

itmp = *((int*)lp_pos);
if(itmp != 16)
return -1;
lp_pos += 4;


memcpy(wf,lp_pos,16);
lp_pos += 16;

if(strncmp(lp_pos,"data",4)!=0)
return -1;
lp_pos += 4;

//真正的數據長度
*out_len = *((int*)lp_pos);
lp_pos += 4;

if(*out_len != (in_len - WAVE_HEADER_SIZE))
return 1;

*out_buffer = (char*)malloc(*out_len);
if(*out_buffer == NULL)
return -2;

memcpy(*out_buffer,lp_pos,*out_len);

return 0;
}

以及寫wav頭媒體信息
int CWaveRecordDlg::write_wav_head(WAVEFORMATEX *wf,char *in_buffer,int in_len,char **out_buffer, int *out_len)
{
char *buffer;
int *int_tmp,pos=0;

*out_len = WAVE_HEADER_SIZE + in_len;
buffer = (char*)malloc(*out_len);
if(buffer == NULL)
return -1;
memcpy(buffer,"RIFF",4);
pos = 4;

int_tmp = (int*)(buffer+pos);
*int_tmp = WAVE_HEADER_SIZE + in_len - 8;
pos += 4;

memcpy(buffer+pos,"WAVEfmt ",8);
pos += 8;

int_tmp = (int*)(buffer+pos);
*int_tmp = 16;
pos += 4;


memcpy(buffer+pos,wf,16);
pos += 16;

memcpy(buffer+pos,"data",4);
pos += 4;

int_tmp = (int*)(buffer+pos);
*int_tmp = in_len;
pos += 4;

memcpy(buffer+pos,in_buffer,in_len);

*out_buffer = buffer;

return 0;
}

Powered by Zoundry Raven

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章