Windows Core Audio 音頻開發技術指南

在音視頻通訊處理流程中,音頻方面最基本的無外乎就是音頻的採集和播放。windows 平臺下,有不少音頻採集播放的方法。做爲一個 windows 端音頻應用程序開發人員,常常會被各類可用的API淹沒,好比 MME、DirectSound、WDM/KS 和 Core Audio。可是幾乎全部作音視頻通訊的開發者都會選擇 Core Audio 做爲採集播放的底層 API。在本篇內容中咱們將主要圍繞 Core Audio,講解它的優劣勢,以及咱們基於它來作 windows 音頻採集播放的技術實踐。git

#1windows

Why Core Audio?緩存

爲何選擇 Core Audio?咱們先來了解一下如今主流的一些 Windows APIs的優劣勢。安全

1.1 Windows Multimedia Extensions (MME/WinMM)markdown

MME是第一個適用於Windows的標準API。app

優點:MME方法實現簡單。框架

劣勢:延時是一個重大的問題,動態,實時的音頻(好比實時音頻通話,遊戲事件通知等)有點難以及時處理,通常最小時延能達到120ms。在實時音頻場景中,任何比大腦認爲應該發生的時間晚10毫秒的事情都被認爲是不一樣步的。佈局

1.2 DirectSound(DirectX Audio)性能

DirectX 是基於COM的多媒體API集合的總稱,其中包括 DirectSound。ui

優點:

1)它能夠很是接近硬件工做,極限最小延遲可到60毫秒左右,並支持更高質量的音頻;

2)可經過簡單的API使得與硬件交互變得切實可行;

3)爲平臺帶來了可插拔的、基於軟件的音頻效果(DX 效果)和樂器(DXi Instruments)。

1.3 Windows Driver Model/Kernel Streaming (WDM/KS)

使用 WDM 後,MME 和 DirectSound 音頻如今都經過稱爲內核音頻混合器(一般稱爲 KMixer)的東西。KMixer 是一個內核模式組件,負責將全部系統音頻混合在一塊兒。可是 KMixer 也會引入了延遲,大概30毫秒,事實上,有時會更多。爲了減小 KMixer 帶來的時延,WDM/KS 的方案誕生了。

優點:可將延遲作到極低的狀態,通常最小延遲能夠到1毫秒~10毫秒,且在必定狀況下可使用非分頁內存、直接硬件IRP和RT,獨佔聲卡的全部資源。

劣勢:

1)獨佔了聲卡的全部資源,致使只能聽到特定應用程序的聲音。當多個程序開啓時,是沒法聽到其餘應用程序的聲音的;

2)KS 也沒有音頻輸入,即麥克風也是沒法使用的。

注意:在 Vista 和 Windows7以後,KMixer 已經被棄用了,KS並不適用於 Vista 和 Windows7以後的版本。

1.4 Audio Stream Input Output(ASIO)

ASIO 最初是 Windows 的專業音頻級驅動程序規範,由一家名叫 Steinberg 的德國公司所提出的。

優點:爲應用程序提供直接從應用程序到聲音硬件的高質量、低延遲的數據路徑。對於能夠支持 ASIO 的應用程序,能夠徹底避免全部處理Windows 音頻堆棧的業務,將系統對音頻流的響應時間降至最短。使用 ASIO 的狀況下,緩衝器依照設定的不一樣可至10毫秒如下,也有因環境較佳而到1毫秒如下的狀況產生。

劣勢:若是您嘗試使用的音頻應用程序僅支持 ASIO,而您的聲卡是廉價的、缺少 ASIO 支持的,那麼使用 ASIO 就是一個問題了。ASIO 的實際性能取決於製造商提供的驅動程序的質量。

1.5 Windows Core Audio

