次の方法で共有


オーディオ処理オブジェクト用の Windows 11 API

このトピックでは、オーディオ ドライバーに付属するオーディオ処理オブジェクト (API) 用の新しい Windows 11 API のセットについて説明します。

Windows を使用すると、サードパーティのオーディオ ハードウェアメーカーは、カスタムのホストベースのデジタル信号処理効果を含めることができます。 これらの効果は、ユーザー モードのオーディオ処理オブジェクト (API) としてパッケージ化されます。 詳細については、「 Windows オーディオ処理オブジェクト」を参照してください。

ここで説明する API の中には、独立系ハードウェア ベンダー (IHV) と独立系ソフトウェア ベンダー (ISV) の新しいシナリオを実現する API もあれば、オーディオの全体的な信頼性とデバッグ機能を向上させる代替手段を提供するための API もあります。

  • Acoustic Echo Cancellation (AEC) フレームワークを使用すると、APO は自身を AEC APO として識別し、参照ストリームと追加のコントロールへのアクセスを許可します。
  • Settings フレームワークを使用すると、API は、オーディオ エンドポイントでオーディオ効果 ("FX プロパティ ストア") のプロパティ ストアを照会および変更するためのメソッドを公開できます。 これらのメソッドが APO によって実装されている場合は、その APO に関連付けられているハードウェア サポート アプリ (HSA) によって呼び出すことができます。
  • Notifications フレームワークを使用すると、オーディオ エフェクト (API) は、ボリューム、エンドポイント、およびオーディオ効果のプロパティ ストアの変更を処理するための通知を要求できます。
  • ログ 記録フレームワークは、API の開発とデバッグに役立ちます。
  • スレッド フレームワークを使用すると、OS マネージド MMCSS 登録済みスレッド プールを使用して、API をマルチスレッド化できます。
  • オーディオ効果検出および制御 API を使用すると、OS はストリームでの処理に使用できる効果を検出、有効化、無効化できます。

これらの新しい API を活用するために、API は新しい IAudioSystemEffects3 インターフェイスを利用することが期待されます。 APO がこのインターフェイスを実装すると、OS はこれを APO が APO 設定フレームワークをサポートし、APO がオーディオ エンジンからの一般的なオーディオ関連通知をサブスクライブすることを可能にする暗黙的なシグナルとして解釈されます。

Windows 11 APO CAPX の開発要件

Windows 11 用デバイスに付属する新しい API は、HLK を使用して検証される、このトピックに記載されている API に準拠している必要があります。 さらに、AEC を利用するすべての API は、HLK を使用して検証された、このトピックで概説されている実装に従う必要があります。 これらのコア オーディオ処理拡張機能 (設定、ログ記録、通知、スレッド処理、AEC) のカスタム実装では、CAPX API を活用することが期待されます。 これは、Windows 11 HLK テストを通じて検証されます。 たとえば、APO が Settings Framework を使用する代わりにレジストリ データを使用して設定を保存している場合、関連付けられている 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 Insider Program を通じて以下にダウンロードできます。 パートナー センターを通じて Microsoft と連携しているパートナーは、共同作業を通じてこのコンテンツにアクセスすることもできます。 共同作業の詳細については、「 Microsoft 共同作業の概要」を参照してください。

Windows 11 WHCP コンテンツは、パートナーにこれらの API を検証する手段を提供するように更新されました。

このトピックで説明するコンテンツのサンプル コードは、Audio/SYSVAD/APO - github にあります。

音響エコーキャンセレーション (Acoustic Echo Cancellation, AEC)

Acoustic Echo Cancellation (AEC) は、マイク キャプチャ パイプラインのオーディオ処理オブジェクト (APO) として独立系ハードウェア ベンダー (IHV) および独立系ソフトウェア ベンダー (ISV) によって実装される一般的なオーディオ効果です。 この効果は、マイクからのオーディオ ストリームと、参照信号として機能するレンダー デバイスからのオーディオ ストリームという 2 つの入力が必要であるという点で、IHV や ISV によって一般的に実装される他の効果とは異なります。

この新しいインターフェイス セットにより、AEC APO はオーディオ エンジンに対して自身を識別できます。 これにより、オーディオ エンジンは複数の入力と 1 つの出力で APO を適切に構成します。

APO によって新しい AEC インターフェイスが実装されると、オーディオ エンジンは次の処理を行います。

  • 適切なレンダー エンドポイントからの参照ストリームを APO に提供する追加の入力を使用して APO を構成します。
  • レンダー デバイスが変更されると、参照ストリームを切り替えます。
  • APO が入力マイクと参照ストリームの形式を制御できるようにします。
  • APO がマイクと参照ストリームのタイムスタンプを取得できるようにします。

