共用方式為


ACX 串流

本主題討論 ACX 串流和相關聯的緩衝處理,這對無問題音訊體驗至關重要。 它描述驅動程式用來傳達數據流狀態及管理數據流緩衝區的機制。 如需一般 ACX 音訊詞彙和 ACX 簡介的清單,請參閱 ACX 音訊類別延伸模組概觀

ACX 串流類型

AcxStream 代表特定線路硬體上的音訊串流。 AcxStream 可能會匯總一或多個類似 AcxElements 的物件。

ACX 架構支援兩種數據流類型。 第一個數據流類型 RT 封包串流提供配置 RT 封包的支援,以及使用 RT 封包來傳輸音訊資料到裝置硬體,以及串流狀態轉換。 第二個 數據流類型基本數據流僅支持數據流狀態轉換。

在單一線路端點中,線路必須是建立 RT 封包數據流的串流線路。 如果兩個以上的線路連線到建立端點,則端點中的第一個線路是串流線路,並建立 RT 封包串流;連線的線路會建立基本數據流,以接收與串流狀態轉換相關的事件。

如需詳細資訊,請參閱 ACX 物件摘要中的 ACX 數據流。 數據流的 DIS 定義於 acxstreams.h 標頭中

ACX 串流通訊堆疊

ACX 串流有兩種類型的通訊。 其中一個通訊路徑用於控制串流行為,例如 Start、Create 和 Allocate 等命令,將會使用標準 ACX 通訊。 ACX 架構會使用IO佇列,並使用佇列傳遞 WDF 要求。 佇列行為會透過使用 Evt 回呼和 ACX 函式,從實際的驅動程式程式代碼隱藏,不過驅動程式也會有機會預先處理所有 WDF 要求。

第二個更有趣的通訊路徑會用於音訊串流訊號。 這牽涉到在驅動程式完成處理封包時,告知驅動程式何時準備好封包並接收數據。 

串流訊號的主要需求:

  • 支援無問題播放
    • 低延遲
    • 任何必要的鎖定僅限於有問題的數據流
  • 方便驅動程式開發人員使用

若要與驅動程式通訊以訊號串流狀態,ACX 會使用事件搭配共用緩衝區和直接 IRP 呼叫。 接下來會說明這些。

共用緩衝區

若要從驅動程式與客戶端通訊,會使用共用緩衝區和事件。 這可確保用戶端不需要等候或輪詢,而且用戶端可以判斷其需要繼續串流的所有專案,同時減少或消除直接 IRP 呼叫的需求。

裝置驅動程式會使用共用緩衝區來與客戶端通訊,該用戶端正在轉譯或擷取封包。 此共用緩衝區包含最後一個已完成封包的封包計數(1 起始),以及完成時間的 QPC (QueryPerformanceCounter) 值。 對於設備驅動器,它必須呼叫 AcxRtStreamNotifyPacketComplete 來指出這項資訊。 當裝置驅動程式呼叫 AcxRtStreamNotifyPacketComplete 時,ACX 架構會以新的封包計數和 QPC 更新共用緩衝區,併發出與用戶端共用的事件訊號,以指出用戶端可能會讀取新的封包計數。

直接 IRP 呼叫

若要從客戶端與驅動程序通訊,則會使用直接 IRP 呼叫。 這可減少確保 WDF 要求及時處理的複雜度,並已證明可在現有架構中正常運作。

用戶端可以隨時要求目前的封包計數,或向設備驅動器指出目前的封包計數。 這些要求會呼叫 EvtAcxStreamGetCurrentPacketEvtAcxStreamSetRenderPacket 裝置驅動程式事件處理程式。 用戶端也可以要求目前的擷取封包,這會呼叫 EvtAcxStreamGetCapturePacket 設備驅動器事件處理程式。

PortCls 的相似性