2007年,Vista 最終上架時,Windows Core Audio 也面世了。微軟宣稱,vista/7 已經開始棄用了 kmixer 和依賴 dma 的 audio IO。熟悉和喜好的全部音頻API都被洗牌,忽然發現本身創建在這個新的用戶模式API之上。這包括 DirectSound,此時它徹底失去了對硬件加速音頻的支持。

優點:

1)低延遲、故障恢復的音頻流;

2)提升可靠性(許多音頻功能已從內核模式轉移到用戶模式);

3)提升安全性(受保護的音頻內容的處理在安全、低權限的過程當中進行);

4)將特定的系統範圍角色(控制檯、多媒體和通訊)分配給各個音頻設備;

5)用戶直接操做的音頻端點設備(例如,揚聲器、耳機和麥克風)的軟件抽象。

Windows 採集播放中有着多種API,可是大多數API都是位於 Core Audio之上,在實時音頻領域應該推崇使用更接近底層的 API(ASIO或者Core Audio),可減小必定的時延。因爲 ASIO 存在必定的侷限性,Core Audio 更具備適用性。所以在現有大多數的 Windows 音視頻通訊客戶端中採集播放使用的都是 Core Audio APIs。

#2

Core Audio 詳解

Windows Core Audio,不要與 OSX 的相似名稱的 Core Audio 混淆,它是對 Windows 上音頻處理方式的完全從新設計。大多數的音頻組件從內核態轉移到用戶態,這對應用程序的穩定性產生了巨大的影響。幾乎全部的Windows APIs 都創建在 Core Audio 之上。

2.1 Core Audio 系統內核框架詳解

Core Audio 問世以後,新的音頻系統內核框架也隨之改變。

1.png 圖一 基於Core Audio音頻系統框架圖

從系統框架圖能夠看到Core Audio APIs包含了4個API——MMDevice、WASAPI、DeviceTopology 和 EndpointVolume。

MMDevice API

客戶端發現音頻終端設備,枚舉出全部可以使用的音頻設備屬性以及肯定其功能,併爲這些設備建立驅動程序實例。是最基本的 Core Audio API,服務於其餘3個 APIs。

WASAPI

客戶端應用程序能夠管理應用程序和音頻終端設備之間音頻數據的流。

DeviceTopology API

客戶端能夠遍歷音頻適配器設備和音頻終端設備的內部拓撲,並單步執行將設備連接到另外一臺設備的鏈接。經過 DeviceTopology API 中的接口和方法,客戶端程序可直接沿着音頻適配器 (audio adapters) 的硬件設備裏的數據通道進入佈局特徵(例如,沿着音頻終端設備的數據路徑上進行音量控制) 。

EndpointVolume API

客戶端能夠控制和監視音頻終端設備的音量級別。

圖中顯示的是渲染的音頻數據如何從大多數應用程序流向揚聲器的簡化表示。對於採集來講,音頻數據的路徑是徹底相同,但流向是相反的。從圖中能夠看到,一些高階API(例如MME,DirectSound等),對 Core Audio APIs 進行了封裝,使用這些API可以更容易完成某些應用程序需求。可是對於音視頻來講,須要減小時延使用更底層API。

從API處理後,音頻流會通過兩種路徑到達音頻端點緩存區——Shared Mode(共享模式)和Exclusive Mode(獨佔模式)。

共享模式和獨佔模式是 Core Audio 帶來的一項重大改進。

共享模式

共享模式與舊的 KMixer 模式有一些類似之處。在共享模式下,應用程序寫入傳遞給系統音頻引擎的緩衝區。音頻引擎負責將全部應用程序的音頻混合在一塊兒並將混合發送到音頻驅動程序。與 KMixer 同樣,這會引入延遲。音頻引擎有時不只須要轉換音頻數據,並且還必須混合來自多個共享模式應用程序的數據。這須要時間,一般是幾毫秒。在大多數狀況下,這種延遲是沒法察覺的。

獨佔模式

