Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo articolo illustra lo streaming ACX e il buffering, che sono fondamentali per un'esperienza audio senza problemi. Descrive come il driver comunica lo stato del flusso e gestisce il buffer del 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. AcxStream può aggregare uno o più oggetti simili a AcxElements.
ACX supporta due tipi di flusso. Il primo tipo di flusso, il flusso di pacchetti RT, consente di allocare pacchetti RT e usarli per trasferire dati audio da o verso l'hardware del dispositivo, insieme alle transizioni di stato del flusso. Il secondo tipo di flusso, il flusso di base, supporta solo le transizioni di stato del flusso.
In un singolo endpoint del circuito, il circuito è un circuito di streaming che crea un flusso di pacchetti RT. Se due o più circuiti si connettono per creare un endpoint, il primo circuito nell'endpoint è il circuito di streaming e crea un flusso di pacchetti RT. I circuiti connessi creano 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 i flussi sono definite nell'intestazione acxstreams.h .
Stack di comunicazioni di streaming ACX
Esistono due tipi di comunicazioni per lo streaming ACX. Un percorso di comunicazione controlla il comportamento di streaming. Ad esempio, comandi come Start, Create e Allocate, che usano comunicazioni ACX standard. Il framework ACX utilizza code di I/O e inoltra le richieste WDF usando le code. Il comportamento della coda è nascosto dal codice del driver effettivo usando callback di eventi e funzioni ACX. Il driver ha anche la possibilità di pre-elaborare tutte le richieste WDF.
Il secondo e più interessante percorso di comunicazione gestisce la segnalazione di streaming audio. La segnalazione implica la segnalazione del driver quando un pacchetto è pronto e riceve i dati e quando il driver termina l'elaborazione di un pacchetto.
Requisiti principali per la segnalazione di streaming:
- Supportare la riproduzione senza intoppi
- 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 tecniche sono descritte di seguito.
Buffer condiviso
Un buffer condiviso e un evento comunicano dal driver al client. L'evento e il buffer condiviso assicurano che il client non debba attendere o eseguire il polling. Il client può determinare tutto ciò che deve continuare a trasmettere riducendo o eliminando la necessità di chiamate IRP dirette.
Il driver del dispositivo utilizza un buffer condiviso per comunicare al client quale pacchetto viene reso o acquisito. Questo buffer condiviso include il numero di pacchetti (in base uno) 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 aggiorna il buffer condiviso con il nuovo numero di pacchetti e QPC e segnala un evento condiviso con il client per indicare che il client può leggere il nuovo numero di pacchetti.
Chiamate dirette IRP
Le chiamate IRP dirette comunicano dal client al driver.
Il client può richiedere il numero di pacchetti corrente o indicare il numero di pacchetti corrente al driver di dispositivo in qualsiasi momento. Queste richieste chiamano i gestori eventi EvtAcxStreamGetCurrentPacket e EvtAcxStreamSetRenderPacket. Il client può anche richiedere il pacchetto di cattura corrente, che chiama il gestore eventi del driver di dispositivo EvtAcxStreamGetCapturePacket.
Analogie con PortCls
La combinazione di chiamate IRP dirette e buffer condiviso usato da ACX è simile a come PortCls comunica la gestione del completamento del buffer.
Per evitare l'interruzione, i driver devono assicurarsi che non eseseguono alcuna operazione che richiede l'accesso ai blocchi usati anche nei percorsi di controllo del flusso.
Supporto di buffer di grandi dimensioni per la riproduzione a basso consumo
Per ridurre il consumo di energia durante la riproduzione, ridurre il tempo trascorso dall'APU in uno stato di alimentazione elevato. Poiché la normale riproduzione audio usa buffer da 10 ms, l'APU rimane attivo. I driver ACX possono annunciare il supporto per buffer più grandi, nell'intervallo di 1-2 secondi, per consentire all'APU di entrare in uno stato di alimentazione inferiore.
Nei modelli di streaming esistenti, la riproduzione con offload supporta il basso consumo energetico. 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 motore audio) indica allo stack audio quali pin sul filtro wave forniscono supporto di offload e loopback (e supporto per la riproduzione host).
- Intervallo delle dimensioni del buffer indica allo stack audio le dimensioni minime e massime del buffer supportabili 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.
- Volume, incluso il supporto per il ramp-up, perché, con buffer di dimensioni maggiori, il volume software non sarà reattivo.
- Protezione Loopback, che indica al driver di disattivare il pin di Loopback dell'AudioEngine se uno o più dei Flussi Offloaded contengono contenuto protetto.
- Stato FX globale, per abilitare o disabilitare GFX in AudioEngine.
Quando si crea un flusso sul pin di offload, il flusso supporta il volume, gli effetti locali e la protezione 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 aggiunge 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.
Ogni driver ACX controlla una parte separata dell'hardware audio, che potrebbe provenire da un fornitore diverso. ACX fornisce un'interfaccia di streaming del kernel compatibile in modo che le applicazioni vengano eseguite senza modifiche.
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. Il flusso ACXCIRCUIT è responsabile della creazione degli oggetti ACXPIN appropriati per il percorso audio dell'endpoint in fase di creazione del circuito e la registrazione degli ACXPIN con ACX. ACXCIRCUIT crea solo i pin di rendering o acquisizione per il circuito. Il framework ACX crea 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, ACXCOMPOSITETEMPLATE che descrive l'endpoint composto determina l'ordine specifico che connette i circuiti. 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 consente 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 del client e dell'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 consentono ai driver di configurare l'hardware in risposta alle modifiche dello stato del flusso, ad esempio Pause o Run.
Il sistema di streaming ACXCIRCUIT riceve la prima richiesta di creazione di un flusso dati. La richiesta include il dispositivo, il pin e il formato dei dati (inclusa la modalità).
Ogni ACXCIRCUIT nel percorso audio crea un oggetto ACXSTREAM che rappresenta l'istanza del flusso del circuito. Il framework ACX collega gli oggetti ACXSTREAM insieme, in modo analogo al modo in cui collega gli oggetti ACXCIRCUIT.
Circuiti di trasmissione 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 crea uno o più pin bridge per un circuito se il driver non li aggiunge al momento della creazione del circuito.
Per ogni circuito a partire dal circuito di streaming, il pin del bridge AcxPinTypeSource si connette al circuito downstream successivo. Il circuito finale ha un pin dell'endpoint che descrive l'hardware dell'endpoint audio, ad esempio se l'endpoint è microfono o altoparlante e se il jack è collegato.
Per ogni circuito che segue il circuito di streaming, il pin del bridge AcxPinTypeSink si connette 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 utilizzare un ACXSTREAMBRIDGE per coordinare la modalità e il supporto del formato tra i Circuiti ACX. Gli ACXPIN creati dal circuito di streaming definiscono i formati di streaming supportati per l'endpoint. I formati usati dai circuiti seguenti sono determinati dal pin bridge del circuito precedente nell'endpoint.
Per impostazione predefinita, il framework ACX crea un ACXSTREAMBRIDGE per ogni circuito in un endpoint multi-circuito. L'impostazione predefinita di ACXSTREAMBRIDGE utilizza il formato predefinito della modalità RAW del pin di bridge del circuito upstream quando inoltra la richiesta di creazione del flusso al circuito downstream. Se il pin di connessione del circuito a monte non ha formati, si utilizza il formato originale del flusso. Se il pin connesso del circuito downstream non supporta il formato utilizzato, la creazione del flusso non avrà successo.
Se un circuito del dispositivo esegue una modifica del formato di flusso, il driver di dispositivo deve aggiungere il formato in uscita al pin del ponte in uscita.
Creazione dello streaming
Il primo passaggio della creazione di flussi consiste nel creare l'istanza ACXSTREAM per ogni ACXCIRCUIT nel percorso audio dell'endpoint. ACX chiama EvtAcxCircuitCreateStream di ogni circuito. ACX inizia con il circuito di testa e chiama, in ordine, la funzione EvtAcxCircuitCreateStream per ogni circuito, 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 creano un oggetto flusso, gli oggetti flusso gestiscono la logica di streaming.
La richiesta di creazione del flusso viene inviata al PIN appropriato generato nell'ambito della creazione della topologia del circuito principale mediante la chiamata della funzione EvtAcxCircuitCreateStream specificata durante la creazione del circuito principale.
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 a valle 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. Ogni circuito ha accesso a una coda di I/O per il circuito di fronte a esso e al circuito dietro di esso nel percorso audio dell'endpoint. Il percorso audio dell'endpoint è lineare e bidirezionale. Il framework ACX gestisce l'elaborazione effettiva della coda di I/O.
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 il convertitore digitale-analogico, fornendo la funzionalità di ricevitore audio. Il circuito AMP rappresenta l'hardware analogico tra il DAC e l'altoparlante. Il circuito AMP potrebbe gestire il rilevamento dei jack o altri dettagli hardware dell'endpoint.
- AudioKSE chiama NtCreateFile per creare un flusso.
- 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.
- Il circuito DSP convalida le informazioni sul formato dei dati per assicurarsi che possa gestire il flusso creato.
- Il circuito DSP crea l'oggetto ACXSTREAM per rappresentare il flusso.
- Il circuito DSP alloca una struttura di contesto privata e la associa ad ACXSTREAM.
- Il circuito DSP restituisce il flusso di esecuzione al framework ACX, che quindi avvia il circuito successivo nel percorso audio dell'endpoint, il circuito CODEC.
- Il circuito CODEC convalida le informazioni sul formato dei dati per verificare che possa gestire il rendering dei dati.
- Il circuito CODEC alloca una struttura di contesto privata e la associa ad ACXSTREAM.
- Il circuito CODEC si aggiunge come sink di flusso all'ACXSTREAM.
- Il circuito CODEC restituisce il flusso di esecuzione al framework ACX, che poi richiama il circuito successivo nel percorso audio di endpoint, il circuito AMP.
- Il circuito AMP alloca una struttura di contesto privata e la associa ad ACXSTREAM.
- 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 sul ACXPIN designato per Offload dall'elemento ACXAUDIOENGINE di ACXCIRCUIT.
Per supportare i flussi di offload, il driver di dispositivo deve eseguire le azioni seguenti durante la creazione del circuito di streaming:
- Creare gli oggetti Host, Offload e Loopback ACXPIN e aggiungerli a ACXCIRCUIT.
- Creare elementi ACXVOLUME, ACXMUTE e ACXPEAKMETER. Questi non verranno aggiunti direttamente all'ACXCIRCUIT.
- Inizializzare una struttura ACX_AUDIOENGINE_CONFIG, assegnando gli oggetti HostPin, OffloadPin, LoopbackPin, VolumeElement, MuteElement e PeakMeterElement.
- Creare l'elemento ACXAUDIOENGINE.
I driver devono 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 restituire 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. Il secondo pacchetto ha WdfMemoryDescriptorTypeInvalid e l'offset del secondo pacchetto si trova nel buffer descritto dal primo pacchetto.
Se viene allocato un singolo pacchetto, il driver deve allocare un buffer allineato alla pagina con una lunghezza divisibile per pagina. Anche l'offset per il singolo pacchetto deve essere 0. Il framework ACX esegue il mapping di questo pacchetto in modalità utente due volte consecutivamente.
| 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. Dopo l'esecuzione dell'allocazione e del mapping, il mapping della memoria virtuale del client rimane valido, senza modifiche per tutta la durata del flusso. Esiste un evento associato al flusso per indicare il completamento dei pacchetti per entrambi i pacchetti. Esiste anche un buffer condiviso che il framework ACX utilizza per comunicare quale pacchetto ha completato l'elaborazione con l'evento.
Per PacketCount=1, se l'applicazione richiede 10 ms di dati, lo stack audio invia una richiesta per un buffer singolo da 10 ms al driver (non raddoppia le dimensioni del buffer inviate al driver).
Il driver alloca un buffer allineato alla pagina con almeno 10 ms di lunghezza. Per un flusso di 48k 2ch 2 byte per campione, il buffer più piccolo a intervallo di timer che può essere allocato è di 1.024 campioni (una pagina di memoria), che è 21,333 ms. Per un flusso di 48 k 8 ch a 2 byte per campione, il buffer controllato da timer più piccolo che può essere allocato è di 512 campioni (una pagina di memoria) ovvero 10,667 ms. Per un flusso di campionamento di 48k 6ch 2 byte per campione, il buffer guidato dal timer più piccolo è ancora di 1.024 campioni (tre pagine di memoria, per assicurarsi che la fine di un campione sia allineata alla fine del buffer), ovvero 21,333 ms.
Il framework ACX mappa due volte questo buffer allineato alla pagina nel processo in modalità utente, consecutivamente. Il processo in modalità utente può quindi scrivere fino al valore di dati di un buffer nel mapping in modalità utente a partire da qualsiasi punto del buffer senza dover eseguire il wrapping.
Il driver chiama NotifyPacketComplete dopo aver letto l'intero pacchetto dalla memoria di sistema, in modo che il sistema sappia che può scrivere il pacchetto di dati audio successivo nel buffer del pacchetto.
Si verifica un ritardo tra NotifyPacketComplete e l'ultimo esempio di rendering del pacchetto. Questo ritardo viene espresso come risultato da EvtAcxStreamGetHwLatency.
Buffer ping-pong
I buffer ping-pong possono essere utilizzati, dove un buffer viene letto (ping), mentre l'altro viene riempito (pong). In questo modo è possibile elaborare un buffer mentre l'altro raccoglie il set di dati successivo. In ACX il driver gestisce internamente il passaggio quando viene riempito un buffer. Una volta riempito il buffer di ping, viene notificato con un callback registrato. Nel callback viene ottenuto l'indirizzo del buffer elaborato e il buffer viene inviato di nuovo. Nel frattempo, il buffer pong raccoglie i dati in background. Questo meccanismo garantisce l'elaborazione continua dei dati senza interruzioni.
Per un buffer ping-pong, la dimensione del pacchetto richiesta è per un singolo buffer (ping o pong) e il conteggio dei pacchetti è due.
Quando si condivide un singolo buffer tra due pacchetti, configurare il secondo pacchetto come descritto nella funzione di callback EVT_ACX_STREAM_ALLOCATE_RTPACKETS. La parte del buffer descritta dal primo pacchetto (memoria, offset e lunghezza) è il buffer ping, mentre la parte descritta dal secondo pacchetto (nessuna memoria per indicare che il buffer viene condiviso con il primo pacchetto, più offset che punta al buffer subito dopo il primo pacchetto) è il buffer pong.
Aggiunta di informazioni aggiuntive all'intestazione del pacchetto
È possibile aggiungere informazioni aggiuntive solo alle informazioni sull'intestazione del pacchetto, ad esempio per la registrazione o la contabilità, all'inizio del pacchetto per i flussi basati su eventi ping/pong (dove numero di pacchetti = 2). Per i flussi basati su timer con un solo pacchetto, il pacchetto deve essere completamente allineato su pagina (inizio e fine devono trovarsi su un limite di pagina) poiché il pacchetto è mappato due volte in modalità utente.
In questo caso, l'app può scrivere oltre la fine della prima mappatura nella seconda mappatura, che scrive alla fine del buffer di sistema poi all'inizio dello stesso buffer di sistema.
Il singolo buffer allocato deve essere allineato alla pagina perché il mapping della memoria virtuale in modalità utente avviene in base alle singole pagine.
Buffer a controllo di temporizzazione
I buffer basati su timer in ACX possono essere usati per garantire un'esperienza audio senza problemi mantenendo tempi e sincronizzazioni precisi. Per i buffer temporizzati di ACX:
- Il client usa il valore di EvtAcxStreamGetPresentationPosition per determinare il numero di fotogrammi scrivibili.
- La posizione della presentazione deve essere aggiornata più volte per ogni passaggio del buffer. Il client scrive nel buffer a partire dall'ultima posizione di scrittura fino alla posizione segnalata dal driver (che dovrebbe essere rappresentato dai dati consumati dall'hardware dall'ultima query sulla posizione).
- Più granulare è la posizione, meno è probabile che si verifichino glitch.
- Nei buffer basati su timer, il DSP non può semplicemente consumare l'intero buffer prima di aggiornare la posizione.
- In modalità a tempo, il driver potrebbe potenzialmente suddividere il buffer a tempo in più buffer DSP, aggiornando la posizione man mano che il DSP elabora ogni buffer (ad esempio, un buffer a tempo di 20 ms suddiviso in 10 buffer da 2 ms si comporta ragionevolmente bene in modalità a tempo).
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 che verrà utilizzato per determinare le dimensioni minime e massime dei pacchetti per il playback 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 sono presenti modifiche alle dimensioni minime e massime del buffer.
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 del flusso nel percorso audio di endpoint per il flusso riceve 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 il SINK) riceve prima l'evento. Quando il circuito gestisce l'evento, il circuito successivo nel percorso audio dell'Endpoint riceve l'evento.
Per i flussi di rendering che passano da uno stato più attivo a uno stato meno attivo, il circuito di streaming riceve l'ultimo evento.
Per i flussi di acquisizione che passano da uno stato meno attivo a uno stato più attivo, il circuito di streaming riceve per ultimo l'evento.
Per i flussi di acquisizione che passano da uno stato di maggiore attività a uno stato di minore attività, il circuito di streaming riceve l'evento per primo.
L'ordinamento è l'impostazione predefinita assegnata 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 allocato i buffer appropriati, il flusso si trova nello stato Pausa e attende l'avvio del flusso. Quando il client inserisce il flusso nello stato di Riproduzione, il framework ACX chiama tutti gli oggetti ACXSTREAM associati al flusso per indicare che lo stato del flusso è in Riproduzione. ACXPIN viene quindi inserito nello stato Play e i dati iniziano a fluire.
Rendering dei dati audio
Dopo aver creato il flusso e allocato le risorse, l'applicazione chiama Avvia sul flusso per avviare la riproduzione. L'applicazione deve chiamare GetBuffer/ReleaseBuffer prima di avviare il flusso per assicurarsi che il primo pacchetto che inizia a riprodurre abbia dati audio validi.
Il client inizia precaricando un buffer. Quando il client chiama ReleaseBuffer, questo si traduce in una chiamata in AudioKSE che agisce sul livello ACX, chiamando EvtAcxStreamSetRenderPacket sull'ACXSTREAM attivo. La proprietà include l'indice del pacchetto (a partire da zero) e, se opportuno, un flag EOS con l'offset in byte della fine dello stream nel pacchetto corrente.
Al termine del circuito di streaming con un pacchetto, attiva la notifica completa del buffer che rilascia 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
Quando il flusso è attivo, il circuito di origine riempie il pacchetto di acquisizione con dati audio. Dopo aver 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 zero) del pacchetto che ha terminato l'acquisizione. Quando il client invia GETCAPTUREPACKET, il driver può presupporre che tutti i pacchetti precedenti vengano 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. Il framework ACX aggiorna questa struttura prima di segnalare il completamento del pacchetto.
Comportamento di streaming del kernel KS legacy
In alcuni casi, ad esempio quando un driver implementa l'acquisizione burst (ad esempio un rilevatore di parole chiave), è 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 restituisce 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 del flusso dati
Il framework ACX chiama il callback EvtAcxStreamGetPresentationPosition per ottenere la posizione corrente del flusso. La posizione corrente dello stream include il PlayOffset e il 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 alcun registro HW, in quanto impedirebbe che si verifichi un ribilanciamento.
Ogni volta che il circuito di streaming completa un pacchetto, chiama AcxRtStreamNotifyPacketComplete con l'indice del pacchetto in base zero e il valore QPC considerato il più vicino possibile al completamento del pacchetto (ad esempio, la routine del servizio interrupt può calcolare il valore QPC). I client possono ottenere queste informazioni tramite KSPROPERTY_RTAUDIO_PACKETVREGISTER, che restituisce un puntatore a una struttura contenente CompletedPacketCount, CompletedPacketQPC e un valore che combina i due ( in modo che il client possa verificare 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 esegue la transizione del flusso dallo stato AcxStreamStateStop allo stato AcxStreamStatePause. Il driver deve riservare hardware necessario, ad esempio motori DMA, quando riceve EvtAcxStreamPrepareHardware.
- EvtAcxStreamRun esegue la transizione del flusso dallo stato AcxStreamStatePause allo stato AcxStreamStateRun.
- EvtAcxStreamPause esegue la transizione del flusso dallo stato AcxStreamStateRun allo stato AcxStreamStatePause.
- EvtAcxStreamReleaseHardware esegue 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 potrebbe ricevere il callback EvtAcxStreamPrepareHardware dopo aver ricevuto il callback EvtAcxStreamReleaseHardware. In questo modo il flusso torna allo stato AcxStreamStatePause.
L'allocazione di pacchetti con EvtAcxStreamAllocateRtPackets avviene normalmente prima della prima chiamata a EvtAcxStreamPrepareHardware. I pacchetti allocati vengono normalmente liberati con EvtAcxStreamFreeRtPackets dopo l'ultima chiamata a EvtAcxStreamReleaseHardware. Questo ordinamento non è garantito.
Lo stato AcxStreamStateAcquire non viene usato. ACX rimuove la necessità che il driver abbia lo stato di acquisizione perché 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 scarico
ACX usa l'elemento ACXAUDIOENGINE per designare un ACXPIN che gestirà la creazione dell'offload stream e i diversi elementi necessari per il volume dell'offload stream, la disattivazione audio e lo stato dell'indicatore 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 riceve EvtAcxStreamPause e EvtAcxStreamReleaseHardware prima che l'oggetto ACXSTREAM venga eliminato dal framework ACX. 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 chiama EvtCleanupCallback quando il framework tenta di eliminare l'oggetto. Non usare EvtDestroyCallback, che viene chiamato solo dopo il rilascio di tutti i riferimenti all'oggetto, 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.
Il driver non deve pulire le risorse che supportano il flusso fino a quando il client non lo richiede.
Lo stato AcxStreamStateAcquire non viene usato. ACX rimuove la necessità che il driver abbia lo stato di acquisizione perché 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 è scollegato), il circuito arresta 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 l'eliminazione di qualsiasi elemento condiviso nel callback di distruzione del contesto di un oggetto. Queste indicazioni si applicano a tutti gli oggetti ACX.
Il callback di eliminazione definitiva viene richiamato dopo che l'ultimo riferimento è scomparso, il che non è determinato.
In generale, il callback di pulizia del flusso viene chiamato quando l'handle viene chiuso. Un'eccezione è quando il driver crea il flusso nel suo callback. Se ACX non riesce ad aggiungere questo flusso al bridge di flusso appena prima di tornare dall'operazione di creazione del flusso, il flusso viene annullato in modo asincrono e il thread corrente restituisce un errore al client di creazione del flusso. A questo punto, il flusso non deve avere allocazioni di memoria. 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 dell'oggetto di flusso e quindi il callback di eliminazione dell'oggetto di flusso quando il conteggio dei riferimenti all'oggetto diventa zero.
È possibile che ACX posticipi l'eliminazione di un oggetto stream a un elemento di lavoro quando il driver ha creato un oggetto stream e si verifica un errore nel callback di creazione dello stream. Per evitare un deadlock con un thread WDF in fase di arresto, ACX posticipa 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 dal processo di stream-create.
Il driver deve liberare i buffer audio quando ACX richiama il callback EVT_ACX_STREAM_FREE_RTPACKETS. Questo callback si verifica 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 rilasciare o 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 elabora il callback di rilascio dell'hardware WDF perché il client in modalità utente può mantenere i relativi handle per molto tempo. Il driver non dovrebbe aspettare che questi handle vadano via. Questa azione crea una verifica bug 0x9f DRIVER_POWER_STATE_FAILURE. Per ulteriori informazioni, consultare la funzione di callback EVT_WDF_DEVICE_RELEASE_HARDWARE.
Questo codice EvtDeviceReleaseHardware del driver ACX di esempio mostra un esempio di chiamata di AcxDeviceRemoveCircuit e quindi del rilascio della memoria hardware 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:
- Rilascio delle risorse hardware del dispositivo WDF: rilascia le risorse hardware del dispositivo.
- AcxStreamFreeRtPackets: rilasciare o liberare il buffer audio associato all'handle.
Per altre informazioni sulla gestione di oggetti WDF e circuiti, vedere Gestione della durata del 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 riceve 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 la tenza di 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 continua ad aumentare CurrentRenderPacket man mano che vengono eseguiti i rendering dei pacchetti anziché modificare CurrentRenderPacket in modo che corrisponda a questo valore.
EvtAcxStreamGetCurrentPacket
EvtAcxStreamGetCurrentPacket indica al driver di indicare quale pacchetto (in base zero) è attualmente sottoposto a rendering sull'hardware o è attualmente riempito dall'hardware di acquisizione.
EvtAcxStreamGetCapturePacket
EvtAcxStreamGetCapturePacket indica al driver di indicare quale pacchetto (in base zero) è stato riempito più 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 DELLO STREAMING
Le API seguenti gestiscono lo stato di streaming per un ACXSTREAM.
- EVT_ACX_STREAM_PREPARE_HARDWARE
- EVT_ACX_STREAM_RELEASE_HARDWARE
- EVT_ACX_STREAM_RUN
- EVT_ACX_STREAM_PAUSE
API di Streaming ACX
AcxStreamCreare
AcxStreamCreate crea un flusso ACX che può essere usato per controllare il comportamento di streaming.
AcxRtStreamCreare
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 del pacchetto e l'indice del pacchetto in base zero sono inclusi per migliorare le prestazioni del client. Il framework ACX imposta tutti gli eventi di notifica associati al flusso.
Vedere anche
- Panoramica delle estensioni della classe audio ACX
- Documentazione di riferimento su ACX
- Riepilogo degli oggetti ACX