Condividi tramite


Streaming ACX

Questo argomento illustra lo streaming ACX e il buffering associato, che è fondamentale per un'esperienza audio senza problemi. Descrive i meccanismi usati dal driver per comunicare sullo stato del flusso e gestire il buffer per il flusso. Per un elenco di termini audio ACX comuni e un'introduzione ad ACX, vedi Panoramica delle estensioni della classe audio ACX.

Tipi di streaming ACX

AcxStream rappresenta un flusso audio su hardware di un circuito specifico. Un AcxStream può aggregare uno o più oggetti simili a AcxElements.

Il framework ACX supporta due tipi di flusso. Il primo tipo di flusso, il flusso di pacchetti RT, fornisce il supporto per l'allocazione di pacchetti RT e l'uso di pacchetti RT per il trasferimento di dati audio da o verso l'hardware del dispositivo insieme alle transizioni di stato del flusso. Il secondo tipo di flusso, Basic Stream, fornisce solo il supporto per le transizioni di stato del flusso.

In un singolo endpoint circuito il circuito deve essere un circuito di streaming che crea un flusso di pacchetti RT. Se due o più circuiti sono connessi per creare un endpoint, il primo circuito nell'endpoint è il circuito di streaming e crea un flusso di pacchetti RT; i circuiti connessi creeranno flussi di base per ricevere eventi correlati alle transizioni di stato del flusso.

Per altre informazioni, vedere Flusso ACX in Riepilogo degli oggetti ACX. Le DDI per il flusso vengono definite nell'intestazione acxstreams.h .

Stack di comunicazioni di streaming ACX

Esistono due tipi di comunicazioni per ACX Streaming. Un percorso di comunicazione viene usato per controllare il comportamento di streaming, per i comandi come Start, Create e Allocate, che useranno le comunicazioni ACX standard. Il framework ACX usa code di I/O e passa le richieste WDF usando le code. Il comportamento della coda è nascosto dal codice del driver effettivo tramite l'uso di callback evt e funzioni ACX, anche se al driver viene data la possibilità di pre-elaborare tutte le richieste WDF.

Il secondo e più interessante percorso di comunicazione, viene usato per la segnalazione di streaming audio. Ciò comporta l'indicare al driver quando un pacchetto è pronto e ricevere dati quando il driver ha terminato l'elaborazione di un pacchetto. 

I requisiti principali per la segnalazione di streaming:

  • Supporto della riproduzione senza interruzioni
    • Bassa latenza
    • Tutti i blocchi necessari sono limitati al flusso in questione
  • Facilità d'uso per gli sviluppatori di driver

Per comunicare con il driver per segnalare lo stato di streaming, ACX usa eventi con un buffer condiviso e chiamate IRP dirette. Queste sono descritte di seguito.

Buffer condiviso

Per comunicare dal driver al client, vengono usati un buffer condiviso e un evento. In questo modo il client non deve attendere o eseguire il polling e che il client può determinare tutto ciò che deve continuare a trasmettere riducendo o eliminando la necessità di chiamate IRP dirette.

Il driver di dispositivo usa un buffer condiviso per comunicare con il client a cui viene eseguito il rendering del pacchetto da o acquisito. Questo buffer condiviso include il numero di pacchetti (basato su 1) dell'ultimo pacchetto completato insieme al valore QPC (QueryPerformanceCounter) del tempo di completamento. Per il driver di dispositivo, deve indicare queste informazioni chiamando AcxRtStreamNotifyPacketComplete. Quando il driver di dispositivo chiama AcxRtStreamNotifyPacketComplete, il framework ACX aggiornerà il buffer condiviso con il nuovo numero di pacchetti e QPC e segnalerà un evento condiviso con il client per indicare che il client potrebbe leggere il nuovo numero di pacchetti.

Chiamate IRP dirette

Per comunicare dal client al driver, vengono usate chiamate IRP dirette. In questo modo si riducono le complessità per garantire che le richieste WDF vengano gestite in modo tempestivo ed è stato dimostrato di funzionare correttamente nell'architettura esistente.

Il client può richiedere in qualsiasi momento il numero di pacchetti corrente o indicare il numero di pacchetti corrente al driver di dispositivo. Queste richieste chiameranno i gestori eventi evtAcxStreamGetCurrentPacket e EvtAcxStreamSetRenderPacket . Il client può anche richiedere il pacchetto di acquisizione corrente che chiamerà il gestore eventi del driver di dispositivo EvtAcxStreamGetCapturePacket .

Analogie con PortCls

La combinazione di chiamate IRP dirette e buffer condiviso usato da ACX è simile alla modalità di comunicazione della gestione del completamento del buffer in PortCls. I runtime di integrazione sono molto simili e il buffer condiviso introduce la possibilità per il driver di comunicare direttamente il numero di pacchetti e la tempistica senza basarsi su IRP.   I driver dovranno assicurarsi di non eseguire alcuna operazione che richiede l'accesso ai blocchi usati anche nei percorsi di controllo del flusso. Ciò è necessario per evitare l'interruzione. 

Supporto di buffer di grandi dimensioni per la riproduzione a basso consumo

