ACX ストリーミング

このトピックでは、ACX ストリーミングと、障害のないオーディオ エクスペリエンスに不可欠な関連バッファリングの概要について説明します。 ドライバーがストリームの状態について通信し、ストリームのバッファーを管理するために使用されるメカニズムについて説明します。 一般的な ACX オーディオ用語のリストと ACX の概要については、「ACX オーディオ クラス拡張機能の概要」を参照してください。

Note

ACX ヘッダーとライブラリは、WDK 10.0.22621.2428 (2023 年 10 月 24 日リリース) には含まれていませんが、以前のバージョンと WDK の最新 (25000 シリーズ ビルド) Insider Preview で使用できます。 WDK のプレビュー バージョンの詳細については、「Windows Driver Kit (WDK) のプレビュー バージョンのインストール」を参照してください。

ACX ストリーミング タイプ

AcxStream は、特定の回線のハードウェア上のオーディオ ストリームを表します。 AcxStream は、1 つ以上の AcxElements のようなオブジェクトを集計できます。

ACX フレームワークでは、2 つのストリーム型がサポートされています。 最初のストリーム型である RT パケット ストリームは、RT パケットの割り当てと、ストリームの状態遷移とともに、デバイス ハードウェアとの間でオーディオ データを転送するために RT パケットを使用するためのサポートを提供します。 2 番目のストリーム型である基本ストリームでは、ストリームの状態遷移のみがサポートされます。

単一回線エンドポイントでは、回線は RT パケット ストリームを作成するストリーミング回線である必要があります。 エンドポイントを作成するために複数の回線が接続されている場合、エンドポイントの最初の回線はストリーミング回線であり、RT パケット ストリームが作成されます。接続された回線は、ストリームの状態遷移に関連するイベントを受信する基本ストリームを作成します。

詳しくは、「ACX ストリーム」 (ACX オブジェクトの概要) を参照してください。 ストリームの DDI については、acxstreams.h ヘッダーで定義しています。

ACX ストリーミング通信スタック

ACX ストリーミングには、2 つの通信の種類があります。 1 つの通信パスは、標準の ACX 通信を使用する [開始]、[作成]、[割り当て] などのコマンドのストリーミング動作を制御するために使用されます。 ACX フレームワークは IO キューを使用し、キューを使用して WDF 要求に沿って渡します。 キューの動作は、Evt コールバックと ACX 関数を使用して実際のドライバー コードから非表示になりますが、ドライバーにはすべての WDF 要求を前処理する機会も与えられます。

2 番目の興味深い通信パスは、オーディオ ストリーミング シグナリングに使用されます。 ここでは、パケットの準備ができたときにドライバーに指示し、ドライバーがパケットの処理を完了したときにデータを受信します。 

ストリーミング シグナリングの主要な要件は次のとおりです。

  • 障害のない再生のサポート
    • 低遅延
    • 必要なロックは、該当するストリームに限定されます
  • ドライバー開発者にとって使いやすいこと

ACX は、ストリーミング状態を通知するためにドライバーと通信するために、共有バッファーと直接 IRP 呼び出しを含むイベントを使用します。 これらは次に説明します。

共有バッファー

ドライバーからクライアントに通信するには、共有バッファーとイベントが使用されます。 これにより、クライアントは待機またはポーリングする必要がなくなり、直接 IRP 呼び出しの必要性を減らしたり排除したりしながら、ストリーミングを続行するために必要なすべてのものをクライアントが判断できるようになります。

デバイス ドライバーは、共有バッファーを使用して、パケットのレンダリング元またはキャプチャ先のクライアントと通信します。 この共有バッファーには、完了時刻の QPC (QueryPerformanceCounter) 値と共に、最後に完了したパケットのパケット数 (1 から始まる) が含まれます。 デバイス ドライバーの場合は、AcxRtStreamNotifyPacketComplete を呼び出してこの情報を示す必要があります。 デバイス ドライバーが AcxRtStreamNotifyPacketComplete を呼び出すと、ACX フレームワークは、新しいパケット数と QPC で共有バッファーを更新し、クライアントが新しいパケット数を読み取る可能性があることを示すためにクライアントと共有されるイベントを通知します。

直接の IRP 呼び出し

クライアントからドライバーへの通信には、直接 IRP 呼び出しが使用されます。 これにより、WDF 要求がタイムリーに処理されるようにするための複雑さが軽減され、既存のアーキテクチャでも適切に機能することが証明されています。

クライアントは、いつでも現在のパケット数を要求したり、デバイス ドライバーに現在のパケット数を示したりすることができます。 これらの要求は、EvtAcxStreamGetCurrentPacketEvtAcxStreamSetRenderPacket デバイス ドライバー イベント ハンドラーを呼び出します。 クライアントは、EvtAcxStreamGetCapturePacket デバイス ドライバー イベント ハンドラーを呼び出す現在のキャプチャ パケットを要求することもできます。

PortCls との類似点

直接 IRP 呼び出しと ACX によって使用される共有バッファーの組み合わせは、バッファー完了処理が PortCls で通信される方法と似ています。 IRP は非常によく似ていおり、共有バッファーでは、IRP に依存せずに、ドライバーがパケット数とタイミングを直接通信する機能が導入されています。   障害を防ぐためにも、ドライバーは、ストリーム制御パスでも使用されているロックへのアクセスをする必要がないことを確認してください。 

低電力再生のためのラージ バッファー サポート

メディア コンテンツを再生するときに消費される電力量を減らすには、APU が高電力状態で費やす時間を減らすことが重要です。 通常のオーディオ再生では 10 ミリ秒のバッファーが使用されるため、APU は常にアクティブである必要があります。 ACX ドライバーは、APU に状態を減らす必要がある時間を与えるために、1 ~ 2 秒のサイズ範囲で、大幅に大きなバッファーのサポートをアドバタイズできます。 つまり、APU は 1 秒 ~ 2 秒ごとに 1 回起動し、次の 1 ~ 2 秒のバッファーを準備するために必要な操作を最大速度で実行して、次のバッファーが必要になるまで、可能な限り最小の電源状態に移行できます。 