以前の方法 - Windows 10

API は単一の入力であり、単一の出力オブジェクトです。 オーディオ エンジンは、入力時にマイク エンドポイントからのオーディオを AEC APO に提供します。 参照ストリームを取得するには、APO は、独自のインターフェイスを使用してドライバーと対話してレンダー エンドポイントから参照オーディオを取得するか、WASAPI を使用してレンダリング エンドポイントでループバック ストリームを開くことができます。

上記のどちらの方法にも欠点があります。

  • プライベート チャネルを使用してドライバーから参照ストリームを取得する AEC APO。通常は、統合オーディオ レンダー デバイスからのみ行うことができます。 その結果、ユーザーが USB やBluetoothオーディオ デバイスなどの非統合デバイスからオーディオを再生している場合、エコー キャンセルは機能しません。 参照エンドポイントとして機能できる適切なレンダリング エンドポイントは、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 の一般的な情報については、「 オーディオ処理オブジェクトのアーキテクチャ」を参照してください。

キャプチャ エンドポイントに対するモード効果が一連のチェーン API として実装されている場合、デバイスに最も近い APO のみがこのインターフェイスを実装できます。 このインターフェイスを実装する API には、 IAudioProcessingobjectRT::APOProcess の呼び出しでAPO_CONNECTION_PROPERTY_V2構造体が提供されます。 APO は、接続プロパティでAPO_CONNECTION_PROPERTY_V2_SIGNATURE署名を確認し、受信APO_CONNECTION_PROPERTY構造体をAPO_CONNECTION_PROPERTY_V2構造体に型キャストできます。

AEC API は通常、特定のサンプリング レート/チャネル数でアルゴリズムを実行するという事実を認識して、オーディオ エンジンは IApoAcousticEchoCancellation インターフェイスを実装する API にリサンプリング のサポートを提供します。

AEC APO が IAudioProcessingObject::OutInputFormatSupported の呼び出しでAPOERR_FORMAT_NOT_SUPPORTEDを返すと、オーディオ エンジンは、APO で IAudioProcessingObject::IsInputFormatSupported を NULL 出力形式と null 以外の入力形式で再度呼び出して、APO の推奨形式を取得します。 オーディオ エンジンは、AEC APO に送信する前に、提案された形式にマイク オーディオを再サンプリングします。 これにより、AEC APO でサンプリング レートとチャネル数の変換を実装する必要がなくなります。

IApoAuxiliaryInputConfiguration

IApoAuxiliaryInputConfiguration インターフェイスは、オーディオ エンジンが補助入力ストリームを追加および削除できるように、API が実装できるメソッドを提供します。

このインターフェイスは AEC APO によって実装され、オーディオ エンジンによって参照入力を初期化するために使用されます。 Windows 11 では、AEC APO は、エコー キャンセル用の参照オーディオ ストリームを持つ 1 つの補助入力でのみ初期化されます。 AddAuxiliaryInput メソッドは、APO に参照入力を追加するために使用されます。 初期化パラメーターには、ループバック ストリームが取得されるレンダー エンドポイントへの参照が含まれます。

IsInputFormatSupported メソッドは、補助入力の形式をネゴシエートするためにオーディオ エンジンによって呼び出されます。 AEC APO が特定の形式を優先する場合は、IsInputFormatSupported の呼び出しでS_FALSEを返し、推奨される形式を指定できます。 オーディオ エンジンは、参照オーディオを推奨形式に再サンプリングし、AEC APO の補助入力で提供します。

IApoAuxiliaryInputRT

IApoAuxiliaryInputRT インターフェイスは、APO の補助入力を駆動するために使用されるリアルタイムセーフ インターフェイスです。

このインターフェイスは、補助入力のオーディオ データを APO に提供するために使用されます。 補助オーディオ入力は 、IAudioProcessingObjectRT::APOProcess の呼び出しと同期されないことに注意してください。 レンダリング エンドポイントからオーディオがレンダリングされていない場合、ループバック データは補助入力では使用できません。 つまり、IApoAuxiliaryInputRT::AcceptInput の呼び出しはありません

AEC CAPX API の概要

詳細については、次のページで追加情報を参照してください。

サンプル コード - AEC

次の Sysvad Audio AecApo コード サンプルを参照してください。