Per ridurre la quantità di energia consumata durante la riproduzione di contenuti multimediali, è importante ridurre la quantità di tempo usata dall'APU in uno stato ad alta potenza. Poiché la normale riproduzione audio usa buffer di 10 ms, l'APU deve sempre essere attiva. Per concedere all'APU il tempo necessario per ridurre lo stato, i driver ACX possono annunciare il supporto per buffer notevolmente più grandi, nell'intervallo di dimensioni di 1-2 secondi. Ciò significa che l'APU può riattivarsi una volta ogni 1-2 secondi, eseguire le operazioni necessarie alla massima velocità per preparare il buffer successivo da 1 a 2 secondi e quindi passare allo stato di alimentazione più basso possibile fino a quando non è necessario il buffer successivo. 

Nei modelli di streaming esistenti la riproduzione a basso consumo è supportata tramite Offload Playback. Un driver audio annuncia il supporto per La riproduzione offload esponendo un nodo AudioEngine sul filtro wave per un endpoint. Il nodo AudioEngine consente di controllare il motore DSP usato dal driver per eseguire il rendering dell'audio dai buffer di grandi dimensioni con l'elaborazione desiderata.

Il nodo AudioEngine offre queste funzionalità:

  • Audio Engine Description( Descrizione del motore audio, che indica allo stack audio quali puntini sul filtro wave forniscono supporto di offload e loopback (e supporto per la riproduzione host). 
  • Intervallo dimensioni buffer, che indica allo stack audio le dimensioni minime e massime del buffer che possono essere supportate per l'offload. riproduzione. L'intervallo dimensioni buffer può cambiare in modo dinamico in base all'attività di sistema. 
  • Supporto del formato, inclusi i formati supportati, il formato di combinazione di dispositivi corrente e il formato del dispositivo. 
  • Il volume, incluso il supporto di rampa, poiché con il volume software dei buffer di dimensioni maggiori non sarà reattivo.
  • Protezione loopback, che indica al driver di disattivare il pin di Loopback AudioEngine se uno o più dei flussi offloaded contengono contenuto protetto. 
  • Stato FX globale, per abilitare o disabilitare GFX in AudioEngine. 

Quando viene creato un flusso nel pin offload, il flusso supporta la protezione volume, fx locale e loopback. 

Riproduzione a basso consumo di energia con ACX

Il framework ACX usa lo stesso modello per la riproduzione a basso consumo. Il driver crea tre oggetti ACXPIN separati per lo streaming di host, offload e loopback, insieme a un elemento ACXAUDIOENGINE che descrive quali di questi pin vengono usati per l'host, l'offload e il loopback. Il driver aggiunge i pin e l'elemento ACXAUDIOENGINE all'ACXCIRCUIT durante la creazione del circuito.

Creazione di flussi offloaded

Il driver aggiungerà anche un elemento ACXAUDIOENGINE ai flussi creati per l'offload per consentire il controllo su volume, disattivazione e contatore di picco.

Diagramma di streaming

Questo diagramma mostra un driver ACX multi stack.

Diagramma che illustra le caselle DSP, CODEC e AMP con un'interfaccia di streaming del kernel nella parte superiore.

Ogni driver ACX controlla una parte separata dell'hardware audio e può essere fornita da un fornitore diverso. ACX fornisce un'interfaccia di streaming del kernel compatibile per consentire l'esecuzione delle applicazioni così come sono.

Pin di flusso

Ogni ACXCIRCUIT ha almeno un pin sink e un pin di origine. Questi pin vengono usati dal framework ACX per esporre le connessioni del circuito allo stack audio. Per un circuito render, il pin di origine viene usato per controllare il comportamento di rendering di qualsiasi flusso creato dal circuito. Per un circuito capture, il pin sink viene usato per controllare il comportamento di acquisizione di qualsiasi flusso creato dal circuito.   ACXPIN è l'oggetto usato per controllare lo streaming nel percorso audio. Lo streaming ACXCIRCUIT è responsabile della creazione degli oggetti ACXPIN appropriati per il percorso audio endpoint in fase di creazione del circuito e la registrazione degli ACXPIN con ACX. L'ACXCIRCUIT deve creare solo il pin o il pin di rendering o acquisizione per il circuito; il framework ACX creerà l'altro pin necessario per connettersi e comunicare con il circuito.   

Circuito di streaming

Quando un endpoint è composto da un singolo circuito, tale circuito è il circuito di streaming.

Quando un endpoint è composto da più circuiti creati da uno o più driver di dispositivo, i circuiti vengono connessi nell'ordine specifico determinato dall'OGGETTO ACXCOMPOSITETEMPLATE che descrive l'endpoint composto. Il primo circuito nell'endpoint è il circuito di streaming per l'endpoint.

Il circuito di streaming deve usare AcxRtStreamCreate per creare un flusso di pacchetti RT in risposta a EvtAcxCircuitCreateStream. ACXSTREAM creato con AcxRtStreamCreate consentirà al driver del circuito di streaming di allocare il buffer usato per lo streaming e di controllare il flusso di streaming in risposta alle esigenze client e hardware.

I circuiti seguenti nell'endpoint devono usare AcxStreamCreate per creare un flusso di base in risposta a EvtAcxCircuitCreateStream. Gli oggetti ACXSTREAM creati con AcxStreamCreate dai circuiti seguenti consentiranno ai driver di configurare l'hardware in risposta alle modifiche dello stato del flusso, ad esempio Pause o Run.

Il flusso ACXCIRCUIT è il primo circuito a ricevere le richieste di creazione di un flusso. La richiesta include il dispositivo, il pin e il formato dei dati (inclusa la modalità).

Ogni ACXCIRCUIT nel percorso audio creerà un oggetto ACXSTREAM che rappresenta l'istanza del flusso del circuito. Il framework ACX collega gli oggetti ACXSTREAM insieme (allo stesso modo in cui gli oggetti ACXCIRCUIT sono collegati). 

Circuiti upstream e downstream

La creazione del flusso inizia nel circuito di streaming e viene inoltrata a ogni circuito downstream nell'ordine in cui i circuiti sono connessi. Le connessioni vengono effettuate tra pin bridge creati con Communication uguale ad AcxPinCommunicationNone. Il framework ACX creerà uno o più pin bridge per un circuito se il driver non li aggiunge in fase di creazione del circuito.

Per ogni circuito che inizia con il circuito di streaming, il pin del bridge AcxPinTypeSource si connetterà al circuito downstream successivo. Il circuito finale avrà un pin dell'endpoint che descrive l'hardware dell'endpoint audio, ad esempio se l'endpoint è un microfono o un altoparlante e se il jack è collegato.

Per ogni circuito che segue il circuito di streaming, il pin del bridge AcxPinTypeSink si connetterà al circuito upstream successivo.

Negoziazione del formato di flusso

Il driver annuncia i formati supportati per la creazione del flusso aggiungendo i formati supportati per modalità all'ACXPIN usato per la creazione di flussi con AcxPinAssignModeDataFormatList e AcxPinGetRawDataFormatList. Per gli endpoint multi circuito, è possibile usare ACXSTREAMBRIDGE per coordinare la modalità e il supporto del formato tra circuiti ACX. I formati di flusso supportati per l'endpoint sono determinati dagli ACXPIN di streaming creati dal circuito di streaming. I formati usati dai circuiti seguenti sono determinati dal pin bridge del circuito precedente nell'endpoint.

Per impostazione predefinita, il framework ACX creerà un ACXSTREAMBRIDGE tra ogni circuito in un endpoint multi circuito. Il formato predefinito di ACXSTREAMBRIDGE userà il formato predefinito della modalità RAW del pin di bridge del circuito upstream durante l'inoltro della richiesta di creazione del flusso al circuito downstream. Se il pin del bridge del circuito upstream non ha formati, verrà usato il formato del flusso originale. Se il pin connesso del circuito downstream non supporta il formato utilizzato, la creazione del flusso avrà esito negativo.

Se un circuito del dispositivo esegue una modifica del formato di flusso, il driver di dispositivo deve aggiungere il formato downstream al pin del bridge downstream.  

Creazione del flusso

Il primo passaggio della creazione di flussi consiste nel creare l'istanza ACXSTREAM per ogni ACXCIRCUIT nel percorso audio dell'endpoint. ACX chiamerà EvtAcxCircuitCreateStream di ogni circuito. ACX inizierà con il circuito head e chiamerà EvtAcxCircuitCreateStream di ogni circuito in ordine, terminando con il circuito di coda. L'ordine può essere invertito specificando il flag AcxStreamBridgeInvertChangeStateSequence (definito in ACX_STREAM_BRIDGE_CONFIG_FLAGS) per Stream Bridge. Dopo che tutti i circuiti hanno creato un oggetto flusso, gli oggetti flusso gestiranno la logica di streaming.

La richiesta di creazione del flusso viene inviata al PIN appropriato generato come parte della generazione della topologia del circuito head chiamando evtAcxCircuitCreateStream specificato durante la creazione del circuito head. 

Il circuito di streaming è il circuito upstream che gestisce inizialmente la richiesta di creazione del flusso.

  • Aggiorna la struttura ACXSTREAM_INIT, assegnando AcxStreamCallbacks e AcxRtStreamCallbacks
  • Crea l'oggetto ACXSTREAM usando AcxRtStreamCreate
  • Crea tutti gli elementi specifici del flusso (ad esempio ACXVOLUME o ACXAUDIOENGINE)
  • Aggiunge gli elementi all'oggetto ACXSTREAM
  • Restituisce l'oggetto ACXSTREAM creato nel framework ACX

ACX inoltra quindi la creazione del flusso al circuito downstream successivo.

  • Aggiorna la struttura ACXSTREAM_INIT, assegnando AcxStreamCallbacks
  • Crea l'oggetto ACXSTREAM usando AcxStreamCreate
  • Crea tutti gli elementi specifici del flusso
  • Aggiunge gli elementi all'oggetto ACXSTREAM
  • Restituisce l'oggetto ACXSTREAM creato nel framework ACX

Il canale di comunicazione tra circuiti in un percorso audio usa oggetti ACXTARGETSTREAM. In questo esempio, ogni circuito avrà accesso a una coda di I/O per il circuito davanti a esso e al circuito dietro di esso nel percorso audio dell'endpoint. Inoltre, un percorso audio endpoint è lineare e bidirezionale. La gestione effettiva delle code di I/O viene eseguita dal framework ACX.    Durante la creazione dell'oggetto ACXSTREAM, ogni circuito può aggiungere informazioni di contesto all'oggetto ACXSTREAM per archiviare e tenere traccia dei dati privati per il flusso.

Esempio di flusso di rendering

Creazione di un flusso di rendering in un percorso audio endpoint composto da tre circuiti: DSP, CODEC e AMP. Il circuito DSP funziona come circuito di streaming e ha fornito un gestore EvtAcxPinCreateStream. Il circuito DSP funziona anche come circuito di filtro: a seconda della modalità di flusso e della configurazione, può applicare l'elaborazione dei segnali ai dati audio. Il circuito CODEC rappresenta l'applicazione livello dati, fornendo la funzionalità del sink audio. Il circuito AMP rappresenta l'hardware analogico tra l'applicazione livello dati e l'altoparlante. Il circuito AMP potrebbe gestire il rilevamento dei jack o altri dettagli hardware dell'endpoint.

  1. AudioKSE chiama NtCreateFile per creare un flusso.
  2. Questo filtra tramite ACX e termina con la chiamata di EvtAcxPinCreateStream del circuito DSP con il pin, il formato dati (inclusa la modalità) e le informazioni sul dispositivo. 
  3. Il circuito DSP convalida le informazioni sul formato dei dati per assicurarsi che possa gestire il flusso creato. 
  4. Il circuito DSP crea l'oggetto ACXSTREAM per rappresentare il flusso. 
  5. Il circuito DSP alloca una struttura di contesto privata e la associa ad ACXSTREAM. 
  6. Il circuito DSP restituisce il flusso di esecuzione al framework ACX, che quindi chiama nel circuito successivo nel percorso audio dell'endpoint, il circuito CODEC. 
  7. Il circuito CODEC convalida le informazioni sul formato dei dati per verificare che possa gestire il rendering dei dati. 
  8. Il circuito CODEC alloca una struttura di contesto privata e la associa ad ACXSTREAM. 
  9. Il circuito CODEC si aggiunge come sink di flusso a ACXSTREAM.
  10. Il circuito CODEC restituisce il flusso di esecuzione al framework ACX, che quindi chiama nel circuito successivo nel percorso audio dell'endpoint, il circuito AMP. 
  11. Il circuito AMP alloca una struttura di contesto privata e la associa ad ACXSTREAM. 
  12. Il circuito AMP restituisce il flusso di esecuzione al framework ACX. A questo punto, la creazione del flusso è stata completata. 

Flussi di buffer di grandi dimensioni

I flussi di buffer di grandi dimensioni vengono creati in ACXPIN designato per Offload dall'elemento ACXCIRCUIT ACXAUDIOENGINE di ACXCIRCUIT.

Per supportare i flussi di offload, il driver di dispositivo deve eseguire le operazioni seguenti durante la creazione del circuito di streaming:

  1. Creare gli oggetti Host, Offload e Loopback ACXPIN e aggiungerli a ACXCIRCUIT.
  2. Creare elementi ACXVOLUME, ACXMUTE e ACXPEAKMETER. Questi non verranno aggiunti direttamente all'ACXCIRCUIT.
  3. Inizializzare una struttura ACX_AUDIOENGINE_CONFIG, assegnando gli oggetti HostPin, OffloadPin, LoopbackPin, VolumeElement, MuteElement e PeakMeterElement.
  4. Creare l'elemento ACXAUDIOENGINE.

I driver dovranno eseguire passaggi simili per aggiungere un elemento ACXSTREAMAUDIOENGINE durante la creazione di un flusso nel pin Offload.

Allocazione delle risorse di flusso

Il modello di streaming per ACX è basato su pacchetti, con supporto per uno o due pacchetti per un flusso. Al rendering o all'acquisizione ACXPIN per il circuito di streaming viene assegnata una richiesta di allocare i pacchetti di memoria usati nel flusso. Per supportare il ribilanciamento, la memoria allocata deve essere la memoria di sistema anziché la memoria del dispositivo mappata nel sistema. Il driver può usare le funzioni WDF esistenti per eseguire l'allocazione e restituirà una matrice di puntatori alle allocazioni del buffer. Se il driver richiede un singolo blocco contiguo, può allocare entrambi i pacchetti come un singolo buffer, restituendo un puntatore a un offset del buffer come secondo pacchetto.

Se viene allocato un singolo pacchetto, il pacchetto deve essere allineato a pagina ed è mappato due volte in modalità utente:

| pacchetto 0 | pacchetto 0 |

In questo modo GetBuffer restituisce un puntatore a un singolo buffer di memoria contiguo che può estendersi dalla fine del buffer all'inizio senza richiedere all'applicazione di gestire il wrapping dell'accesso alla memoria. 

Se vengono allocati due pacchetti, vengono mappati in modalità utente :

| pacchetto 0 | pacchetto 1 |

Con il flusso iniziale di pacchetti ACX, all'inizio sono stati allocati solo due pacchetti. Il mapping della memoria virtuale del client rimarrà valido senza modificare la durata del flusso dopo l'esecuzione dell'allocazione e del mapping. Esiste un evento associato al flusso per indicare il completamento del pacchetto per entrambi i pacchetti. Sarà anche presente un buffer condiviso che verrà usato dal framework ACX per comunicare il pacchetto completato con l'evento.  

Dimensioni dei pacchetti di flussi di buffer di grandi dimensioni

Quando si espone il supporto per buffer di grandi dimensioni, il driver fornirà anche un callback usato per determinare le dimensioni minime e massime dei pacchetti per la riproduzione di buffer di grandi dimensioni.   Le dimensioni del pacchetto per l'allocazione del buffer di flusso sono determinate in base al valore minimo e massimo.

Poiché le dimensioni minime e massime del buffer possono essere volatili, il driver può non riuscire la chiamata di allocazione dei pacchetti se il valore minimo e massimo è stato modificato.

Specifica dei vincoli di buffer ACX

Per specificare vincoli di buffer ACX, i driver ACX possono usare l'impostazione delle proprietà KS/PortCls, KSAUDIO_PACKETSIZE_CONSTRAINTS2 e la struttura KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT.

L'esempio di codice seguente illustra come impostare i vincoli di dimensione del buffer per i buffer WaveRT per diverse modalità di elaborazione dei segnali.

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

Viene utilizzata una struttura DSP_DEVPROPERTY per archiviare i vincoli.

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

E viene creata una matrice di tali strutture.

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

Più avanti nella funzione EvtCircuitCompositeCircuitInitialize, la funzione helper AddPropertyToCircuitInterface viene usata per aggiungere la matrice di proprietà dell'interfaccia al circuito.

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

La funzione helper AddPropertyToCircuitInterface accetta acxCircuitGetSymbolicLinkName per il circuito e quindi chiama IoGetDeviceInterfaceAlias per individuare l'interfaccia audio usata dal circuito.

La funzione SetDeviceInterfacePropertyDataMultiple chiama quindi la funzione IoSetDeviceInterfacePropertyData per modificare il valore corrente della proprietà dell'interfaccia del dispositivo, ovvero i valori della proprietà audio KS nell'interfaccia audio per ACXCIRCUIT.

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

Modifiche dello stato del flusso

Quando si verifica una modifica dello stato del flusso, ogni oggetto flusso nel percorso audio dell'endpoint per il flusso riceverà un evento di notifica dal framework ACX. L'ordine in cui ciò avviene dipende dalla modifica dello stato e dal flusso del flusso.

  • Per i flussi di rendering che passano da uno stato meno attivo a uno stato più attivo, il circuito di streaming (che ha registrato sink) riceverà prima l'evento. Dopo aver gestito l'evento, il circuito successivo nel percorso audio dell'endpoint riceverà l'evento.

  • Per i flussi di rendering che passano da uno stato più attivo a uno stato meno attivo, il circuito di streaming riceverà l'ultimo evento. 

  • Per i flussi di acquisizione che passano da uno stato meno attivo a uno stato più attivo, il circuito di streaming riceverà l'ultimo evento. 

  • Per i flussi di acquisizione che passano da uno stato più attivo a uno stato meno attivo, il circuito di streaming riceverà prima l'evento. 

L'ordinamento precedente è l'impostazione predefinita fornita dal framework ACX. Un driver può richiedere il comportamento opposto impostando AcxStreamBridgeInvertChangeStateSequence (definito in ACX_STREAM_BRIDGE_CONFIG_FLAGS) durante la creazione di ACXSTREAMBRIDGE che il driver aggiunge al circuito di streaming.  

Streaming di dati audio

Dopo aver creato il flusso e aver allocato i buffer appropriati, il flusso si trova nello stato Pausa in attesa dell'avvio del flusso. Quando il client inserisce il flusso nello stato Play, il framework ACX chiamerà tutti gli oggetti ACXSTREAM associati al flusso per indicare che lo stato del flusso è in Play. AcXPIN verrà quindi inserito nello stato Di riproduzione, in corrispondenza del quale i dati inizieranno a fluire. 

Rendering dei dati audio

Dopo aver creato il flusso e aver allocato le risorse, l'applicazione chiamerà Start sul flusso per avviare la riproduzione. Si noti che un'applicazione deve chiamare GetBuffer/ReleaseBuffer prima di avviare il flusso per assicurarsi che il primo pacchetto che inizierà a riprodurre immediatamente avrà dati audio validi. 

Il client inizia pre-rotolando un buffer. Quando il client chiama ReleaseBuffer, verrà convertito in una chiamata in AudioKSE che chiamerà nel livello ACX, che chiamerà EvtAcxStreamSetRenderPacket sull'ACXSTREAM attivo. La proprietà includerà l'indice del pacchetto (basato su 0) e, se appropriato, un flag EOS con l'offset di byte della fine del flusso nel pacchetto corrente.    Al termine del circuito di streaming con un pacchetto, attiverà la notifica di completamento del buffer che rilascerà i client in attesa di riempire il pacchetto successivo con i dati audio di rendering. 

La modalità di streaming basata su timer è supportata e viene indicata usando un valore PacketCount pari a 1 quando si chiama il callback EvtAcxStreamAllocateRtPackets del driver.

Acquisizione di dati audio

Dopo aver creato il flusso e aver allocato le risorse, l'applicazione chiamerà Start sul flusso per avviare la riproduzione. 

Quando il flusso è in esecuzione, il circuito di origine riempie il pacchetto di acquisizione con dati audio. Una volta riempito il primo pacchetto, il circuito di origine rilascia il pacchetto al framework ACX. A questo punto il framework ACX segnala l'evento di notifica del flusso. 

Dopo aver segnalato la notifica del flusso, il client può inviare KSPROPERTY_RTAUDIO_GETREADPACKET per ottenere l'indice (in base 0) del pacchetto che ha terminato l'acquisizione. Quando il client ha inviato GETCAPTUREPACKET, il driver può presupporre che tutti i pacchetti precedenti siano stati elaborati e siano disponibili per il riempimento. 

Per l'acquisizione burst, il circuito di origine può rilasciare un nuovo pacchetto nel framework ACX non appena viene chiamato GETREADPACKET.

Il client può anche usare KSPROPERTY_RTAUDIO_PACKETVREGISTER per ottenere un puntatore alla struttura RTAUDIO_PACKETVREGISTER per il flusso. Questa struttura verrà aggiornata dal framework ACX prima di segnalare il completamento del pacchetto.

Comportamento di streaming del kernel KS legacy

Possono verificarsi situazioni, ad esempio quando un driver implementa l'acquisizione burst (come in un'implementazione spotter di parole chiave), in cui è necessario usare il comportamento di gestione dei pacchetti di streaming del kernel legacy anziché PacketVRegister. Per usare il comportamento precedente basato su pacchetti, il driver deve restituire STATUS_NOT_SUPPORTED per KSPROPERTY_RTAUDIO_PACKETVREGISTER.