既存のストリーミング モデルでは、オフロード再生を通じて低電力再生がサポートされています。 オーディオ ドライバーは、エンドポイントのウェーブ フィルターで AudioEngine ノードを公開することによって、オフロード再生のサポートをアドバタイズします。 AudioEngine ノードは、ドライバーが必要とする処理でラージ バッファーからオーディオをレンダリングするために使用する DSP エンジンを制御する手段を提供します。

AudioEngine ノードには、次の機能があります。

  • オーディオ エンジンの説明。ウェーブ フィルター上のどのピンがオフロードとループバックのサポート (およびホスト再生のサポート) を提供するかをオーディオ スタックに通知します。 
  • バッファー サイズ範囲。オーディオ スタックに、オフロード再生に対応できる最小バッファー サイズと 最大バッファー サイズを通知します。 バッファー サイズ範囲は、システム アクティビティに基づいて動的に変更できます。 
  • サポートされている形式、現在のデバイス ミックス形式、デバイス形式など、書式のサポート。 
  • 大きなバッファーではソフトウェア ボリュームの応答性が得られないため、ボリューム (ランピングのサポートを含む)。
  • ループバック保護。1 つ以上のオフロード ストリームに保護されたコンテンツが含まれている場合に AudioEngine ループバック ピンをミュートするようにドライバーに指示します。 
  • グローバル FX 状態。AudioEngine で GFX を有効または無効にします。 

オフロード ピンでストリームが作成されると、ストリームはボリューム、ローカル FX、ループバック保護をサポートします。 

ACX による低電力再生

ACX フレームワークは、低電力再生に同じモデルを使用します。 ドライバーは、ホスト、オフロード、ループバック ストリーミング用の 3 つの個別の ACXPIN オブジェクトと、これらのピンのどれがホスト、オフロード、ループバックに使用されるかを記述する ACXAUDIOENGINE 要素を作成します。 ドライバーは、回線の作成時に ACXCIRCUIT にピンと ACXAUDIOENGINE 要素を追加します。

オフロード ストリームの作成

また、ドライバーは、ボリューム、ミュート、およびピーク メーターを制御できるようにオフロード用に作成されたストリームに ACXAUDIOENGINE 要素を追加します。

ストリーミング図

この図は、マルチスタック ACX ドライバーを示しています。

上にカーネル ストリーミング インターフェイスがある DSP、CODEC、AMP ボックスを示す図。

各 ACX ドライバーは、オーディオ ハードウェアの個別の部分を制御し、別のベンダーによって提供される可能性があります。 ACX は、アプリケーションをそのまま実行できるようにするための互換性のあるカーネル ストリーミング インターフェイスを提供します。

ストリーム ピン

各 ACXCIRCUIT には、少なくとも 1 つのシンク ピンと 1 つのソース ピンがあります。 これらのピンを使用して、ACX フレームワークによって、回線の接続をオーディオ スタックに公開します。 レンダー回線の場合、ソース ピンを使用して、回線から作成されたストリームのレンダリング動作を制御します。 キャプチャ回線の場合、シンク ピンを使用して、回線から作成されたストリームのキャプチャ動作を制御します。   ACXPIN は、オーディオ パスのストリーミングを制御するために使用されるオブジェクトです。 ストリーミング ACXCIRCUIT は、回線作成時にエンドポイント オーディオ パスに適切な ACXPIN オブジェクトを作成し、ACXPIN を ACX に登録する役割を担います。 ACXCIRCUIT は、回路のレンダー ピンまたはキャプチャ ピンを作成するだけで済みます。ACX フレームワークは回線に接続し、通信するために必要な他のピンを作成します。   

ストリーミング回線

エンドポイントが 1 つの回線で構成されている場合、その回線がストリーミング回線になります。

エンドポイントが 1 つ以上のデバイス ドライバーによって作成された複数の回線で構成されている場合、回線は、構成されたエンドポイントを記述する ACXCOMPOSITETEMPLATE によって決定される特定の順序で接続されます。 エンドポイントの最初の回線は、エンドポイントのストリーミング回線です。

ストリーミング回線では、AcxRtStreamCreate を使用して、EvtAcxCircuitCreateStream に応じて RT パケット ストリームを作成する必要があります。 AcxRtStreamCreate で作成された ACXSTREAM を使用すると、ストリーミング回線ドライバーは、ストリーミングに使用されるバッファーを割り当て、クライアントとハードウェアのニーズに応じてストリーミング フローを制御できます。

エンドポイント内の以下の回路では、AcxStreamCreate を使用して、EvtAcxCircuitCreateStream に応じて基本ストリームを作成する必要があります。 次の回線によって AcxStreamCreate で作成された ACXSTREAM オブジェクトを使用すると、ドライバーは、一時停止や実行などのストリーム状態の変更に応じてハードウェアを構成できます。

ストリーミング ACXCIRCUIT は、ストリームを作成する要求を受信する最初の回線です。 要求には、デバイス、ピン、データ形式 (モードを含む) が含まれます。

オーディオ パス内の各 ACXCIRCUIT は、回線のストリーム インスタンスを表す ACXSTREAM オブジェクトを作成します。 ACX フレームワークは、ACXSTREAM オブジェクトをリンクします (ACXCIRCUIT オブジェクトがリンクされるのとほぼ同じ方法)。 

アップストリーム回線およびダウンストリーム回線

ストリームの作成はストリーミング回線から開始され、回線が接続された順序で各ダウンストリーム回線に転送されます。 この接続は、AcxPinCommunicationNone と等しい通信で作成されたブリッジ ピン間で行われます。 ACX フレームワークは、ドライバーが回線の作成時に追加しない場合、回線の 1 つ以上のブリッジ ピンを作成します。

