Windows Vista 以後系統,音頻系統相比以前的系統有很大的變化,產生了一套新的底層 API 即 Core Audio APIs 。該低層 API 爲高層 API( 如 Media Foundation( 將要取代DirectShow 等高層 API) 等 ) 提供服務。該系統API具備低延遲、高可靠性、安全性等特色。算法
本文主要從實時音視頻場景中,簡單介紹該API的使用。segmentfault
Core Audio APIs 的組成:MMDevice、EndpointVolume、WASAPI等。對於實時音視頻系統,主要用到的是MMDevice及EndpointVolume這兩套API。其在系統中的位置以下圖:windows
我對實時音視頻中音頻設備的使用簡單的分爲:緩存
一、設備列表管理安全
二、設備初始化多線程
三、設備功能管理架構
四、數據交互函數
五、音量管理ui
六、設備終端監聽spa
接下來爲你們介紹相關功能的實現:
音頻設備的管理,由MMDevice API來實現。
首先咱們要建立一個IMMDeviceEnumerator對象來開始相關功能的調用。
IMMDeviceEnumerator* ptrEnumerator;
CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&ptrEnumerator));
並經過IMMDeviceEnumerator能夠實現:獲取系統默認設備GetDefaultAudioEndpoint、獲取設備集合IMMDeviceCollection、獲取指定設備GetDevice、註冊設備監聽IMMNotificationClient(監聽設備插拔及狀態變動)。
經過這些方法,咱們能獲得系統默認設備、遍歷設備列表、打開指定設備並監聽設備變動。這樣就實現了實時音視頻中的設備管理相關的功能。
2、設備初始化
音頻設備的啓動是整個音頻模塊的可靠性的重要節點。根據設備類型和設備數據捕獲方式,咱們可分爲3類設備:麥克風採集、揚聲器播放、揚聲器採集。
首先咱們須要一個IMMDevice對象,能夠在設備管理的相關功能中獲取。
IMMDevice* pDevice;
//GetDefault
ptrEnumerator->GetDefaultAudioEndpoint((EDataFlow)dir, (ERole)role/* eCommunications */, &pDevice);
//Get by path
ptrEnumerator->GetDevice(device_path, &pDevice);
//GetIndex
pCollection->Item(index, &pDevice);
再經過IMMDevice獲得IAudioClient,設備的格式設置及初始化經過IAudioClient對象實現。通常都以共享模式打開,其中麥克風採集及揚聲器播使用事件驅動方式處理數據,而揚聲器採集以迴環的方式驅動處理數據。簡單示例以下:
//mic capturer
ptrClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
AUDCLNT_STREAMFLAGS_NOPERSIST,
0,
0,
(WAVEFORMATEX*)&Wfx,
NULL);
//playout render
ptrClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
0,
0,
(WAVEFORMATEX*)&Wfx,
NULL);
//playout capturer
ptrClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK,
0,
0,
(WAVEFORMATEX*)&Wfx,
NULL);
其中Wfx是設備格式參數,通常爲了保證設備的可用性,使用默認格式(經過IAudioClient::GetMixFormat獲取),若是須要使用自定義格式,能夠經過IAudioClient::IsFormatSupported方法去遍歷嘗試設備支持格式。
3、設備功能管理
針對麥克風設備,咱們一般須要對其進行數據處理。部分硬件設備和系統支持自帶的降噪、增益、消迴音等功能。可是通常windows系統下設備比較繁雜不可控,大都使用軟件算法處理。若是咱們須要檢測設備是否使用了自帶的處理功能及相關參數,須要使用Topology模塊的功能。
IDeviceTopology* pTopo;
pDevice->Activate(__uuidof(IDeviceTopology), CLSCTX_INPROC_SERVER, 0,&pTopo);
經過IDeviceTopology,咱們可以遍歷IConnector對象,得到IAudioAutoGainControl、IAudioVolumeLevel等能力對象,並處理相關能力。
注意:IConnector多是循環嵌套,在遍歷IConnector的IPart時須要判別成員對象IPart的類型。
4、數據交互
在設備初始化的時候,咱們就根據不一樣的設備選擇了不一樣的模式進行了啓動。不一樣的設備在各自的模式下,數據驅動也各有不一樣:
麥克風採集:
揚聲器播放:
揚聲器採集:
在和設備進行數據交互時,咱們須要根據數據獲取模式,獲取對應的服務對象來獲取設備數據。其中採集部分使用IAudioCaptureClient服務用於獲取設備數據,播放使用IAudioRenderClient服務獲取設備數據傳入指針。示例以下:
//capturer
IAudioCaptureClient* ptrCaptureClient;//audioin or audioout
ptrClient->GetService(__uuidof(IAudioCaptureClient), (void**)&ptrCaptureClient);
{//work thread
//Wait Event
ptrCaptureClient->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
//pData processing
ptrCaptureClient->ReleaseBuffer(framesAvailable);
}
//render
IAudioRenderClient* ptrRenderClient;//audioout
ptrClient->GetService(__uuidof(IAudioRenderClient), (void**)&ptrRenderClient);
{//work thread
BYTE* pData;//form buffer
UINT32 bufferLength = 0;
ptrClient->GetBufferSize(&bufferLength);
UINT32 playBlockSize = nSamplesPerSec / 100;
//Wait Event
UINT32 padding = 0;
ptrClient->GetCurrentPadding(&padding);
if (bufferLength - padding > playBlockSize)
{
ptrRenderClient->GetBuffer(playBlockSize, &pData);
//request and getdata
ptrCaptureClient->ReleaseBuffer(playBlockSize, 0);
}
}
在實際的數據交互中,須要另開單獨線程處理GetBuffer及ReleaseBuffer。其中麥克風採集及揚聲器播放時,都是經過設備事件驅動,能夠在設備初始化完成後設置響應的事件句柄(IAudioClient::SetEventHandle)。
在整個音視頻系統中,設備數據線程還須要統計數據處理時長、採集播放緩存大小等,用戶監聽檢查設備狀態及aec延遲計算。
5、音量管理
通常音量管理只在設備選定後處理當前設備的音量,因此通常使用IAudioEndpointVolume,該對象經過設備對象IMMDevice獲取:
IAudioEndpointVolume* pVolume;
pDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, reinterpret_cast<void**>(&pVolume));
獲得IAudioEndpointVolume對象後,咱們能處理當前設備的音量控制:
pVolume->GetMasterVolumeLevelScalar(&fLevel);
pVolume->SetMasterVolumeLevelScalar(fLevel, NULL);
靜音控制:
BOOL mute;
pVolume->GetMute(&mute);
pVolume->SetMute(mute, NULL);
以及註冊IAudioEndpointVolumeCallback監聽音量狀態:
IAudioEndpointVolumeCallback* cbSessionVolume;//need to do
pVolume->RegisterControlChangeNotify(cbSessionVolume);
6、設備終端監聽
在運行過程當中除了設備的插拔等操做,還可能有一些屬性變動等,通常用IAudioSessionEvents監聽:
IAudioSessionControl* ptrSessionControl;
ptrClient->GetService(__uuidof(IAudioSessionControl), (void**)&ptrSessionControl);
IAudioSessionEvents* notify;
ptrSessionControl->RegisterAudioSessionNotification(notify);
該回調監聽,能監聽該設備的鏈接工做狀態,名稱變動等。
在實際的工程開發過程當中,咱們須要對音頻線程的工做線程進行處理。一般經過調用系統模塊Avrt.dll,動態調用其下的函數,將調用線程與指定任務(Pro Audio)相關聯。上代碼:
函數綁定:
avrt_module_ = LoadLibrary(TEXT("Avrt.dll"));
if (avrt_module_)
{
_PAvRevertMmThreadCharacteristics = (PAvRevertMmThreadCharacteristics)GetProcAddress(avrt_module_, "AvRevertMmThreadCharacteristics");
_PAvSetMmThreadCharacteristicsA = (PAvSetMmThreadCharacteristicsA)GetProcAddress(avrt_module_, "AvSetMmThreadCharacteristicsA");
_PAvSetMmThreadPriority = (PAvSetMmThreadPriority)GetProcAddress(avrt_module_, "AvSetMmThreadPriority");
}
在實際的數據處理線程關聯:
hMmTask_ = _PAvSetMmThreadCharacteristicsA("Pro Audio", &taskIndex);
if (hMmTask_)
{
_PAvSetMmThreadPriority(hMmTask_, AVRT_PRIORITY_CRITICAL);
}
經過任務綁定,能有效的提高音頻數據處理線程的可靠性。
設備的相關初始化和釋放操做,須要在統一的線程處理,部分系統com對象在釋放時須要在建立線程釋放,否則可能致使釋放崩潰。而一些音量選擇、監聽等的處理能夠在用戶線程處理,但須要作好多線程安全。
在設備的採樣率、聲道等格式選擇時,若是須要使用自定義的格式,可能出現格式匹配失敗或者選擇匹配的格式後設備初始化失敗的場景。一般此類場景下直接使用默認格式啓動。
在數據處理線程處理音頻數據時,一般會出現事件響應超時、設備對象異常等狀況。一般的處理方法是,先退出數據線程並結束設備,而後檢查當前設備是否正常功能,而後從新啓動當前設備或選用默認設備。
瞭解網易雲信音視頻通話>>>
瞭解網易雲信,來自網易核心架構的通訊與視頻雲服務>>
更多技術乾貨,歡迎關注vx公衆號「網易智慧企業技術+」。系列課程提早看,精品禮物免費得,還可直接對話CTO。
聽網易CTO講述前沿觀察,看最有價值技術乾貨,學網易最新實踐經驗。網易智慧企業技術+,陪你從思考者成長爲技術專家。