ACX 所使用的直接 IRP 呼叫和共用緩衝區的組合類似於在 PortCls 中傳達緩衝區完成處理的方式。 IRP 非常類似,且共用緩衝區引進了驅動程式直接通訊封包計數和計時的能力,而不需要依賴 IRP。   驅動程式必須確保它們不需要存取數據流控制路徑中也會使用的鎖定,這是防止故障的必要動作。 

低功率播放的大型緩衝區支援

若要減少播放媒體內容時耗用的電力量,請務必減少 APU 花費在高功率狀態的時間量。 由於一般音訊播放使用 10 毫秒的緩衝區,因此 APU 一律需要作用中。 為了為 APU 提供減少狀態所需的時間,ACX 驅動程式允許在 1-2 秒的大小範圍內公告對明顯較大緩衝區的支援。 這表示 APU 可以每隔 1-2 秒喚醒一次,以最大速度執行所需的作業,以準備下一個 1-2 秒的緩衝區,然後移至最低的可能電源狀態,直到需要下一個緩衝區為止。 

在現有的串流模型中,透過卸除播放支援低功率播放。 音訊驅動程式會在端點的波浪篩選上公開 AudioEngine 節點,以公告卸除播放的支援。 AudioEngine 節點提供一種方法來控制驅動程式用來從大型緩衝區轉譯音訊與所需處理之 DSP 引擎的方法。

AudioEngine 節點提供下列設施:

  • 音訊引擎描述,告知音訊堆疊在波浪篩選器上的針腳提供卸除和回送支援(以及主機播放支援)。 
  • 緩衝區大小範圍,可告知音訊堆疊可支援卸除的最小和最大緩衝區大小。 播放。 緩衝區大小範圍可以根據系統活動動態變更。 
  • 格式支援,包括支援的格式、目前的裝置混合格式,以及裝置格式。 
  • 磁碟區,包括遞增支援,因為具有較大的緩衝區軟體磁碟區將無法回應。
  • 如果一或多個卸除數據流包含受保護的內容,回送保護會告知驅動程式將 AudioEngine 回送針腳設為靜音。 
  • 全域 FX 狀態,在 AudioEngine 上啟用或停用 GFX。 

在卸除 Pin 上建立數據流時,數據流支援磁碟區、本機 FX 和回送保護。 

使用 ACX 的低功率播放

ACX 架構會使用相同的模型進行低功率播放。 驅動程式會為主機、卸除和回送串流建立三個不同的 ACXPIN 物件,以及描述這些針腳用於主機、卸除和回送的 ACXAUDIOENGINE 元素。 驅動程式會在線路建立期間將針腳和 ACXAUDIOENGINE 元素新增至 ACXCIRCUIT。

卸除串流建立

驅動程式也會將 ACXAUDIOENGINE 元素新增至為了卸除而建立的數據流,以允許控制音量、靜音和尖峰計量。

串流圖

此圖顯示 ACX 驅動程式 多堆疊。

圖表說明頂端具有核心串流介面的 DSP、CODEC 和 AMP 方塊。

每個 ACX 驅動程式都會控制音訊硬體的不同部分,而且可由不同的廠商提供。 ACX 提供相容的核心串流介面,以允許應用程式依目前執行。

串流釘選

每個 ACXCIRCUIT 至少有一個接收針腳和一個來源 Pin。 ACX 架構會使用這些 Pin 來公開線路與音訊堆疊的連線。 對於轉譯線路,來源釘選可用來控制從線路建立的任何數據流的轉譯行為。 針對擷取線路,使用接收針腳來控制從線路建立之任何數據流的擷取行為。   ACXPIN 是用來控制音訊路徑中串流的物件。 串流 ACXCIRCUIT 負責在線路建立期間為端點音訊路徑建立適當的 ACXPIN 物件,以及向 ACX 註冊 ACXPIN。 ACXCIRCUIT 只需要建立線路的轉譯或擷取針腳或針腳;ACX 架構會建立連線到線路並與線路通訊所需的其他針腳。   

串流線路

當端點由單一線路組成時,該線路就是串流線路。