ストリーミング回線から始まる回線ごとに、AcxPinTypeSource ブリッジ ピンが次のダウンストリーム回線に接続されます。 最後の回線には、オーディオ エンドポイント ハードウェア (エンドポイントがマイクかスピーカーか、ジャックが接続されているかどうかなど) を説明するエンドポイント ピンがあります。

ストリーミング回線に続く回線ごとに、AcxPinTypeSink ブリッジ ピンが次のアップストリーム回線に接続されます。

ストリーム形式ネゴシエーション

ドライバーは、AcxPinAssignModeDataFormatListAcxPinGetRawDataFormatList でストリーム作成に使用される ACXPIN にモードごとにサポートされるフォーマットを追加することによって、ストリーム作成のためにサポートされるフォーマットをアドバタイズします。 マルチ回線エンドポイントの場合、ACXSTREAMBRIDGE を使用して、ACX 回線間のモードとフォーマットのサポートを調整できます。 エンドポイントでサポートされるストリーム形式は、ストリーミング回線によって作成されたストリーミング ACXPIN によって決まります。 次の回線で使用される形式は、エンドポイント内の前の回線のブリッジ ピンによって決まります。

既定では、ACX フレームワークは、マルチ回線エンドポイント内の各回線間に ACXSTREAMBRIDGE を作成します。 既定の ACXSTREAMBRIDGE は、ストリーム作成要求をダウンストリーム回線に転送するときに、アップストリーム回線のブリッジ ピンの RAW モードの既定の形式を使用します。 アップストリーム回線のブリッジ ピンにフォーマットがない場合は、元のストリーム形式が使用されます。 ダウンストリーム回線の接続されたピンが使用されている形式をサポートしていない場合、ストリームの作成は失敗します。

デバイス回線がストリーム形式の変更を行っている場合、デバイス ドライバーはダウンストリーム ブリッジ ピンにダウンストリーム形式を追加する必要があります。  

ストリームの作成

ストリームの作成の最初の手順は、エンドポイント オーディオ パス内の各 ACXCIRCUIT の ACXSTREAM インスタンスを作成することです。 ACX は、各回線の EvtAcxCircuitCreateStream を呼び出します。 ACX はヘッド回線から開始し、各回線の EvtAcxCircuitCreateStream を順番に呼び出して、末尾の回線で終わります。 ストリーム ブリッジの AcxStreamBridgeInvertChangeStateSequenceフラグ (ACX_STREAM_BRIDGE_CONFIG_FLAGS で定義) を指定することによって、順序を逆にすることができます。 すべての回線でストリーム オブジェクトが作成されると、ストリーム オブジェクトはストリーミング ロジックを処理します。

ストリーム作成要求は、ヘッド回線の作成時に指定された EvtAcxCircuitCreateStream を呼び出すことによって、ヘッド回線のトポロジ生成の一部として生成された適切な PIN に送信されます。 

ストリーミング回線は、ストリーム作成要求を最初に処理するアップストリーム回線です。

  • AcxStreamCallbacks と AcxRtStreamCallbacks を割り当てて、ACXSTREAM_INIT 構造体を更新します
  • AcxRtStreamCreate を使用して ACXSTREAM オブジェクトを作成します
  • ストリーム固有の要素 (ACXVOLUME や ACXAUDIOENGINE など) を作成します
  • ACXSTREAM オブジェクトに要素を追加します
  • ACX フレームワークに作成された ACXSTREAM オブジェクトを返します

その後、ACX はストリームの作成を次のダウンストリーム回線に転送します

  • ACXSTREAM_INIT 構造体が更新され、AcxStreamCallbacks が割り当てられます
  • AcxStreamCreate を使用して ACXSTREAM オブジェクトを作成します
  • ストリーム固有の要素が作成されます
  • ACXSTREAM オブジェクトに要素を追加します
  • ACX フレームワークに作成された ACXSTREAM オブジェクトを返します

オーディオ パス内の回線間の通信チャネルでは、ACXTARGETSTREAM オブジェクトを使用します。 この例では、各回路は、エンドポイント オーディ オパスの前の回路と後ろの回路の IO キューにアクセスできます。 さらに、エンドポイント オーディオ パスは線形で双方向です。 実際の IO キュー処理は、ACX フレームワークによって実行されます。    ACXSTREAM オブジェクトを作成するときに、各回線は ACXSTREAM オブジェクトにコンテキスト情報を追加して、ストリームのプライベート データを格納および追跡できます。

レンダー ストリームの例

DSP、CODEC、AMP の 3 つの回線で構成されるエンドポイント オーディオ パスにレンダー ストリームを作成します。 DSP 回線はストリーミング回線として機能し、EvtAcxPinCreateStream ハンドラーを提供しています。 DSP 回路はフィルター回路としても機能します。ストリーム モードと構成によっては、オーディオ データに信号処理を適用する場合があります。 CODEC 回路は DAC を表し、オーディオ シンク機能を提供します。 AMP 回路は、DAC とスピーカーの間のアナログ ハードウェアを表します。 AMP 回路は、ジャック検出またはその他のエンドポイント ハードウェアの詳細を処理する場合があります。

  1. AudioKSE は NtCreateFile を呼び出してストリームを作成します。
  2. これにより、ACX をフィルター処理し、DSP 回線の EvtAcxPinCreateStream をピン、データフォーマット (モードを含む)、デバイス情報を使用して呼び出すことで終了します。 
  3. DSP 回線は、データフォーマット情報を検証して、作成されたストリームを確実に処理できるようにします。 
  4. DSP 回線は、ストリームを表す ACXSTREAM オブジェクトを作成します。 
  5. DSP 回線は、プライベート コンテキスト構造を割り当て、それを ACXSTREAM に関連付けます。 
  6. DSP 回線は ACX フレームワークに実行フローを返し、エンドポイント オーディオ パス (CODEC 回線) の次の回線を呼び出します。 
  7. CODEC 回線は、データフォーマット情報を検証して、データのレンダリングを処理できることを確認します。 
  8. CODEC 回線は、プライベート コンテキスト構造を割り当て、それを ACXSTREAM に関連付けます。 
  9. CODEC 回線は、それ自体をストリーム シンクとして ACXSTREAM に追加します。
  10. CODEC 回線は ACX フレームワークに実行フローを返し、エンドポイント オーディオ パス (AMP 回線) の次の回線を呼び出します。 
  11. AMP 回線は、プライベート コンテキスト構造を割り当て、それを ACXSTREAM に関連付けます。 
  12. AMP 回線は、実行フローを ACX フレームワークに返します。 この時点で、ストリームの作成は完了です。 