獨享是微軟對專業音頻世界的迴應。獨佔模式的應用程序具備對硬件的獨佔訪問權限,音頻數據直接從應用程序傳輸到驅動程序再到硬件。獨佔模式的流媒體徹底繞過了 Windows 音頻引擎。它有效地鎖定了全部的應用程序,相比於共享模式,獨佔模式音頻的一個明顯優點是,隨着音頻引擎的退出,它所帶來的延遲被徹底消除了。

可是獨佔模式流媒體的最大缺點是,對音頻格式沒有多少靈活性。只能使用音頻適配器原生支持的格式。若是須要進行數據格式轉換,應用程序將須要手動完成。值得指出的是,獨佔模式的流媒體實際上並不保證對應用程序可用。它是用戶可配置的。用戶能夠爲一個給定的音頻適配器徹底禁用獨佔模式音頻。以下圖:

2.jpg

圖二 音頻設備屬性圖

系統框架圖中音頻流最終流向了音頻適配器。音頻適配器不多有單一的輸入和/或輸出鏈接。事實上,大多數現代消費類我的電腦的音頻適配器都支持至少三種類型的鏈接:耳機、揚聲器和話筒。

在這一章,已經看到了音頻流這個短語,音頻流指的是應用程序和音頻終端設備之間的一個鏈接。

2.2 Core Audio 的設備管理

2.2.1 設備的枚舉

在音視頻客戶端設備列表中,客戶一般能看到電腦上可以使用的麥克風以及揚聲器的列表。上面已經介紹過設備的枚舉由 MMDevice API 控制,能夠經過 MMDevice API 去枚舉出設備數量及設備屬性。首先須要經過 COM 接口來獲取音頻設備枚舉實例,再經過 IMMDeviceEnumerator 對象去獲取須要的設備屬性。

constCLSIDCLSID_MMDeviceEnumerator=__uuidof(MMDeviceEnumerator); constIIDIID_IMMDeviceEnumerator=__uuidof(IMMDeviceEnumerator); IMMDeviceEnumerator*ptrEnumerator; hr=CoCreateInstance( CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator); 經過上述代碼能夠獲取到一個 IMMDeviceEnumerator 對象。經過這個對象,客戶端能夠直接或者間接獲取到 MMDevice API 中包括 IMMDevice,IMMDeviceCollection 以及音頻端點設備狀態更改的通知 IMMNotificationClient 在內的對象。

IMMDeviceCollectionpCollection=NULL; hr=pEnumerator->EnumAudioEndpoints( dataFlow, // data-flow direction (input parameter) DEVICE_STATE_ACTIVE|DEVICE_STATE_DISABLED|DEVICE_STATE_UNPLUGGED, &pCollection); // release interface when done IMMDevicepEndpoint = NULL; hr=pCollection->Item(index, &pEndpoint); //device Index Value 經過 pCollection 和 pEndpoint 對象能夠調用 IMMDeviceCollection 中的 GetCount 接口獲取設備個數;調用 IMMDevice 的 GetId 獲取終端端口的設備ID;若是須要獲取設備名須要稍微複雜的操做,首先要經過 IMMDevice 的 OpenPropertyStore 接口獲取一個 IPropertyStore 對象,經過 IPropertyStore 的 GetValue 來獲取到設備名。音視頻客戶端經過這些方法就可枚舉出當前 windows 電腦中存在的音頻終端設備及信息。

2.2.二、打開指定設備

在枚舉完設備後,在用戶指定某個特定設備時,通常客戶端會選擇把系統默認設備當成採集/播放設備。對於默認設備,Core Audio 有一個特定接口去 打開默認設備,調用 IMMDeviceEnumerator 的 GetDefaultAudioEndpoint 便可。可是當用戶指定到某一個特定設備時,經過經過 IMMDevice 的 Item 接口可打開用戶指定的設備。

2.2.3 設備初始化

設備初始化是整個工做線程中一個重要環節,客戶端可以在音視頻頻客戶端和音頻引擎(對於共享模式的流)或音頻終端設備的硬件緩衝器(對於獨佔模式的流)之間建立和初始化一個音頻流。須要先調用 IMMDevice 的 Activate 方法建立具備指定接口的 IAudioClient 對象。

constIIDIID_IAudioClient=__uuidof(IAudioClient); IAudioClient*pAudioClient=NULL; hr=pDevice->Activate( IID_IAudioClient, CLSCTX_ALL, NULL, (void**) &pAudioClient); 在獲取到 IAudioClient 對象後,進行設備初始化,但在 Initialize 調用中,客戶端須要爲流指定共享或者獨佔模式,控制流建立的標誌、音頻數據格式、緩衝區大小和音頻會話。音視頻客戶端通常會選用共享模式,採集和播放通常使用事件驅動的方式,音頻數據格式可使用 IAudioClient 的 GetMixFormat 接口去獲取默認格式,可是實際上獲取到的默認格式並不必定符合客戶端所須要的設備格式參數,那麼會遍歷通道數、採樣率,調用 IAudioClient 的 IsFormatSupported 接口查詢出最適合的設備格式參數。

hr=pAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, hnsRequestedDuration, 0, pwfx, NULL); 2.3 Core Audio的音量管理

