Win32下的录音编程
生活随笔
收集整理的這篇文章主要介紹了
Win32下的录音编程
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
1 引言
在Win32 APIs基礎上編寫錄音程序繁瑣易錯,使用封裝好的類是個不錯的注意。不幸的是所謂封裝好的類對你而言,往往是代碼羅嗦且功能不足,因此盡管你可能希望在某個項目上因使用封裝好的類而避開Win32 APIs,可最終你發(fā)現(xiàn)你還得面對它。不是為了編寫自己的類,就是為了修改別人的代碼。
Win32 APIs中有一組被稱成多媒體控制接口(即MCI)的函數(shù),該接口提供了多媒體編程所需的系統(tǒng)級APIs。對絕大多數(shù)C/C++程序員而言,這些函數(shù)也就是Windows多媒體編程的最低層接口。
由于錄音代碼直接操作真實的錄音設備而非單純的邏輯過程,因此會遇到一些“意外”或與時序有關的困難,從而使編寫健壯的代碼成了一件困難的事。
錄音的目的往往是將聲音寫入文件保存下來或是通過網絡發(fā)送,這兩類需求對錄音代碼影響較大。前者無實時性要求,一般也不限制數(shù)據(jù)量,代碼較易編寫,而后者既對實時性又要求,又對數(shù)據(jù)量(也就是音頻格式)有要求,且網絡系統(tǒng)的穩(wěn)定性也難比文件系統(tǒng)。期望在本文中試圖解決所有的問題是不現(xiàn)實的,諸如如何壓縮音頻數(shù)據(jù)以減小網絡帶寬需求或如何通過網絡傳輸音頻數(shù)據(jù)之類的問題,讀者需參閱專業(yè)著作,本文只講述如何獲得適宜某類需求的原始音頻數(shù)據(jù)。
本文中的示例代碼均是一個名為CWaveRecord的錄音類的成員函數(shù),該類是一個網絡電話程序的核心類。為減小篇幅或簡單明了,作了簡化處理等。
2 基本過程
MCI按打開設備、配置設備、實現(xiàn)功能(或曰發(fā)送命令)、撤銷配置、關閉設備的標準次序組織APIs。對于錄音編程而言,其要點在于根據(jù)音頻格式打開對應的設備、配置錄音所需的參數(shù)(主要是設置數(shù)據(jù)區(qū)以及根據(jù)數(shù)據(jù)接收方式設置回調函數(shù)或消息)、按一定次序發(fā)送命令給設備、接收數(shù)據(jù)并配置參數(shù)以繼續(xù)錄音、停止錄音釋放資源、關閉設備等幾個步驟上。所需的函數(shù)說明于mmsystem.h,引入庫是winmm.lib。
2.1 打開設備
調用waveInOpen()打開錄音設備,打開成功后函數(shù)返回MMSYSERR_NOERROR,而第一個參數(shù)返回設備句柄。
調用該函數(shù)時,必須指定設備表示符(可能有多個設備)、音頻格式以及返回音頻數(shù)據(jù)的方式。
你可以將設備表示符,即第二個參數(shù)設為WAVE_MAPPER。這意味著,你不在意具體使用哪個設備,由系統(tǒng)決定。
第三個參數(shù)是個WAVEFORMATEX結構指針,對應的結構指定音頻格式。一個音頻格式結構的例子:
PCMWAVEFORMAT wf_PCM8S11K =
{
{
WAVE_FORMAT_PCM, // PCM格式
1, // 單聲道
11025L, // 11.025KHZ
11025L, // 11025字節(jié)/s
1 // 字節(jié)對齊
},
8 // 8位采樣
};
后面的三個參數(shù)用于指定獲取音頻數(shù)據(jù)的方式:事件方式、線程方式、窗口方式以及回調函數(shù)方式。最后一個參數(shù),即第六個參數(shù)指定方式,而第四個參數(shù)和第五個參數(shù)指定該方式所需的信息。
調用示例:
waveInOpen( &m_hWaveIn, dwDeviceID, m_pwf,
(DWORD)waveInProc, (DWORD)this, CALLBACK_FUNCTION );
該示例使用回調函數(shù)方式獲取音頻數(shù)據(jù)。其中dwDeviceID通常即為WAVE_MAPPER,m_pwf指向音頻格式結構,缺省值即wf_PCM8S11K的地址,waveInProc是回調函數(shù)(內部細節(jié)后面論述)。值得注意的是,this指針被設為實例數(shù)據(jù),這是一個編程小技巧,方便waveInProc調用處理函數(shù)。
2.2 配置設備
按指定音頻格式成功打開錄音設備之后,需要配置錄音設備,也即為音頻數(shù)據(jù)分配數(shù)據(jù)區(qū)。需要考慮的第一個問題是數(shù)據(jù)塊的個數(shù)和大小。
系統(tǒng)一般在填滿一個數(shù)據(jù)塊后將其返回,這意味著大的數(shù)據(jù)塊導致獲取數(shù)據(jù)的頻率降低。如果試圖通過網絡傳輸這些數(shù)據(jù),這意味著傳輸次數(shù)降低,但每次傳輸?shù)臄?shù)據(jù)量較大,這將導致音頻的實時性降低。另外,在網絡傳輸過程中,數(shù)據(jù)包(指每次發(fā)送的UDP或TCP數(shù)據(jù)包)的開始一般需設置一個信息頭,紀錄本數(shù)據(jù)包所含音頻數(shù)據(jù)的長度、錄制起始時間、錄制時間、發(fā)送時間、識別用的標識符以及其他一些信息,方便接受者處理。如果設置的音頻數(shù)據(jù)塊太小,比如100字節(jié),將導致低的網絡傳輸效率。這意味著網絡將為小量數(shù)據(jù)啟動傳輸過程,而這小量數(shù)據(jù)中又有相當一部分屬于附加信息,這是不合算的。
數(shù)據(jù)塊的個數(shù)相對來說要好處理的多。較多的數(shù)據(jù)塊只有一個問題,那就是浪費,但這對Windows這樣的操作系統(tǒng)而言,浪費幾百K的內存根本不是問題。數(shù)據(jù)塊個數(shù)不能太少,這是因為在你處理上次返回的數(shù)據(jù)塊時,錄音設備正在工作,需要內存。如果在某一段時間內,比如說網絡傳輸變壞,你處理數(shù)據(jù)的速度也將變慢,而錄音設備卻如常工作。這導致你無法迅速地返回當前內存供下一次錄音使用,因此需要一些備用的內存塊以備不時之需。
調用waveInAddBuffer為錄音設備配置數(shù)據(jù)塊,而在此之前需調用waveInPrepareHeader準備數(shù)據(jù)塊。
WaveInPrepareHeader的參數(shù)有三個,就是錄音設備句柄、一個描述數(shù)據(jù)塊的結構WAVEHDR和該結構的大小。
WAVEHDR結構是錄音編程的常用數(shù)據(jù)結構,不同的使用環(huán)境設置不同的參數(shù),具體內容參見MSDN。
下面的示例為錄音設備添加4個大小為4096字節(jié)的數(shù)據(jù)塊:
for( i=0; i<4; i++ )
{
pwh = (WAVEHDR*)malloc( 4096 + sizeof(WAVEHDR) )
ZeroMemory( pwh, sizeof(WAVEHDR) );
pwh->dwBufferLength = 4096;
pwh->lpData = (LPSTR)(pwh + 1);
waveInPrepareHeader( hWaveIn, pwh, sizeof(WAVEHDR) );
waveInAddBuffer( hWaveIn, pwh, sizeof(WAVEHDR) );
}
每次添加數(shù)據(jù)塊都需一個WAVEHDR(其他操作也如此),能否讓這些數(shù)據(jù)塊共用一個WAVEHDR或使用局部變量以減少內存消耗呢?最好不要這么做,因為你是在與實際設備打交道。很可能當調用返回時,系統(tǒng)內部卻還在使用WAVEHDR結構。如果共用或使用局部變量將導致信息混亂或非法內存操作。這一點MSDN沒有強調,我花了一星期才得到如此教訓。其他錄音操作也類似于此,切記!
配置好內存以后就可啟動錄音了:
waveInStart( hWaveIn );
如果所有操作均正確,很快你將得到第一塊音頻數(shù)據(jù)。
2.3 處理音頻數(shù)據(jù)塊
獲取音頻數(shù)據(jù)塊的方式有多種(詳見MSDN),處理方式大同小異,本文以回調函數(shù)方式為例說明處理過程。如上所述,waveInProc在waveInOpen調用中被設置為回調函數(shù)。
waveInProc通過處理WIM_OPEN(打開設備)、WIM_DATA(音頻數(shù)據(jù))、WIM_CLOSE(設備關閉)消息的方式與系統(tǒng)交互,通常只需處理WIM_DATA消息,其他兩條消息可以忽略。
當系統(tǒng)返回一個音頻數(shù)據(jù)塊時,系統(tǒng)調用回調函數(shù)。此時,回調函數(shù)第一個參數(shù)被設置為錄音設備句柄;第二個參數(shù)是消息,即WIM_DATA;第三個參數(shù)則是在waveInOpen第五個參數(shù)中給出的實例數(shù)據(jù);其他參數(shù)是與消息有關的參數(shù),參見MSDN。
處理WIM_DATA的任務由成員函數(shù)OnWaveInData完成,因此waveInProc及其簡單:
CWaveRecord *pObject = (CWaveRecord *)dwInstance;
if( uMsg == WIM_DATA )
pObject->OnWaveInData( (WAVEHDR*)dwParam1 );
在OnWaveInData中需要完成兩件事:一是保存系統(tǒng)返回的音頻數(shù)據(jù),二是給錄音設備添加數(shù)據(jù)塊以繼續(xù)錄音,否則會因數(shù)據(jù)塊耗盡而停止錄音。
在保存系統(tǒng)返回的音頻數(shù)據(jù)塊之前,需要調用waveInUnprepareHeader發(fā)送一個通知告訴設備驅動程序該數(shù)據(jù)塊已不能用于錄音。保存音頻數(shù)據(jù)塊是個單純的數(shù)據(jù)保存問題,不需多說,只是有三個要點需要特別關注:
一是MSDN中注明在回調函數(shù)中嚴格限制系統(tǒng)調用,因此你不能隨心所欲地設計方案,比如說直接將音頻數(shù)據(jù)寫入文件是不允許的。通常,你需要設計一個數(shù)據(jù)塊鏈表結構進行緩沖,這多半涉及多線程編程。特別是如果你使用MFC中的CSocket類,那么必須清楚CSocket依賴一個名為CSocketWnd的內部類,而該類與TLS(線程局部存貯)有關,這意味著CSocket類是不支持多線程共享的。然而,Win32的SOCKET句柄是全局性。因此,獲取CSocketWnd類對象的窗口句柄之后,我通過直接調用Win32 APIs使用CSocket::m_hSocket避開這個由MFC封裝引起的問題。
二是系統(tǒng)是在另一個線程(不是你自己創(chuàng)建的線程)中調用回調函數(shù),這會給使用TLS機制的程序帶來一些微妙的影響。如果你使用MFC,AfxGetApp之類的函數(shù)返回值不正確。
三是盡可能地快速。
至于給錄音設備添加數(shù)據(jù)塊以繼續(xù)錄音,其步驟與初始化過程一樣:
waveInPrepareHeader( hWaveIn, pwh, sizeof(WAVEHDR));
waveInAddBuffer( hWaveIn, pwh, sizeof(WAVEHDR) );
2.4 關閉設備釋放資源
簡單的調用waveInClose即可關閉設備。不過,在關閉時必須保證所有錄音數(shù)據(jù)塊已全部返回,這有點麻煩。我的解決方法是設置一個停止錄音標志和一個錄音數(shù)據(jù)塊計數(shù)。當用戶發(fā)出停止錄音的命令后,該標志置為TRUE,而OnWaveInData(在回調函數(shù)中被調用)檢查該標志,在標志置位的情況下,OnWaveInData不添加錄音數(shù)據(jù)塊。每添加一個錄音數(shù)據(jù)塊計數(shù)增加,每返回一個則減少(均在OnWaveInData中實現(xiàn))。執(zhí)行停止錄音命令時,先將停止標志值位,等待計數(shù)變?yōu)榱?#xff0c;然后才調用waveInClose。需要注意的是,計數(shù)涉及多線程數(shù)據(jù)共享,應使用線程互斥機制,在單CPU的機子上使用InterlockedIncrement和InterlockedDecrement是個簡單的選擇。
3 編程技巧
3.1 簡單的聲音檢測
如果你通過網絡發(fā)送音頻數(shù)據(jù),自然需要進行音頻處理。專業(yè)級的處理難度相當高,如果你是在一個局域網上進行通話,那么無需進行音頻壓縮也可以將就使用(性能自然不高)。但如果你不說話,程序還不停地發(fā)送音頻數(shù)據(jù)(靜音或噪音),那就太說不過去了。
你可以定義一個靜音區(qū)間,一旦音頻強度進入該區(qū)間就意味著沒有聲音。統(tǒng)計音頻數(shù)據(jù)塊中不是靜音采樣點的個數(shù),當總數(shù)超過預設的限制時,才發(fā)送該數(shù)據(jù)塊。
對于8位采樣,127是靜音點。簡單的代碼如下:
for( i=0; i<nMax; i++ )
{
uValue = (UCHAR)data[i];
if( uValue<=125 || uValue>=129 )
uHasCnt++;
if( uHasCnt >= uLestNum )
return TRUE;
}
return FALSE;
3.2 創(chuàng)建音頻文件
創(chuàng)建任意格式的音頻文件在編程上不是一件輕松的事,但對于8位單聲道音頻數(shù)據(jù)而言較容易。定義文件信息頭結構:
// wav文件頭結構, 對齊方式為1字節(jié)
typedef struct
{
char RiffId[4]; // "RIFF"
DWORD dwFileDataSize; // file size - 8
char WaveId[4]; // "WAVE"
char FMTId[4]; // "fmt "
DWORD dwFmtSize; // 16
WORD wFormatTag; // WAVE_FORMAT_PCM
WORD wChannels; // 1
DWORD dwSamplesPerSec; // 11025
DWORD dwAvgBytesPerSec; // 11025
WORD wBlockAlign; // 1
WORD wBitsPerSample; // 8
char DataId[4]; // "data"
DWORD dwDataSize;
} WAVEFILEHDR, *PWAVEFILEHDR;
打開文件時,預留該結構大小的空間:
SetFilePointer(hFile,sizeof(WAVEFILEHDR), NULL, FILE_BEGIN );
然后就如寫一般文件一樣將接收的音頻數(shù)據(jù)寫入文件:
WriteFile(hFile,pwh->lpData,pwh->dwBytesRecorded,&n, NULL );
m_dwSizeWrite += dwSize;
最后一行代碼是將寫入的數(shù)據(jù)量統(tǒng)計下來用于信息頭填寫。
在關閉音頻文件時填寫信息頭:
WAVEFILEHDR wfd =
{
'R', 'I', 'F', 'F', 0,
'W', 'A', 'V', 'E',
'f', 'm', 't', ' ', 16,
WAVE_FORMAT_PCM, 1, 11025, 11025, 1, 8,
'd', 'a', 't', 'a', 0
};
wfd.dwFileDataSize = dwBytes + sizeof(WAVEFILEHDR)- 8;
wfd.dwDataSize = dwBytes;
::SetFilePointer( hFile, 0, NULL, FILE_BEGIN );
::WriteFile( hFile, &wfd, sizeof(wfd), &dwSize, NULL );
參考文獻
1 Visual C++ 6.0 MSDN
2 Win32程序員開發(fā)指南(二)
3 Visual C++ 6.0附帶MFC源碼
在Win32 APIs基礎上編寫錄音程序繁瑣易錯,使用封裝好的類是個不錯的注意。不幸的是所謂封裝好的類對你而言,往往是代碼羅嗦且功能不足,因此盡管你可能希望在某個項目上因使用封裝好的類而避開Win32 APIs,可最終你發(fā)現(xiàn)你還得面對它。不是為了編寫自己的類,就是為了修改別人的代碼。
Win32 APIs中有一組被稱成多媒體控制接口(即MCI)的函數(shù),該接口提供了多媒體編程所需的系統(tǒng)級APIs。對絕大多數(shù)C/C++程序員而言,這些函數(shù)也就是Windows多媒體編程的最低層接口。
由于錄音代碼直接操作真實的錄音設備而非單純的邏輯過程,因此會遇到一些“意外”或與時序有關的困難,從而使編寫健壯的代碼成了一件困難的事。
錄音的目的往往是將聲音寫入文件保存下來或是通過網絡發(fā)送,這兩類需求對錄音代碼影響較大。前者無實時性要求,一般也不限制數(shù)據(jù)量,代碼較易編寫,而后者既對實時性又要求,又對數(shù)據(jù)量(也就是音頻格式)有要求,且網絡系統(tǒng)的穩(wěn)定性也難比文件系統(tǒng)。期望在本文中試圖解決所有的問題是不現(xiàn)實的,諸如如何壓縮音頻數(shù)據(jù)以減小網絡帶寬需求或如何通過網絡傳輸音頻數(shù)據(jù)之類的問題,讀者需參閱專業(yè)著作,本文只講述如何獲得適宜某類需求的原始音頻數(shù)據(jù)。
本文中的示例代碼均是一個名為CWaveRecord的錄音類的成員函數(shù),該類是一個網絡電話程序的核心類。為減小篇幅或簡單明了,作了簡化處理等。
2 基本過程
MCI按打開設備、配置設備、實現(xiàn)功能(或曰發(fā)送命令)、撤銷配置、關閉設備的標準次序組織APIs。對于錄音編程而言,其要點在于根據(jù)音頻格式打開對應的設備、配置錄音所需的參數(shù)(主要是設置數(shù)據(jù)區(qū)以及根據(jù)數(shù)據(jù)接收方式設置回調函數(shù)或消息)、按一定次序發(fā)送命令給設備、接收數(shù)據(jù)并配置參數(shù)以繼續(xù)錄音、停止錄音釋放資源、關閉設備等幾個步驟上。所需的函數(shù)說明于mmsystem.h,引入庫是winmm.lib。
2.1 打開設備
調用waveInOpen()打開錄音設備,打開成功后函數(shù)返回MMSYSERR_NOERROR,而第一個參數(shù)返回設備句柄。
調用該函數(shù)時,必須指定設備表示符(可能有多個設備)、音頻格式以及返回音頻數(shù)據(jù)的方式。
你可以將設備表示符,即第二個參數(shù)設為WAVE_MAPPER。這意味著,你不在意具體使用哪個設備,由系統(tǒng)決定。
第三個參數(shù)是個WAVEFORMATEX結構指針,對應的結構指定音頻格式。一個音頻格式結構的例子:
PCMWAVEFORMAT wf_PCM8S11K =
{
{
WAVE_FORMAT_PCM, // PCM格式
1, // 單聲道
11025L, // 11.025KHZ
11025L, // 11025字節(jié)/s
1 // 字節(jié)對齊
},
8 // 8位采樣
};
后面的三個參數(shù)用于指定獲取音頻數(shù)據(jù)的方式:事件方式、線程方式、窗口方式以及回調函數(shù)方式。最后一個參數(shù),即第六個參數(shù)指定方式,而第四個參數(shù)和第五個參數(shù)指定該方式所需的信息。
調用示例:
waveInOpen( &m_hWaveIn, dwDeviceID, m_pwf,
(DWORD)waveInProc, (DWORD)this, CALLBACK_FUNCTION );
該示例使用回調函數(shù)方式獲取音頻數(shù)據(jù)。其中dwDeviceID通常即為WAVE_MAPPER,m_pwf指向音頻格式結構,缺省值即wf_PCM8S11K的地址,waveInProc是回調函數(shù)(內部細節(jié)后面論述)。值得注意的是,this指針被設為實例數(shù)據(jù),這是一個編程小技巧,方便waveInProc調用處理函數(shù)。
2.2 配置設備
按指定音頻格式成功打開錄音設備之后,需要配置錄音設備,也即為音頻數(shù)據(jù)分配數(shù)據(jù)區(qū)。需要考慮的第一個問題是數(shù)據(jù)塊的個數(shù)和大小。
系統(tǒng)一般在填滿一個數(shù)據(jù)塊后將其返回,這意味著大的數(shù)據(jù)塊導致獲取數(shù)據(jù)的頻率降低。如果試圖通過網絡傳輸這些數(shù)據(jù),這意味著傳輸次數(shù)降低,但每次傳輸?shù)臄?shù)據(jù)量較大,這將導致音頻的實時性降低。另外,在網絡傳輸過程中,數(shù)據(jù)包(指每次發(fā)送的UDP或TCP數(shù)據(jù)包)的開始一般需設置一個信息頭,紀錄本數(shù)據(jù)包所含音頻數(shù)據(jù)的長度、錄制起始時間、錄制時間、發(fā)送時間、識別用的標識符以及其他一些信息,方便接受者處理。如果設置的音頻數(shù)據(jù)塊太小,比如100字節(jié),將導致低的網絡傳輸效率。這意味著網絡將為小量數(shù)據(jù)啟動傳輸過程,而這小量數(shù)據(jù)中又有相當一部分屬于附加信息,這是不合算的。
數(shù)據(jù)塊的個數(shù)相對來說要好處理的多。較多的數(shù)據(jù)塊只有一個問題,那就是浪費,但這對Windows這樣的操作系統(tǒng)而言,浪費幾百K的內存根本不是問題。數(shù)據(jù)塊個數(shù)不能太少,這是因為在你處理上次返回的數(shù)據(jù)塊時,錄音設備正在工作,需要內存。如果在某一段時間內,比如說網絡傳輸變壞,你處理數(shù)據(jù)的速度也將變慢,而錄音設備卻如常工作。這導致你無法迅速地返回當前內存供下一次錄音使用,因此需要一些備用的內存塊以備不時之需。
調用waveInAddBuffer為錄音設備配置數(shù)據(jù)塊,而在此之前需調用waveInPrepareHeader準備數(shù)據(jù)塊。
WaveInPrepareHeader的參數(shù)有三個,就是錄音設備句柄、一個描述數(shù)據(jù)塊的結構WAVEHDR和該結構的大小。
WAVEHDR結構是錄音編程的常用數(shù)據(jù)結構,不同的使用環(huán)境設置不同的參數(shù),具體內容參見MSDN。
下面的示例為錄音設備添加4個大小為4096字節(jié)的數(shù)據(jù)塊:
for( i=0; i<4; i++ )
{
pwh = (WAVEHDR*)malloc( 4096 + sizeof(WAVEHDR) )
ZeroMemory( pwh, sizeof(WAVEHDR) );
pwh->dwBufferLength = 4096;
pwh->lpData = (LPSTR)(pwh + 1);
waveInPrepareHeader( hWaveIn, pwh, sizeof(WAVEHDR) );
waveInAddBuffer( hWaveIn, pwh, sizeof(WAVEHDR) );
}
每次添加數(shù)據(jù)塊都需一個WAVEHDR(其他操作也如此),能否讓這些數(shù)據(jù)塊共用一個WAVEHDR或使用局部變量以減少內存消耗呢?最好不要這么做,因為你是在與實際設備打交道。很可能當調用返回時,系統(tǒng)內部卻還在使用WAVEHDR結構。如果共用或使用局部變量將導致信息混亂或非法內存操作。這一點MSDN沒有強調,我花了一星期才得到如此教訓。其他錄音操作也類似于此,切記!
配置好內存以后就可啟動錄音了:
waveInStart( hWaveIn );
如果所有操作均正確,很快你將得到第一塊音頻數(shù)據(jù)。
2.3 處理音頻數(shù)據(jù)塊
獲取音頻數(shù)據(jù)塊的方式有多種(詳見MSDN),處理方式大同小異,本文以回調函數(shù)方式為例說明處理過程。如上所述,waveInProc在waveInOpen調用中被設置為回調函數(shù)。
waveInProc通過處理WIM_OPEN(打開設備)、WIM_DATA(音頻數(shù)據(jù))、WIM_CLOSE(設備關閉)消息的方式與系統(tǒng)交互,通常只需處理WIM_DATA消息,其他兩條消息可以忽略。
當系統(tǒng)返回一個音頻數(shù)據(jù)塊時,系統(tǒng)調用回調函數(shù)。此時,回調函數(shù)第一個參數(shù)被設置為錄音設備句柄;第二個參數(shù)是消息,即WIM_DATA;第三個參數(shù)則是在waveInOpen第五個參數(shù)中給出的實例數(shù)據(jù);其他參數(shù)是與消息有關的參數(shù),參見MSDN。
處理WIM_DATA的任務由成員函數(shù)OnWaveInData完成,因此waveInProc及其簡單:
CWaveRecord *pObject = (CWaveRecord *)dwInstance;
if( uMsg == WIM_DATA )
pObject->OnWaveInData( (WAVEHDR*)dwParam1 );
在OnWaveInData中需要完成兩件事:一是保存系統(tǒng)返回的音頻數(shù)據(jù),二是給錄音設備添加數(shù)據(jù)塊以繼續(xù)錄音,否則會因數(shù)據(jù)塊耗盡而停止錄音。
在保存系統(tǒng)返回的音頻數(shù)據(jù)塊之前,需要調用waveInUnprepareHeader發(fā)送一個通知告訴設備驅動程序該數(shù)據(jù)塊已不能用于錄音。保存音頻數(shù)據(jù)塊是個單純的數(shù)據(jù)保存問題,不需多說,只是有三個要點需要特別關注:
一是MSDN中注明在回調函數(shù)中嚴格限制系統(tǒng)調用,因此你不能隨心所欲地設計方案,比如說直接將音頻數(shù)據(jù)寫入文件是不允許的。通常,你需要設計一個數(shù)據(jù)塊鏈表結構進行緩沖,這多半涉及多線程編程。特別是如果你使用MFC中的CSocket類,那么必須清楚CSocket依賴一個名為CSocketWnd的內部類,而該類與TLS(線程局部存貯)有關,這意味著CSocket類是不支持多線程共享的。然而,Win32的SOCKET句柄是全局性。因此,獲取CSocketWnd類對象的窗口句柄之后,我通過直接調用Win32 APIs使用CSocket::m_hSocket避開這個由MFC封裝引起的問題。
二是系統(tǒng)是在另一個線程(不是你自己創(chuàng)建的線程)中調用回調函數(shù),這會給使用TLS機制的程序帶來一些微妙的影響。如果你使用MFC,AfxGetApp之類的函數(shù)返回值不正確。
三是盡可能地快速。
至于給錄音設備添加數(shù)據(jù)塊以繼續(xù)錄音,其步驟與初始化過程一樣:
waveInPrepareHeader( hWaveIn, pwh, sizeof(WAVEHDR));
waveInAddBuffer( hWaveIn, pwh, sizeof(WAVEHDR) );
2.4 關閉設備釋放資源
簡單的調用waveInClose即可關閉設備。不過,在關閉時必須保證所有錄音數(shù)據(jù)塊已全部返回,這有點麻煩。我的解決方法是設置一個停止錄音標志和一個錄音數(shù)據(jù)塊計數(shù)。當用戶發(fā)出停止錄音的命令后,該標志置為TRUE,而OnWaveInData(在回調函數(shù)中被調用)檢查該標志,在標志置位的情況下,OnWaveInData不添加錄音數(shù)據(jù)塊。每添加一個錄音數(shù)據(jù)塊計數(shù)增加,每返回一個則減少(均在OnWaveInData中實現(xiàn))。執(zhí)行停止錄音命令時,先將停止標志值位,等待計數(shù)變?yōu)榱?#xff0c;然后才調用waveInClose。需要注意的是,計數(shù)涉及多線程數(shù)據(jù)共享,應使用線程互斥機制,在單CPU的機子上使用InterlockedIncrement和InterlockedDecrement是個簡單的選擇。
3 編程技巧
3.1 簡單的聲音檢測
如果你通過網絡發(fā)送音頻數(shù)據(jù),自然需要進行音頻處理。專業(yè)級的處理難度相當高,如果你是在一個局域網上進行通話,那么無需進行音頻壓縮也可以將就使用(性能自然不高)。但如果你不說話,程序還不停地發(fā)送音頻數(shù)據(jù)(靜音或噪音),那就太說不過去了。
你可以定義一個靜音區(qū)間,一旦音頻強度進入該區(qū)間就意味著沒有聲音。統(tǒng)計音頻數(shù)據(jù)塊中不是靜音采樣點的個數(shù),當總數(shù)超過預設的限制時,才發(fā)送該數(shù)據(jù)塊。
對于8位采樣,127是靜音點。簡單的代碼如下:
for( i=0; i<nMax; i++ )
{
uValue = (UCHAR)data[i];
if( uValue<=125 || uValue>=129 )
uHasCnt++;
if( uHasCnt >= uLestNum )
return TRUE;
}
return FALSE;
3.2 創(chuàng)建音頻文件
創(chuàng)建任意格式的音頻文件在編程上不是一件輕松的事,但對于8位單聲道音頻數(shù)據(jù)而言較容易。定義文件信息頭結構:
// wav文件頭結構, 對齊方式為1字節(jié)
typedef struct
{
char RiffId[4]; // "RIFF"
DWORD dwFileDataSize; // file size - 8
char WaveId[4]; // "WAVE"
char FMTId[4]; // "fmt "
DWORD dwFmtSize; // 16
WORD wFormatTag; // WAVE_FORMAT_PCM
WORD wChannels; // 1
DWORD dwSamplesPerSec; // 11025
DWORD dwAvgBytesPerSec; // 11025
WORD wBlockAlign; // 1
WORD wBitsPerSample; // 8
char DataId[4]; // "data"
DWORD dwDataSize;
} WAVEFILEHDR, *PWAVEFILEHDR;
打開文件時,預留該結構大小的空間:
SetFilePointer(hFile,sizeof(WAVEFILEHDR), NULL, FILE_BEGIN );
然后就如寫一般文件一樣將接收的音頻數(shù)據(jù)寫入文件:
WriteFile(hFile,pwh->lpData,pwh->dwBytesRecorded,&n, NULL );
m_dwSizeWrite += dwSize;
最后一行代碼是將寫入的數(shù)據(jù)量統(tǒng)計下來用于信息頭填寫。
在關閉音頻文件時填寫信息頭:
WAVEFILEHDR wfd =
{
'R', 'I', 'F', 'F', 0,
'W', 'A', 'V', 'E',
'f', 'm', 't', ' ', 16,
WAVE_FORMAT_PCM, 1, 11025, 11025, 1, 8,
'd', 'a', 't', 'a', 0
};
wfd.dwFileDataSize = dwBytes + sizeof(WAVEFILEHDR)- 8;
wfd.dwDataSize = dwBytes;
::SetFilePointer( hFile, 0, NULL, FILE_BEGIN );
::WriteFile( hFile, &wfd, sizeof(wfd), &dwSize, NULL );
參考文獻
1 Visual C++ 6.0 MSDN
2 Win32程序員開發(fā)指南(二)
3 Visual C++ 6.0附帶MFC源碼
總結
以上是生活随笔為你收集整理的Win32下的录音编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言1e12怎么识别,求大神帮助词法分
- 下一篇: tsp遗传算法 c语言,【分享】遗传算法