ラージ バッファー ストリーム

ラージ バッファー ストリームは、ACXCIRCUIT の ACXAUDIOENGINE 要素によってオフロード用に指定された ACXPIN に作成されます。

オフロード ストリームをサポートするには、デバイス ドライバーは、ストリーミング回線の作成中に次の操作を行う必要があります。

  1. ホスト、オフロード、ループバックの ACXPIN オブジェクトを作成し、ACXCIRCUIT に追加します。
  2. ACXVOLUME、ACXMUTE、および ACXPEAKMETER 要素を作成します。 これらは ACXCIRCUIT に直接追加されません。
  3. HostPin、OffloadPin、LoopbackPin、VolumeElement、MuteElement、PeakMeterElement オブジェクトを割り当てて、ACX_AUDIOENGINE_CONFIG 構造体を初期化します。
  4. ACXAUDIOENGINE 要素を作成します。

ドライバーは、オフロード ピンでストリームを作成するときに ACXSTREAMAUDIOENGINE 要素を追加する同様の手順を実行する必要があります。

ストリーム リソースの割り当て

ACX のストリーミング モデルはパケット ベースであり、ストリームに対して 1 つまたは 2 つのパケットがサポートされています。 ストリーミング回線のレンダー ACXPIN またはキャプチャ ACXPIN には、ストリームで使用されるメモリ パケットを割り当てる要求が与えられます。 再調整をサポートするには、システムにマップされたデバイス メモリではなく、割り当てられたメモリがシステム メモリである必要があります。 ドライバーは、既存の WDF 関数を使用して割り当てを実行し、バッファーの割り当てへのポインターの配列を返します。 ドライバーが 1 つの連続したブロックを必要とする場合は、両方のパケットを 1 つのバッファーとして割り当て、バッファーのオフセットへのポインターを 2 番目のパケットとして返す可能性があります。

1 つのパケットが割り当てられている場合、パケットはページアラインされ、ユーザー モードに 2 回マップされる必要があります。

| パケット 0 | パケット 0 |

これにより、GetBuffer は、メモリ アクセスのラップを処理するアプリケーションを必要とせずに、バッファーの末尾から先頭に及ぶ可能性がある単一の連続したメモリ バッファーへのポインターを返します。 

2 つのパケットが割り当てられている場合、それらはユーザー モードにマップされます。

| パケット 0 | パケット 1 |

最初の ACX パケット ストリーミングでは、最初に割り当てられるパケットは 2 つのみです。 クライアント仮想メモリのマッピングは、一度割り当てとマッピングが実行されると、ストリームの有効期限まで変更されることなく有効のままになります。 両方のパケットのパケットの完了を示すために、ストリームに関連付けられたイベントが 1 つあります。 また、ACX フレームワークが、どのパケットがイベントで終了したかを通信するために使用する共有バッファも存在します。  

ラージ バッファー ストリームのパケット サイズ

ラージ バッファーのサポートを公開する場合、ドライバーは、ラージ バッファー再生の最小および最大パケット サイズの決定に使用するコールバックも提供します。   ストリーム バッファー割り当てのパケット サイズは、最小値と最大値に基づいて決定されます。

最小および最大のバッファー サイズは変動する可能性があるため、最小と最大値が変更された場合、ドライバーはパケット割り当ての呼び出しに失敗する可能性があります。

ACX バッファー制約の指定

ACX バッファー制約を指定するために、ACX ドライバーは KS/PortCls プロパティ設定のKSAUDIO_PACKETSIZE_CONSTRAINTS2KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT 構造体を使用することができます。

次のコード サンプルは、さまざまなシグナル処理モードに対して WaveRT バッファーのバッファー サイズ制約を設定する方法を示しています。

//
// Describe buffer size constraints for WaveRT buffers
// Note: 10msec for each of the Modes is the default system behavior.
//
static struct
{
    KSAUDIO_PACKETSIZE_CONSTRAINTS2                 TransportPacketConstraints;         // 1
    KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT    AdditionalProcessingConstraints[4]; // + 4 = 5
} DspR_RtPacketSizeConstraints =
{
    {
        10 * HNSTIME_PER_MILLISECOND,                           // 10 ms minimum processing interval
        FILE_BYTE_ALIGNMENT,                                    // 1 byte packet size alignment
        0,                                                      // no maximum packet size constraint
        5,                                                      // 5 processing constraints follow
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_RAW,              // constraint for raw processing mode
            0,                                                  // NA samples per processing frame
            10 * HNSTIME_PER_MILLISECOND,                       // 100000 hns (10ms) per processing frame
        },
    },
    {
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_DEFAULT,          // constraint for default processing mode
            0,                                                  // NA samples per processing frame
            10 * HNSTIME_PER_MILLISECOND,                       // 100000 hns (10ms) per processing frame
        },
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_COMMUNICATIONS,   // constraint for movie communications mode
            0,                                                  // NA samples per processing frame
            10 * HNSTIME_PER_MILLISECOND,                       // 100000 hns (10ms) per processing frame
        },
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_MEDIA,            // constraint for default media mode
            0,                                                  // NA samples per processing frame
            10 * HNSTIME_PER_MILLISECOND,                       // 100000 hns (10ms) per processing frame
        },
        {
            STATIC_AUDIO_SIGNALPROCESSINGMODE_MOVIE,            // constraint for movie movie mode
            0,                                                  // NA samples per processing frame
            10 * HNSTIME_PER_MILLISECOND,                       // 100000 hns (10ms) per processing frame
        },
    }
};