音頻設備中的音量控制系統主要由 EndpointVolume API 提供。音量控制須要使用到 IAudioEndpointVolume 對象,該對象由 IMMDevice 接口獲取。

IAudioCaptureClientpCaptureClient=NULL; IAudioEndpointVolumepEndpointVolume=NULL; hr=pEndpoint->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void**)&pEndpointVolume); 經過 pEndpointVolume 對象能夠處理音量控制,靜音控制。

floatfLevel; //Get Volume pEndpointVolume->GetMasterVolumeLevelScalar(&fLevel);
//Set Volume fLevel = 255.0; pEndpointVolume->SetMasterVolumeLevelScalar(fLevel, NULL); BOOLmute; //Get mute state pEndpointVolume->GetMute(&mute);
//Set mute state mute=0; pEndpointVolume->SetMute(mute, NULL); 2.4 Core Audio事件監聽管理

2.4.1 設備事件監聽主要是監聽設備的插播消息,由 IMMDeviceEnumerator 調用 RegisterEndpointNotificationCallback 接口即可實現當設備狀態出現變化時能通知到音視頻客戶端中。

IMMNotificationClient*pClient=NULL; ptrEnumerator->RegisterEndpointNotificationCallback(pClient); 2.4.2 音量事件監聽由 EndpointVolume 調用 RegisterControlChangeNotify 接口實現

IAudioEndpointVolumeCallback*pVolume=NULL; pEndpointVolume->RegisterControlChangeNotify(pVolume); 2.5 Core Audio 線程模型與 Call-Flow

在設備初始化完成後,接下來就到了最重要的環節:採集/播放的數據的交互。可是數據應該如何進行交互的,採集播放實踐中這麼多的環節是如何創建線程模型?

2.5.1 線程模型

實時音視頻中,須要得得一個實時,高效的採集/播放,爲防止二者相互 block,因此通常在實時音視頻中,會將採集和播放建立單獨的線程,稱爲採集/播放線程。同時爲了防止被其餘線程佔用資源,採集/播放線程優先級通常都會設置爲最高級別。固然對於設備枚舉、設備初始化等低密度操做,通常在工做線程完成。而音量管理以及事件監控,都是經過用戶去操做的,會用一個用戶線程去管理。

3.png

圖三 各線程示意圖

2.5.2 採集 Call-Flow

瞭解一下采集的流程圖。

4.png

圖四 採集線程流程圖

在圖中,能夠看到,麥克風設備採集是由 event 事件來驅動的,在初始化設備後,會設置一個啓動事件 SetEvent(startEvent) 啓動麥克風採集,而且生成一個 IAudioCaptureClient 的對象,經過 IAudioCaptureClient 對象來調用接口獲取麥克風數據。