當端點是由一或多個設備驅動器所建立的多個線路所組成時,線路會依描述所組成端點的 ACXCOMPOSITETEMPLATE 所決定的特定順序進行連線。 端點中的第一個線路是端點的串流線路。

串流線路應該使用 AcxRtStreamCreate 來建立 RT 封包串流,以回應 EvtAcxCircuitCreateStream。 使用 AcxRtStreamCreate 建立的 ACXSTREAM 可讓串流線路驅動程式配置用於串流處理的緩衝區,並控制串流流程以回應客戶端和硬體需求。

端點中的下列線路應該使用 AcxStreamCreate 來建立基本數據流,以回應 EvtAcxCircuitCreateStream。 下列線路使用 AcxStreamCreate 建立的 ACXSTREAM 物件可讓驅動程式設定硬體,以回應串流狀態變更,例如暫停或執行。

串流 ACXCIRCUIT 是接收建立數據流要求的第一個線路。 要求包括裝置、針腳和數據格式(包括模式)。

音訊路徑中的每個 ACXCIRCUIT 都會建立代表線路數據流實例的 ACXSTREAM 物件。 ACX 架構會將 ACXSTREAM 對象連結在一起(與 ACXCIRCUIT 物件連結的方式大致相同)。 

上游和下游線路

串流建立會從串流線路開始,並依線路連接的順序轉送至每個下游線路。 這些連接會在通訊等於 AcxPinCommunicationNone 的網橋接之間建立。 如果驅動程式未在線路建立時新增它們,ACX 架構將會為線路建立一或多個網橋針。

針對從串流線路開始的每個線路,AcxPinTypeSource 網橋接腳會連線到下一個下游線路。 最後一個線路會有描述音訊端點硬體的端點接腳(例如端點是麥克風或喇叭,以及傑克是否插入)。

針對串流線路後面的每個線路,AcxPinTypeSink 網橋接腳會連線到下一個上游線路。

數據流格式交涉

驅動程式會將每個模式支援的格式新增至 ACXPIN,以使用 AcxPinAssignModeDataFormatListAcxPinGetRawDataFormatList 建立串流,來公告串流建立的支援格式。 針對多重線路端點,ACXSTREAMBRIDGE 可用來協調 ACX 線路之間的模式和格式支援。 端點支持的數據流格式是由串流線路所建立的串流 ACXPIN 所決定。 下列線路所使用的格式取決於端點中上一個線路的網橋接腳。

根據預設,ACX 架構會在多重線路端點中的每個線路之間建立ACXSTREAMBRIDGE。 將數據流建立要求轉送至下游線路時,預設 ACXSTREAMBRIDGE 會使用上游線路之網橋接的預設格式。 如果上游線路的網橋接沒有格式,則會使用原始數據流格式。 如果下游線路的連線針腳不支援所使用的格式,則串流建立將會失敗。

如果裝置線路正在執行數據流格式變更,裝置驅動程式應將下游格式新增至下游網橋接點。  

串流建立

串流建立的第一個步驟是建立端點音訊路徑中每個 ACXCIRCUIT 的 ACXSTREAM 實例。 ACX 會呼叫每個線路的 EvtAcxCircuitCreateStream。 ACX 會從前端線路開始,並依序呼叫每個線路的 EvtAcxCircuitCreateStream,結尾為尾電路。 您可以藉由指定 Stream Bridge 的 AcxStreamBridgeInvertChangeStateSequence 旗標(定義於 ACX_STREAM_BRIDGE_CONFIG_FLAGS 中)來反轉順序。 所有線路建立數據流對象之後,數據流物件就會處理串流邏輯。

串流建立要求會藉由呼叫前端線路建立期間指定的 EvtAcxCircuitCreateStream,傳送至作為前端線路拓撲產生一部分所產生的適當 PIN。 