DSP_DEVPROPERTY 構造体を使用して、制約を格納します。

typedef struct _DSP_DEVPROPERTY {
    const DEVPROPKEY   *PropertyKey;
    DEVPROPTYPE Type;
    ULONG BufferSize;
    __field_bcount_opt(BufferSize) PVOID Buffer;
} DSP_DEVPROPERTY, PDSP_DEVPROPERTY;

これらの構造体の配列が作成されます。

const DSP_DEVPROPERTY DspR_InterfaceProperties[] =
{
    {
        &DEVPKEY_KsAudio_PacketSize_Constraints2,       // Key
        DEVPROP_TYPE_BINARY,                            // Type
        sizeof(DspR_RtPacketSizeConstraints),           // BufferSize
        &DspR_RtPacketSizeConstraints,                  // Buffer
    },
};

EvtCircuitCompositeCircuitInitialize 関数の後半では、AddPropertyToCircuitInterface ヘルパー関数を使用して、インターフェイス プロパティの配列を回線に追加します。

   // Set RT buffer constraints.
    //
    status = AddPropertyToCircuitInterface(Circuit, ARRAYSIZE(DspC_InterfaceProperties), DspC_InterfaceProperties);

AddPropertyToCircuitInterface ヘルパー関数は、回路の AcxCircuitGetSymbolicLinkName を受け取り、IoGetDeviceInterfaceAlias を呼び出し、回路によって使用されるオーディオ インターフェイスを見つけます。

次に SetDeviceInterfacePropertyDataMultiple 関数は、IoSetDeviceInterfacePropertyData 関数を呼び出して、デバイス インターフェイス プロパティの現在の値 (ACXCIRCUIT のオーディオ インターフェイスの KS オーディオ プロパティ値) を変更します。

PAGED_CODE_SEG
NTSTATUS AddPropertyToCircuitInterface(
    _In_ ACXCIRCUIT                                         Circuit,
    _In_ ULONG                                              PropertyCount,
    _In_reads_opt_(PropertyCount) const DSP_DEVPROPERTY   * Properties
)
{
    PAGED_CODE();

    NTSTATUS        status      = STATUS_UNSUCCESSFUL;
    UNICODE_STRING  acxLink     = {0};
    UNICODE_STRING  audioLink   = {0};
    WDFSTRING       wdfLink     = AcxCircuitGetSymbolicLinkName(Circuit);
    bool            freeStr     = false;

    // Get the underline unicode string.
    WdfStringGetUnicodeString(wdfLink, &acxLink);

    // Make sure there is a string.
    if (!acxLink.Length || !acxLink.Buffer)
    {
        status = STATUS_INVALID_DEVICE_STATE;
        DrvLogError(g_BthLeVDspLog, FLAG_INIT,
            L"AcxCircuitGetSymbolicLinkName failed, Circuit: %p, %!STATUS!",
            Circuit, status);
        goto exit;
    }

    // Get the audio interface.
    status = IoGetDeviceInterfaceAlias(&acxLink, &KSCATEGORY_AUDIO, &audioLink);
    if (!NT_SUCCESS(status))
    {
        DrvLogError(g_BthLeVDspLog, FLAG_INIT,
            L"IoGetDeviceInterfaceAlias failed, Circuit: %p, symbolic link name: %wZ, %!STATUS!",
            Circuit, &acxLink, status);
        goto exit;
    }

    freeStr = true;

    // Set specified properties on the audio interface for the ACXCIRCUIT.
    status = SetDeviceInterfacePropertyDataMultiple(&audioLink, PropertyCount, Properties);
    if (!NT_SUCCESS(status))
    {
        DrvLogError(g_BthLeVDspLog, FLAG_INIT,
            L"SetDeviceInterfacePropertyDataMultiple failed, Circuit: %p, symbolic link name: %wZ, %!STATUS!",
            Circuit, &audioLink, status);
        goto exit;
    }

    status = STATUS_SUCCESS;

exit:

    if (freeStr)
    {
        RtlFreeUnicodeString(&audioLink);
        freeStr = false;
    }

    return status;
}

ストリーム状態の変更

ストリームの状態が変更されると、そのストリームのエンドポイント オーディオ パスの各ストリーム オブジェクトは、ACX フレームワークから通知イベントを受け取ります。 この順序は、状態の変化とストリームのフローによって異なります。

  • レンダー ストリームがアクティブでない状態からよりアクティブな状態に向かう場合は、ストリーミング回線 (SINK を登録) が最初にイベントを受信します。 イベントが処理されると、エンドポイント オーディオ パス内の次の回線がイベントを受信します。

  • レンダー ストリームがよりアクティブな状態からアクティブでない状態に向かう場合、ストリーミング回線は最後にイベントを受信します。 

  • キャプチャ ストリームがアクティブでない状態からよりアクティブな状態に向かう場合、ストリーミング回線は最後にイベントを受信します。 

  • キャプチャ ストリームがよりアクティブな状態からアクティブでない状態に向かう場合、ストリーミング回線は最初にイベントを受信します。 

上記の順序は、ACX フレームワークが提供する既定です。 ドライバーは、ドライバーがストリーミング回路に追加する ACXSTREAMBRIDGE を作成するときに、AcxStreamBridgeInvertChangeStateSequence (ACX_STREAM_BRIDGE_CONFIG_FLAGS で定義) を設定することによって、反対の動作を要求することができます。  

オーディオ データのストリーミング

ストリームが作成され、適切なバッファーが割り当てられると、ストリームは、ストリームの開始を待機している [一時停止] 状態になります。 クライアントがストリームを [再生] 状態にすると、ACX フレームワークはストリームに関連付けられているすべての ACXSTREAM オブジェクトを呼び出して、ストリーム状態が [再生] であることを示します。 その後、ACXPIN は [再生] 状態になり、その時点でデータのフローが開始されます。 

オーディオ データのレンダリング

