Windows下Core Audio APIs的使用简介
文|網易云信資深PC端開發工程師
Windows Vista?之后的系統中,音頻系統相比之前的系統有很大的變化,產生了一套新的底層 API 即 Core Audio APIs 。
該低層 API 為高層 API【如 Media Foundation(將要取代DirectShow 等高層 API)等 】?提供服務。該系統API具有低延遲、高可靠性、安全性等特點。
本文主要從實時音視頻場景中,簡單介紹該API的使用。
Core Audio APIs 的組成:MMDevice、EndpointVolume、WASAPI等。對于實時音視頻系統,主要用到的是MMDevice及EndpointVolume這兩套API。其在系統中的位置如下圖:
?
我對實時音視頻中音頻設備的使用簡單的分為:
1. 設備列表管理
2. 設備初始化
3. 設備功能管理
4. 數據交互
5. 音量管理
6. 設備終端監聽
?
接下來為大家介紹相關功能的實現。
相關功能的實現
?
1、設備列表管理?
?
音頻設備的管理,由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;//GetDefaultptrEnumerator->GetDefaultAudioEndpoint((EDataFlow)dir, (ERole)role/* eCommunications */, &pDevice);//Get by pathptrEnumerator->GetDevice(device_path, &pDevice);//GetIndexpCollection->Item(index, &pDevice)?再通過IMMDevice得到IAudioClient,設備的格式設置及初始化通過IAudioClient對象實現。一般都以共享模式打開,其中麥克風采集及揚聲器播使用事件驅動方式處理數據,而揚聲器采集以回環的方式驅動處理數據。
簡單示例如下:
//mic capturerptrClient->Initialize(AUDCLNT_SHAREMODE_SHARED,AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,0,0, (WAVEFORMATEX*)&Wfx,NULL);//playout renderptrClient->Initialize(AUDCLNT_SHAREMODE_SHARED,AUDCLNT_STREAMFLAGS_EVENTCALLBACK,0,0, (WAVEFORMATEX*)&Wfx,NULL);//playout capturerptrClient->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服務獲取設備數據傳入指針。
示例如下:
//capturerIAudioCaptureClient* ptrCaptureClient;//audioin or audiooutptrClient->GetService(__uuidof(IAudioCaptureClient), (void**)&ptrCaptureClient);{//work thread //Wait EventptrCaptureClient->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 processingptrCaptureClient->ReleaseBuffer(framesAvailable);}//renderIAudioRenderClient* ptrRenderClient;//audiooutptrClient->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 getdataptrCaptureClient->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 dopVolume->RegisterControlChangeNotify(cbSessionVolume);?
6、設備終端監聽
在運行過程中除了設備的插拔等操作,還可能有一些屬性變更等,一般用IAudioSessionEvents監聽:
IAudioSessionControl* ptrSessionControl;ptrClient->GetService(__uuidof(IAudioSessionControl), (void**)&ptrSessionControl);IAudioSessionEvents* notify;ptrSessionControl->RegisterAudioSessionNotification(notify);該回調監聽,能監聽該設備的連接工作狀態,名稱變更等。
?
一些注意事項
1、線程優先級
在實際的工程開發過程中,我們需要對音頻線程的工作線程進行處理。通常通過調用系統模塊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);}通過任務綁定,能有效的提升音頻數據處理線程的可靠性。
?
2、工作線程
設備的相關初始化和釋放操作,需要在統一的線程處理,部分系統com對象在釋放時需要在創建線程釋放,不然可能導致釋放崩潰。而一些音量選擇、監聽等的處理可以在用戶線程處理,但需要做好多線程安全。
?
3、設備格式選擇
在設備的采樣率、聲道等格式選擇時,如果需要使用自定義的格式,可能出現格式匹配失敗或者選擇匹配的格式后設備初始化失敗的場景。通常此類場景下直接使用默認格式啟動。
?
4、數據處理異常
在數據處理線程處理音頻數據時,通常會出現事件響應超時、設備對象異常等情況。通常的處理方法是,先退出數據線程并結束設備,然后檢查當前設備是否正常功能,然后重新啟動當前設備或選用默認設備。
點擊閱讀原文,了解【網易云信】。
推薦閱讀
《網易實戰分享|Docker文件系統實戰》
《開源|如何開發一個高性能的redis cluster proxy?》
總結
以上是生活随笔為你收集整理的Windows下Core Audio APIs的使用简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 技术系列课|音视频测试实战——记音视频测
- 下一篇: 细说websocket快速重连机制