L'esempio seguente illustra come eseguire questa operazione in AcxStreamInitAssignAcxRequestPreprocessCallback per acxstream. Per altre informazioni, vedere 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);
}

Posizione flusso

Il framework ACX chiamerà il callback EvtAcxStreamGetPresentationPosition per ottenere la posizione corrente del flusso. La posizione corrente del flusso includerà PlayOffset e WriteOffset. 

Il modello di streaming WaveRT consente al driver audio di esporre un registro di posizione HW al client. Il modello di streaming ACX non supporterà l'esposizione di eventuali registri HW, in quanto impedirebbe che si verifichi un ribilanciamento. 

Ogni volta che il circuito di streaming completa un pacchetto, chiama AcxRtStreamNotifyPacketComplete con l'indice di pacchetti basato su 0 e il valore QPC considerato il più vicino possibile al completamento del pacchetto (ad esempio, il valore QPC può essere calcolato dalla routine del servizio interrupt). Queste informazioni sono disponibili per i client tramite KSPROPERTY_RTAUDIO_PACKETVREGISTER, che restituisce un puntatore a una struttura che contiene CompletedPacketCount, CompletedPacketQPC e un valore che combina i due (che consente al client di assicurarsi che CompletedPacketCount e CompletedPacketQPC siano dello stesso pacchetto).  