Aec APO サンプル ヘッダー AecAPO.h の次のコードは、追加される 3 つの新しいパブリック メソッドを示しています。

 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 、APO が 1 つの補助入力のみを処理できる場合の AddAuxiliaryInput の実装を示しています。

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::IsInputFormatSupportedCAecApoMFX::AcceptInput の実装と、 APO_CONNECTION_PROPERTY_V2の処理を示すサンプル コードも確認します。

操作のシーケンス - AEC

初期化時:

  1. IAudioProcessingObject::Initialize
  2. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  3. IAudioProcessingObjectConfiguration:: LockForProcess
  4. IAudioProcessingObjectConfiguration ::UnlockForProcess
  5. IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput

レンダー デバイスの変更時:

  1. IAudioProcessingObject::Initialize
  2. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  3. IAudioProcessingObjectConfiguration::LockForProcess
  4. 既定のデバイスの変更
  5. IAudioProcessingObjectConfiguration::UnlockForProcess
  6. IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
  7. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  8. IAudioProcessingObjectConfiguration::LockForProcess

これは AEC に推奨されるバッファー動作です。

  • IApoAuxiliaryInputRT::AcceptInput の呼び出しで取得したバッファーは、メイン スレッドをロックせずに循環バッファーに書き込む必要があります。
  • IAudioProcessingObjectRT::APOProcess の呼び出しでは、参照ストリームから最新のオーディオ パケットの循環バッファーを読み取る必要があります。このパケットは、エコー キャンセル アルゴリズムを使用して実行するために使用する必要があります。
  • リファレンスデータとマイクデータのタイムスタンプを使用して、スピーカーとマイクのデータを整列することができます。

リファレンス・ループバック・ストリーム

既定では、ループバック ストリームは、ボリュームまたはミュートが適用される前にオーディオ ストリームを "タップ" (リッスン) します。 ボリュームが適用される前にタップされたループバック ストリームは、ボリューム前ループバック ストリームと呼ばれます。 プリボリューム ループバック ストリームを使用する利点は、現在のボリューム設定に関係なく、クリアで統一されたオーディオ ストリームです。

一部の AEC アルゴリズムでは、ボリューム処理 (ミュート中を含む) の後に接続されたループバック ストリームを取得することを好む場合があります。 この構成は、ボリューム後ループバックと呼ばれます。

次のメジャー バージョンの Windows AEC API では、サポートされているエンドポイントでボリューム後ループバックを要求できます。

制限事項

すべてのレンダー エンドポイントで使用できるボリューム前ループバック ストリームとは異なり、ボリューム後ループバック ストリームはすべてのエンドポイントで使用できるわけではありません。

ボリューム設定後のループバック要求

ボリューム後ループバックを使用する AEC API は、 IApoAcousticEchoCancellation2 インターフェイスを実装する必要があります。

AEC APO は、IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties の実装で Properties パラメーターを使用してAPO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK フラグを返すことによって、ボリューム後ループバックを要求できます。

現在使用されているレンダー エンドポイントによっては、ボリューム後ループバックを使用できない場合があります。 AEC APO は、IApoAuxiliaryInputConfiguration::AddAuxiliaryInput メソッドが呼び出されたときに、ポストボリュームループバックが使用されている場合に通知されます。 AcousticEchoCanceller_Reference_Input streamProperties フィールドにAPO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACKが含まれている場合は、ボリューム後ループバックが使用されます。

AEC APO サンプル ヘッダー AecAPO.h の次のコードは、追加される 3 つの新しいパブリック メソッドを示しています。

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.

設定フレームワーク

Settings Framework を使用すると、API は、オーディオ エンドポイントでのオーディオ効果 ("FX プロパティ ストア") のプロパティ ストアのクエリと変更のメソッドを公開できます。 このフレームワークは、その APO に設定を通信する API とハードウェア サポート アプリ (HSA) によって使用できます。 HSA はユニバーサル Windows プラットフォーム (UWP) アプリにすることができ、Settings Framework で API を呼び出すために特別な機能を必要とします。 HSA アプリの詳細については、「 UWP デバイス アプリ」を参照してください。

FxProperty ストア構造

新しい FxProperty ストアには、Default、User、Volatile の 3 つのサブストアがあります。

"Default" サブキーにはカスタム効果プロパティが含まれており、INF ファイルから設定されます。 これらのプロパティは、OS のアップグレード間で保持されません。 たとえば、INF で通常定義されるプロパティは、ここに収まります。 これらは INF から再入力されます。

"User" サブキーには、効果プロパティに関連するユーザー設定が含まれています。 これらの設定は、アップグレードと移行の間、OS によって保持されます。 たとえば、アップグレード後も保持されることが期待される、ユーザーが構成できるプリセットなどです。

