對於Windows的音頻採集來講,方法有不少。能夠經過較爲上層的多媒體框架去採集,如DirectShow、MediaFoundation這些;也能夠經過較爲底層的API去採集,如WASAPI(Windows Audio Session API)。這裏我介紹的是使用WASAPI的方法,畢竟採集和渲染均可以在這裏去實現了。緩存
這裏簡單介紹一下使用這個API來採集須要瞭解的兩個接口:bash
咱們先來看看Windows下的音頻框架關係圖:框架
從圖中咱們能夠看到,音頻流會有兩條路,一條是走向shared mode,一條則是exclusive mode,分別表明着共享模式和獨佔模式。對於獨佔模式這條路線,咱們能夠看到它直接連到了Audio Driver,也就是音頻驅動中了,而共享模式還須要通過一個Audio Engine的過程,這個Audio Engine最終會把當前的音頻流,還有其它來自其它應用的音頻流進行混音,最後才傳送到Audio Driver中去。WASAPI有兩種採集模式,一種是輸入設備的採集,如帶麥克風的耳機,麥克風這些;還有一種則是聲卡輸出的採集,也稱做loop back mode。因爲兩種採集模式是分開的,若是須要同時聽到輸入設備的聲音和聲卡輸出的聲音,則須要對採集到的兩路音頻流進行混音。oop
首先,咱們須要經過COM接口來獲取音頻設備枚舉實例,代碼以下:ui
HRSULT hr;
IMMDeviceEnumerator* pEnumerator = NULL;
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pEnumerator));
複製代碼
枚舉音頻設備,咱們能夠經過EnumAudioEndPoints來一個個進行枚舉並選擇合適的,但更多狀況下咱們是直接使用GetDefaultAudioEndpoint來獲取一個默認的,且處於active狀態的音頻設備。代碼以下:spa
IMMDevice* pDevice = NULL;
hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice);
複製代碼
獲取完音頻設備以後,咱們能夠直接調用其activate方法(相似於工廠模式的用法),來獲取IAudioClient:code
IAudioClient* pAudioClient = NULL;
hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL,(void**) &pAudioClient);
複製代碼
因爲咱們使用的是共享模式,所以咱們須要得到Audio Engine的混音格式,經過調用GetMixFormat來得到,代碼以下:orm
WAVEFORMATEX* pwfx = NULL;
hr = pAudioClient->GetMixFormat(&pwfx);
複製代碼
其中,WAVEFORMATEX的結構體就包含了咱們所須要的混音格式,裏面有音頻格式,聲道數,採樣率,樣本大小等信息。其中音頻格式還須要將這個結構體擴展(也就是強轉)成WAVEFORMATEXTENSIBLE這個結構體才能得到,這部分具體可參閱文檔。cdn
接下來就是初始化了,即調用Initialize接口。這裏面的參數仍是須要特別注意的,咱們先來看看有哪些參數:blog
HRESULT Initialize(
AUDCLNT_SHAREMODE ShareMode,
DWORD StreamFlags,
REFERENCE_TIME hnsBufferDuration,
REFERENCE_TIME hnsPeriodicity,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid
);
複製代碼
對於第一個參數,以前咱們也說了,咱們是在共享模式下進行採集的,所以這裏填AUDCLNT_SHAREMODE_SHARED。
對於第二個參數,若是是初始化輸入設備的採集的話,直接填NULL便可。若是是採集聲卡輸出的,即loop back模式的話,則須要填入AUDCLNT_STREAMFLAGS_LOOPBACK。
第三個是咱們的緩存大小,對於採集到的音樂,WASAPI會幫咱們放到這個緩存中。至於要設置成多大,其實通常是根據你的採集週期來肯定的,你能夠根據實際狀況簡單計算一下,設置成稍微比實際的大一點就好,這樣能夠避免溢出致使數據丟失的狀況。
第四個參數是用於獨佔模式的,咱們這裏用不到,直接填0。
第五個參數就是咱們以前得到的混音格式。
最後一個參數是指定音頻Session,這裏咱們不關心這個,直接填NULL,讓它本身幫咱們建立一個Session,並把咱們的音頻流加入到其中。
最後一步即是獲取咱們的採集接口,並開始採集,代碼以下:
IAudioCaptureClient* pCaptureClient = NULL;
hr = pAudioClient->GetService(
IID_IAudioCaptureClient,
(void**)&pCaptureClient);
hr = pAudioClient->Start();
複製代碼
咱們經過GetNextPacketSize來判斷下一個數據包的大小,若是不爲0的話,咱們就經過GetBuffer來獲取一個數據包,依此類推,直到GetNextPacketSize返回的大小爲0,說明咱們這一個週期的數據已經獲取完了,此時咱們能夠休眠一個週期,而後再去獲取數據。代碼以下:
while(1)
{
//休眠一個週期
Sleep(duration);
hr = pCaptureClient->GetNextPacketSize(&packetLength);
while(packetLength)
{
hr = pCaptureClient->GetBuffer(
&pData,
&numFramesAvailable,
&flags, NULL, NULL);
//DoSomething()....
hr = pCaptureClient->ReleaseBuffer(numFramesAvailable);
hr = pCaptureClient->GetNextPacketSize(&packetLength);
}
}
複製代碼
結束採集後調用stop方法便可:
hr = pAudioClient->Stop();
複製代碼
若是要同時聽到人說話的聲音和電腦裏的聲音,就須要對這兩路音頻流進行混音,即對兩路音頻流按必定的比例進行疊加。這一部分能夠由第三方的媒體庫來完成,如ffmpeg,咱們能夠利用amix濾鏡來實現。若是要調整音量的話,能夠加上volume濾鏡來去調節。具體細節不屬於本節範疇,所以就不展開細講了。 須要注意的是,當電腦的中沒有產生聲音時,咱們是獲取不到音頻數據的,也就是GetNextPacketSize返回的長度是0.對於混音來講,所傳入的音頻數據的長度應該是要對等的,傳入採集到的輸入設備的一秒鐘的音頻數據,對應loop back來講對應的確定也得是一秒鐘的音頻數據。所以,對於loop back的採集來講,若此刻只有麥克風的輸入,咱們能夠適當的填充一些silence字節,來表示這部分是沒有聲音的,而後再做爲輸入去混音便可。