Transizioni di stato del flusso

Dopo aver creato un flusso, ACX eseguirà la transizione del flusso a stati diversi usando i callback seguenti:

  • EvtAcxStreamPrepareHardware eseguirà la transizione del flusso dallo stato AcxStreamStateStop allo stato AcxStreamStatePause. Il driver deve riservare hardware necessario, ad esempio motori DMA, quando riceve EvtAcxStreamPrepareHardware.
  • EvtAcxStreamRun eseguirà la transizione del flusso dallo stato AcxStreamStatePause allo stato AcxStreamStateRun.
  • EvtAcxStreamPause eseguirà la transizione del flusso dallo stato AcxStreamStateRun allo stato AcxStreamStatePause.
  • EvtAcxStreamReleaseHardware eseguirà la transizione del flusso dallo stato AcxStreamStatePause allo stato AcxStreamStateStop. Il driver deve rilasciare hardware necessario, ad esempio motori DMA, quando riceve EvtAcxStreamReleaseHardware.

Il flusso può ricevere il callback EvtAcxStreamPrepareHardware dopo aver ricevuto il callback EvtAcxStreamReleaseHardware. Verrà eseguito il passaggio del flusso allo stato AcxStreamStatePause.

L'allocazione di pacchetti con EvtAcxStreamAllocateRtPackets avviene normalmente prima della prima chiamata a EvtAcxStreamPrepareHardware. I pacchetti allocati verranno normalmente liberati con EvtAcxStreamFreeRtPackets dopo l'ultima chiamata a EvtAcxStreamReleaseHardware. Questo ordinamento non è garantito.

