このトピックでは、ACX ストリーミングと関連するバッファリングについて説明します。これは、障害のないオーディオ エクスペリエンスにとって重要です。 ドライバーがストリームの状態について通信し、ストリームのバッファーを管理するために使用されるメカニズムについて説明します。 一般的な ACX オーディオ用語の一覧と ACX の概要については、 ACX オーディオ クラス拡張機能の概要を参照してください。
ACX ストリーミングの種類
AcxStream は、特定の回線のハードウェア上のオーディオ ストリームを表します。 AcxStream は、1 つ以上の AcxElements のようなオブジェクトを集計できます。
ACX フレームワークでは、2 つのストリームの種類がサポートされています。 最初のストリーム タイプである RT パケット ストリームでは、RT パケットの割り当てと、デバイス ハードウェアとの間でオーディオ データを転送するための RT パケットの使用と、ストリーム状態の遷移がサポートされます。 2 番目のストリームの種類である Basic Stream では、ストリームの状態遷移のみがサポートされます。
単一回線エンドポイントでは、回線は RT パケット ストリームを作成するストリーミング回線である必要があります。 エンドポイントを作成するために複数の回線が接続されている場合、エンドポイントの最初の回線はストリーミング回線であり、RT パケット ストリームが作成されます。接続された回線は、ストリーム状態遷移に関連するイベントを受信する基本ストリームを作成します。
詳細については、ACX オブジェクトの概要の ACX ストリームを参照してください。 ストリームの DDI は acxstreams.h ヘッダーで定義されます。
ACX ストリーミング通信スタック
ACX ストリーミングには、2 種類の通信があります。 1 つの通信パスは、標準の ACX 通信を使用する Start、Create、Allocate などのコマンドのストリーミング動作を制御するために使用されます。 ACX フレームワークは IO キューを使用して、WDF 要求をキュー経由で渡します。 キューの動作は、Evt コールバックと ACX 関数を使用して実際のドライバー コードから非表示になりますが、ドライバーにはすべての WDF 要求を前処理する機会も与えられます。
2 番目の興味深い通信パスは、オーディオ ストリーミング シグナリングに使用されます。 これには、パケットの準備ができたときにドライバーに指示し、ドライバーがパケットの処理を完了したときにデータを受信する必要があります。
ストリーミング シグナリングの主な要件は次のとおりです。
- Glitch-Free 再生のサポート
- 待機時間が短い
- 必要なロックは、該当するストリームに限定されます
- ドライバー開発者向けの使いやすさ
ACX は、ストリーミング状態を通知するためにドライバーと通信するために、共有バッファーと直接 IRP 呼び出しを含むイベントを使用します。 これらは次に説明します。
共有バッファー
ドライバーからクライアントに通信するには、共有バッファーとイベントが使用されます。 これにより、クライアントは待機またはポーリングする必要がなくなり、直接 IRP 呼び出しの必要性を減らしたり排除したりしながら、ストリーミングを続行するために必要なすべてのものをクライアントが判断できるようになります。
デバイス ドライバーは、共有バッファーを使用して、パケットのレンダリング元またはキャプチャ先のクライアントと通信します。 この共有バッファーには、完了時刻の QPC (QueryPerformanceCounter) 値と共に、最後に完了したパケットのパケット数 (1 から始まる) が含まれます。 デバイス ドライバーの場合は、 AcxRtStreamNotifyPacketComplete を呼び出してこの情報を示す必要があります。 デバイス ドライバーが AcxRtStreamNotifyPacketComplete を呼び出すと、ACX フレームワークは、新しいパケット数と QPC で共有バッファーを更新し、クライアントが新しいパケット数を読み取る可能性があることを示すためにクライアントと共有されるイベントを通知します。
直接 IRP 呼び出し
クライアントからドライバーへの通信には、直接 IRP 呼び出しが使用されます。 これにより、WDF 要求がタイムリーに処理され、既存のアーキテクチャで適切に動作することが証明されていることを確認する際の複雑さが軽減されます。
クライアントは、いつでも現在のパケット数を要求したり、デバイス ドライバーに現在のパケット数を示したりすることができます。 これらの要求は、 EvtAcxStreamGetCurrentPacket および EvtAcxStreamSetRenderPacket デバイス ドライバー イベント ハンドラーを呼び出します。 クライアントは、 EvtAcxStreamGetCapturePacket デバイス ドライバー イベント ハンドラーを呼び出す現在のキャプチャ パケットを要求することもできます。
PortCls との類似点
直接 IRP 呼び出しと ACX によって使用される共有バッファーの組み合わせは、バッファー完了処理が PortCls で通信される方法と似ています。 IRP は非常によく似ています。共有バッファーでは、IRP に依存せずに、ドライバーがパケット数とタイミングを直接通信する機能が導入されています。 ドライバーは、ストリーム制御パスでも使用されるロックへのアクセスを必要とする何も行わない必要があります。これは、グリッチを防ぐために必要です。
低電力再生のための大きなバッファーサポート
メディア コンテンツを再生するときに消費される電力量を減らすには、APU が高電力状態で費やす時間を減らすことが重要です。 通常のオーディオ再生では 10 ミリ秒のバッファーが使用されるため、APU は常にアクティブである必要があります。 APU に状態を減らす必要がある時間を与えるために、ACX ドライバーは、1 ~ 2 秒のサイズ範囲で、大幅に大きなバッファーのサポートをアドバタイズできます。 つまり、APU は 1 秒から 2 秒ごとに 1 回起動し、次の 1 ~ 2 秒のバッファーを準備するために必要な操作を最大速度で実行し、次のバッファーが必要になるまで、可能な限り最小の電源状態に移行できます。
既存のストリーミング モデルでは、オフロード再生を通じて低電力再生がサポートされています。 オーディオ ドライバーは、エンドポイントのウェーブ フィルターで AudioEngine ノードを公開することによって、オフロード再生のサポートをアドバタイズします。 AudioEngine ノードは、ドライバーが目的の処理で大きなバッファーからオーディオをレンダリングするために使用する DSP エンジンを制御する手段を提供します。
AudioEngine ノードには、次の機能があります。
- オーディオ エンジンの説明。ウェーブ フィルター上のどのピンがオフロードとループバックのサポート (およびホスト再生のサポート) を提供するかをオーディオ スタックに通知します。
- バッファー サイズ範囲。オフロードでサポートできる最小および最大バッファー サイズをオーディオ スタックに通知します。 再生。 バッファー サイズ範囲は、システム アクティビティに基づいて動的に変更できます。
- 書式サポートには、サポートされている形式、現在のデバイスミックス形式、およびデバイス形式が含まれます。
- ランピングのサポートを含むボリューム機能は、バッファが大きいとソフトウェアのボリュームの応答が鈍くなる可能性があります。
- ループバック保護。1 つ以上のオフロード ストリームに保護されたコンテンツが含まれている場合に AudioEngine ループバック ピンをミュートするようにドライバーに指示します。
- AudioEngine で GFX を有効または無効にするグローバル FX 状態。
オフロード ピンでストリームが作成されると、ストリームはボリューム、ローカル FX、ループバック保護をサポートします。
ACX による低電力再生
ACX フレームワークは、低電力再生に同じモデルを使用します。 ドライバーは、ホスト、オフロード、ループバック ストリーミング用の 3 つの個別の ACXPIN オブジェクトと、ホスト、オフロード、ループバックに使用されるこれらのピンのどれを記述する ACXAUDIOENGINE 要素を作成します。 ドライバーは、回線の作成時に ACXCIRCUIT にピンと ACXAUDIOENGINE 要素を追加します。
オフロード ストリームの作成
また、ドライバーは、ボリューム、ミュート、およびピーク メーターを制御できるようにオフロード用に作成されたストリームに ACXAUDIOENGINE 要素を追加します。
ストリーミング図
この図は、マルチスタック ACX ドライバーを示しています。
各 ACX ドライバーは、オーディオ ハードウェアの個別の部分を制御し、別のベンダーによって提供される可能性があります。 ACX は、アプリケーションをそのまま実行できるようにするための互換性のあるカーネル ストリーミング インターフェイスを提供します。
ストリーム ピン
各 ACXCIRCUIT には、少なくとも 1 つのシンク ピンと 1 つのソース ピンがあります。 これらのピンは、ACX フレームワークによって、回線の接続をオーディオ スタックに公開するために使用されます。 レンダー回線の場合、ソース ピンは、回線から作成されたストリームのレンダリング動作を制御するために使用されます。 キャプチャ回線の場合、シンク ピンは、回線から作成されたストリームのキャプチャ動作を制御するために使用されます。 ACXPIN は、オーディオ パスのストリーミングを制御するために使用されるオブジェクトです。 ストリーミング ACXCIRCUIT は、回線作成時にエンドポイント オーディオ パスに適切な ACXPIN オブジェクトを作成し、ACXPIN を ACX に登録する役割を担います。 ACXCIRCUIT は、回路のレンダー ピンまたはキャプチャ ピンを作成するだけで済みます。ACX フレームワークは回線に接続し、通信するために必要な他のピンを作成します。
ストリーミング回線
エンドポイントが 1 つの回線で構成されている場合、その回線がストリーミング回線になります。
エンドポイントが 1 つ以上のデバイス ドライバーによって作成された複数の回線で構成されている場合、回線は、構成されたエンドポイントを記述する ACXCOMPOSITETEMPLATE によって決定される特定の順序で接続されます。 エンドポイントの最初の回線は、エンドポイントのストリーミング回線です。
ストリーミング回線では 、AcxRtStreamCreate を使用して 、EvtAcxCircuitCreateStream に応答して RT パケット ストリームを作成する必要があります。 AcxRtStreamCreate で作成された ACXSTREAM を使用すると、ストリーミング回線ドライバーは、ストリーミングに使用されるバッファーを割り当て、クライアントとハードウェアのニーズに応じてストリーミング フローを制御できます。
エンドポイント内の次の回線では 、AcxStreamCreate を使用して、EvtAcxCircuitCreateStream に応答して Basic Stream を作成する必要があります。 次の回線によって AcxStreamCreate で作成された ACXSTREAM オブジェクトを使用すると、ドライバーは、一時停止や実行などのストリーム状態の変更に応じてハードウェアを構成できます。
ストリーミング ACXCIRCUIT は、ストリームを作成する要求を受信する最初の回線です。 要求には、デバイス、ピン、データ形式 (モードを含む) が含まれます。
オーディオ パス内の各 ACXCIRCUIT は、回線のストリーム インスタンスを表す ACXSTREAM オブジェクトを作成します。 ACX フレームワークは、ACXSTREAM オブジェクトをリンクします (ACXCIRCUIT オブジェクトがリンクされるのとほぼ同じ方法です)。
アップストリームおよびダウンストリーム回線
ストリームの作成はストリーミング回線から開始され、回線が接続された順序で各ダウンストリーム回線に転送されます。 この接続は、AcxPinCommunicationNone と等しい通信で作成されたブリッジ ピン間で行われます。 ACX フレームワークは、ドライバーが回線の作成時に追加しない場合、回線の 1 つ以上のブリッジ ピンを作成します。
ストリーミング回線から始まる回線ごとに、AcxPinTypeSource ブリッジ ピンが次のダウンストリーム回線に接続されます。 最後の回線には、オーディオ エンドポイント ハードウェア (エンドポイントがマイクかスピーカーか、ジャックが接続されているかどうかなど) を説明するエンドポイント ピンがあります。
ストリーミング回線に続く回線ごとに、AcxPinTypeSink ブリッジ ピンが次のアップストリーム回線に接続されます。
ストリーム形式ネゴシエーション
ドライバーは、 AcxPinAssignModeDataFormatList と AcxPinGetRawDataFormatList を使用してストリームの作成に使用される 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 回路は、ジャック検出またはその他のエンドポイント ハードウェアの詳細を処理する場合があります。
- AudioKSE は NtCreateFile を呼び出してストリームを作成します。
- これにより、ACX をフィルター処理し、DSP 回線の EvtAcxPinCreateStream をピン、データフォーマット (モードを含む)、デバイス情報を使用して呼び出すことで終了します。
- DSP 回線は、データフォーマット情報を検証して、作成されたストリームを確実に処理できるようにします。
- DSP 回線は、ストリームを表す ACXSTREAM オブジェクトを作成します。
- DSP 回線は、プライベート コンテキスト構造を割り当て、それを ACXSTREAM に関連付けます。
- DSP 回線は ACX フレームワークに実行フローを返し、エンドポイント オーディオ パス(CODEC 回線)の次の回線を呼び出します。
- CODEC 回線は、データフォーマット情報を検証して、データのレンダリングを処理できることを確認します。
- CODEC 回線は、プライベート コンテキスト構造を割り当て、それを ACXSTREAM に関連付けます。
- CODEC 回線は、それ自体をストリーム シンクとして ACXSTREAM に追加します。
- CODEC 回線は ACX フレームワークに実行フローを返し、次にエンドポイント オーディオ パスの次の回線である AMP 回線を呼び出します。
- AMP 回線はプライベート コンテキスト構造を割り当て、それを ACXSTREAM に関連付けます。
- AMP 回線は、実行のフローを ACX フレームワークに返します。 この時点で、ストリームの作成は完了です。
大きなバッファー ストリーム
大きなバッファー ストリームは、ACXCIRCUIT の ACXAUDIOENGINE 要素によってオフロード用に指定された ACXPIN に作成されます。
オフロード ストリームをサポートするには、デバイス ドライバーは、ストリーミング回線の作成中に次の操作を行う必要があります。
- ホスト、オフロード、ループバックの ACXPIN オブジェクトを作成し、ACXCIRCUIT に追加します。
- ACXVOLUME、ACXMUTE、および ACXPEAKMETER 要素を作成します。 これらは ACXCIRCUIT に直接追加されません。
- HostPin、OffloadPin、LoopbackPin、VolumeElement、MuteElement、PeakMeterElement オブジェクトを割り当てて、 ACX_AUDIOENGINE_CONFIG構造体を初期化します。
- ACXAUDIOENGINE 要素を作成します。
ドライバーは、オフロード ピンでストリームを作成するときに ACXSTREAMAUDIOENGINE 要素を追加する同様の手順を実行する必要があります。
ストリーム リソースの割り当て
ACX のストリーミング モデルはパケット ベースであり、ストリームに対して 1 つまたは 2 つのパケットがサポートされています。 ストリーミング回線の Render または Capture ACXPIN には、ストリームで使用されるメモリ パケットを割り当てる要求が与えられます。 再調整をサポートするには、システムにマップされたデバイス メモリではなく、割り当てられたメモリがシステム メモリである必要があります。 ドライバーは、既存の WDF 関数を使用して割り当てを実行し、バッファーの割り当てへのポインターの配列を返します。 ドライバーが 1 つの連続したブロックを必要とする場合は、両方のパケットを 1 つのバッファーとして割り当て、バッファーのオフセットへのポインターを 2 番目のパケットとして返す可能性があります。
1 つのパケットが割り当てられている場合、パケットはページアラインされ、ユーザー モードに 2 回マップされる必要があります。
|パケット0 |パケット0 |
これにより、GetBuffer は、メモリ アクセスのラップを処理するアプリケーションを必要とせずに、バッファーの末尾から先頭に及ぶ可能性がある単一の連続したメモリ バッファーへのポインターを返します。
2 つのパケットが割り当てられている場合、それらはユーザー モードにマップされます。
|packet 0 |パケット 1 |
最初の ACX パケット ストリーミングでは、最初に割り当てられるパケットは 2 つだけです。 クライアント仮想メモリ マッピングは、割り当てとマッピングが実行された後、ストリームの有効期間を変更せずに有効なままです。 両方のパケットのパケットの完了を示すために、ストリームに関連付けられたイベントが 1 つあります。 また、ACX フレームワークがイベントで終了したパケットを通信するために使用する共有バッファーもあります。
大容量バッファストリームのパケットサイズ
大きなバッファーのサポートを公開する場合、ドライバーは、大きなバッファー再生の最小および最大パケット サイズを決定するために使用されるコールバックも提供します。 ストリーム バッファー割り当てのパケット サイズは、最小値と最大値に基づいて決定されます。
最小および最大のバッファー サイズは揮発性である可能性があるため、最小と最大値が変更された場合、ドライバーはパケット割り当ての呼び出しに失敗する可能性があります。
ACX バッファー制約の指定
ACX バッファー制約を指定するために、ACX ドライバーは KS/PortCls プロパティ設定を使用できます - KSAUDIO_PACKETSIZE_CONSTRAINTS2 と KSAUDIO_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;
}
ストリーム状態の変更
ストリームの状態の変化が発生すると、ストリームの Endpoint Audio Path 内の各ストリーム オブジェクトは、ACX フレームワークから通知イベントを受信します。 この順序は、状態の変化とストリームのフローによって異なります。
レンダー ストリームがアクティブでない状態からよりアクティブな状態に向かう場合は、ストリーミング回線 (SINK を登録) が最初にイベントを受信します。 イベントが処理されると、エンドポイント オーディオ パス内の次の回線がイベントを受信します。
レンダー ストリームがよりアクティブな状態からアクティブでない状態に向かう場合、ストリーミング回線は最後にイベントを受信します。
キャプチャ ストリームがアクティブでない状態からよりアクティブな状態に向かう場合、ストリーミング回線は最後にイベントを受信します。
キャプチャ ストリームがよりアクティブな状態からアクティブでない状態に向かう場合、ストリーミング回線は最初にイベントを受信します。
上記の順序は、ACX フレームワークによって提供される既定です。 ドライバーは、ストリーミング回線に追加する ACXSTREAMBRIDGE を作成するときに AcxStreamBridgeInvertChangeStateSequence ( ACX_STREAM_BRIDGE_CONFIG_FLAGS で定義) を設定することで、反対の動作を要求できます。
オーディオ データのストリーミング
ストリームが作成され、適切なバッファーが割り当てられると、ストリームは、ストリームの開始を待機している一時停止状態になります。 クライアントがストリームを Play 状態にすると、ACX フレームワークはストリームに関連付けられているすべての ACXSTREAM オブジェクトを呼び出して、ストリームの状態が Play であることを示します。 その後、ACXPIN は Play 状態になり、その時点でデータのフローが開始されます。
オーディオ データのレンダリング
ストリームが作成され、リソースが割り当てられると、アプリケーションはストリームで Start を呼び出して再生を開始します。 アプリケーションでは、ストリームを開始する前に GetBuffer/ReleaseBuffer を呼び出して、すぐに再生を開始する最初のパケットに有効なオーディオ データがあることを確認する必要があることに注意してください。
クライアントは、まずバッファーを事前にローリングします。 クライアントが ReleaseBuffer を呼び出すと、これは、アクティブな ACXSTREAM で EvtAcxStreamSetRenderPacket を呼び出す ACX レイヤーを呼び出す AudioKSE の呼び出しに変換されます。 このプロパティには、パケット インデックス (0 ベース) と、必要に応じて、現在のパケット内のストリームの末尾のバイト オフセットを含む EOS フラグが含まれます。 ストリーミング回線がパケットで終了すると、バッファー完了通知がトリガーされ、次のパケットへのレンダリング オーディオ データの入力を待機しているクライアントが解放されます。
タイマー ドリブン ストリーミング モードはサポートされており、ドライバーの EvtAcxStreamAllocateRtPackets コールバックを呼び出すときに PacketCount 値 1 を使用して示されます。
オーディオ データのキャプチャ
ストリームが作成され、リソースが割り当てられると、アプリケーションはストリームで Start を呼び出して再生を開始します。
ストリームが実行中の場合、ソース回線はキャプチャ パケットにオーディオ データを入力します。 最初のパケットが入力されると、送信元回線はパケットを 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 レジスタの公開はサポートされません。
ストリーミング回線がパケットを完了するたびに、 AcxRtStreamNotifyPacketComplete を呼び出し、0 ベースのパケット インデックスと、パケットの完了にできるだけ近い QPC 値を使用します (たとえば、QPC 値は割り込みサービス ルーチンによって計算できます)。 この情報は 、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 を受け取ります。 ドライバーは、AcxStreamCreate を呼び出して ACXSTREAM の最終クリーンアップを実行するときに 、WDF_OBJECT_ATTRIBUTES構造体 に標準の WDF EvtCleanupCallback エントリを提供できます。 フレームワークがオブジェクトを削除しようとすると、WDF によって EvtCleanupCallback が呼び出されます。 EvtDestroyCallback は使用しないでください。この呼び出しは、オブジェクトへのすべての参照が解放された後でのみ呼び出されます。これは不確定です。
EvtAcxStreamReleaseHardware でリソースがまだクリーンアップされていない場合、ドライバーは EvtCleanupCallback の ACXSTREAM オブジェクトに関連付けられているシステム メモリ リソースをクリーンアップする必要があります。
ドライバーは、クライアントから要求されるまで、ストリームをサポートするリソースをクリーンアップしないことが重要です。
AcxStreamStateAcquire 状態は使用されません。 ACX では、準備ハードウェア (EvtAcxStreamPrepareHardware) およびリリース ハードウェア (EvtAcxStreamReleaseHardware) コールバックによってこの状態が暗黙的であるため、ドライバーが取得状態になる必要がなくなります。
ストリームの突然の削除と無効化
ドライバーがストリームが無効になった (ジャックが取り外されたなど) と判断した場合、回線はすべてのストリームをシャットダウンします。
ストリーム メモリのクリーンアップ
ストリームのリソースの破棄は、ドライバーのストリーム コンテキストのクリーンアップで行うことができます (破棄されません)。 オブジェクトのコンテキスト破棄コールバックで共有されているものは破棄しないでください。 このガイダンスは、すべての ACX オブジェクトに適用されます。
破棄コールバックは、最後の ref がなくなった後、不明な場合に呼び出されます。
通常、ストリームのクリーンアップ コールバックは、ハンドルが閉じられたときに呼び出されます。 これに対する 1 つの例外は、ドライバーがコールバックでストリームを作成したときです。 ストリーム作成操作から戻る直前に ACX がこのストリームをストリーム ブリッジに追加できなかった場合、ストリームは非同期で取り消され、現在のスレッドは作成ストリーム クライアントにエラーを返します。 ストリームには、この時点で mem 割り当てを割り当てないようにする必要があります。 詳細については、EVT_ACX_STREAM_RELEASE_HARDWAREのコールバックを参照してください。
ストリーム メモリクリーンアップシーケンス
ストリーム バッファーはシステム リソースであり、ユーザー モード クライアントがストリームのハンドルを閉じるときにのみ解放する必要があります。 (デバイスのハードウェア リソースとは異なる) バッファーの有効期間は、ストリームのハンドルと同じです。 クライアントがハンドルを閉じると、ACX はストリーム オブジェクトのクリーンアップ コールバックを呼び出し、オブジェクトの ref が 0 になるとストリーム obj の削除コールバックを呼び出します。
ドライバーが stream-obj を作成し、ストリームの作成コールバックに失敗したときに、ACX が STREAM obj の削除を作業項目に延期する可能性があります。 シャットダウン WDF スレッドによるデッドロックを防ぐために、ACX は削除を別のスレッドに延期します。 この動作の副作用 (リソースの遅延リリース) を回避するために、ドライバーは、ストリーム作成からエラーを返す前に、割り当てられたストリーム リソースを解放できます。
ACX が EVT_ACX_STREAM_FREE_RTPACKETS コールバックを呼び出すとき、ドライバーはオーディオ バッファーを解放する必要があります。 このコールバックは、ユーザーがストリーム ハンドルを閉じるときに呼び出されます。
RT バッファーはユーザー モードでマップされるため、バッファーの有効期間はハンドルの有効期間と同じです。 ドライバーは、ACX がこのコールバックを呼び出す前に、オーディオ バッファーを解放または解放しようとしないでください。
EVT_ACX_STREAM_FREE_RTPACKETSコールバックは、EVT_ACX_STREAM_RELEASE_HARDWAREコールバックの後に呼び出し、EvtDeviceReleaseHardwareの前に終了する必要があります。
このコールバックは、ドライバーが WDF リリース ハードウェア コールバックを処理した後に発生する可能性があります。ユーザー モード クライアントは、そのハンドルを長時間保持できるためです。 ドライバーは、これらのハンドルが消えるのを待とうと試みるべきではありません。そうすると、0x9f DRIVER_POWER_STATE_FAILURE バグ チェックが発生してしまいます。 詳細についてはEVT_WDF_DEVICE_RELEASE_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 には、最初のパケットの末尾 (packet[2]) と一致する RtPacketOffset を持つ Type = WdfMemoryDescriptorTypeInvalid のWDF_MEMORY_DESCRIPTORが必要です。RtPacketOffset = packet[1]。RtPacketOffset+packet[1]。RtPacketSize)。
EvtAcxStreamFreeRtPackets
EvtAcxStreamFreeRtPackets イベントは、以前の EvtAcxStreamAllocateRtPackets の呼び出しで割り当てられた RtPackets を解放するようにドライバーに指示します。 その呼び出しと同じパケットが含まれます。
EvtAcxStreamGetHwLatency
EvtAcxStreamGetHwLatency イベントは、このストリームの特定の回線にストリーム待機時間を提供するようにドライバーに指示します (全体的な待機時間は、さまざまな回線の待機時間の合計になります)。 FifoSize はバイト単位で、遅延は 100 ナノ秒単位です。
EvtAcxStreamSetRenderPacket
EvtAcxStreamSetRenderPacket イベントは、クライアントによって解放されたパケットをドライバーに通知します。 障害がない場合、このパケットは (CurrentRenderPacket + 1) にする必要があります。CurrentRenderPacket は、ドライバーが現在ストリーミングしているパケットです。
フラグは 0 または KSSTREAM_HEADER_OPTIONSF_ENDOFSTREAM = 0x200で、パケットがストリームの最後のパケットであることを示し、EosPacketLength はパケットの有効な長さ (バイト単位) です。 詳細については、「KSSTREAM_HEADER 構造体 (ks.h)の OptionsFlags を参照してください。
この値に一致するように CurrentRenderPacket を変更するのではなく、パケットがレンダリングされるため、ドライバーは引き続き CurrentRenderPacket を増やす必要があります。
EvtAcxStreamGetCurrentPacket
EvtAcxStreamGetCurrentPacket は、現在ハードウェアにレンダリングされているパケット (0 ベース) またはキャプチャ ハードウェアによって現在埋め込まれているパケットを指し示すようにドライバーに指示します。
EvtAcxStreamGetCapturePacket
EvtAcxStreamGetCapturePacket は、ドライバーがパケットの入力を開始した時点の QPC 値を含め、どのパケット (0 ベース) が最後に完全に入力されたかを示すようにドライバーに指示します。
EvtAcxStreamGetPresentationPosition
EvtAcxStreamGetPresentationPosition は、現在位置が計算された時点の QPC 値と共に現在の位置を示すようにドライバーに指示します。
ストリーム状態イベント
ACXSTREAM のストリーミング状態は、次の API によって管理されます。
EVT_ACX_STREAM_PREPARE_HARDWARE
EVT_ACX_STREAM_RELEASE_HARDWARE
ストリーミング ACX API
AcxStreamCreate の
AcxStreamCreate は、ストリーミング動作を制御するために使用できる ACX ストリームを作成します。
AcxRtStreamCreate
AcxRtStreamCreate は、ストリーミング動作を制御し、パケットの割り当てを処理し、ストリーミング状態を通信するために使用できる ACX ストリームを作成します。
AcxRtStreamNotifyPacketComplete
ドライバーは、パケットが完了したときにこの ACX API を呼び出します。 クライアントのパフォーマンスを向上させるために、パケット完了時間と 0 ベースのパケット インデックスが含まれています。 ACX フレームワークは、ストリームに関連付けられているすべての通知イベントを設定します。