串流線路是一開始處理串流建立要求的上游線路。

  • 它會更新ACXSTREAM_INIT結構,指派 AcxStreamCallbacks 和 AcxRtStreamCallbacks
  • 它會使用 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。 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. 建立 Host、Offload 和 Loopback ACXPIN 物件,並將其新增至 ACXCIRCUIT。
  2. 建立 ACXVOLUME、ACXMUTE 和 ACXPEAKMETER 元素。 這些不會直接新增至 ACXCIRCUIT。
  3. 初始化ACX_AUDIOENGINE_CONFIG結構,並指派 HostPin、OffloadPin、LoopbackPin、VolumeElement、MuteElement 和 PeakMeterElement 物件。
  4. 建立 ACXAUDIOENGINE 元素。

驅動程式必須在卸除釘選上建立數據流時,執行類似的步驟來新增 ACXSTREAMAUDIOENGINE 元素。

串流資源配置

ACX 的串流模型是以封包為基礎,支持數據流的一或兩個封包。 針對串流線路提供轉譯或擷取 ACXPIN 的要求,以配置數據流中使用的記憶體封包。 若要支援重新平衡,配置的記憶體必須是系統記憶體,而不是對應到系統的裝置記憶體。 驅動程式可以使用現有的 WDF 函式來執行配置,並將指標陣列傳回緩衝區配置。 如果驅動程式需要單一連續區塊,它可以將這兩個封包配置為單一緩衝區,並將緩衝區的指標傳回為第二個封包的位移。

如果已配置單一封包,封包必須對齊頁面,而且會對應到使用者模式兩次:

|封包 0 |封包 0 |

這可讓 GetBuffer 傳回單一連續記憶體緩衝區的指標,該緩衝區可能從緩衝區結尾延伸到開頭,而不需要應用程式處理包裝記憶體存取。 

如果配置兩個封包,則會對應到使用者模式:

|封包 0 |封包 1 |

使用初始 ACX 封包串流時,開頭只會配置兩個封包。 一旦執行配置和對應,用戶端虛擬記憶體對應將會維持有效狀態,而不會變更數據流的存留期。 有一個事件與數據流相關聯,表示這兩個封包的封包完成。 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 架構所提供的預設值。 驅動程式可以在建立驅動程式新增至串流線路 ACX_STREAM_BRIDGE_CONFIG_FLAGSACXSTREAMBRIDGE 時設定 AcxStreamBridgeInvertChangeStateSequence 來要求相反的行為。  

串流音訊數據

建立數據流並配置適當的緩衝區之後,數據流就會處於等候數據流啟動的暫停狀態。 當用戶端將數據流放入 Play 狀態時,ACX 架構會呼叫與數據流相關聯的所有 ACXSTREAM 物件,以指出數據流狀態為 Play。 ACXPIN 接著會置於 Play 狀態,此時數據會開始流動。 

轉譯音頻數據

建立數據流並配置資源之後,應用程式會在數據流上呼叫 Start 以開始播放。 請注意,應用程式應該先呼叫 GetBuffer/ReleaseBuffer,再啟動數據流,以確保第一個會立即開始播放的封包將具有有效的音訊數據。 

用戶端會從預先輪替緩衝區開始。 當用戶端呼叫 ReleaseBuffer 時,這會轉譯為 AudioKSE 中的呼叫,該呼叫會呼叫 ACX 層,這會在作用中 ACXSTREAM 上呼叫 EvtAcxStreamSetRenderPacket 。 屬性會包含封包索引(以 0 為基礎),如果適當的話,EOS 旗標的位元組位移位於目前封包的數據流結尾。    串流線路使用封包完成之後,將會觸發緩衝區完成通知,以釋放等候填滿下一個封包的用戶端,並呈現音訊數據。 

支援定時器驅動串流模式,並在呼叫驅動程式的 EvtAcxStreamAllocateRtPackets 回呼時,使用 PacketCount 值 1 表示。

擷取音訊數據

建立數據流並配置資源之後,應用程式會在數據流上呼叫 Start 以開始播放。 

當數據流執行時,來源線路會以音訊數據填滿擷取封包。 填入第一個封包之後,來源線路會將封包釋放至ACX架構。 此時,ACX 架構會發出數據流通知事件的訊號。 