Lo stato AcxStreamStateAcquire non viene utilizzato. ACX rimuove la necessità che il driver abbia lo stato di acquisizione, in quanto questo stato è implicito con i callback dell'hardware di preparazione (EvtAcxStreamPrepareHardware) e dell'hardware di rilascio (EvtAcxStreamReleaseHardware).

Supporto dei flussi di buffer di grandi dimensioni e del motore di offload

ACX usa l'elemento ACXAUDIOENGINE per designare un ACXPIN che gestirà la creazione del flusso Offload e i diversi elementi necessari per l'offload del volume del flusso, il mute e lo stato del contatore di picco. Questo è simile al nodo del motore audio esistente nei driver WaveRT.

Processo di chiusura del flusso

Quando il client chiude il flusso, il driver riceverà EvtAcxStreamPause e EvtAcxStreamReleaseHardware prima che l'oggetto ACXSTREAM venga eliminato da ACX Framework. Il driver può fornire la voce WDF EvtCleanupCallback standard nella struttura WDF_OBJECT_ATTRIBUTES quando si chiama AcxStreamCreate per eseguire la pulizia finale per ACXSTREAM. WDF chiamerà EvtCleanupCallback quando il framework tenta di eliminare l'oggetto. Non usare EvtDestroyCallback, che viene chiamato solo dopo che tutti i riferimenti all'oggetto sono stati rilasciati, che è indeterminato.