ストリームが作成され、リソースが割り当てられると、アプリケーションはストリームで [開始] を呼び出して再生を開始します。 アプリケーションでは、ストリームを開始する前に GetBuffer/ReleaseBuffer を呼び出して、すぐに再生を開始する最初のパケットに有効なオーディオ データがあることを確認する必要があります。 

クライアントは、まずバッファーを事前にローリングします。 クライアントが ReleaseBuffer を呼び出すと、これは AudioKSE の呼び出しに変換され、ACX レイヤーに呼び出され、アクティブな ACXSTREAM 上で EvtAcxStreamSetRenderPacket を呼び出します。 このプロパティには、パケット インデックス (0 ベース) と、必要に応じて、現在のパケット内のストリームの末尾のバイト オフセットを含む EOS フラグが含まれます。    ストリーミング回路がパケットを終えると、バッファ完了通知がトリガーされ、次のパケットへのレンダリング オーディオ データの入力を待機しているクライアントが解放されます。 

タイマー駆動型ストリーミング モードがサポートされており、ドライバーの EvtAcxStreamAllocateRtPackets コールバックを呼び出す際に、PacketCount 値として 1 を使用することで示されます。

オーディオ データのキャプチャ

ストリームが作成され、リソースが割り当てられると、アプリケーションはストリームで [開始] を呼び出して再生を開始します。 

ストリームが実行中の場合、ソース回線はキャプチャ パケットにオーディオ データを入力します。 最初のパケットが入力されると、ソース回線はパケットを ACX フレームワークに解放します。 この時点で、ACX フレームワークはストリーム通知イベントを通知します。 

ストリーム通知が通知されると、クライアントは KSPROPERTY_RTAUDIO_GETREADPACKET を送信して、キャプチャを終了したパケットのインデックス (0 ベース) を取得することができます。 クライアントが GETCAPTUREPACKET を送信すると、ドライバーは、以前のすべてのパケットが処理され、入力可能であると想定できます。 

バースト キャプチャの場合、ソース回線は GETREADPACKET が呼び出されるとすぐに ACX フレームワークに新しいパケットを解放できます。

クライアントは、KSPROPERTY_RTAUDIO_PACKETVREGISTER を使用して、ストリームの RTAUDIO_PACKETVREGISTER 構造体へのポインターを取得することもできます。 この構造体は、パケットの完了を通知する前に、ACX フレームワークによって更新されます。

従来の KS カーネル ストリーミング動作

ドライバーがバースト キャプチャを実装する場合 (重要な単語のスポッター実装など)、PacketVRegister の代わりに従来のカーネル ストリーミング パケット処理動作を使用する必要があることがあります。 以前のパケットベースの動作を使用するには、KSPROPERTY_RTAUDIO_PACKETVREGISTER の STATUS_NOT_SUPPORTED を返す必要があります。

次の例では、ACXSTREAM に対して AcxStreamInitAssignAcxRequestPreprocessCallback でこれを行う方法を示しています。 詳細については、AcxStreamDispatchAcxRequest を参照してください。

Circuit_EvtStreamRequestPreprocess(
    _In_  ACXOBJECT  Object,
    _In_  ACXCONTEXT DriverContext,
    _In_  WDFREQUEST Request)
{
    ACX_REQUEST_PARAMETERS params;
    PCIRCUIT_STREAM_CONTEXT streamCtx;

    streamCtx = GetCircuitStreamContext(Object);
    // The driver would define the pin type to track which pin is the keyword pin.
    // The driver would add this to the driver-defined context when the stream is created.
    // The driver would use AcxStreamInitAssignAcxRequestPreprocessCallback to set
    // the Circuit_EvtStreamRequestPreprocess callback for the stream.
    if (streamCtx && streamCtx->PinType == CapturePinTypeKeyword)
    {
        if (IsEqualGUID(params.Parameters.Property.Set, KSPROPSETID_RtAudio) &&
            params.Parameters.Property.Id == KSPROPERTY_RTAUDIO_PACKETVREGISTER)
        {
            status = STATUS_NOT_SUPPORTED;
            outDataCb = 0;

            WdfRequestCompleteWithInformation(Request, status, outDataCb);
            return;
        }
    }

    (VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request);
}

ストリームの位置

ACX フレームワークは、EvtAcxStreamGetPresentationPosition コールバックを呼び出して、現在のストリーム ポジションを取得します。 現在のストリームの位置には、PlayOffset と WriteOffset が含まれます。 

WaveRT ストリーミング モデルを使用すると、オーディオ ドライバーは HW 位置レジスタをクライアントに公開できます。 ACX ストリーミング モデルでは、再調整が行われるのを妨げるため、HW レジスタの公開はサポートされません。 

ストリーミング回路がパケットを完了するたびに、0 ベースのパケット インデックスとパケット完了にできるだけ近いところで取られた QPC 値 (たとえば、QPC 値は割り込みサービス ルーチンによって計算できます) で AcxRtStreamNotifyPacketComplete を呼び出します。 この情報は、KSPROPERTY_RTAUDIO_PACKETVREGISTER を通してクライアントに提供されます。この KSPROPERTY_RTAUDIO_PACKETVREGISTER は、CompletedPacketCount、CompletedPacketQPC、およびその 2 つを組み合わせた値 (これにより、クライアントは CompletedPacketCount と CompletedPacketQPC が同じパケットのものであることを確認できます) を含む構造体へのポインターを返します。  

ストリームの状態遷移

ストリームが作成されると、ACX は次のコールバックを使用してストリームを別の状態に移行します。

  • EvtAcxStreamPrepareHardware は、ストリームを AcxStreamStateStop 状態から AcxStreamStatePause 状態に移行します。 ドライバーは、EvtAcxStreamPrepareHardware を受信するときに、DMA エンジンなどの必要なハードウェアを予約する必要があります。
  • EvtAcxStreamRun は、ストリームを AcxStreamStatePause 状態から AcxStreamStateRun 状態に移行します。
  • EvtAcxStreamPause は、ストリームを AcxStreamStateRun 状態から AcxStreamStatePause 状態に移行します。
  • EvtAcxStreamReleaseHardware は、ストリームを AcxStreamStatePause 状態から AcxStreamStateStop 状態に移行します。 ドライバーは、EvtAcxStreamReleaseHardware を受け取ったときに、DMA エンジンなどの必要なハードウェアを解放する必要があります。

