本主題介紹隨音訊驅動程式提供的最新 Windows 11 音訊處理物件 (APO) API。
Windows 允許第三方音訊硬體製造商包含自定義主機型數位訊號處理效果。 這些效果會封裝為使用者模式音訊處理物件 (API)。 如需詳細資訊,請參閱 Windows 音訊處理物件。
此處所述的部分 API 可為獨立硬體廠商 (IHV) 和獨立軟體廠商 (ISV) 啟用新案例,而其他 API 則旨在提供替代方案,以改善整體音訊可靠性和偵錯功能。
- 聲學回聲消除(AEC)架構可讓 APO 將自己識別為 AEC APO,並授與參考串流和其他控制的存取權。
- 設定架構可讓 API 公開音訊端點上查詢和修改音訊效果屬性存放區 (「FX 屬性存放區」) 的方法。 當這些方法由 APO 實作時,它們可以由與該 APO 相關聯的硬體支援應用程式 (HSA) 叫用。
- 通知架構可讓音訊效果 (APO) 要求通知,以處理音量、端點和音訊效果屬性存放區變更。
- 日誌記錄框架有助於 API 的開發和調試。
- 執行緒架構可讓 API 使用 OS 管理的 MMCSS 註冊執行緒集區進行多執行緒處理。
- 音訊效果探索和控制 API 讓作業系統能夠偵測、啟用和停用可用於處理串流上的效果。
若要利用這些新的 API,API 預期會利用新的 IAudioSystemEffects3 介面。 當 APO 實作此介面時,OS 會將此解譯為 APO 支援 APO 設定架構的隱含訊號,並允許 APO 訂閱來自音訊引擎的常見音訊相關通知。
Windows 11 APO CAPX 開發需求
Windows 11 裝置上隨附的任何新 API 都必須符合本主題中列出的 API,並透過 HLK 進行驗證。 此外,任何使用 AEC 的音訊處理物件 (APO) 都應該遵循此主題所述的實施方式,並經由 HLK 驗證。 這些核心音訊處理延伸模組的自定義實作(設定、記錄、通知、線程、AEC)預期會利用 CAPX API。 這將透過 Windows 11 HLK 測試進行驗證。 例如,如果 APO 使用登錄資料來儲存設定,而不是使用設定架構,則相關聯的 HLK 測試將會失敗。
Windows 版本需求
本主題中所述的 API 可從 Windows 11 OS、WDK 和 SDK 的組建 22000 開始使用。 Windows 10 將不支援這些 API。 如果 APO 想要在 Windows 10 和 Windows 11 上運作,它可以檢查它是否使用 APOInitSystemEffects2 或 APOInitSystemEffects3 結構進行初始化,以判斷它是否在支援 CAPX API 的 OS 上執行。
最新版本的 Windows、WDK 和 SDK 可以通過以下方式下載 Windows 測試人員計劃。 透過合作夥伴中心與 Microsoft 互動的合作夥伴也可以透過共同作業存取此內容。 如需共同作業的詳細資訊,請參閱 共同作業Microsoft簡介。
Windows 11 WHCP 內容已更新,可為合作夥伴提供驗證這些 API 的方法。
您可以在此處找到本主題中概述內容的範例程式碼: 音訊/SYSVAD/APO - github
聲音回音消除 (AEC)
聲學迴聲消除 (AEC) 是由獨立硬體廠商 (IHV) 和獨立軟體廠商 (ISV) 實作的常見音訊效果,做為麥克風擷取管線中的音訊處理物件 (APO) 。 此效果與 IHV 和 ISV 通常實作的其他效果不同,因為它需要兩個輸入——一個來自麥克風的音訊流,另一個來自作為參考訊號的渲染裝置的音訊流。
這組新的介面可讓 AEC APO 向音訊引擎標識自己。 這樣做會導致音訊引擎將多個輸入和單一輸出妥善地配置給 APO。
當 APO 實作新的 AEC 介面時,音訊引擎會:
- 為 APO 配置額外的輸入,從適當的呈現端點提供參考流給 APO。
- 當渲染裝置變更時,切換參考串流。
- 允許 APO 控制輸入麥克風和參考流的格式。
- 允許 APO 取得麥克風和參考串流上的時間戳記。
先前的方法 - Windows 10
API 是單一輸入 – 單一輸出物件。 音訊引擎將來自麥克風端點的音訊提供給 AEC APO 作為其輸入。 若要取得參考音訊流,APO 可以使用專屬介面與驅動程式互動,以從渲染端點擷取參考音訊流,或使用 WASAPI 在渲染端點上啟動回送音訊流。
以上兩種方法都有缺點:
AEC APO 使用私有通道從驅動程式取得參考訊號,通常只能從整合音訊渲染裝置取得。 因此,如果使用者正在從非整合裝置(例如 USB 或藍牙音訊裝置)播放音訊,則回音消除將無法運作。 只有 OS 知道可作為參考端點的正確轉譯端點。
APO 可以使用 WASAPI 來挑選預設轉譯端點來執行回應取消。 不過,從 audiodg.exe 進程開啟回送資料流程時,需要注意一些陷阱 (這是裝載 APO 的位置)。
- 當音訊引擎正在呼叫主要 APO 方法時,不可開啟或關閉回送串流,因為這樣可能會導致死結。
- 擷取 APO 不知道其用戶端數據流的狀態。 也就是說,擷取應用程式可能具有處於「STOP」狀態的擷取資料流程,但 APO 不知道此狀態,因此會讓回送資料流程在「RUN」狀態下保持開啟狀態,這在耗電量方面效率低下。
API 定義 - AEC
AEC 架構提供 API 可以利用的新結構和介面。 這些新的結構和介面如下所述。
APO_CONNECTION_PROPERTY_V2 結構
實作 IApoAcousticEchoCancellation 介面的 API 會在呼叫 IAudioProcessingObjectRT::APOProcess 時傳遞 APO_CONNECTION_PROPERTY_V2 結構。 除了 APO_CONNECTION_PROPERTY 結構中的所有欄位之外,結構的第 2 版也會提供音訊緩衝區的時間戳記資訊。
APO 可以檢查 APO_CONNECTION_PROPERTY.u32Signature 欄位,以判斷它從音訊引擎接收的結構類型是 APO_CONNECTION_PROPERTY 還是 APO_CONNECTION_PROPERTY_V2。 APO_CONNECTION_PROPERTY結構具有APO_CONNECTION_PROPERTY_SIGNATURE的簽章,而APO_CONNECTION_PROPERTY_V2具有等於APO_CONNECTION_PROPERTY_V2_SIGNATURE的簽章。 如果簽章的值等於 APO_CONNECTION_PROPERTY_V2_SIGNATURE,則APO_CONNECTION_PROPERTY結構的指標可以安全地類型轉換成APO_CONNECTION_PROPERTY_V2指標。
以下程式碼來自 Aec APO MFX 範例 - AecApoMfx.cpp,並顯示重構。
if (ppInputConnections[0]->u32Signature == APO_CONNECTION_PROPERTY_V2_SIGNATURE)
{
const APO_CONNECTION_PROPERTY_V2* connectionV2 = reinterpret_cast<const APO_CONNECTION_PROPERTY_V2*>(ppInputConnections[0]);
}
IApoAcousticEchoCancellation
IApoAcousticEchoCancellation 介面上沒有明確的方法。 其目的是將 AEC APO 識別給音訊引擎。 此介面只能由擷取端點上的模式效果 (MFX) 實作。 在任何其他 APO 上實作此介面,會導致載入該 APO 失敗。 如需 MFX 的一般資訊,請參閱 音訊處理物件架構。
如果擷取端點上的模式效果實作為一系列鏈結的 APO,則只有最接近裝置的 APO 才能實作此介面。 實作此介面的 API 會在呼叫 IAudioProcessingobjectRT::APOProcess 時提供APO_CONNECTION_PROPERTY_V2結構。 APO 可以檢查連線屬性上的APO_CONNECTION_PROPERTY_V2_SIGNATURE簽章,並將傳入APO_CONNECTION_PROPERTY結構類型轉換成APO_CONNECTION_PROPERTY_V2結構。
為了辨識 AEC APOs 通常會以特定取樣率/通道計數執行其演算法的事實,音訊引擎會為實作 IApoAcousticEchoCancellation 介面的 APOs 提供重新取樣支援。
當 AEC APO 在呼叫 IAudioProcessingObject::OutInputFormatSupported 時傳回 APOERR_FORMAT_NOT_SUPPORTED,音訊引擎會再次在 APO 上呼叫 IAudioProcessingObject::IsInputFormatSupported 並使用 Null 輸出格式和非 Null 輸入格式,以取得 APO 的建議格式。 然後,音訊引擎會在將麥克風音訊傳送至 AEC APO 之前,將麥克風音訊重新取樣為建議的格式。 這消除了 AEC APO 實現採樣率和通道數轉換的需要。
IApoAuxiliaryInputConfiguration
IApoAuxiliaryInputConfiguration 介面提供可以被 APO 實作的方法,使得音訊引擎可以新增和移除輔助輸入資料流。
此介面是由 AEC APO 實作,並由音訊引擎用來初始化參考輸入。 在 Windows 11 中,AEC APO 只會使用單一輔助輸入來初始化,該輸入具有用於迴聲消除的參考音訊串流。 AddAuxiliaryInput 方法將用來將參考輸入新增至 APO。 初始化參數將包含對從中取得回送資料流的渲染端點的參考。
音訊引擎會呼叫 IsInputFormatSupported 方法 ,以交涉輔助輸入上的格式。 如果 AEC APO 偏好特定格式,它可以在呼叫 IsInputFormatSupported 時傳回 S_FALSE,並指定建議的格式。 音訊引擎會將參考音訊訊號重新取樣為建議的格式,並在 AEC APO 的輔助輸入處提供。
IApoAuxiliaryInputRT
IApoAuxiliaryInputRT 介面是用來驅動 APO 輔助輸入的即時安全介面。
此介面可用來提供 APO 輔助輸入上的音訊資料。 請注意,輔助音訊輸入不會與 IAudioProcessingObjectRT::APOProcess 的呼叫同步處理。 當沒有音訊從渲染端點輸出時,輔助輸入將無法取得回送資料。 也就是說,不會呼叫 IApoAuxiliaryInputRT::AcceptInput
AEC CAPX API 摘要
如需詳細資訊,請參閱下列頁面的其他資訊。
- APO_CONNECTION_PROPERTY_V2結構 (audioapotypes.h)
- IApoAcousticEchoCancellation 介面
- IApoAuxiliaryInputConfiguration
- IApoAuxiliaryInputRT
範例程式碼 - AEC
請參閱下列 Sysvad Audio AecApo 程式碼範例。
Aec APO 範例標頭中的下列程式碼 - AecAPO.h 顯示要新增的三個新公用方法。
public IApoAcousticEchoCancellation,
public IApoAuxiliaryInputConfiguration,
public IApoAuxiliaryInputRT
...
COM_INTERFACE_ENTRY(IApoAcousticEchoCancellation)
COM_INTERFACE_ENTRY(IApoAuxiliaryInputConfiguration)
COM_INTERFACE_ENTRY(IApoAuxiliaryInputRT)
...
// IAPOAuxiliaryInputConfiguration
STDMETHOD(AddAuxiliaryInput)(
DWORD dwInputId,
UINT32 cbDataSize,
BYTE *pbyData,
APO_CONNECTION_DESCRIPTOR *pInputConnection
) override;
STDMETHOD(RemoveAuxiliaryInput)(
DWORD dwInputId
) override;
STDMETHOD(IsInputFormatSupported)(
IAudioMediaType* pRequestedInputFormat,
IAudioMediaType** ppSupportedInputFormat
) override;
...
// IAPOAuxiliaryInputRT
STDMETHOD_(void, AcceptInput)(
DWORD dwInputId,
const APO_CONNECTION_PROPERTY *pInputConnection
) override;
// IAudioSystemEffects3
STDMETHODIMP GetControllableSystemEffectsList(_Outptr_result_buffer_maybenull_(*numEffects) AUDIO_SYSTEMEFFECT** effects, _Out_ UINT* numEffects, _In_opt_ HANDLE event) override
{
UNREFERENCED_PARAMETER(effects);
UNREFERENCED_PARAMETER(numEffects);
UNREFERENCED_PARAMETER(event);
return S_OK;
}
下列程式碼來自 Aec APO MFX 範例 - AecApoMfx.cpp ,並顯示 AddAuxiliaryInput 的實作,當 APO 只能處理一個輔助輸入時。
STDMETHODIMP
CAecApoMFX::AddAuxiliaryInput(
DWORD dwInputId,
UINT32 cbDataSize,
BYTE *pbyData,
APO_CONNECTION_DESCRIPTOR * pInputConnection
)
{
HRESULT hResult = S_OK;
CComPtr<IAudioMediaType> spSupportedType;
ASSERT_NONREALTIME();
IF_TRUE_ACTION_JUMP(m_bIsLocked, hResult = APOERR_APO_LOCKED, Exit);
IF_TRUE_ACTION_JUMP(!m_bIsInitialized, hResult = APOERR_NOT_INITIALIZED, Exit);
BOOL bSupported = FALSE;
hResult = IsInputFormatSupportedForAec(pInputConnection->pFormat, &bSupported);
IF_FAILED_JUMP(hResult, Exit);
IF_TRUE_ACTION_JUMP(!bSupported, hResult = APOERR_FORMAT_NOT_SUPPORTED, Exit);
// This APO can only handle 1 auxiliary input
IF_TRUE_ACTION_JUMP(m_auxiliaryInputId != 0, hResult = APOERR_NUM_CONNECTIONS_INVALID, Exit);
m_auxiliaryInputId = dwInputId;
另請檢閱範例程式碼,展示CAecApoMFX::IsInputFormatSupported和CAecApoMFX::AcceptInput的實作及APO_CONNECTION_PROPERTY_V2的處理方式。
操作順序 - AEC
初始化時:
- IAudioProcessingObject::初始化
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration:: LockForProcess
- IAudioProcessingObjectConfiguration ::UnlockForProcess
- IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
在轉譯裝置變更時:
- IAudioProcessingObject::初始化
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration::LockForProcess
- 預設裝置變更
- IAudioProcessingObjectConfiguration::UnlockForProcess
- IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration::LockForProcess
建議的緩衝模式 - AEC
這是 AEC 的建議緩衝區行為。
- 在呼叫 IApoAuxiliaryInputRT::AcceptInput 時取得的緩衝區,應該寫入循環緩衝區,而不鎖定主線程。
- 在呼叫 IAudioProcessingObjectRT::APOProcess 時,應該從參考資料流程讀取最新音訊封包的循環緩衝區,而且此封包應該用於執行回應消除演算法。
- 參考和麥克風數據上的時間戳可用於對齊揚聲器和麥克風數據。
參考回送串流
根據預設,迴圈串流會在套用任何音量或靜音之前「點選」(監聽)音訊串流。 在套用音量之前擷取的循環回送流稱為音量前循環回送流。 無論目前的音量設定為何,具有前置音量回送串流的優點是清晰且統一的音訊串流。
某些 AEC 演算法可能偏好取得在任何音量處理(包括靜音)之後已連接的回送串流。 此配置稱為後音量回送。
在下一個主要版本的 Windows AEC 中,APO 可以在支援的端點上要求音量後回送。
局限性
不同於適用於所有播放端點的音量前回送串流,音量後回送串流可能在所有端點上都無法使用。
請求磁碟區後循環回送
想要使用音量後回送的 AEC APO 應該實作 IApoAcousticEchoCancellation2 介面。
AEC APO 可以在其實作的 IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties 中,透過 Properties 參數傳回 APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK 標誌,以請求音量後循環回送。
視目前使用的渲染端點而定,後置音量回送可能無法使用。 當呼叫 IApoAuxiliaryInputConfiguration::AddAuxiliaryInput 方法時,如果正在使用後音量回送,則會通知 AEC APO。 如果 AcousticEchoCanceller_Reference_Input streamProperties 欄位包含 APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK,則會使用音量後回送。
AEC APO 範例標頭中的下列程式碼 - AecAPO.h 顯示要新增的三個新公用方法。
public:
// IApoAcousticEchoCancellation2
STDMETHOD(GetDesiredReferenceStreamProperties)(
_Out_ APO_REFERENCE_STREAM_PROPERTIES * properties) override;
// IApoAuxiliaryInputConfiguration
STDMETHOD(AddAuxiliaryInput)(
DWORD dwInputId,
UINT32 cbDataSize,
_In_ BYTE* pbyData,
_In_ APO_CONNECTION_DESCRIPTOR *pInputConnection
) override;
下列程式碼片段來自 AEC APO MFX 範例 - AecApoMfx.cpp,並顯示 GetDesiredReferenceStreamProperties 的實作,以及 AddAuxiliaryInput 的相關部分。
STDMETHODIMP SampleApo::GetDesiredReferenceStreamProperties(
_Out_ APO_REFERENCE_STREAM_PROPERTIES * properties)
{
RETURN_HR_IF_NULL(E_INVALIDARG, properties);
// Always request that a post-volume loopback stream be used, if
// available. We will find out which type of stream was actually
// created when AddAuxiliaryInput is invoked.
*properties = APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK;
return S_OK;
}
STDMETHODIMP
CAecApoMFX::AddAuxiliaryInput(
DWORD dwInputId,
UINT32 cbDataSize,
BYTE *pbyData,
APO_CONNECTION_DESCRIPTOR * pInputConnection
)
{
// Parameter checking skipped for brevity, please see sample for
// full implementation.
AcousticEchoCanceller_Reference_Input* referenceInput = nullptr;
APOInitSystemEffects3* papoSysFxInit3 = nullptr;
if (cbDataSize == sizeof(AcousticEchoCanceller_Reference_Input))
{
referenceInput =
reinterpret_cast<AcousticEchoCanceller_Reference_Input*>(pbyData);
if (WI_IsFlagSet(
referenceInput->streamProperties,
APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK))
{
// Post-volume loopback is being used.
m_bUsingPostVolumeLoopback = TRUE;
// Note that we can get to the APOInitSystemEffects3 from
// AcousticEchoCanceller_Reference_Input.
papoSysFxInit3 = (APOInitSystemEffects3*)pbyData;
}
else if (cbDataSize == sizeof(APOInitSystemEffects3))
{
// Post-volume loopback is not supported.
papoSysFxInit3 = (APOInitSystemEffects3*)pbyData;
}
// Remainder of method skipped for brevity.
設定架構
設定框架允許音訊處理運算子公開方法,以便在音訊端點上查詢和修改音訊效果的屬性存放區(「FX 屬性存放區」)。 此架構可供 API 和硬體支援應用程式 (HSA) 使用,這些應用程式想要將設定傳達給該 APO。 HSA 可以是通用 Windows 平臺 (UWP) 應用程式,而且需要特殊功能才能叫用設定架構中的 API。 如需 HSA 應用程式的詳細資訊,請參閱 UWP 裝置應用程式。
FxProperty 存放區結構
新的 FxProperty 儲存區有三個子儲存區:預設、使用者和揮發性。
「Default」子金鑰包含自訂效果屬性,並從 INF 檔案填入。 這些屬性不會在作業系統升級之間持續存在。 例如,通常在 INF 中定義的屬性會適合這裡。 這些項目將從 INF 中重新填入。
「使用者」子機碼包含與效果屬性相關的使用者設定。 這些設定會由 OS 在升級和移轉中保存。 例如,使用者可以設定的任何預設集,這些預設集預期會在升級期間持續存在。
「揮發性」子機碼包含揮發性效應屬性。 這些屬性會在裝置重新啟動時遺失,而且每次端點轉換為作用中時都會清除。 這些預期會包含時間變體屬性 (例如,根據目前執行中的應用程式、裝置狀態等)例如,相依於目前環境的任何設定。
考慮使用者與預設的方法是,您是否希望屬性在作業系統和驅動程式升級之間持續存在。 使用者屬性將會保存。 預設屬性將會從 INF 檔案重新填入。
APO 情境
CAPX 設定架構可讓 APO 作者依 環境定義將 APO 內容分組。 每個 APO 都可以定義自己的內容,並更新相對於其自己內容的屬性。 音訊端點的效果屬性存儲可能包含零個或多個上下文。 廠商可自由地選擇建立情境的方式,這是通過 SFX/MFX/EFX 或依模式。 廠商也可以選擇為該廠商所出貨的所有 API 設定單一內容。
設定受限功能
設定 API 旨在支援所有有興趣查詢和修改與音訊裝置相關聯的音訊效果設定的 OEM 和 HSA 開發人員。 此 API 會公開給 HSA 和 Win32 應用程式,以透過必須在資訊清單中宣告的受限制功能 「audioDeviceConfiguration」 來提供屬性存放區的存取權。 此外,必須宣告對應的命名空間,如下所示:
<Package
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
...
<Capabilities>
<rescap:Capability Name="audioDeviceConfiguration" />
</Capabilities>
</Package>
IAudioSystemEffectsPropertyStore 可由 ISV/IHV 服務、UWP 存放區應用程式、非系統管理員傳統型應用程式和 API 讀取和寫入。 此外,這可以作為 API 將訊息傳遞回服務或 UWP 存放區應用程式的機制。
備註
這是受限制的功能:如果將具有此功能的應用程式提交至 Microsoft Store,則會觸發密切審查。 該應用程序必須是硬件支持應用程序 (HSA),並且在提交獲得批准之前將對其進行檢查以評估它確實是 HSA。
API 定義 - 設定架構
新的 IAudioSystemEffectsPropertyStore 介面可讓 HSA 存取音訊系統效果屬性存放區,並註冊屬性變更通知。
ActiveAudioInterfaceAsync 函式提供以非同步方式取得 IAudioSystemEffectsPropertyStore 介面的方法。
當系統效果屬性存放區變更時,應用程式可以使用新的 IAudioSystemEffectsPropertyChangeNotificationClient 回呼介面來接收通知。
嘗試使用 IMMDevice::Activate 取得 IAudioSystemEffectsPropertyStore 的應用程式
此範例示範硬體支援應用程式如何使用 IMMDevice::Activate 來啟用 IAudioSystemEffectsPropertyStore。 此範例示範如何使用 IAudioSystemEffectsPropertyStore 開啟具有使用者設定的 IPropertyStore。
#include <mmdeviceapi.h>
// This function opens an IPropertyStore with user settings on the specified IMMDevice.
// Input parameters:
// device - IMMDevice object that identifies the audio endpoint.
// propertyStoreContext - GUID that identifies the property store. Each APO can have its own GUID. These
// GUIDs are chosen by the audio driver at installation time.
HRESULT GetPropertyStoreFromMMDevice(_In_ IMMDevice* device,
REFGUID propertyStoreContext,
_COM_Outptr_ IPropertyStore** userPropertyStore)
{
*userPropertyStore = nullptr;
wil::unique_prop_variant activationParam;
RETURN_IF_FAILED(InitPropVariantFromCLSID(propertyStoreContext, &activationParam));
wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effectsPropertyStore;
RETURN_IF_FAILED(device->Activate(__uuidof(effectsPropertyStore), CLSCTX_INPROC_SERVER, activationParam.addressof(), effectsPropertyStore.put_void()));
RETURN_IF_FAILED(effectsPropertyStore->OpenUserPropertyStore(STGM_READWRITE, userPropertyStore));
return S_OK;
}
使用 ActivateAudioInterfaceAsync 的範例
此範例會執行與上一個範例相同的動作,但不會使用 IMMDevice,而是使用 ActivateAudioInterfaceAsync API 來非同步取得 IAudioSystemEffectsPropertyStore 介面。
include <mmdeviceapi.h>
class PropertyStoreHelper :
public winrt::implements<PropertyStoreHelper, IActivateAudioInterfaceCompletionHandler>
{
public:
wil::unique_event_nothrow m_asyncOpCompletedEvent;
HRESULT GetPropertyStoreAsync(
_In_ PCWSTR deviceInterfacePath,
REFGUID propertyStoreContext,
_COM_Outptr_ IActivateAudioInterfaceAsyncOperation** operation);
HRESULT GetPropertyStoreResult(_COM_Outptr_ IPropertyStore** userPropertyStore);
// IActivateAudioInterfaceCompletionHandler
STDMETHOD(ActivateCompleted)(_In_ IActivateAudioInterfaceAsyncOperation *activateOperation);
private:
wil::com_ptr_nothrow<IPropertyStore> m_userPropertyStore;
HRESULT m_hrAsyncOperationResult = E_FAIL;
HRESULT GetUserPropertyStore(
_In_ IActivateAudioInterfaceAsyncOperation* operation,
_COM_Outptr_ IPropertyStore** userPropertyStore);
};
// This function opens an IPropertyStore with user settings asynchronously on the specified audio endpoint.
// Input parameters:
// deviceInterfacePath - the Device Interface Path string that identifies the audio endpoint. Can be
// obtained from Windows.Devices.Enumeration.DeviceInformation.
// propertyStoreContext - GUID that identifies the property store. Each APO can have its own GUID. These
// GUIDs are chosen by the audio driver at installation time.
//
// The function returns an IActivateAudioInterfaceAsyncOperation, which can be used to check the result of
// the asynchronous operation.
HRESULT PropertyStoreHelper::GetPropertyStoreAsync(
_In_ PCWSTR deviceInterfacePath,
REFGUID propertyStoreContext,
_COM_Outptr_ IActivateAudioInterfaceAsyncOperation** operation)
{
*operation = nullptr;
wil::unique_prop_variant activationParam;
RETURN_IF_FAILED(InitPropVariantFromCLSID(propertyStoreContext, &activationParam));
RETURN_IF_FAILED(ActivateAudioInterfaceAsync(deviceInterfacePath,
__uuidof(IAudioSystemEffectsPropertyStore),
activationParam.addressof(),
this,
operation));
return S_OK;
}
// Once the IPropertyStore is available, the app can call this function to retrieve it.
// (The m_asyncOpCompletedEvent event is signaled when the asynchronous operation to retrieve
// the IPropertyStore has completed.)
HRESULT PropertyStoreHelper::GetPropertyStoreResult(_COM_Outptr_ IPropertyStore** userPropertyStore)
{
*userPropertyStore = nullptr;
// First check if the asynchronous operation completed. If it failed, the error code
// is stored in the m_hrAsyncOperationResult variable.
RETURN_IF_FAILED(m_hrAsyncOperationResult);
RETURN_IF_FAILED(m_userPropertyStore.copy_to(userPropertyStore));
return S_OK;
}
// Implementation of IActivateAudioInterfaceCompletionHandler::ActivateCompleted.
STDMETHODIMP PropertyStoreHelper::ActivateCompleted(_In_ IActivateAudioInterfaceAsyncOperation* operation)
{
m_hrAsyncOperationResult = GetUserPropertyStore(operation, m_userPropertyStore.put());
// Always signal the event that our caller might be waiting on before we exit,
// even in case of failure.
m_asyncOpCompletedEvent.SetEvent();
return S_OK;
}
HRESULT PropertyStoreHelper::GetUserPropertyStore(
_In_ IActivateAudioInterfaceAsyncOperation* operation,
_COM_Outptr_ IPropertyStore** userPropertyStore)
{
*userPropertyStore = nullptr;
// Check if the asynchronous operation completed successfully, and retrieve an
// IUnknown pointer to the result.
HRESULT hrActivateResult;
wil::com_ptr_nothrow<IUnknown> audioInterfaceUnknown;
RETURN_IF_FAILED(operation->GetActivateResult(&hrActivateResult, audioInterfaceUnknown.put()));
RETURN_IF_FAILED(hrActivateResult);
// Convert the result to IAudioSystemEffectsPropertyStore
wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effctsPropertyStore;
RETURN_IF_FAILED(audioInterfaceUnknown.query_to(&effectsPropertyStore));
// Open an IPropertyStore with the user settings.
RETURN_IF_FAILED(effectsPropertyStore->OpenUserPropertyStore(STGM_READWRITE, userPropertyStore));
return S_OK;
}
使用 IAudioSystemEffectsPropertyStore 的 IAudioProcessingObject::Initialize 代碼
此範例示範 APO 的實作可以使用 APOInitSystemEffects3 結構,在 APO 初始化期間擷取 APO 的使用者、預設和揮發性 IPropertyStore 介面。
#include <audioenginebaseapo.h>
// Partial implementation of APO to show how an APO that implements IAudioSystemEffects3 can handle
// being initialized with the APOInitSystemEffects3 structure.
class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
public:
// IAudioProcessingObject
STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);
// Implementation of IAudioSystemEffects2, IAudioSystemEffects3 has been omitted from this sample for brevity.
private:
wil::com_ptr_nothrow<IPropertyStore> m_defaultStore;
wil::com_ptr_nothrow<IPropertyStore> m_userStore;
wil::com_ptr_nothrow<IPropertyStore> m_volatileStore;
// Each APO has its own private collection of properties. The collection is identified through a
// a property store context GUID, which is defined below and in the audio driver INF file.
const GUID m_propertyStoreContext = ...;
};
// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
if (cbDataSize == sizeof(APOInitSystemEffects3))
{
// SampleApo supports the new IAudioSystemEffects3 interface so it will receive APOInitSystemEffects3
// in pbyData if the audio driver has declared support for this.
// Use IMMDevice to activate IAudioSystemEffectsPropertyStore that contains the default, user and
// volatile settings.
IMMDeviceCollection* deviceCollection =
reinterpret_cast<APOInitSystemEffects3*>(pbyData)->pDeviceCollection;
if (deviceCollection != nullptr)
{
UINT32 numDevices;
wil::com_ptr_nothrow<IMMDevice> endpoint;
// Get the endpoint on which this APO has been created
// (It is the last device in the device collection)
if (SUCCEEDED(deviceCollection->GetCount(&numDevices)) &&
numDevices > 0 &&
SUCCEEDED(deviceCollection->Item(numDevices - 1, &endpoint)))
{
wil::unique_prop_variant activationParam;
RETURN_IF_FAILED(InitPropVariantFromCLSID(m_propertyStoreContext, &activationParam));
wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effectsPropertyStore;
RETURN_IF_FAILED(endpoint->Activate(__uuidof(effectsPropertyStore), CLSCTX_ALL, &activationParam, effectsPropertyStore.put_void()));
// Read default, user and volatile property values to set up initial operation of the APO
RETURN_IF_FAILED(effectsPropertyStore->OpenDefaultPropertyStore(STGM_READWRITE, m_defaultStore.put()));
RETURN_IF_FAILED(effectsPropertyStore->OpenUserPropertyStore(STGM_READWRITE, m_userStore.put()));
RETURN_IF_FAILED(effectsPropertyStore->OpenVolatilePropertyStore(STGM_READWRITE, m_volatileStore.put()));
// At this point the APO can read and write settings in the various property stores,
// as appropriate. (Not shown.)
// Note that APOInitSystemEffects3 contains all the members of APOInitSystemEffects2,
// so an APO that knows how to initialize from APOInitSystemEffects2 can use the same
// code to continue its initialization here.
}
}
}
else if (cbDataSize == sizeof(APOInitSystemEffects2))
{
// Use APOInitSystemEffects2 for the initialization of the APO.
// If we get here, the audio driver did not declare support for IAudioSystemEffects3.
}
else if (cbDataSize == sizeof(APOInitSystemEffects))
{
// Use APOInitSystemEffects for the initialization of the APO.
}
return S_OK;
}
註冊財產變更通知的應用程式
此範例示範如何使用註冊機制來進行屬性變更通知。 這不應該與 APO 搭配使用,而且應該由 Win32 應用程式使用。
class PropertyChangeNotificationClient : public
winrt::implements<PropertyChangeNotificationClient, IAudioSystemEffectsPropertyChangeNotificationClient>
{
private:
wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> m_propertyStore;
bool m_isListening = false;
public:
HRESULT OpenPropertyStoreOnDefaultRenderEndpoint(REFGUID propertyStoreContext);
HRESULT StartListeningForPropertyStoreChanges();
HRESULT StopListeningForPropertyStoreChanges();
// IAudioSystemEffectsPropertyChangeNotificationClient
STDMETHOD(OnPropertyChanged)(AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE type, const PROPERTYKEY key);
};
// Open the IAudioSystemEffectsPropertyStore. This should be the first method invoked on this class.
HRESULT PropertyChangeNotificationClient::OpenPropertyStoreOnDefaultRenderEndpoint(
REFGUID propertyStoreContext)
{
wil::com_ptr_nothrow<IMMDeviceEnumerator> deviceEnumerator;
RETURN_IF_FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator)));
wil::com_ptr_nothrow<IMMDevice> device;
RETURN_IF_FAILED(deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.put()));
wil::unique_prop_variant activationParam;
RETURN_IF_FAILED(InitPropVariantFromCLSID(propertyStoreContext, &activationParam));
RETURN_IF_FAILED(device->Activate(__uuidof(m_propertyStore), CLSCTX_INPROC_SERVER,
&activationParam, m_propertyStore.put_void()));
return S_OK;
}
// Start subscribing to callbacks that are invoked when there are changes to any of the IPropertyStores
// that are managed by IAudioSystemEffectsPropertyStore.
// The OpenPropertyStoreOnDefaultRenderEndpoint should have been invoked prior to invoking this function.
HRESULT PropertyChangeNotificationClient::StartListeningForPropertyStoreChanges()
{
RETURN_HR_IF(E_FAIL, !m_propertyStore);
RETURN_IF_FAILED(m_propertyStore->RegisterPropertyChangeNotification(this));
m_isListening = true;
return S_OK;
}
// Unsubscribe to event callbacks. Since IAudioSystemEffectsPropertyStore takes a reference on our
// PropertyChangeNotificationClient class, it is important that this method is invoked prior to cleanup,
// to break the circular reference.
HRESULT PropertyChangeNotificationClient::StopListeningForPropertyStoreChanges()
{
if (m_propertyStore != nullptr && m_isListening)
{
RETURN_IF_FAILED(m_propertyStore->UnregisterPropertyChangeNotification(this));
m_isListening = false;
}
return S_OK;
}
// Callback method that gets invoked when there have been changes to any of the IPropertyStores
// that are managed by IAudioSystemEffectsPropertyStore. Note that calls to
// IAudioSystemEffectsPropertyChangeNotificationClient are not marshalled across COM apartments.
// Therefore, the OnPropertyChanged is most likely invoked on a different thread than the one used when
// invoking RegisterPropertyChangeNotification. If necessary, concurrent access to shared state should be
// protected with a critical section.
STDMETHODIMP PropertyChangeNotificationClient::OnPropertyChanged(AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE type, const PROPERTYKEY key)
{
if (type == AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE_USER)
{
// Handle changes to the User property store.
wil::com_ptr_nothrow<IPropertyStore> userPropertyStore;
RETURN_IF_FAILED(m_propertyStore->OpenUserPropertyStore(STGM_READ, userPropertyStore.put()));
// Here we can call IPropertyStore::GetValue to read the current value of PROPERTYKEYs that we are
// interested in.
}
else if (type == AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE_VOLATILE)
{
// Handle changes to the Volatile property store, if desired
}
return S_OK;
}
範例程式碼 - 設定架構
此範例程式碼來自 sysvad SFX 交換 APO 範例 - SwapAPOSFX.cpp。
// SampleApo supports the new IAudioSystemEffects3 interface so it will receive APOInitSystemEffects3
// in pbyData if the audio driver has declared support for this.
// Use IMMDevice to activate IAudioSystemEffectsPropertyStore that contains the default, user and
// volatile settings.
IMMDeviceCollection* deviceCollection = reinterpret_cast<APOInitSystemEffects3*>(pbyData)->pDeviceCollection;
if (deviceCollection != nullptr)
{
UINT32 numDevices;
wil::com_ptr_nothrow<IMMDevice> endpoint;
// Get the endpoint on which this APO has been created
// (It is the last device in the device collection)
if (SUCCEEDED(deviceCollection->GetCount(&numDevices)) && numDevices > 0 &&
SUCCEEDED(deviceCollection->Item(numDevices - 1, &endpoint)))
{
wil::unique_prop_variant activationParam;
hr = InitPropVariantFromCLSID(SWAP_APO_SFX_CONTEXT, &activationParam);
IF_FAILED_JUMP(hr, Exit);
wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effectsPropertyStore;
hr = endpoint->Activate(__uuidof(effectsPropertyStore), CLSCTX_ALL, &activationParam, effectsPropertyStore.put_void());
IF_FAILED_JUMP(hr, Exit);
// This is where an APO might want to open the volatile or default property stores as well
// Use STGM_READWRITE if IPropertyStore::SetValue is needed.
hr = effectsPropertyStore->OpenUserPropertyStore(STGM_READ, m_userStore.put());
IF_FAILED_JUMP(hr, Exit);
}
}
INF 區段 - 設定架構
使用新的 CAPX 設定架構宣告效果屬性的 INF 檔案語法如下:
HKR, FX\0\{ApoContext}\{Default|User}, %CUSTOM_PROPERTY_KEY%,,,
這會取代宣告效果屬性的舊語法,如下所示:
# Old way of declaring FX properties
HKR, FX\0, %CUSTOM_PROPERTY_KEY_1%,,,
INF 不能同時具有相同音訊端點的 IAudioSystemEffectsPropertyStore 項目和 IPropertyStore 項目。 不支援。
顯示使用新屬性存放區的範例:
HKR,FX\0\%SWAP_APO_CONTEXT%,%PKEY_FX_Association%,,%KSNODETYPE_ANY%
; Enable the channel swap in the APO
HKR,FX\0\%SWAP_APO_CONTEXT%\User,%PKEY_Endpoint_Enable_Channel_Swap_SFX%,REG_DWORD,0x1
PKEY_Endpoint_Enable_Channel_Swap_SFX = "{A44531EF-5377-4944-AE15-53789A9629C7},2"
REG_DWORD = 0x00010001 ; FLG_ADDREG_TYPE_DWORD
SWAP_APO_CONTEXT = "{24E7F619-5B33-4084-9607-878DA8722417}"
PKEY_FX_Association = "{D04E05A6-594B-4FB6-A80D-01AF5EED7D1D},0"
KSNODETYPE_ANY = "{00000000-0000-0000-0000-000000000000}"
通知架構
通知架構可讓音訊效果 (APO) 要求並處理音量、端點和音訊效果屬性存放區變更通知。 此框架旨在取代目前由 APO 使用來註冊及取消註冊通知的 API。
新的 API 引進了一個介面,APO 可以利用這個介面來宣告 APO 感興趣的通知類型。 Windows 會查詢 APO 以取得它感興趣的通知,並將通知轉送至 API。 APO 不再需要明確呼叫註冊或取消註冊 API。
通知將透過序列佇列傳送至 APO。 適用時,第一個通知會廣播所要求值的初始狀態 (例如,音訊端點音量) 。 一旦 audiodg.exe 不再打算使用 APO 進行串流,通知就會停止。 API 會在 UnlockForProcess 之後停止接收通知。 仍然需要同步處理 UnlockForProcess 和任何正在傳送的通知。
實作 - 通知架構
若要利用通知架構,APO 會宣告它感興趣的通知。 沒有明確的註冊/取消註冊呼叫。 所有通知傳送至 APO 時會被序列化,務必確保不要讓通知回呼執行緒封鎖過久。
API 定義 - 通知架構
通知架構會實作新的 IAudioProcessingObjectNotifications 介面,用戶端可實作該介面,以註冊和接收 APO 端點和系統效果通知的常見音訊相關通知。
如需詳細資訊,請在下列頁面上尋找其他內容:
範例程式碼 - 通知架構
此範例示範 APO 如何實作 IAudioProcessingObjectNotifications 介面。 在 GetApoNotificationRegistrationInfo 方法中,範例 APO 會註冊系統效果屬性存放區變更的通知。
OS 會叫用 HandleNotification 方法,以通知 APO 符合 APO 註冊內容的變更。
class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3,
IAudioProcessingObjectNotifications>
{
public:
// IAudioProcessingObjectNotifications
STDMETHOD(GetApoNotificationRegistrationInfo)(
_Out_writes_(count) APO_NOTIFICATION_DESCRIPTOR** apoNotifications, _Out_ DWORD* count);
STDMETHOD_(void, HandleNotification)(_In_ APO_NOTIFICATION *apoNotification);
// Implementation of IAudioSystemEffects2, IAudioSystemEffects3 has been omitted from this sample for brevity.
private:
wil::com_ptr_nothrow<IMMDevice> m_device;
// Each APO has its own private collection of properties. The collection is identified through a
// a property store context GUID, which is defined below and in the audio driver INF file.
const GUID m_propertyStoreContext = ...;
float m_masterVolume = 1.0f;
BOOL m_isMuted = FALSE;
BOOL m_allowOffloading = FALSE;
// The rest of the implementation of IAudioProcessingObject is omitted for brevity
};
// The OS invokes this method on the APO to find out what notifications the APO is interested in.
STDMETHODIMP SampleApo::GetApoNotificationRegistrationInfo(
_Out_writes_(count) APO_NOTIFICATION_DESCRIPTOR** apoNotificationDescriptorsReturned,
_Out_ DWORD* count)
{
*apoNotificationDescriptorsReturned = nullptr;
*count = 0;
// Before this function can be called, our m_device member variable should already have been initialized.
// This would typically be done in our implementation of IAudioProcessingObject::Initialize, by using
// APOInitSystemEffects3::pDeviceCollection to obtain the last IMMDevice in the collection.
RETURN_HR_IF_NULL(E_FAIL, m_device);
// Let the OS know what notifications we are interested in by returning an array of
// APO_NOTIFICATION_DESCRIPTORs.
constexpr DWORD numDescriptors = 3;
wil::unique_cotaskmem_ptr<APO_NOTIFICATION_DESCRIPTOR[]> apoNotificationDescriptors;
apoNotificationDescriptors.reset(static_cast<APO_NOTIFICATION_DESCRIPTOR*>(
CoTaskMemAlloc(sizeof(APO_NOTIFICATION_DESCRIPTOR) * numDescriptors)));
RETURN_IF_NULL_ALLOC(apoNotificationDescriptors);
// Our APO wants to get notified when any change occurs on the user property store on the audio endpoint
// identified by m_device.
// The user property store is different for each APO. Ours is identified by m_propertyStoreContext.
apoNotificationDescriptors[0].type = APO_NOTIFICATION_TYPE_AUDIO_SYSTEM_EFFECTS_PROPERTY_CHANGE;
(void)m_device.query_to(&apoNotificationDescriptors[0].audioSystemEffectsPropertyChange.device);
apoNotificationDescriptors[0].audioSystemEffectsPropertyChange.propertyStoreContext = m_propertyStoreContext;
// Our APO wants to get notified when an endpoint property changes on the audio endpoint.
apoNotificationDescriptors[1].type = APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE;
(void)m_device.query_to(&apoNotificationDescriptors[1].audioEndpointPropertyChange.device);
// Our APO also wants to get notified when the volume level changes on the audio endpoint.
apoNotificationDescriptors [2].type = APO_NOTIFICATION_TYPE_ENDPOINT_VOLUME;
(void)m_device.query_to(&apoNotificationDescriptors[2].audioEndpointVolume.device);
*apoNotificationDescriptorsReturned = apoNotificationDescriptors.release();
*count = numDescriptors;
return S_OK;
}
static bool IsSameEndpointId(IMMDevice* device1, IMMDevice* device2)
{
bool isSameEndpointId = false;
wil::unique_cotaskmem_string deviceId1;
if (SUCCEEDED(device1->GetId(&deviceId1)))
{
wil::unique_cotaskmem_string deviceId2;
if (SUCCEEDED(device2->GetId(&deviceId2)))
{
isSameEndpointId = (CompareStringOrdinal(deviceId1.get(), -1, deviceId2.get(), -1, TRUE) == CSTR_EQUAL);
}
}
return isSameEndpointId;
}
// HandleNotification is called whenever there is a change that matches any of the
// APO_NOTIFICATION_DESCRIPTOR elements in the array that was returned by GetApoNotificationRegistrationInfo.
// Note that the APO will have to query each property once to get its initial value because this method is
// only invoked when any of the properties have changed.
STDMETHODIMP_(void) SampleApo::HandleNotification(_In_ APO_NOTIFICATION* apoNotification)
{
// Check if a property in the user property store has changed.
if (apoNotification->type == APO_NOTIFICATION_TYPE_AUDIO_SYSTEM_EFFECTS_PROPERTY_CHANGE
&& IsSameEndpointId(apoNotification->audioSystemEffectsPropertyChange.endpoint, m_device.get())
&& apoNotification->audioSystemEffectsPropertyChange.propertyStoreContext == m_propertyStoreContext
&& apoNotification->audioSystemEffectsPropertyChange.propertyStoreType == AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE_USER)
{
// Check if one of the properties that we are interested in has changed.
// As an example, we check for "PKEY_Endpoint_Enable_Channel_Swap_SFX" which is a fictitious
// PROPERTYKEY that could be set on our user property store.
if (apoNotification->audioSystemEffectsPropertyChange.propertyKey ==
PKEY_Endpoint_Enable_Channel_Swap_SFX)
{
wil::unique_prop_variant var;
if (SUCCEEDED(apoNotification->audioSystemEffectsPropertyChange.propertyStore->GetValue(
PKEY_Endpoint_Enable_Channel_Swap_SFX, &var)) &&
var.vt != VT_EMPTY)
{
// We have retrieved the property value. Now we can do something interesting with it.
}
}
}
else if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE
&& IsSameEndpointId(apoNotification->audioEndpointPropertyChange.endpoint, m_device.get())
{
// Handle changes to PROPERTYKEYs in the audio endpoint's own property store.
// In this example, we are interested in a property called "PKEY_Endpoint_AllowOffloading" that the
// user might change in the audio control panel, and we update our member variable if this
// property changes.
if (apoNotification->audioEndpointPropertyChange.propertyKey == PKEY_Endpoint_AllowOffloading)
{
wil::unique_prop_variant var;
if (SUCCEEDED(propertyStore->GetValue(PKEY_Endpoint_AllowOffloading, &var)) && var.vt == VT_BOOL)
{
m_allowOffloading = var.boolVal;
}
}
}
else if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_VOLUME
&& IsSameEndpointId(apoNotification->audioEndpointVolumeChange.endpoint, m_device.get())
{
// Handle endpoint volume change
m_masterVolume = apoNotification->audioEndpointVolumeChange.volume->fMasterVolume;
m_isMuted = apoNotification->audioEndpointVolumeChange.volume->bMuted;
}
}
下列程式碼來自 交換 APO MFX 範例 - swapapomfx.cpp,透過傳回 APO_NOTIFICATION_DESCRIPTORs 陣列來註冊事件。
HRESULT CSwapAPOMFX::GetApoNotificationRegistrationInfo(_Out_writes_(*count) APO_NOTIFICATION_DESCRIPTOR **apoNotifications, _Out_ DWORD *count)
{
*apoNotifications = nullptr;
*count = 0;
RETURN_HR_IF_NULL(E_FAIL, m_device);
// Let the OS know what notifications we are interested in by returning an array of
// APO_NOTIFICATION_DESCRIPTORs.
constexpr DWORD numDescriptors = 1;
wil::unique_cotaskmem_ptr<APO_NOTIFICATION_DESCRIPTOR[]> apoNotificationDescriptors;
apoNotificationDescriptors.reset(static_cast<APO_NOTIFICATION_DESCRIPTOR*>(
CoTaskMemAlloc(sizeof(APO_NOTIFICATION_DESCRIPTOR) * numDescriptors)));
RETURN_IF_NULL_ALLOC(apoNotificationDescriptors);
// Our APO wants to get notified when an endpoint property changes on the audio endpoint.
apoNotificationDescriptors[0].type = APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE;
(void)m_device.query_to(&apoNotificationDescriptors[0].audioEndpointPropertyChange.device);
*apoNotifications = apoNotificationDescriptors.release();
*count = numDescriptors;
return S_OK;
}
下列程式碼來自 SwapAPO MFX HandleNotifications 範例 - swapapomfx.cpp ,並示範如何處理通知。
void CSwapAPOMFX::HandleNotification(APO_NOTIFICATION *apoNotification)
{
if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE)
{
// If either the master disable or our APO's enable properties changed...
if (PK_EQUAL(apoNotification->audioEndpointPropertyChange.propertyKey, PKEY_Endpoint_Enable_Channel_Swap_MFX) ||
PK_EQUAL(apoNotification->audioEndpointPropertyChange.propertyKey, PKEY_AudioEndpoint_Disable_SysFx))
{
struct KeyControl
{
PROPERTYKEY key;
LONG* value;
};
KeyControl controls[] = {
{PKEY_Endpoint_Enable_Channel_Swap_MFX, &m_fEnableSwapMFX},
};
m_apoLoggingService->ApoLog(APO_LOG_LEVEL_INFO, L"HandleNotification - pkey: " GUID_FORMAT_STRING L" %d", GUID_FORMAT_ARGS(apoNotification->audioEndpointPropertyChange.propertyKey.fmtid), apoNotification->audioEndpointPropertyChange.propertyKey.pid);
for (int i = 0; i < ARRAYSIZE(controls); i++)
{
LONG fNewValue = true;
// Get the state of whether channel swap MFX is enabled or not
fNewValue = GetCurrentEffectsSetting(m_userStore.get(), controls[i].key, m_AudioProcessingMode);
SetAudioSystemEffectState(m_effectInfos[i].id, fNewValue ? AUDIO_SYSTEMEFFECT_STATE_ON : AUDIO_SYSTEMEFFECT_STATE_OFF);
}
}
}
}
記錄架構
日誌記錄框架為 APO 開發人員提供了收集資料的額外方法,以改進開發和調試。 此架構會統一不同廠商所使用的不同記錄方法,並將它繫結至音訊追蹤記錄提供者,以建立更有意義的記錄。 新架構提供記錄 API,其餘工作由作業系統完成。
提供者的定義如下:
IMPLEMENT_TRACELOGGING_CLASS(ApoTelemetryProvider, "Microsoft.Windows.Audio.ApoTrace",
// {8b4a0b51-5dcf-5a9c-2817-95d0ec876a87}
(0x8b4a0b51, 0x5dcf, 0x5a9c, 0x28, 0x17, 0x95, 0xd0, 0xec, 0x87, 0x6a, 0x87));
每個 APO 都有自己的活動識別碼。 由於這使用現有的追蹤記錄機制,因此可以使用現有的主控台工具來篩選這些事件並即時顯示它們。 您可以使用追蹤記錄和 tracefmt 等現有工具,如 軟體追蹤工具 - Windows 驅動程式中所述。 如需追蹤會話的詳細資訊,請參閱 使用控制 GUID 建立追蹤會話。
追蹤記錄事件不會標示為遙測,而且不會在 xperf 等工具中顯示為遙測提供者。
實作 - 記錄架構
記錄架構是以 ETW 追蹤所提供的記錄機制為基礎。 如需 ETW 的詳細資訊,請參閱 事件追蹤。 這不是為了記錄音訊資料,而是為了記錄通常在生產環境中記錄的事件。 不應從即時串流執行緒使用記錄 API,因為這些 API 可能會導致 OS CPU 排程器先佔泵浦執行緒。 記錄日誌應該主要用於幫助解決經常在現場發現的偵錯問題的事件。
API 定義 - 日誌記錄架構
記錄架構引進 IAudioProcessingObjectLoggingService 介面,為 API 提供新的記錄服務。
如需詳細資訊,請參閱 IAudioProcessingObjectLoggingService。
範例程式碼 - 記錄架構
此範例示範如何使用 IAudioProcessingObjectLoggingService::ApoLog 方法,以及如何在 IAudioProcessingObject::Initialize 中取得此介面指標。
class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
private:
wil::com_ptr_nothrow<IAudioProcessingObjectLoggingService> m_apoLoggingService;
public:
// IAudioProcessingObject
STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);
// Implementation of IAudioProcessingObject, IAudioSystemEffects2 andIAudioSystemEffects3 has been omitted for brevity.
};
// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
if (cbDataSize == sizeof(APOInitSystemEffects3))
{
APOInitSystemEffects3* apoInitSystemEffects3 = reinterpret_cast<APOInitSystemEffects3*>(pbyData);
// Try to get the logging service, but ignore errors as failure to do logging it is not fatal.
(void)apoInitSystemEffects3->pServiceProvider->QueryService(SID_AudioProcessingObjectLoggingService,
__uuidof(IAudioProcessingObjectLoggingService), IID_PPV_ARGS(&m_apoLoggingService));
}
// Do other APO initialization work
if (m_apoLoggingService != nullptr)
{
m_apoLoggingService->ApoLog(APO_LOG_LEVEL_INFO, L"APO Initialization completed");
}
return S_OK;
}
執行緒架構
執行緒架構可透過簡單的 API 使用適當多媒體類別排程器服務 (MMCSS) 工作佇列,讓效果進行多執行緒處理。 建立即時序列工作佇列及其與主泵執行緒的關聯由作業系統處理。 此架構可讓 APO 將短時間執行的工作專案排入佇列。 任務之間的同步處理仍然是 APO 的責任。 如需 MMCSS 線程的詳細資訊,請參閱 多媒體類別排程器服務和Real-Time 工作佇列 API。
API 定義 - 執行緒架構
執行緒架構引進 IAudioProcessingObjectQueueService 介面,提供 API 即時工作佇列的存取權。
如需詳細資訊,請在下列頁面上尋找其他內容:
範例程式碼 - 執行緒架構
此範例示範如何使用 IAudioProcessingObjectRTQueueService::GetRealTimeWorkQueue 方法,以及如何在 IAudioProcessingObject::Initialize 中取得 IAudioProcessingObjectRTQueueService 介面指標。
#include <rtworkq.h>
class SampleApo3 :
public winrt::implements<SampleApo3, IAudioProcessingObject, IAudioProcessingObjectConfiguration,
IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
private:
DWORD m_queueId = 0;
wil::com_ptr_nothrow<SampleApo3AsyncCallback> m_asyncCallback;
public:
// IAudioProcessingObject
STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);
// IAudioProcessingObjectConfiguration
STDMETHOD(LockForProcess)(
_In_ UINT32 u32NumInputConnections,
_In_reads_(u32NumInputConnections) APO_CONNECTION_DESCRIPTOR** ppInputConnections,
_In_ UINT32 u32NumOutputConnections,
_In_reads_(u32NumOutputConnections) APO_CONNECTION_DESCRIPTOR** ppOutputConnections);
// Non-interface methods called by the SampleApo3AsyncCallback helper class.
HRESULT DoWorkOnRealTimeThread()
{
// Do the actual work here
return S_OK;
}
void HandleWorkItemCompleted(_In_ IRtwqAsyncResult* asyncResult);
// Implementation of IAudioProcessingObject, IAudioSystemEffects2, IAudioSystemEffects3 and IAudioProcessingObjectConfiguration is omitted
// for brevity.
};
// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo3::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
if (cbDataSize == sizeof(APOInitSystemEffects3))
{
APOInitSystemEffects3* apoInitSystemEffects3 = reinterpret_cast<APOInitSystemEffects3*>(pbyData);
wil::com_ptr_nothrow<IAudioProcessingObjectRTQueueService> apoRtQueueService;
RETURN_IF_FAILED(apoInitSystemEffects3->pServiceProvider->QueryService(
SID_AudioProcessingObjectRTQueue, IID_PPV_ARGS(&apoRtQueueService)));
// Call the GetRealTimeWorkQueue to get the ID of a work queue that can be used for scheduling tasks
// that need to run at a real-time priority. The work queue ID is used with the Rtwq APIs.
RETURN_IF_FAILED(apoRtQueueService->GetRealTimeWorkQueue(&m_queueId));
}
// Do other initialization here
return S_OK;
}
STDMETHODIMP SampleApo3::LockForProcess(
_In_ UINT32 u32NumInputConnections,
_In_reads_(u32NumInputConnections) APO_CONNECTION_DESCRIPTOR** ppInputConnections,
_In_ UINT32 u32NumOutputConnections,
_In_reads_(u32NumOutputConnections) APO_CONNECTION_DESCRIPTOR** ppOutputConnections)
{
// Implementation details of LockForProcess omitted for brevity
m_asyncCallback = winrt::make<SampleApo3AsyncCallback>(m_queueId).get();
RETURN_IF_NULL_ALLOC(m_asyncCallback);
wil::com_ptr_nothrow<IRtwqAsyncResult> asyncResult;
RETURN_IF_FAILED(RtwqCreateAsyncResult(this, m_asyncCallback.get(), nullptr, &asyncResult));
RETURN_IF_FAILED(RtwqPutWorkItem(m_queueId, 0, asyncResult.get()));
return S_OK;
}
void SampleApo3::HandleWorkItemCompleted(_In_ IRtwqAsyncResult* asyncResult)
{
// check the status of the result
if (FAILED(asyncResult->GetStatus()))
{
// Handle failure
}
// Here the app could call RtwqPutWorkItem again with m_queueId if it has more work that needs to
// execute on a real-time thread.
}
class SampleApo3AsyncCallback :
public winrt::implements<SampleApo3AsyncCallback, IRtwqAsyncCallback>
{
private:
DWORD m_queueId;
public:
SampleApo3AsyncCallback(DWORD queueId) : m_queueId(queueId) {}
// IRtwqAsyncCallback
STDMETHOD(GetParameters)(_Out_ DWORD* pdwFlags, _Out_ DWORD* pdwQueue)
{
*pdwFlags = 0;
*pdwQueue = m_queueId;
return S_OK;
}
STDMETHOD(Invoke)(_In_ IRtwqAsyncResult* asyncResult);
};
STDMETHODIMP SampleApo3AsyncCallback::Invoke(_In_ IRtwqAsyncResult* asyncResult)
{
// We are now executing on the real-time thread. Invoke the APO and let it execute the work.
wil::com_ptr_nothrow<IUnknown> objectUnknown;
RETURN_IF_FAILED(asyncResult->GetObject(objectUnknown.put_unknown()));
wil::com_ptr_nothrow<SampleApo3> sampleApo3 = static_cast<SampleApo3*>(objectUnknown.get());
HRESULT hr = sampleApo3->DoWorkOnRealTimeThread();
RETURN_IF_FAILED(asyncResult->SetStatus(hr));
sampleApo3->HandleWorkItemCompleted(asyncResult);
return S_OK;
}
更多如何使用此介面的範例,請參閱以下範例程式碼:
音訊效果發現和效果控制
發現架構可讓 OS 控制其音訊流上的音效。 這些 API 支援應用程式使用者需要控制串流的某些效果 (例如,深度雜訊抑制) 的案例。 為了實現這一點,該框架添加了以下內容:
- 用於從 APO 查詢以判斷是否可以啟用或停用音效效果的新 API。
- 新的 API,可將音訊效果的狀態設定為開啟/關閉。
- 當音訊效果清單發生變更或資源可用時,您將收到通知,以便音訊效果可以啟用或停用。
實作 - 音訊效果探索
如果 APO 想要公開可動態啟用和停用的效果,則必須實作 IAudioSystemEffects3 介面。 APO 會透過 IAudioSystemEffects3::GetControllableSystemEffectsList 函式公開其音訊效果,並透過 IAudioSystemEffects3::SetAudioSystemEffectState 函式啟用和停用其音訊效果。
範例程式代碼 - 音訊效果探索
您可以在 SwapAPOSFX 範例中找到音訊效果探索範例程式代碼 - swapaposfx.cpp。
下列範例程式碼說明如何擷取可設定效果的清單。 GetControllableSystemEffectsList 範例 - swapaposfx.cpp
HRESULT CSwapAPOSFX::GetControllableSystemEffectsList(_Outptr_result_buffer_maybenull_(*numEffects) AUDIO_SYSTEMEFFECT** effects, _Out_ UINT* numEffects, _In_opt_ HANDLE event)
{
RETURN_HR_IF_NULL(E_POINTER, effects);
RETURN_HR_IF_NULL(E_POINTER, numEffects);
*effects = nullptr;
*numEffects = 0;
// Always close existing effects change event handle
if (m_hEffectsChangedEvent != NULL)
{
CloseHandle(m_hEffectsChangedEvent);
m_hEffectsChangedEvent = NULL;
}
// If an event handle was specified, save it here (duplicated to control lifetime)
if (event != NULL)
{
if (!DuplicateHandle(GetCurrentProcess(), event, GetCurrentProcess(), &m_hEffectsChangedEvent, EVENT_MODIFY_STATE, FALSE, 0))
{
RETURN_IF_FAILED(HRESULT_FROM_WIN32(GetLastError()));
}
}
if (!IsEqualGUID(m_AudioProcessingMode, AUDIO_SIGNALPROCESSINGMODE_RAW))
{
wil::unique_cotaskmem_array_ptr<AUDIO_SYSTEMEFFECT> audioEffects(
static_cast<AUDIO_SYSTEMEFFECT*>(CoTaskMemAlloc(NUM_OF_EFFECTS * sizeof(AUDIO_SYSTEMEFFECT))), NUM_OF_EFFECTS);
RETURN_IF_NULL_ALLOC(audioEffects.get());
for (UINT i = 0; i < NUM_OF_EFFECTS; i++)
{
audioEffects[i].id = m_effectInfos[i].id;
audioEffects[i].state = m_effectInfos[i].state;
audioEffects[i].canSetState = m_effectInfos[i].canSetState;
}
*numEffects = (UINT)audioEffects.size();
*effects = audioEffects.release();
}
return S_OK;
}
下列範例程式碼說明如何啟用和停用效果。 SetAudioSystemEffectState 範例 - swapaposfx.cpp
HRESULT CSwapAPOSFX::SetAudioSystemEffectState(GUID effectId, AUDIO_SYSTEMEFFECT_STATE state)
{
for (auto effectInfo : m_effectInfos)
{
if (effectId == effectInfo.id)
{
AUDIO_SYSTEMEFFECT_STATE oldState = effectInfo.state;
effectInfo.state = state;
// Synchronize access to the effects list and effects changed event
m_EffectsLock.Enter();
// If anything changed and a change event handle exists
if (oldState != effectInfo.state)
{
SetEvent(m_hEffectsChangedEvent);
m_apoLoggingService->ApoLog(APO_LOG_LEVEL_INFO, L"SetAudioSystemEffectState - effect: " GUID_FORMAT_STRING L", state: %i", effectInfo.id, effectInfo.state);
}
m_EffectsLock.Leave();
return S_OK;
}
}
return E_NOTFOUND;
}
在 Windows 11 版本 22H2 中重用 WM SFX 和 MFX APOs
從 Windows 11 版本 22H2 開始,重複使用收件匣 WM SFX 和 MFX API 的 INF 組態檔現在可以重複使用 CAPX SFX 和 MFX API。 本節說明執行此操作的三種方法。
APOs 有三個插入點:混合前渲染、混合後渲染和擷取。 每個邏輯裝置的音訊引擎都支援每個資料流程 (轉譯 SFX) 的一個混合前轉譯 APO 實例,以及一個混音後轉譯 APO (MFX) 。 音訊引擎也支援在每個擷取串流中插入一個捕獲 APO (捕獲 SFX)實例。 如需如何重複使用或包裝收件匣 API 的詳細資訊,請參閱 結合自訂 API 和 Windows API。
CAPX SFX 和 MFX APO 可以透過以下三種方式之一重複使用。
使用 INF DDInstall 區段
使用 mssysfx.CopyFilesAndRegisterCapX 從 wdmaudio.inf 新增下列項目。
Include=wdmaudio.inf
Needs=mssysfx.CopyFilesAndRegisterCapX
使用擴充功能 INF 檔案
wdmaudioapo.inf 是 AudioProcessingObject 類別延伸 inf。 它包含針對設備的SFX和MFX音頻處理對象(APOs)的特定註冊資料。
直接參考適用於數據流和模式效果的 WM SFX 和 MFX API
若要直接參考這些 APOs 的串流和模式效果,請使用下列 GUID 值。
- 使用
{C9453E73-8C5C-4463-9984-AF8BAB2F5447}作為 WM SFX APO - 將
{13AB3EBD-137E-4903-9D89-60BE8277FD17}用作 WM MFX APO。
在 Windows 8.1 中,SFX(流)和 MFX(模式)被分別稱為 LFX(本地)和 GFX(全域)。 這些登錄項目會繼續使用先前的名稱。
裝置特定註冊使用 HKR 而不是 HKCR。
INF 檔案需要增加以下項目。
HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%
HKR,"FX\\0\\%WMALFXGFXAPO_Context%\\User",,,
WMALFXGFXAPO_Context = "{B13412EE-07AF-4C57-B08B-E327F8DB085B}"
這些 INF 檔案項目將會建立一個屬性存放區,以供 Windows 11 API 用於新的 APO。
PKEY_FX_Association 在 INF 示例中
HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%,應取代為 HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%。