Il driver deve pulire le risorse di memoria di sistema associate all'oggetto ACXSTREAM in EvtCleanupCallback, se le risorse non sono già state pulite in EvtAcxStreamReleaseHardware.

È importante che il driver non pulisca le risorse che supportano il flusso, fino a quando non viene richiesto dal client.

Lo stato AcxStreamStateAcquire non viene utilizzato. ACX rimuove la necessità che il driver abbia lo stato di acquisizione, in quanto questo stato è implicito con i callback dell'hardware di preparazione (EvtAcxStreamPrepareHardware) e dell'hardware di rilascio (EvtAcxStreamReleaseHardware).

Rimozione e invalidazione delle sorprese di flusso

Se il driver determina che il flusso non è valido (ad esempio, il jack viene scollegato), il circuito arresterà tutti i flussi. 

Pulizia della memoria di flusso

L'eliminazione delle risorse del flusso può essere eseguita nella pulizia del contesto del flusso del driver (non distruggere). Non inserire mai l'eliminazione di qualsiasi elemento condiviso nel contesto di un oggetto distrugga il callback. Queste linee guida si applicano a tutti gli oggetti ACX.

Il callback di eliminazione viene richiamato dopo che l'ultimo riferimento è andato, quando è sconosciuto.

In generale, il callback di pulizia del flusso viene chiamato quando l'handle viene chiuso. Un'eccezione è quando il driver ha creato il flusso nel callback. Se ACX non è riuscito ad aggiungere questo flusso al bridge di flusso appena prima di tornare dall'operazione di creazione del flusso, il flusso viene annullato asincrono e il thread corrente restituisce un errore al client create-stream. Il flusso non deve avere allocazioni mem allocate a questo punto. Per altre informazioni, vedere EVT_ACX_STREAM_RELEASE_HARDWARE callback.