"Volatile" サブキーには、揮発性効果のプロパティが含まれています。 これらのプロパティは、デバイスの再起動時に失われ、エンドポイントがアクティブに移行するたびにクリアされます。 これには、時間バリアントプロパティ (現在実行中のアプリケーション、デバイスの状態などに基づくなど) が含まれていると予想されます。たとえば、現在の環境に依存する設定などです。

ユーザーと既定値を考慮する方法は、OS とドライバーのアップグレード全体でプロパティを保持するかどうかです。 ユーザー のプロパティは保持されます。 既定のプロパティは INF から再入力されます。

APO の文脈

CAPX 設定フレームワークを使用すると、APO 作成者はコンテキスト別に APO プロパティをグループ化 できます。 各 APO は、独自のコンテキストを定義し、独自のコンテキストを基準にしてプロパティを更新できます。 オーディオ エンドポイントのエフェクト プロパティ ストアには、0 個以上のコンテキストが含まれている場合があります。 ベンダーは、SFX/MFX/EFX またはモードに関係なく、選択したコンテキストを自由に作成できます。 ベンダーは、そのベンダーによって出荷されるすべての API に対して 1 つのコンテキストを持つこともできます。

制限付き機能の設定

設定 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;
}

IAudioProcessingObject::IAudioSystemEffectsPropertyStore を使用してコードを初期化する

このサンプルは、APO の初期化時に、APOInitSystemEffects3 構造体を用いて、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;
}

サンプル コード - Settings Framework

このサンプル コードは、sysvad SFX Swap 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}"

通知フレームワーク

Notifications フレームワークを使用すると、オーディオ エフェクト (API) は、ボリューム、エンドポイント、およびオーディオ効果のプロパティ ストアの変更通知を要求して処理できます。 このフレームワークは、通知の登録と登録解除に API によって使用される既存の API を置き換えることを目的としています。

新しい API には、APO が関心を持つ通知の種類を宣言するために API が使用できるインターフェイスが導入されています。 Windows は、関心のある通知について APO にクエリを実行し、その通知を API に転送します。 APOs はもはや登録または登録解除の API を明示的に呼び出す必要がありません。

通知は、シリアル キューを使用して APO に配信されます。 該当する場合、最初の通知は、要求された値の初期状態 (たとえば、オーディオ エンドポイント ボリューム) をブロードキャストします。 通知は、audiodg.exe がストリーミング用の APO を使用する意図を停止すると停止します。 ATO は UnlockForProcess 後に通知の受信を停止します。 UnlockForProcess と実行中の通知を同期する必要があります。

実装 - 通知フレームワーク

通知フレームワークを活用するために、APO は、関心のある通知を宣言します。 明示的な登録/登録解除の呼び出しはありません。 APO に対するすべての通知はシリアル化されるため、通知コールバック スレッドを長時間ブロックしないことが重要です。

API 定義 - Notifications Framework

通知フレームワークは、APO エンドポイントとシステム効果通知の一般的なオーディオ関連の通知を登録および受信するためにクライアントが実装できる新しい IAudioProcessingObjectNotifications インターフェイスを実装します。

詳細については、次のページで追加のコンテンツを参照してください。

サンプル コード - Notifications Framework

このサンプルでは、APO で IAudioProcessingObjectNotifications インターフェイスを実装する方法を示します。 GetApoNotificationRegistrationInfo メソッドでは、サンプル APO はシステム効果プロパティ ストアへの変更に対する通知を登録します。
HandleNotification メソッドは、APO が登録した内容に一致する変更を APO に通知するために OS によって呼び出されます。

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;
    }
}

次のコードは 、Swap 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 が提供され、残りの作業は OS によって実行されます。

プロバイダーは次のように定義されます。

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 には、独自のアクティビティ ID があります。 これは既存のトレース ログ メカニズムを使用するため、既存のコンソール ツールを使用してこれらのイベントをフィルター処理し、リアルタイムで表示できます。 「 ソフトウェア トレース用ツール - Windows ドライバー」の説明に従って、tracelog や tracefmt などの既存のツールを使用できます。 トレース セッションの詳細については、「 コントロール GUID を使用したトレース セッションの作成」を参照してください。

トレース ログ イベントはテレメトリとしてマークされず、xperf などのツールではテレメトリ プロバイダーとして表示されません。

実装 - ログ 記録フレームワーク