ストリームは、EvtAcxStreamReleaseHardware コールバックを受信した後、EvtAcxStreamPrepareHardware コールバックを受信できます。 これにより、ストリームが AcxStreamStatePause 状態に戻ります。

EvtAcxStreamAllocateRtPackets を使用したパケット割り当ては、通常、EvtAcxStreamPrepareHardware の最初の呼び出しの前に発生します。 割り当てられたパケットは、通常、EvtAcxStreamReleaseHardware への最後の呼び出しの後に EvtAcxStreamFreeRtPackets で解放されます。 この順序は保証されません。

AcxStreamStateAcquire 状態は使用されません。 ACX では、ハードウェアの準備 (EvtAcxStreamPrepareHardware) およびハードウェアの解放 (EvtAcxStreamReleaseHardware) コールバックによってこの状態が暗黙的であるため、ドライバーが取得状態を持つ必要性がなくなります。

ラージ バッファー ストリームとオフロード エンジンのサポート

ACX では、ACXAUDIOENGINE 要素を使用して、オフロード ストリームの作成を処理する ACXPIN と、オフロード ストリーム ボリューム、ミュート、ピーク メーターの状態に必要なさまざまな要素を指定します。 これは、WaveRT ドライバーの既存のオーディオ エンジン ノードに似ています。

ストリームの閉じるプロセス

クライアントがストリームを閉じると、ACXSTREAM オブジェクトが ACX Framework によって削除される前に、ドライバーは EvtAcxStreamPause と EvtAcxStreamReleaseHardware を受け取ります。 ドライバーは、ACXSTREAM の最終クリーンアップを実行するために AcxStreamCreate を呼び出す際に、WDF_OBJECT_ATTRIBUTES 構造体内の標準 WDF EvtCleanupCallback エントリを供給することができます。 フレームワークがオブジェクトを削除しようとすると、WDF によって EvtCleanupCallback が呼び出されます。 EvtDestroyCallback を使用しないでください。EvtDestroyCallback は、オブジェクトへのすべての参照が解放された後にのみ呼び出され、不確定になります。

EvtAcxStreamReleaseHardware でリソースがまだクリーンされていない場合、ドライバーは EvtCleanupCallback の ACXSTREAM オブジェクトに関連付けられているシステム メモリ リソースをクリーンする必要があります。

ドライバーは、クライアントから要求されるまで、ストリームをサポートするリソースをクリーンしないことが重要です。

AcxStreamStateAcquire 状態は使用されません。 ACX では、ハードウェアの準備 (EvtAcxStreamPrepareHardware) およびハードウェアの解放 (EvtAcxStreamReleaseHardware) コールバックによってこの状態が暗黙的であるため、ドライバーが取得状態を持つ必要性がなくなります。

ストリームの予期しない削除と無効化

ストリームが無効になった (ジャックが取り外されたなど) とドライバーが判断した場合、回線はすべてのストリームをシャットダウンします。 

Stream memory クリーンup

ストリームのリソースの破棄は、ドライバーのストリーム コンテキスト クリーンup で行うことができます (破棄されません)。 オブジェクトのコンテキスト破棄コールバックで共有されているものは破棄しないでください。 このガイダンスは、すべての ACX オブジェクトに適用されます。

破棄コールバックは、最後の ref がなくなった後、不明な場合に呼び出されます。

一般に、ストリームの クリーンup コールバックは、ハンドルが閉じられたときに呼び出されます。 これに対する 1 つの例外は、ドライバーがコールバックでストリームを作成したときです。 ストリーム作成操作から戻る直前に ACX がこのストリームをストリーム ブリッジに追加できなかった場合、ストリームは非同期で取り消され、現在のスレッドは作成ストリーム クライアントにエラーを返します。 ストリームには、この時点で mem 割り当てを割り当てないようにする必要があります。 詳細については、コールバックEVT_ACX_STREAM_RELEA Standard Edition_HARDWARE参照してください

ストリーム メモリ クリーンアップ シーケンス

ストリーム バッファーはシステム リソースであり、ユーザー モード クライアントがストリームのハンドルを閉じるときにのみ解放する必要があります。 (デバイスのハードウェア リソースとは異なる) バッファーの有効期間は、ストリームのハンドルと同じです。 クライアントがハンドルを閉じると、ACX はストリーム オブジェクトクリーンアップ コールバックを呼び出し、オブジェクトの ref が 0 になったときにストリーム obj の削除コールバックを呼び出します。

ドライバーが stream-obj を作成し、ストリームの作成コールバックに失敗したときに、ACX が STREAM obj の削除を作業項目に延期する可能性があります。 シャットダウン WDF スレッドによるデッドロックを防ぐために、ACX は削除を別のスレッドに延期します。 この動作の副作用 (リソースの遅延リリース) を回避するために、ドライバーは、ストリーム作成からエラーを返す前に、割り当てられたストリーム リソースを解放できます。

ACX がEVT_ACX_STREAM_FR Enterprise Edition_RTPACKETS コールバックを呼び出すとき、ドライバーはオーディオ バッファーを解放する必要があります。 このコールバックは、ユーザーがストリーム ハンドルを閉じるときに呼び出されます。

RT バッファーはユーザー モードでマップされるため、バッファーの有効期間はハンドルの有効期間と同じです。 ドライバーは、ACX がこのコールバックを呼び出す前に、オーディオ バッファーを解放または解放しようとしないでください。

EVT_ACX_STREAM_FR Enterprise Edition_RTPACKETSコールバックは、コールバックEVT_ACX_STREAM_RELEA Standard Edition_HARDWARE後に呼び出し、EvtDeviceReleaseHardware の前に終了する必要があります。