Sequenza di pulizia della memoria di flusso

Il buffer di flusso è una risorsa di sistema e deve essere rilasciato solo quando il client in modalità utente chiude l'handle del flusso. Il buffer (diverso dalle risorse hardware del dispositivo) ha la stessa durata dell'handle del flusso. Quando il client chiude l'handle ACX richiama il callback di pulizia degli oggetti di flusso e quindi il callback di eliminazione del flusso obj quando il riferimento sull'oggetto passa a zero.

È possibile che ACX rinvii un'eliminazione obj STREAM a un elemento di lavoro quando il driver ha creato un oggetto stream-obj e quindi non è riuscito il callback create-stream. Per evitare un deadlock con un thread WDF di arresto, ACX rinvia l'eliminazione a un thread diverso. Per evitare eventuali effetti collaterali di questo comportamento (rilascio posticipato delle risorse), il driver può rilasciare le risorse di flusso allocate prima di restituire un errore dalla creazione del flusso.

Il driver deve liberare i buffer audio quando ACX richiama il callback EVT_ACX_STREAM_FREE_RTPACKETS. Questo callback viene chiamato quando l'utente chiude gli handle del flusso.

Poiché i buffer RT vengono mappati in modalità utente, la durata del buffer corrisponde alla durata dell'handle. Il driver non deve tentare di rilasciare/liberare i buffer audio prima che ACX richiami questo callback.

EVT_ACX_STREAM_FREE_RTPACKETS callback deve essere chiamato dopo EVT_ACX_STREAM_RELEASE_HARDWARE callback e terminare prima di EvtDeviceReleaseHardware.

Questo callback può verificarsi dopo che il driver ha elaborato il callback hardware di rilascio di WDF, perché il client in modalità utente può tenere premuto i relativi handle per molto tempo. Il driver non deve tentare di attendere che questi handle vadano via, in questo modo verrà creato solo un controllo di bug 0x9f DRIVER_POWER_STATE_FAILURE. Per altre informazioni, vedere EVT_WDF_DEVICE_RELEASE_HARDWARE funzione di callback.

