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

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

Windows では、サードパーティのオーディオ ハードウェア メーカーがカスタムのホストベースのデジタル信号処理エフェクトを組み込むことができます。 これらのエフェクトは、ユーザーモードのオーディオ処理オブジェクト (APO) としてパッケージ化されています。 詳細については、Windows オーディオ処理オブジェクトを参照してください。

ここで説明する API の一部は、独立系ハードウェア ベンダー (IHV) および独立系ソフトウェア ベンダー (ISV) 向けの新しいシナリオを可能にしますが、他の API は全体的なオーディオの信頼性とデバッグ機能を向上させる代替手段を提供することを目的としています。

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

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

Windows 11 APO CAPX 開発要件

Windows 11 のデバイスに付属する新しい API は、HLK を介して検証され、このトピックに記載されている API に準拠している必要があります。 さらに、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 Insider Program からダウンロードできます。 パートナー センターを通じて Microsoft と連携しているパートナーは、共同作業を通じてこのコンテンツにアクセスすることもできます。 共同作業の詳細については、Microsoft Collaborate の概要を参照してください。

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

このトピックで概説する内容のサンプル コードは、次の場所にあります:オーディオ/SYSVAD/APO - github

音響エコーキャンセレーション (AEC)

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

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

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

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

以前のアプローチ - Windows 10

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

上記のアプローチには両方とも次のような欠点があります:

  • プライベート チャネルを使用してドライバーから参照ストリームを取得する AEC APO は、通常、統合オーディオ レンダリング デバイスからのみ取得できます。 その結果、ユーザーが USB や Bluetooth オーディオ デバイスなどの非統合デバイスからオーディオを再生している場合、エコー キャンセルは機能しません。 OS だけが、参照エンドポイントとして機能できる適切なレンダリング エンドポイントを認識しています。

  • APO は、WASAPI を使用してデフォルトのレンダリング エンドポイントを選択し、エコー キャンセルを実行できます。 ただし、audiodg.exe プロセス (APO がホストされているプロセス) からループバック ストリームを開くときに注意すべき落とし穴がいくつかあります。

    • ループバック ストリームは、オーディオ エンジンがメイン APO メソッドを呼び出しているときに、デッドロックが発生する可能性があるため、開いたり破棄したりできません。
    • キャプチャ APO は、クライアントのストリームの状態を認識しません。 つまり、キャプチャ アプリは ‘STOP’状態のキャプチャ ストリームを持つことができますが、APO はこの状態を認識しないため、ループバック ストリームを ‘RUN’ 状態で開いたままにするため、電力消費の点で非効率的です。

API 定義 - AEC

AEC フレームワークは、APO が活用できる新しい構造とインターフェイスを提供します。 これらの新しい構造とインターフェイスについては以下で説明します。

APO_CONNECTION_PROPERTY_V2 構造体

IApoAcousticEchoCancellation インターフェイスを実装する APO には、 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 のみがこのインターフェイスを実装できます。 このインターフェイスを実装する APO には、 IAudioProcessingobjectRT::APOProcess の呼び出しでAPO_CONNECTION_PROPERTY_V2構造体が提供されます。 APO は、接続プロパティの APO_CONNECTION_PROPERTY_V2_SIGNATURE 署名を確認し、受信した APO_CONNECTION_PROPERTY 構造体を APO_CONNECTION_PROPERTY_V2 構造体に型キャストできます。

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

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 は、エコー キャンセレーション用の参照オーディオ ストリームを持つ 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;

また、 APO_CONNECTION_PROPERTY_V2と の実装CAecApoMFX::IsInputFormatSupportedCAecApoMFX::AcceptInputの処理を示すサンプル コードも確認してください。

操作のシーケンス - 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 の次のメジャー バージョンでは、APO はサポートされているエンドポイントでポストボリューム ループバックを要求できます。

制限事項

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

ポストボリュームループバックのリクエスト

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

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

現在使用されているレンダー エンドポイントによっては、ポスト ボリューム ループバックを利用できない場合があります。 AEC APO は、 IApoAuxiliaryInputConfiguration::AddAuxiliaryInputメソッドが呼び出されたときに、ボリューム後のループバックが使用されている場合に通知されます。 AcousticEchoCanceller_Reference_InputstreamProperties フィールドに 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.

設定フレームワーク

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

FxProperty ストアの構造

新しい FxProperty ストアには 3 つのサブストアがあります。デフォルト、ユーザー、および揮発性。

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

「User」サブキーには、エフェクトのプロパティに関するユーザー設定が含まれています。 これらの設定は、アップグレードや移行後も OS によって保持されます。 たとえば、ユーザーが構成できるプリセットは、アップグレード後も保持されることが予想されます。

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

ユーザーとデフォルトについて考える方法は、OS とドライバーのアップグレード後もプロパティを維持するかどうかです。 ユーザープロパティは永続化されます。 デフォルトのプロパティは INF から再設定されます。

APO コンテキスト

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

設定制限付き機能

設定 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 ストア アプリケーション、管理者以外のデスクトップ アプリケーション、および APO によって読み取りおよび書き込みが可能です。 さらに、これは、APO がサービスまたは UWP ストア アプリケーションにメッセージを返送するメカニズムとして機能します。

Note

これは制限された機能です:この機能を備えたアプリケーションが 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 の初期化中に、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 dentified 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;
}

// Unsubscrbe 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 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}"

通知フレームワーク

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

新しい API では、APO が関心のある通知の種類を宣言するために APO が利用できるインターフェイスが導入されています。 Windows は、関心のある通知について APO を照会し、その通知を APO に転送します。 APO は登録 API や登録解除 API を明示的に呼び出す必要がなくなりました。

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

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

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

API 定義 - 通知フレームワーク

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

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

サンプルコード - 通知フレームワーク

このサンプルでは、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 dentified 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 a 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 ficticious
        // 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 a 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 定義 - ロギング フレームワーク

ログ フレームワークでは、APO の新しいログ サービスを提供する 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 Initializion completed");
    }
    return S_OK;
}

スレッディングフレームワーク

シンプルな API を介して適切なマルチメディア クラス スケジューラ サービス (MMCSS) タスクからのワーク キューを使用することで、エフェクトをマルチスレッド化できるスレッド フレームワーク。 リアルタイム シリアル ワーク キューの作成とメイン ポンプ スレッドとの関連付けは、OS によって処理されます。 このフレームワークにより、APO は短時間実行される作業項目をキューに入れることができます。 タスク間の同期は引き続き APO の責任です。 MMCSS スレッドの詳細については、マルチメディア クラス スケジューラ サービスおよびリアルタイム ワーク キュー APIを参照してください。

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

スレッド フレームワークでは、APO のリアルタイム作業キューへのアクセスを提供する 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 関数を使用してオーディオ効果を有効または無効にします。

サンプルコード - オーディオエフェクトの発見

オーディオ エフェクト検出のサンプル コードは、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 APO の再利用

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

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

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

INF DDInstall セクションの使用

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

   Include=wdmaudio.inf
   Needs=mssysfx.CopyFilesAndRegisterCapX

拡張子 INF ファイルの使用

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

ストリームおよびモードエフェクトの WM SFX および MFX APO の直接参照

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

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

SFX (ストリーム) と MFX (モード) は、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 の PKEY_FX_Association 例。 HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%は に置き換えてくださいHKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%

関連項目

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