ログ記録フレームワークは、ETW トレースによって提供されるログメカニズムに基づいています。 ETW の詳細については、「 イベント トレース」を参照してください。 これは、オーディオ データのログ記録ではなく、通常は運用環境でログに記録されるイベントをログに記録するためのものです。 ログ記録 API は、リアルタイム ストリーミング スレッドから使用しないでください。これにより、ポンプ スレッドが OS CPU スケジューラによってプリエンプションされる可能性があるためです。 ログ記録は、主に、フィールドでよく見られる問題のデバッグに役立つイベントに使用する必要があります。

API 定義 - ログ 記録フレームワーク

Logging フレームワークには、API 用の新しいログ サービスを提供する IAudioProcessingObjectLoggingService インターフェイスが導入されています。

詳細については、「 IAudioProcessingObjectLoggingService」を参照してください。

サンプル コード - ログ 記録フレームワーク

このサンプルでは、メソッド IAudioProcessingObjectLoggingService::ApoLog の使用方法と、IAudioProcessingObject::Initialize でこのインターフェイス ポインターを取得する方法を示します。

AecApoMfx ログの例

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) タスクの作業キューを使用して、効果をマルチスレッド化できるようにするスレッドフレームワーク。 リアルタイムのシリアル作業キューの作成とメイン ポンプ スレッドとの関連付けは、OS によって処理されます。 このフレームワークにより、API は実行時間の短い作業項目をキューに登録できます。 タスク間の同期は引き続き APO の責任です。 MMCSS スレッドの詳細については、「 マルチメディア クラス スケジューラ サービス 」および 「作業キュー API のReal-Time」を参照してください。

API 定義 - スレッド フレームワーク

Threading フレームワークには、API のリアルタイム作業キューへのアクセスを提供する IAudioProcessingObjectQueueService インターフェイスが導入されています。

詳細については、次のページで追加のコンテンツを参照してください。

サンプル コード - スレッド フレームワーク

このサンプルでは、メソッド IAudioProcessingObjectRTQueueService::GetRealTimeWorkQueue の使用方法と、IAudioProcessingObjectRTQueueService インターフェイス ポインターが IAudioProcessingObject::Initialize でどのように取得されるかを示します。

#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 関数を通じてオーディオ効果を有効または無効にします。

サンプル コード - オーディオ効果の検出

Audio Effect Discovery サンプル コードは、 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 API の再利用

Windows 11 バージョン 22H2 以降では、受信トレイ WM SFX および MFX API を再利用する INF 構成ファイルで、CAPX SFX および MFX API を再利用できるようになりました。 このセクションでは、これを行う 3 つの方法について説明します。

API には、事前ミックス レンダリング、ポストミックス レンダリング、キャプチャの 3 つの挿入ポイントがあります。 各論理デバイスのオーディオ エンジンは、ストリームあたりの事前ミックス レンダリング APO (render SFX) と 1 つのポストミックス レンダー APO (MFX) の 1 つのインスタンスをサポートします。 オーディオ エンジンは、各キャプチャ ストリームに挿入されるキャプチャ APO (キャプチャ SFX) の 1 つのインスタンスもサポートしています。 受信トレイ API を再利用またはラップする方法の詳細については、「 カスタム API と Windows API を組み合わせる」を参照してください。

CAPX SFX および MFX API は、次の 3 つの方法のいずれかで再利用できます。

INF DDInstall セクションの使用

wdmaudio.inf から mssysfx.CopyFilesAndRegisterCapX を使用し、次のエントリを追加します。

   Include=wdmaudio.inf
   Needs=mssysfx.CopyFilesAndRegisterCapX

拡張 INF ファイルの使用

wdmaudioapo.inf は AudioProcessingObject クラス拡張 inf です。 これには、SFX および MFX API のデバイス固有の登録が含まれています。

ストリームとモードの効果のために WM SFX および MFX API を直接参照する

ストリームとモードの効果のためにこれらの API を直接参照するには、次の GUID 値を使用します。

  • WM SFX APO として {C9453E73-8C5C-4463-9984-AF8BAB2F5447} を使用する
  • WM MFX APO として {13AB3EBD-137E-4903-9D89-60BE8277FD17} を使用します。

Sfx (Stream) と MFX (Mode) は Windows 8.1 で LFX (ローカル) と呼ばれ、MFX は GFX (グローバル) と呼ばれていました。 これらのレジストリ エントリでは、以前の名前が引き続き使用されます。

デバイス固有の登録では、HKCR ではなく HKR が使用されます。

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 ファイル エントリは、新しい APO のために Windows 11 API が使用するプロパティ ストアを作成します。

INF ex における PKEY_FX_Association。 HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%に置き換える必要があります。

こちらも参照ください

Windows オーディオ処理オブジェクト