Questo codice EvtDeviceReleaseHardware del driver ACX di esempio mostra un esempio di chiamata di AcxDeviceRemoveCircuit e quindi del rilascio della memoria h/w di streaming.

    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();

Riepilogo:

hardware di rilascio del dispositivo wdf -> Rilasciare le risorse h/w del dispositivo

AcxStreamFreeRtPackets -> Buffer audio libero/rilascio associato all'handle

Per altre informazioni sulla gestione di oggetti WDF e circuiti, vedere Gestione della durata dei driver WDF ACX.

DDI di streaming

Strutture di streaming

struttura ACX_RTPACKET

Questa struttura rappresenta un singolo pacchetto allocato. PacketBuffer può essere un handle WDFMEMORY, un MDL o un buffer. Ha una funzione di inizializzazione associata, ACX_RTPACKET_INIT.

ACX_STREAM_CALLBACKS

Questa struttura identifica i callback del driver per lo streaming nel framework ACX. Questa struttura fa parte della struttura ACX_PIN_CONFIG.

Callback di streaming

EvtAcxStreamAllocateRtPackets

L'evento EvtAcxStreamAllocateRtPackets indica al driver di allocare RtPackets per lo streaming. AcxRtStream riceverà PacketCount = 2 per lo streaming basato su eventi o PacketCount = 1 per lo streaming basato su timer. Se il driver usa un singolo buffer per entrambi i pacchetti, il secondo RtPacketBuffer deve avere un WDF_MEMORY_DESCRIPTOR con Type = WdfMemoryDescriptorTypeInvalid con un oggetto RtPacketOffset allineato alla fine del primo pacchetto (packet[2]. RtPacketOffset = packet[1]. RtPacketOffset+packet[1]. RtPacketSize).

EvtAcxStreamFreeRtPackets

L'evento EvtAcxStreamFreeRtPackets indica al driver di liberare i RtPackets allocati in una chiamata precedente a EvtAcxStreamAllocateRtPackets. Vengono inclusi gli stessi pacchetti di tale chiamata.

EvtAcxStreamGetHwLatency

L'evento EvtAcxStreamGetHwLatency indica al driver di fornire la latenza del flusso per il circuito specifico di questo flusso (la latenza complessiva sarà una somma della latenza dei diversi circuiti). FifoSize è in byte e Il ritardo è espresso in unità di 100 nanosecondi.

EvtAcxStreamSetRenderPacket

L'evento EvtAcxStreamSetRenderPacket indica al driver il pacchetto appena rilasciato dal client. Se non sono presenti errori, questo pacchetto deve essere (CurrentRenderPacket + 1), dove CurrentRenderPacket è il pacchetto da cui il driver è attualmente in streaming.

I flag possono essere 0 o KSSTREAM_HEADER_OPTIONSF_ENDOFSTREAM = 0x200, che indicano che il pacchetto è l'ultimo pacchetto nel flusso e EosPacketLength è una lunghezza valida in byte per il pacchetto. Per altre informazioni, vedere OptionsFlags in KSSTREAM_HEADER structure (ks.h).

Il driver deve continuare ad aumentare CurrentRenderPacket perché viene eseguito il rendering dei pacchetti invece di modificarne CurrentRenderPacket in modo che corrisponda a questo valore.

EvtAcxStreamGetCurrentPacket

EvtAcxStreamGetCurrentPacket indica al driver di indicare quale pacchetto (basato su 0) è attualmente sottoposto a rendering sull'hardware o è attualmente riempito dall'hardware di acquisizione.

EvtAcxStreamGetCapturePacket

EvtAcxStreamGetCapturePacket indica al driver di indicare quale pacchetto (basato su 0) è stato riempito completamente di recente, incluso il valore QPC al momento in cui il driver ha iniziato a riempire il pacchetto.

EvtAcxStreamGetPresentationPosition

EvtAcxStreamGetPresentationPosition indica al driver di indicare la posizione corrente insieme al valore QPC al momento del calcolo della posizione corrente.

EVENTI DI STATO FLUSSO

Lo stato di streaming per un oggetto ACXSTREAM è gestito dalle API seguenti.

EVT_ACX_STREAM_PREPARE_HARDWARE

EVT_ACX_STREAM_RELEASE_HARDWARE

EVT_ACX_STREAM_RUN

EVT_ACX_STREAM_PAUSE

API ACX di streaming

AcxStreamCreate

AcxStreamCreate crea un flusso ACX che può essere usato per controllare il comportamento di streaming.

AcxRtStreamCreate

AcxRtStreamCreate crea un flusso ACX che può essere usato per controllare il comportamento di streaming e gestire l'allocazione dei pacchetti e comunicare lo stato di streaming.

AcxRtStreamNotifyPacketComplete

Il driver chiama questa API ACX al termine di un pacchetto. Il tempo di completamento dei pacchetti e l'indice di pacchetti basato su 0 sono inclusi per migliorare le prestazioni del client. Il framework ACX imposta tutti gli eventi di notifica associati al flusso.

Vedi anche

Panoramica delle estensioni della classe audio ACX

Documentazione di riferimento su ACX

Riepilogo degli oggetti ACX