一旦收到數據流通知的訊號,用戶端就可以傳送 KSPROPERTY_RTAUDIO_GETREADPACKET ,以取得已完成擷取之封包的索引(以0為基礎)。 當用戶端傳送 GETCAPTUREPACKET 時,驅動程式可以假設所有先前的封包都已處理,而且可供填滿。 

針對高載擷取,只要呼叫 GETREADPACKET,來源線路就可以將新的封包釋放至 ACX 架構。

用戶端也可以使用 KSPROPERTY_RTAUDIO_PACKETVREGISTER 來取得數據流RTAUDIO_PACKETVREGISTER結構的指標。 ACX 架構會先更新此結構,再發出封包完成的訊號。

舊版 KS 核心串流行為

在某些情況下,例如驅動程序實作高載擷取時(如關鍵詞現成實作),其中需要使用舊版核心串流封包處理行為,而不是 PacketVRegister。 若要使用先前以封包為基礎的行為,驅動程式應該傳回STATUS_NOT_SUPPORTED KSPROPERTY_RTAUDIO_PACKETVREGISTER

下列範例示範如何在 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 值呼叫 AcxRtStreamNotifyPacketComplete (例如,QPC 值可由中斷服務例程計算)。 此資訊可透過 KSPROPERTY_RTAUDIO_PACKETVREGISTER提供給用戶端,其會傳回包含 CompletedPacketCount、CompletedPacketQPC 和結合這兩個結構之結構的指標(這可讓客戶端確保 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 物件遭到 ACXSTREAM 刪除之前收到 EvtAcxStreamPause 和 EvtAcxStreamReleaseHardware。 驅動程式可以在呼叫 AcxStreamCreate 以執行 ACXSTREAM 的最終清除時,在 WDF_OBJECT_ATTRIBUTES 結構提供標準 WDF EvtCleanupCallback 專案。 WDF 會在架構嘗試刪除物件時呼叫 EvtCleanupCallback。 請勿使用 EvtDestroyCallback,這隻會在物件的所有參考都已發行且不確定之後才會呼叫。

如果 EvtAcxStreamReleaseHardware 中尚未清除資源,驅動程式應該清除與 EvtCleanupCallback 中 ACXSTREAM 對象相關聯的系統記憶體資源。

請務必在用戶端要求之前,驅動程式不會清除支持數據流的資源。

不使用 AcxStreamStateAcquire 狀態。 ACX 會移除驅動程式擁有取得狀態的需求,因為此狀態是隱含的準備硬體 (EvtAcxStreamPrepareHardware) 和發行硬體 (EvtAcxStreamReleaseHardware) 回呼。

串流意外移除和失效

如果驅動程式判斷數據流已變成無效(例如千斤頂未拔下),線路將會關閉所有數據流。 

串流記憶體清除

數據流資源的處置可以在驅動程序的數據流內容清除中完成(而非終結)。 絕對不要處置對象內容中共用的任何項目終結回呼。 本指南適用於所有 ACX 物件。

當最後一個 ref 消失之後,就會叫用終結回呼。

一般而言,當句柄關閉時,會呼叫數據流的清除回呼。 其中一個例外狀況是驅動程式在其回呼中建立數據流時。 如果在從數據流建立作業傳回之前,ACX 無法將此數據流新增至其數據流網橋,則會取消數據流異步處理,而目前的線程會將錯誤傳回至 create-stream 用戶端。 數據流目前不應該配置任何 mem 配置。 如需詳細資訊,請參閱 EVT_ACX_STREAM_RELEASE_HARDWARE回呼

串流記憶體清除順序

數據流緩衝區是系統資源,只有當使用者模式用戶端關閉數據流的句柄時,才應該釋放它。 緩衝區(與裝置的硬體資源不同)的存留期與數據流的句柄相同。 當用戶端關閉句柄 ACX 時,會叫用數據流物件清除回呼,然後在物件上的 ref 移至零時,串流 obj 的刪除回呼。

當驅動程式建立 stream-obj,然後失敗 create-stream 回呼時,ACX 可以將 STREAM obj 刪除延遲至工作專案。 若要防止關閉 WDF 線程的死結,ACX 會將刪除延遲至不同的線程。 為避免此行為的任何可能副作用(資源延後釋放),驅動程式可以在從 stream-create 傳回錯誤之前釋放已配置的數據流資源。

當 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 裝置發行硬體 -> 發行裝置的 h/w 資源

AcxStreamFreeRtPackets -> 與句柄相關聯的發行/免費音訊緩衝區

如需管理 WDF 和線路對象的詳細資訊,請參閱 ACX WDF 驅動程式存留期管理

串流 DIS

串流結構

ACX_RTPACKET 結構

這個結構代表單一配置的封包。 PacketBuffer 可以是 WDFMEMORY 句柄、MDL 或 Buffer。 它有相關聯的初始化函式, ACX_RTPACKET_INIT

ACX_STREAM_CALLBACKS

此結構會識別串流至 ACX 架構的驅動程式回呼。 這個結構是ACX_PIN_CONFIG結構的一部分

串流回呼

EvtAcxStreamAllocateRtPackets

EvtAcxStreamAllocateRtPackets 事件會告知驅動程式配置 RtPackets 以供串流處理。 AcxRtStream 會針對事件驅動串流接收 PacketCount = 2,或針對定時器型串流接收 PacketCount = 1。 如果驅動程式針對這兩個封包使用單一緩衝區,則第二個 RtPacketBuffer 應該具有類型 = WdfMemoryDescriptorTypeInvalid 與第一個 封包結尾對齊的 RtPacketOffset 的 WDF_MEMORY_DESCRIPTOR (packet[2]。RtPacketOffset = packet[1]。RtPacketOffset+packet[1]。RtPacketSize)。

EvtAcxStreamFreeRtPackets

EvtAcxStreamFreeRtPackets 事件會告知驅動程序釋放先前呼叫 EvtAcxStreamAllocateRtPackets 中所配置的 RtPackets。 包含來自該呼叫的相同封包。

EvtAcxStreamGetHwLatency

EvtAcxStreamGetHwLatency 事件會告知驅動程式為此數據流的特定線路提供數據流延遲(整體延遲將是不同線路的延遲總和)。 FifoSize 是以位元組為單位,而Delay是以100奈秒為單位。

EvtAcxStreamSetRenderPacket

EvtAcxStreamSetRenderPacket 事件會告知驅動程式用戶端剛發行的封包。 如果沒有問題,此封包應該是 (CurrentRenderPacket + 1),其中 CurrentRenderPacket 是驅動程式目前串流的來源封包。

旗標可以是 0 或 AcxStreamSetRenderPacketEndOfStream,表示 Packet 是數據流中的最後一個封包,而 EosPacketLength 是封包的有效長度。

驅動程式應該會在轉譯封包時繼續增加 CurrentRenderPacket,而不是變更其 CurrentRenderPacket 以符合此值。

EvtAcxStreamGetCurrentPacket

EvtAcxStreamGetCurrentPacket 會告訴驅動程式指出目前要轉譯為硬體或目前由擷取硬體填滿的封包(0 為基礎)。

EvtAcxStreamGetCapturePacket

EvtAcxStreamGetCapturePacket 會告訴驅動程式指出最近已完全填入哪個封包(0 基底),包括驅動程式開始填入封包時的 QPC 值。

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 Stream。

AcxRtStreamCreate

AcxRtStreamCreate 會建立 ACX Stream,可用來控制串流行為,並處理封包配置和通訊串流狀態。

AcxRtStreamNotifyPacketComplete

驅動程式會在封包完成時呼叫此 ACX API。 封包完成時間和以 0 為基礎的封包索引包含在內,以改善用戶端效能。 ACX 架構會設定與數據流相關聯的任何通知事件。

另請參閱

ACX 音訊類別擴充功能概觀

ACX 參考檔

ACX 對象的摘要