このコールバックは、ドライバーが WDF リリース ハードウェア コールバックを処理した後に発生する可能性があります。ユーザー モード クライアントは、そのハンドルを長時間保持できるためです。 ドライバーは、これらのハンドルが消えるのを待つ必要はありません。これにより、0x9f DRIVER_POWER_STATE_FAILUREバグチェックが作成されます。 詳細については、コールバック関数EVT_WDF_DEVICE_RELEA Standard Edition_HARDWARE参照してください。

サンプル ACX ドライバーからのこの EvtDeviceReleaseHardware コードは、AcxDeviceRemoveCircuit を呼び出し、ストリーミング h/w メモリを解放する例を示しています。

    RETURN_NTSTATUS_IF_FAILED(AcxDeviceRemoveCircuit(Device, devCtx->Render));
    RETURN_NTSTATUS_IF_FAILED(AcxDeviceRemoveCircuit(Device, devCtx->Capture));

    // NOTE: Release streaming h/w resources here.

    CSaveData::DestroyWorkItems();
    CWaveReader::DestroyWorkItems();

要約すると:

wdf デバイス リリース ハードウェア -> デバイスのリソースを解放する

AcxStreamFreeRtPackets -> ハンドルに関連付けられているリリース/空きオーディオ バッファー

WDF および回線オブジェクトの管理の詳細については、ACX WDF ドライバーの有効期間管理を参照してください

DDI のストリーミング

構造体のストリーミング

ACX_RTPACKET 構造体

この構造体は、1 つの割り当てられたパケットを表します。 PacketBuffer には、WDFMEMORY ハンドル、MDL、またはバッファーを指定できます。 関連付けられた初期化関数 (ACX_RTPACKET_INIT) があります。

ACX_STREAM_CALLBACKS

この構造体は、ACX フレームワークにストリーミングするためのドライバー コールバックを識別します。 この構造体は、ACX_PIN_CONFIG 構造体の一部です。

コールバックのストリーミング

EvtAcxStreamAllocateRtPackets

EvtAcxStreamAllocateRtPackets イベントは、ストリーミング用に RtPackets を割り当てるようドライバーに指示します。 AcxRtStream は、イベント ドリブン ストリーミングの場合は PacketCount = 2、タイマー ベースのストリーミングの場合は PacketCount = 1 を受け取ります。 ドライバーが両方のパケットに 1 つのバッファーを使用する場合、2 番目の RtPacketBuffer は、最初のパケットの終わりと一致する RtPacketOffset を持つ Type = WdfMemoryDescriptorTypeInvalid の WDF_MEMORY_DESCRIPTOR が必要です (packet[2].RtPacketOffset = packet[1].RtPacketOffset+packet[1].RtPacketSize)。

EvtAcxStreamFreeRtPackets

EvtAcxStreamFreeRtPackets イベントは、EvtAcxStreamAllocateRtPackets の前の呼び出しで割り当てられた RtPackets を解放するようにドライバーに指示します。 その呼び出しと同じパケットが含まれます。

EvtAcxStreamGetHwLatency

EvtAcxStreamGetHwLatency イベントは、このストリームの特定の回路のストリームの待機時間を提供するようにドライバーに指示します (全体的な待機時間は、異なる回路の待機時間の合計になります)。 FifoSize はバイト単位で、遅延は 100 ナノ秒単位です。

EvtAcxStreamSetRenderPacket

EvtAcxStreamSetRenderPacket イベントは、クライアントによって解放されたパケットをドライバーに伝えます。 障害がない場合、このパケットは (CurrentRenderPacket + 1) となるはずで、CurrentRenderPacket はドライバーが現在ストリーミングしているパケットです。

フラグは、0 または AcxStreamSetRenderPacketEndOfStream で、パケットがストリームの最後のパケットであることを示し、EosPacketLength は、パケットの有効な長さ (バイト単位) です。

ドライバーは、この値に合わせて CurrentRenderPacket を変更するのではなく、パケットがレンダリングされるにつれて CurrentRenderPacket を増やす必要があります。

EvtAcxStreamGetCurrentPacket

EvtAcxStreamGetCurrentPacket は、現在ハードウェアにレンダリングされているパケット、または現在キャプチャ ハードウェアによって入力されているパケット (0ベース) を示すようにドライバーに指示します。

EvtAcxStreamGetCapturePacket

EvtAcxStreamGetCapturePacket は、ドライバーがパケットの入力を開始した時点の QPC 値を含め、どのパケット (0 ベース) が最近完全に入力されたかを示すようにドライバーに指示します。

EvtAcxStreamGetPresentationPosition

EvtAcxStreamGetPresentationPosition は、現在位置が計算された時点の QPC 値とともに現在位置を示すようにドライバーに指示します。

ストリーム状態のイベント

ACXSTREAM のストリーミング状態は、次の API によって管理されます。

EVT_ACX_STREAM_PREPARE_HARDWARE

EVT_ACX_STREAM_RELEASE_HARDWARE

EVT_ACX_STREAM_RUN

EVT_ACX_STREAM_PAUSE

ACX API のストリーミング

AcxStreamCreate

AcxStreamCreate は、ストリーミング動作を制御するために使用できる ACX ストリームを作成します。

AcxRtStreamCreate

AcxRtStreamCreate は、ストリーミング動作を制御し、パケットの割り当てを処理して、ストリーミング状態を通信するために使用できる ACX ストリームを作成します。

AcxRtStreamNotifyPacketComplete

ドライバーは、パケットが完了したときにこの ACX API を呼び出します。 クライアントのパフォーマンスを向上させるために、パケット完了時間と 0 ベースのパケット インデックスが含まれています。 ACX フレームワークは、ストリームに関連付けられているすべての通知イベントを設定します。

関連項目

ACX オーディオ クラス拡張機能の概要

ACX リファレンス ドキュメント

ACX オブジェクトの概要