//在工做線程獲取IAudioCaptureClient的對象

IAudioCaptureClient*pCaptureClient=NULL;

hr=pAudioClient->GetService(__uuidof(IAudioCaptureClient),

(void**)&pCaptureClient);

//採集線程

//獲取麥克風數據

hr=pCaptureClient->GetBuffer( &pData, // packet which is ready to be read by used
&framesAvailable, // #frames in the captured packet (can be zero)
&flags, // support flags (check)
&recPos, // device position of first audio frame in data packet
&recTime); // value of performance counter at the time of recording
// the first audio frame
//處理數據
ProcessCaptureData(&pData); //釋放麥克風數據
DWORDdwFlags(0); hr=_ptrCaptureClient->ReleaseBuffer(framesAvailable); 2.5.3 播放Call-Flow

音頻播放的流程圖以下:

5.png 圖五 播放線程流程圖

揚聲器的播放也是經過 event 事件來驅動的,也會設置一個 startEvent 啓動。與採集不一樣的地方是揚聲器須要先獲取到當前設備的緩存 buffer。若是緩存的 buffer 已經滿了,那麼設備不會再去要數據用於揚聲器的播放。當設備緩存 buffer 不夠時,會先獲取一個設備指針,從遠端傳入的數據寫入到指針指向的地址中,當緩存寫滿,揚聲器就會播放出來。

//工做線程獲取IAudioCaptureClient的對象。 IAudioCaptureClient*pRenderClient=NULL; hr=pAudioClient->GetService(__uuidof(IAudioRenderClient), (void**)&pRenderClient); //播放線程 //獲取當前的Padding緩存 UINT32padding=0; hr=pRenderClient->GetCurrentPadding(&padding); //獲取揚聲器設備指針 hr=pRenderClient->GetBuffer(playBlockSize, &pData); //遠端數據寫入緩存中 RequestPlayoutData(&pData); //釋放揚聲器數據 DWORDdwFlags(0); hr=pRenderClient->ReleaseBuffer(playBlockSize, dwFlags); #3

Core Audio 使用注意事項

3.1 windows 有着本身的計時時鐘。採集過程當中,因爲時鐘精度問題,採集 callback 的數據大小與恆定的 frameSize 有着必定的差別性。例如以 44100Hz 的採樣率去採集,單位時鐘內 callback 的數據大小爲448,與恆定的441有差別。

3.2 一旦採集/播放線程被 block 了,將會致使線程處理時間變長,採集/播放取出的數據會產生斷裂問題。拿播放爲例,用戶就會聽到卡頓聲。

3.3 使用 GetMixFormat方法 獲取默認設備格式時,一般以 WAVEFORMATEX 的結構來指定格式。可是 WAVEFORMATEX 的結構有着必定的侷限性,對於雙通道以上,或者更高位深精度,或者新的壓縮方案的一些設備格式,微軟建議使用 WAVEFORMATEXTENSIBLE 能夠得到更好的支持。因爲某些設備驅動對 WAVEFORMATEX 結構和 WAVEFORMATEXTENSIBLE 結構調用 IsFormatSupported 會獲得不一樣的結果。爲了獲取到可靠的設備格式,微軟建議使用 IsFormatSupported 對 WAVEFORMATEX 格式和 WAVEFORMATEXTENSIBLE 格式都進行一次遍歷。

3.4 音頻設備中還有一些其餘設置,好比 built-in AEC。built-in AEC 是使用編解碼器 DMO 接口配置附加功能,DOM 可能會影響一些設備格式的支持。

相關引用:

1.《Practical Digital Audio for C++ Programmers》;

2.Core Audio APIs - Win32 apps ;

3.Core Audio APIs - Win32 apps ;

4.Configuring Codec DMOs - Win32 apps

相關文章
相關標籤/搜索