談談Windows下的音頻採集

  對於Windows的音頻採集來講,方法有不少。能夠經過較爲上層的多媒體框架去採集,如DirectShow、MediaFoundation這些;也能夠經過較爲底層的API去採集,如WASAPI(Windows Audio Session API)。這裏我介紹的是使用WASAPI的方法,畢竟採集和渲染均可以在這裏去實現了。緩存

WASAPI的簡單介紹

  這裏簡單介紹一下使用這個API來採集須要瞭解的兩個接口:bash

  • IAudioClient:音頻使用端,用於建立並初始化音頻流
  • IAudioCaptureClient:音頻採集端,提供了採集音頻的接口

獨佔模式和共享模式

  咱們先來看看Windows下的音頻框架關係圖:框架

從圖中咱們能夠看到,音頻流會有兩條路,一條是走向shared mode,一條則是exclusive mode,分別表明着共享模式和獨佔模式。對於獨佔模式這條路線,咱們能夠看到它直接連到了Audio Driver,也就是音頻驅動中了,而共享模式還須要通過一個Audio Engine的過程,這個Audio Engine最終會把當前的音頻流,還有其它來自其它應用的音頻流進行混音,最後才傳送到Audio Driver中去。
  對於混音,若是兩條音頻流的採樣率不一樣的話,確定要先對某些音頻流去作重採樣。當高採樣率向低採樣率轉換時,就會發生精度的丟失,而對於獨佔模式來講,因爲獨佔了一路音頻流,所以無需進行這一步重採樣的過程。所以,對於獨佔模式來講,通常音質效果確定是要好一些的。
  至於如何選擇,仍是得看本身的實際需求。對於音頻採集來講,咱們大部分狀況下都會去選擇共享模式,這樣咱們能夠同時採集到遊戲裏的聲音,音樂播放器所播放的聲音等等。

採集模式

  WASAPI有兩種採集模式,一種是輸入設備的採集,如帶麥克風的耳機,麥克風這些;還有一種則是聲卡輸出的採集,也稱做loop back mode。因爲兩種採集模式是分開的,若是須要同時聽到輸入設備的聲音和聲卡輸出的聲音,則須要對採集到的兩路音頻流進行混音。oop

WASAPI的簡單使用

獲取音頻設備枚舉實例:

  首先,咱們須要經過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();
複製代碼

輸入設備與loop back混音

  若是要同時聽到人說話的聲音和電腦裏的聲音,就須要對這兩路音頻流進行混音,即對兩路音頻流按必定的比例進行疊加。這一部分能夠由第三方的媒體庫來完成,如ffmpeg,咱們能夠利用amix濾鏡來實現。若是要調整音量的話,能夠加上volume濾鏡來去調節。具體細節不屬於本節範疇,所以就不展開細講了。   須要注意的是,當電腦的中沒有產生聲音時,咱們是獲取不到音頻數據的,也就是GetNextPacketSize返回的長度是0.對於混音來講,所傳入的音頻數據的長度應該是要對等的,傳入採集到的輸入設備的一秒鐘的音頻數據,對應loop back來講對應的確定也得是一秒鐘的音頻數據。所以,對於loop back的採集來講,若此刻只有麥克風的輸入,咱們能夠適當的填充一些silence字節,來表示這部分是沒有聲音的,而後再做爲輸入去混音便可。

相關文章
相關標籤/搜索