Streaming de ACX
En este tema se describe el streaming de ACX y el almacenamiento en búfer relacionado, que es fundamental para tener una experiencia de audio sin problemas. Describe los mecanismos utilizados por el controlador para comunicarse sobre el estado de la transmisión y administrar el búfer de esta. Para obtener una lista de los términos comunes de audio de ACX y una introducción a ACX, consulte Introducción a las extensiones de clase de audio (ACX).
Tipos de streaming de ACX
AcxStream representa una transmisión de audio en el hardware de un circuito específico. AcxStream puede agregar uno o varios objetos similares a AcxElements.
La arquitectura de ACX admite dos tipos de flujo. El primer tipo de transmisión, el RT Packet Stream permite asignar paquetes RT y usar paquetes RT para transferir datos de audio hacia o desde el hardware del dispositivo junto con transiciones de estado de transmisión. El segundo tipo de transmisión, el Basic Stream, solo permite transiciones de estado de transmisión.
En un único punto de conexión del circuito, este debe ser un circuito de streaming que cree una RT Packet Stream. Si dos o varios circuitos están conectados para crear un punto de conexión, el primer circuito del punto de conexión es el circuito de streaming y crea una RT Packet Stream; los circuitos conectados crearán Basic Streams para recibir eventos relacionados con transiciones de estado de transmisión.
Para obtener más información, consulte Transmisión de ACX en Resumen de objetos ACX. Las DDI de la transmisión vienen definidos en el encabezado acxstreams.h.
Pila de comunicaciones de streaming de ACX
Hay dos tipos de comunicaciones de streaming de ACX. Se usa una ruta de comunicaciones para controlar el funcionamiento del streaming, con comandos como Start, Create y Allocate, que usarán las comunicaciones de ACX estándar. La plataforma de ACX usa colas de E/S y pasa las solicitudes de WDF a través de las colas. El funcionamiento de la cola está oculto en el código del controlador real por medio del uso de devoluciones de llamada de Evt y funciones de ACX, aunque el controlador también tiene la oportunidad de procesar previamente todas las solicitudes de WDF.
La segunda y más interesante ruta de comunicación se usa para las señales de streaming de audio. Esto implica informar al controlador de cuándo un paquete está listo y recibir datos cuando el controlador ha terminado de procesar un paquete.
Principales requisitos para las señales de streaming:
- Función de reproducción sin errores
- Baja latencia
- Los bloqueos necesarios se limitan a la transmisión en cuestión
- Facilidad de uso para el desarrollador de controladores
Para comunicar el controlador con el estado de streaming de la señal, ACX usa eventos con un búfer compartido y llamadas de IRP directas. Estos se describen a continuación.
Búfer compartido
Para comunicar el controlador con el cliente, se usa un búfer compartido y un evento. Esto garantiza que el cliente no necesite esperar o sondear y que el cliente puede determinar todo lo que necesita para seguir transmitiendo al tiempo que reduce o elimina la necesidad de hacer llamadas de IRP directas.
El controlador del dispositivo usa un búfer compartido para comunicarse con el cliente desde el que se renderiza o se captura el paquete. Este búfer compartido incluye el número de paquetes (basado en 1) del último paquete completado junto con el valor QPC (QueryPerformanceCounter) del tiempo de finalización. En el controlador del dispositivo, se debe indicar esta información llamando a AcxRtStreamNotifyPacketComplete. Cuando el controlador del dispositivo llama a AcxRtStreamNotifyPacketComplete, la plataforma de ACX actualizará el búfer compartido con el nuevo número de paquetes y QPC e enviará una señal de un evento compartido al cliente para indicar que el cliente puede leer el nuevo número de paquetes.
Llamadas de IRP directas
Para comunicar el cliente con el controlador, se usan llamadas de IRP directas. Esto reduce las complejidades cuando se intenta garantizar que las solicitudes de WDF se administren en el momento adecuado y se ha demostrado que funcionan bien en la arquitectura existente.
El cliente puede solicitar en cualquier momento el número de paquetes actual o indicar el número de paquetes actual al controlador del dispositivo. Estas solicitudes llamarán a los identificadores de eventos del controlador del dispositivo EvtAcxStreamGetCurrentPacket y EvtAcxStreamSetRenderPacket. El cliente también puede solicitar el paquete de captura actual, que llamará al identificador de eventos del controlador del dispositivo EvtAcxStreamGetCapturePacket.
Similitudes con PortCls
La combinación de llamadas de IRP directas y el búfer compartido usado por ACX es similar a la forma en que se comunica el proceso de finalización del búfer en PortCls. Los IRP son muy similares y el búfer compartido incluye la opción de que el controlador pueda comunicar directamente el número de paquetes y el tiempo sin depender de los IRP. Los controladores deben asegurarse de que no hacen nada que pida acceso a los bloqueos que también se usan en las rutas de control de transmisión: esto es necesario para evitar fallos técnicos.
Compatibilidad con búferes de gran tamaño para la reproducción de bajo consumo
Para reducir la cantidad de energía consumida al reproducir contenido multimedia, es importante reducir la cantidad de tiempo que la APU pasa en un estado de alto rendimiento. Puesto que la reproducción de audio normal usa 10 ms de búferes, la APU siempre debe estar activa. Para dar a la APU el tiempo necesario para reducir el estado, los controladores de ACX pueden revelar la compatibilidad con búferes significativamente mayores, en el rango de tiempo de 1 a 2 segundos. Esto significa que la APU puede reactivarse una vez cada 1 o 2 segundos, realizar las operaciones necesarias a la velocidad máxima para preparar el siguiente búfer entre 1 y 2 segundos y luego activar el estado de consumo más bajo posible hasta que se necesite el siguiente búfer.
En los modelos de streaming existentes se admite la reproducción de bajo consumo a través de la reproducción de descarga. Un controlador de audio revela la compatibilidad con la reproducción de descarga mediante la exposición de un nodo AudioEngine en el filtro de onda de un punto de conexión. El nodo AudioEngine ofrece un medio para controlar el motor DSP que usa el controlador para renderizar el audio de los búferes grandes con el procesamiento deseado.
El nodo AudioEngine ofrece estas prestaciones:
- Descripción del motor de audio (Audio Engine Description), que indica a la pila de audio qué pines del filtro de onda permite incorporar la descarga y el bucle invertido (así como la reproducción del host).
- Rango de tamaño de búfer (Buffer Size Range), que indica a la pila de audio los tamaños de búfer mínimo y máximo que se pueden admitir para la reproducción de descarga. El rango de tamaño del búfer puede cambiar dinámicamente en función de la actividad del sistema.
- Compatibilidad con formatos, incluidos los formatos admitidos, el formato de combinación de dispositivos actual y el formato del dispositivo.
- Volumen, incluido el aumento de señal, ya que con el volumen de software de búferes más grandes no tendrá capacidad de respuesta.
- Protección de bucle invertido (Loopback Protection), que manda al controlador silenciar el pin de bucle invertido AudioEngine si una o varias de las transmisiones descargadas incluyen contenido protegido.
- Estado de Global FX, para habilitar o deshabilitar GFX en AudioEngine.
Cuando se crea una transmisión en el pin de descarga, la transmisión admite Volumen, FX local y Protección de bucle invertido.
Reproducción de bajo consumo con ACX
La plataforma ACX usa el mismo modelo para la reproducción de bajo consumo. El controlador crea tres objetos ACXPIN independientes para el streaming de host, de descarga y de bucle invertido, junto con un elemento ACXAUDIOENGINE que describe cuáles de estos pines se usan para el host, la descarga y el bucle invertido. El controlador agrega los pines y el elemento ACXAUDIOENGINE al ACXCIRCUIT durante la creación del circuito.
Creación de transmisiones descargadas
El controlador también agregará un elemento ACXAUDIOENGINE a las transmisiones creadas para la descarga para poder controlar el volumen, la activación del silencio y medidor máximo.
Diagrama de streaming
En este diagrama se muestra un controlador de ACX multi-pila.
Cada controlador de ACX controla una parte independiente del hardware de audio y podría proceder de un proveedor diferente. ACX ofrece una interfaz de streaming basada en kernel compatible para permitir que las aplicaciones se ejecuten tal cual.
Pines de transmisión
Cada ACXCIRCUIT tiene al menos un pin receptor y un pin de origen. La plataforma de ACX usa estos pines para exponer las conexiones del circuito a la pila de audio. En un circuito de renderización, el pin de origen se usa para controlar la operación de renderizar cualquier transmisión creada a partir del circuito. En un circuito de captura, el pin receptor se usa para controlar la función de capturar cualquier transmisión creada a partir del circuito. ACXPIN es el objeto utilizado para controlar el streaming en la ruta de audio. El ACXCIRCUIT del streaming es responsable de crear los objetos ACXPIN adecuados para la ruta de audio del punto de conexión mientras se crea el circuito y se registran los ACXPIN con ACX. El ACXCIRCUIT solo necesita crear el pin de renderización o de captura para el circuito; la plataforma de ACX creará el otro pin necesario para conectarse y comunicarse con el circuito.
Circuito de streaming
Cuando un punto de conexión se compone de un único circuito, este es el circuito de streaming.
Cuando un punto de conexión se compone de más de un circuito creado por uno o varios controladores de dispositivo, los circuitos se conectan en el orden determinado por ACXCOMPOSITETEMPLATE que describe el punto de conexión compuesto. El primer circuito del punto de conexión es el circuito de streaming del punto de conexión.
El circuito de streaming debe usar AcxRtStreamCreate para crear un RT Packet Stream en respuesta a EvtAcxCircuitCreateStream. El ACXSTREAM creado con AcxRtStreamCreate permitirá al controlador del circuito de streaming asignar el búfer usado para el streaming y controlar el proceso del streaming en respuesta a las necesidades de cliente y el hardware.
Los siguientes circuitos del punto de conexión deben usar AcxStreamCreate para crear una Basic Stream en respuesta a EvtAcxCircuitCreateStream. Los objetos ACXSTREAM creados con AcxStreamCreate mediante los siguientes circuitos permitirán a los controladores configurar el hardware en respuesta a los cambios de estado de transmisión, como Pausar o Ejecutar.
El ACXCIRCUIT de streaming es el primer circuito que recibe las solicitudes para crear una transmisión. La solicitud incluye el dispositivo, el pin y el formato de datos (modo incluido).
Cada ACXCIRCUIT de la ruta de audio creará un objeto ACXSTREAM que representa la instancia de transmisión del circuito. La plataforma de ACX vincula los objetos ACXSTREAM de forma conjunta (de la misma manera que los objetos ACXCIRCUIT están vinculados).
Circuitos por arriba y por debajo
La creación de transmisiones se inicia en el circuito de streaming y se transfiere a cada circuito siguiente según el orden en que los circuitos están conectados. Las conexiones se realizan entre pines de puente creados con Communication igual a AcxPinCommunicationNone. La plataforma de ACX creará uno o varios pines de puente en un circuito si el controlador no los agrega al momento de crear el circuito.
En cada circuito a partir del circuito de streaming, el pin de puente AcxPinTypeSource se conectará al siguiente circuito por debajo. El circuito final tendrá un pin de punto de conexión que describe el hardware del punto de conexión de audio (por ejemplo, si el punto de conexión es micrófono o altavoz y si el conector está conectado).
En cada circuito que siga al circuito de streaming, el pin de puente AcxPinTypeSink se conectará al siguiente circuito por arriba.
Negociación de formato de transmisión
El controlador revela los formatos admitidos para la creación de transmisiones agregando los formatos admitidos por modo al ACXPIN usado para la creación de transmisiones con AcxPinAssignModeDataFormatList y AcxPinGetRawDataFormatList. En el caso de los puntos de conexión de varios circuitos, se puede usar ACXSTREAMBRIDGE para coordinar el modo y la compatibilidad con el formato entre circuitos de ACX. Los formatos de transmisión admitidos para el punto de conexión se determinan mediante los ACXPIN de streaming creados por el circuito de streaming. Los formatos usados por los circuitos siguientes se determinan mediante el pin de puente del circuito anterior en el punto de conexión.
De forma predeterminada, la plataforma de ACX creará un ACXSTREAMBRIDGE entre cada circuito en un punto de conexión de varios circuitos. El ACXSTREAMBRIDGE predeterminado usará el formato predeterminado del modo RAW del pin de puente del circuito previo al reenviar la solicitud de creación de transmisión al circuito por debajo. Si el pin de puente del circuito por arriba no tiene ningún formato, se usará el formato de transmisión original. Si el pin conectado del circuito posterior no admite el formato que se está usando, se producirá un error en la creación de transmisión.
Si un circuito de dispositivo realiza un cambio de formato de transmisión, el controlador de dispositivo debe agregar el formato previo al pin de puente por debajo.
Creación de transmisión
El primer paso para la creación de transmisión consiste en crear la instancia de ACXSTREAM para cada ACXCIRCUIT en la ruta de audio del punto de conexión. ACX llamará al EvtAcxCircuitCreateStream de cada circuito. ACX comenzará con el circuito principal y llamará al EvtAcxCircuitStream de cada circuito en orden, finalizando con el circuito de cola. El orden se puede invertir indicando el indicador AcxStreamBridgeInvertChangeStateSequence (definida en ACX_STREAM_BRIDGE_CONFIG_FLAGS) para el puente de transmisión. Una vez que todos los circuitos hayan creado un objeto de transmisión, los objetos de transmisión controlarán la lógica de streaming.
La solicitud de creación de transmisión se envía al PIN adecuado generado como parte de la generación de topologías del circuito principal llamando a EvtAcxCircuitCreateStream indicado durante la creación del circuito principal.
El circuito de streaming es el circuito por arriba que controla inicialmente la solicitud de creación de transmisión.
- Actualiza la estructura ACXSTREAM_INIT, asignando AcxStreamCallbacks y AcxRtStreamCallbacks
- Crea el objeto ACXSTREAM mediante AcxRtStreamCreate
- Crea cualquier elemento específico de la transmisión (por ejemplo, ACXVOLUME o ACXAUDIOENGINE)
- Agrega los elementos al objeto ACXSTREAM
- Devuelve el objeto ACXSTREAM que se creó en la plataforma de ACX
Luego, ACX reenvía la creación de transmisión al siguiente circuito por debajo.
- Actualiza la estructura ACXSTREAM_INIT, asignando AcxStreamCallbacks
- Crea el objeto ACXSTREAM mediante AcxStreamCreate
- Crea cualquier elemento específico de la transmisión
- Agrega los elementos al objeto ACXSTREAM
- Devuelve el objeto ACXSTREAM que se creó en la plataforma de ACX
El canal de comunicación entre circuitos de una ruta de audio usa objetos ACXTARGETSTREAM. En este ejemplo, cada circuito tendrá acceso a una cola de E/S para el circuito delante de él y el circuito detrás de él en la ruta de audio del punto de conexión. Además, la ruta de audio de un punto de conexión es lineal y bidireccional. El control real de colas de E/S se realiza mediante la plataforma de ACX. Al crear el objeto ACXSTREAM, cada circuito puede incluir información de contexto al objeto ACXSTREAM para almacenar y realizar un seguimiento de los datos privados de la transmisión.
Ejemplo de transmisión de renderización
Crear una transmisión de renderización en una ruta de audio de punto de conexión compuesta por tres circuitos: DSP, CODEC y AMP. El circuito DSP funciona como circuito de streaming y proporciona un controlador EvtAcxPinCreateStream. El circuito DSP también funciona como un circuito de filtro: dependiendo del modo de transmisión y la configuración, puede aplicar el procesamiento de señales a los datos de audio. El circuito CODEC representa la DAC, lo que incorpora la funcionalidad de receptor de audio. El circuito AMP representa el hardware analógico entre la DAC y el altavoz. El circuito AMP puede controlar la detección de conectores u otros datos de hardware del punto de conexión.
- AudioKSE llama a NtCreateFile para crear una transmisión.
- Esto se filtra a través de ACX y termina con la llamada a EvtAcxPinCreateStream del circuito DSP con el pin, el formato de datos (incluido el modo) y la información del dispositivo.
- El circuito DSP valida la información del formato de datos para garantizar que puede controlar la transmisión creada.
- El circuito DSP crea el objeto ACXSTREAM para representar la transmisión.
- El circuito DSP asigna una estructura de contexto privado y la asocia a ACXSTREAM.
- El circuito DSP devuelve el flujo de ejecución a la plataforma de ACX, que luego llama al siguiente circuito en la ruta de audio del punto de conexión, el circuito CODEC.
- El circuito CODEC valida la información del formato de datos para confirmar que puede controlar la reproducción de los datos.
- El circuito CODEC asigna una estructura de contexto privado y la asocia a ACXSTREAM.
- El circuito CODEC se agrega como receptor de transmisión al ACXSTREAM.
- El circuito CODEC devuelve el flujo de ejecución a la plataforma de ACX, que luego llama al siguiente circuito en la ruta de audio del punto de conexión, el circuito AMP.
- El circuito AMP asigna una estructura de contexto privado y la asocia a ACXSTREAM.
- El circuito AMP devuelve el flujo de ejecución a la plataforma de ACX. Llegados a este punto, la creación de transmisiones se habrá completado.
Transmisiones con búferes grandes
Las transmisiones con búferes grandes se crean en el ACXPIN designado para la descarga a través del elemento ACXAUDIOENGINE de ACXCIRCUIT.
Para admitir transmisiones de descarga, el controlador del dispositivo debe hacer lo siguiente durante la creación del circuito de streaming:
- Cree los objetos Host, Offload (descarga) y Loopback (bucle invertido) de ACXPIN y agréguelos al ACXCIRCUIT.
- Cree los elementos ACXVOLUME, ACXMUTE y ACXPEAKMETER. Estos no se agregarán directamente al ACXCIRCUIT.
- Inicialice una estructura ACX_AUDIOENGINE_CONFIG, asignando los objetos HostPin, OffloadPin, LoopbackPin, VolumeElement, MuteElement y PeakMeterElement.
- Cree el elemento ACXAUDIOENGINE.
Los controladores deberán realizar un procedimiento similar para agregar un elemento ACXSTREAMAUDIOENGINE al crear una transmisiones en el pin de descarga.
Asignación de recursos de transmisiones
El modelo de streaming de ACX se basa en paquetes, con posibilidad usar uno o dos paquetes para una transmisión. La renderización o captura de ACXPIN en el circuito de streaming recibe una solicitud para asignar los paquetes de memoria que se usan en la transmisión. Para admitir el reequilibrio, la memoria asignada debe ser la memoria del sistema en lugar de la memoria del dispositivo asignada al sistema. El controlador puede usar funciones WDF existentes para realizar la asignación y devolverá una matriz de punteros a las asignaciones de búfer. Si el controlador necesita un único bloque contiguo, puede asignar ambos paquetes como un solo búfer, devolviendo un puntero a un desplazamiento del búfer como segundo paquete.
Si se asigna un único paquete, el paquete debe estar alineado en páginas y se asigna dos veces al modo de usuario:
| packet 0 | packet 0 |
Esto permite a GetBuffer devolver un puntero a un único búfer de memoria contiguo que puede abarcar desde el final del búfer hasta el principio sin necesidad de que la aplicación controle el acceso a la memoria.
Si se asignan dos paquetes, se asignan al modo de usuario:
| packet 0 | packet 1 |
Con el streaming inicial de paquetes de ACX, solo hay dos paquetes asignados al principio. La asignación de memoria virtual del cliente seguirá siendo válida sin cambiar durante la vida útil de la transmisión una vez realizada la distribución y asignación. Hay un evento asociado a la transmisión para indicar que se han completado ambos paquetes. También habrá un búfer compartido que usará la plataforma de ACX para comunicar qué paquete finalizó con el evento.
Tamaños de paquete de transmisiones con búferes grandes
Al exponer la función de usar búferes grandes, el controlador también hará una devolución de llamada que se usa para determinar los tamaños de paquete mínimo y máximo para la reproducción de búferes grandes. El tamaño del paquete para la asignación del búfer de la transmisión se determina en función del mínimo y del máximo.
Dado que los tamaños de búfer mínimo y máximo pueden ser volátiles, el controlador puede generar un error en la llamada de distribución de paquetes si el mínimo y el máximo cambian.
Especificación de restricciones de búfer de ACX
Para especificar las restricciones de búfer de ACX, los controladores de ACX pueden usar los valores de las propiedades de KS/PortCls: KSAUDIO_PACKETSIZE_CONSTRAINTS2 y la estructura KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT.
En el ejemplo de código siguiente se ve cómo se establecen restricciones de tamaño de búfer para los búferes de WaveRT en diferentes modos de procesamiento de señal.
//
// 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
},
}
};
Se usa una estructura DSP_DEVPROPERTY para almacenar las restricciones.
typedef struct _DSP_DEVPROPERTY {
const DEVPROPKEY *PropertyKey;
DEVPROPTYPE Type;
ULONG BufferSize;
__field_bcount_opt(BufferSize) PVOID Buffer;
} DSP_DEVPROPERTY, PDSP_DEVPROPERTY;
Y además se crea una matriz de esas estructuras.
const DSP_DEVPROPERTY DspR_InterfaceProperties[] =
{
{
&DEVPKEY_KsAudio_PacketSize_Constraints2, // Key
DEVPROP_TYPE_BINARY, // Type
sizeof(DspR_RtPacketSizeConstraints), // BufferSize
&DspR_RtPacketSizeConstraints, // Buffer
},
};
Más adelante en la función EvtCircuitCompositeCircuitInitialize, la función auxiliar AddPropertyToCircuitInterface se usa para agregar la matriz de propiedades de interfaz al circuito.
// Set RT buffer constraints.
//
status = AddPropertyToCircuitInterface(Circuit, ARRAYSIZE(DspC_InterfaceProperties), DspC_InterfaceProperties);
La función auxiliar AddPropertyToCircuitInterface hace uso de AcxCircuitGetSymbolicLinkName en el circuito y luego llama a IoGetDeviceInterfaceAlias para localizar la interfaz de audio utilizada por el circuito.
Después, la función SetDeviceInterfacePropertyDataMultiple llama a la función IoSetDeviceInterfacePropertyData para modificar el valor actual de la propiedad de la interfaz de dispositivo: los valores de propiedades de audio de KS en la interfaz de audio en el 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;
}
Cambios de estado de transmisiones
Cuando se produce un cambio de estado de transmisión, cada objeto de transmisión de la ruta de audio del punto de conexión de la transmisión recibirá un evento de notificación de la plataforma de ACX. El orden en el que esto sucede depende del cambio de estado y del flujo de la transmisión.
Para transmisiones de renderización que pasan de un estado menos activo a un estado más activo, el circuito de streaming (que registró el RECEPTOR) recibirá primero el evento. Una vez que se controla el evento, el siguiente circuito de la ruta de audio del punto de conexión recibirá el evento.
En el caso de las transmisiones de renderización que van de un estado más activo a un estado menos activo, el circuito de streaming recibirá el último evento.
En el caso de las transmisiones de captura que van de un estado menos activo a un estado más activo, el circuito de streaming recibirá el último evento.
En el caso de las transmisiones de captura que van de un estado más activo a un estado menos activo, el circuito de streaming recibirá el evento primero.
El orden anterior es el valor predeterminado facilitado por la plataforma de ACX. Un controlador puede solicitar la operación opuesta al aplicar AcxStreamBridgeInvertChangeStateSequence (definido en ACX_STREAM_BRIDGE_CONFIG_FLAGS) cuando se crea el ACXSTREAMBRIDGE que el controlador agrega al circuito de streaming.
Datos de audio de streaming
Una vez creada la transmisión y se han asignado los búferes adecuados, la transmisión pasa al estado Pause en espera de que se inicie la transmisión. Cuando el cliente coloca la transmisión en estado Play, la plataforma de ACX llamará a todos los objetos ACXSTREAM asociados a la transmisión para indicar que el estado de la transmisión está en Play. A continuación, ACXPIN pasará al estado Play, en cuyo punto comenzarán a fluir los datos.
Datos de audio de renderización
Una vez creada la transmisión y los recursos asignados, la aplicación llamará a Start en la transmisión para iniciar la reproducción. Tenga en cuenta que una aplicación debe llamar a GetBuffer/ReleaseBuffer antes de iniciar la transmisión para asegurarse de que el primer paquete que empezará a reproducirse inmediatamente tendrá datos de audio válidos.
El cliente se inicia preparando previamente un búfer. Cuando el cliente llama a ReleaseBuffer, esto se traducirá en una llamada en AudioKSE que llamará a la capa de ACX, que llamará a EvtAcxStreamSetRenderPacket en el ACXSTREAM activo. La propiedad incluirá el índice de paquetes (basado en 0) y, si procede, un indicador de EOS con el direccionamiento de bytes del final de la transmisión en el paquete actual. Una vez que el circuito de streaming termine con un paquete, activará la notificación de búfer completa que liberará a los clientes que están en espera de rellenar el siguiente paquete con datos de audio de renderización.
Se incluye el modo de streaming controlado por temporizador y se indica mediante el uso de un valor PacketCount de 1 al llamar a la devolución de llamada EvtAcxStreamAllocateRtPackets.
Datos de audio de captura
Una vez creada la transmisión y los recursos asignados, la aplicación llamará a Start en la transmisión para iniciar la reproducción.
Cuando se ejecuta la transmisión, el circuito de origen rellena el paquete de captura con datos de audio. Una vez lleno el primer paquete, el circuito de origen libera el paquete en la plataforma de ACX. En este momento, la plataforma de ACX indica el evento de notificación de transmisión.
Cuando se haya señalado la notificación de transmisión, el cliente podrá enviar KSPROPERTY_RTAUDIO_GETREADPACKET para obtener el índice (basado en 0) del paquete que ha terminado de capturar. Cuando el cliente ha enviado GETCAPTUREPACKET, el controlador puede suponer que se han procesado todos los paquetes anteriores y están disponibles para rellenarse.
Para la captura en ráfaga, el circuito de origen puede liberar un nuevo paquete en la plataforma de ACX en cuanto se llame a GETREADPACKET.
El cliente también puede usar KSPROPERTY_RTAUDIO_PACKETVREGISTER para obtener un puntero a la estructura RTAUDIO_PACKETVREGISTER de la transmisión. La plataforma de ACX actualizará esta plataforma antes de que se termine de señalar el paquete.
Método heredado de streaming de kernel de KS
Puede haber situaciones, como cuando un controlador implementa la captura en ráfaga (como pasa en una implementación de detector de palabras clave), donde el método heredado de control de paquetes de streaming de kernel debe usarse en lugar del PacketVRegister. Para usar el método anterior basado en paquetes, el controlador debe devolver STATUS_NOT_SUPPORTED en KSPROPERTY_RTAUDIO_PACKETVREGISTER.
En el ejemplo siguiente se muestra cómo hacerlo en AcxStreamInitAssignAcxRequestPreprocessCallback para un ACXSTREAM. Para obtener más información, consulte 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);
}
Posición de transmisión
La plataforma de ACX llamará a la devolución de llamada EvtAcxStreamGetPresentationPosition para obtener la posición actual de la transmisión. La posición actual de la transmisión incluirá PlayOffset y WriteOffset.
El modelo de streaming WaveRT permite al controlador de audio exponer un registro de posición de HW al cliente. El modelo de streaming de ACX no admitirá la exposición de registros de HW, ya que esto impediría que se produzca un reequilibrio.
Cada vez que el circuito de streaming completa un paquete, llama a AcxRtStreamNotifyPacketComplete con el índice de paquetes basado en 0 y el valor QPC usado cerca de la finalización del paquete (por ejemplo, el valor QPC se puede calcular mediante la rutina de servicio de interrupción). Esta información está disponible para los clientes a través de KSPROPERTY_RTAUDIO_PACKETVREGISTER, que devuelve un puntero a una estructura que contiene CompletedPacketCount, el CompletedPacketQPC y un valor que combina los dos (lo que permite al cliente asegurarse de que CompletedPacketCount y CompletedPacketQPC proceden del mismo paquete).
Transiciones de estado de transmisión
Una vez creada una transmisión, ACX realizará la transición de la transmisión a distintos estados mediante las siguientes devoluciones de llamada:
- EvtAcxStreamPrepareHardware pasará la transmisión del estado AcxStreamStateStop al estado AcxStreamStatePause. El controlador debe reservar el hardware necesario, como los motores de DMA, cuando recibe EvtAcxStreamPrepareHardware.
- EvtAcxStreamRun pasará la transmisión del estado AcxStreamStatePause al estado AcxStreamStateRun.
- EvtAcxStreamPause pasará la transmisión del estado AcxStreamStateRun al estado AcxStreamStatePause.
- EvtAcxStreamReleaseHardware pasará la transmisión del estado AcxStreamStatePause al estado AcxStreamStateStop. El controlador debe liberar el hardware necesario, como los motores DMA, cuando recibe EvtAcxStreamReleaseHardware.
La transmisión puede recibir la devolución de llamada EvtAcxStreamPrepareHardware después de haber recibido la devolución de llamada EvtAcxStreamReleaseHardware. Esto hará que la transmisión pase de nuevo al estado AcxStreamStatePause.
Normalmente, la asignación de paquetes con EvtAcxStreamAllocateRtPackets se realizará antes de la primera llamada a EvtAcxStreamPrepareHardware. Normalmente, los paquetes asignados se liberarán con EvtAcxStreamFreeRtPackets después de la última llamada a EvtAcxStreamReleaseHardware. No se garantiza esta ordenación.
No se usa el estado AcxStreamStateAcquire. Con ACX no es necesario que el controlador tenga el estado de adquisición, ya que este estado viene implícito con las devoluciones de llamada de preparación de hardware (EvtAcxStreamPrepareHardware) y de liberación de hardware (EvtAcxStreamReleaseHardware).
Compatibilidad de transmisiones con búferes grandes y motor de descarga
ACX usa el elemento ACXAUDIOENGINE para designar un ACXPIN que controlará la creación de transmisiones de descarga y los diferentes elementos necesarios para el volumen, la activación de silencio y el estado del medidor máximo de las transmisiones de descarga. Esto se parece al nodo del motor de audio existente en los controladores WaveRT.
Proceso de cierre de transmisiones
Cuando el cliente cierra la transmisión, el controlador recibirá EvtAcxStreamPause y EvtAcxStreamReleaseHardware antes de que la plataforma de ACX elimine el objeto ACXSTREAM. El controlador puede integrar la entrada EvtCleanupCallback de WDF estándar en la estructura WDF_OBJECT_ATTRIBUTES al llamar a AcxStreamCreate para realizar la limpieza final de ACXSTREAM. WDF llamará a EvtCleanupCallback cuando la plataforma intente eliminar el objeto. No use EvtDestroyCallback, al que solo se llama una vez que se han liberado todas las referencias al objeto y que es indeterminado.
El controlador debe limpiar los recursos de memoria del sistema asociados al objeto ACXSTREAM en EvtCleanupCallback, si los recursos aún no se han limpiado en EvtAcxStreamReleaseHardware.
Es importante que el controlador no limpie los recursos que actúen en la transmisión hasta que el cliente lo solicite.
No se usa el estado AcxStreamStateAcquire. Con ACX no es necesario que el controlador tenga el estado de adquisición, ya que este estado viene implícito con las devoluciones de llamada de preparación de hardware (EvtAcxStreamPrepareHardware) y de liberación de hardware (EvtAcxStreamReleaseHardware).
Eliminación e invalidación imprevista de transmisiones
Si el controlador determina que la transmisión no es válida (por ejemplo, el conector se desenchufa), el circuito anulará todas las transmisiones.
Limpieza de memoria de transmisiones
La eliminación de los recursos de la transmisión se puede realizar con la limpieza del contexto de transmisión del controlador (no destruir). No inserte nada de lo que se elimine y que se comparta en la devolución de llamada de destrucción de contexto de un objeto. Esta regla se aplica a todos los objetos de ACX.
La devolución de llamada de destrucción se invoca después de que se haya perdido la última referencia, cuando es desconocida.
En general, se llama a la devolución de llamada de limpieza de la transmisión cuando se cierra el identificador. Hay una excepción a esto, que es cuando el controlador ha creado la transmisión en su devolución de llamada. Si ACX no consiguió agregar esta transmisión al puente de transmisión justo antes de volver de la operación stream-create, la transmisión se cancela de forma asincrónica y el subproceso actual devuelve un error al cliente de create-stream. La transmisión no debe tener asignaciones de memoria atribuidas en este momento. Para obtener más información, consulte la devolución de llamada EVT_ACX_STREAM_RELEASE_HARDWARE.
Secuencia de limpieza de memoria de transmisión
El búfer de transmisión es un recurso del sistema y solo se debe liberar cuando el cliente en modo de usuario cierra el identificador de la transmisión. El búfer (que es diferente de los recursos de hardware del dispositivo) tiene la misma duración que el identificador de la transmisión. Cuando el cliente cierra el identificador de ACX, invoca la devolución de llamada de limpieza de objetos de transmisión y luego la devolución de llamada de eliminación de objetos de la transmisión cuando la referencia del objeto pasa a cero.
Es posible que ACX aplace la eliminación de un objeto STREAM por un work-item cuando el controlador crea un stream-obj y luego se produce un error en la devolución de llamada de create-stream. Para evitar un interbloqueo con un subproceso de cierre de WDF, ACX aplaza la eliminación para otro subproceso. Para evitar posibles efectos secundarios de esta acción (liberación aplazada de recursos), el controlador puede liberar los recursos de transmisión asignados antes de devolver un error a partir de stream-create.
El controlador debe liberar los búferes de audio cuando ACX invoca la devolución de llamada EVT_ACX_STREAM_FREE_RTPACKETS. Se llama a esta devolución de llamada cuando el usuario cierra los identificadores de transmisión.
Dado que los búferes de RT se asignan en modo de usuario, la duración del búfer es la misma que la duración del identificador. El controlador no debe tratar de liberar los búferes de audio antes de que ACX invoque esta devolución de llamada.
La devolución de llamada EVT_ACX_STREAM_FREE_RTPACKETS devolución se llamar después de la devolución de llamada EVT_ACX_STREAM_RELEASE_HARDWARE y finalizar antes de EvtDeviceReleaseHardware.
Esta devolución de llamada puede ocurrir después de que el controlador procese la devolución de llamada de hardware de versión de WDF, ya que el cliente en modo de usuario puede conservar sus identificadores durante mucho tiempo. El controlador no debe esperar a que estos identificadores desaparezcan, ya que crearía una comprobación de errores 0x9f DRIVER_POWER_STATE_FAILURE. Consulte la función de devolución de llamada EVT_WDF_DEVICE_RELEASE_HARDWARE para obtener más información.
En este código EvtDeviceReleaseHardware del controlador de ACX se ve un ejemplo de cómo se llama a AcxDeviceRemoveCircuit y luego se libera la memoria del hardware de 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();
En resumen:
Hardware de versión de dispositivo de WDF:> Liberar recursos de hardware de dispositivo
AcxStreamFreeRtPackets:> liberar búfer de audio asociado al identificador
Para obtener más información sobre cómo administrar objetos de WDF y de circuito, consulte Administración de la vida útil del controlador de ACX WDF.
DDI de streaming
Estructuras de streaming
Estructura ACX_RTPACKET
Esta estructura representa un único paquete asignado. El PacketBuffer puede ser un identificador WDFMEMORY, un MDL o un búfer. Tiene una función de inicialización asociada, ACX_RTPACKET_INIT.
ACX_STREAM_CALLBACKS
Esta estructura identifica las devoluciones de llamada del controlador para el streaming en la plataforma de ACX. Esta estructura forma parte de la estructura ACX_PIN_CONFIG.
Devoluciones de llamada de streaming
EvtAcxStreamAllocateRtPackets
El evento EvtAcxStreamAllocateRtPackets indica al controlador que asigne RtPackets para el streaming. AcxRtStream recibirá PacketCount = 2 en el streaming controlado por eventos o PacketCount = 1 en el streaming basado en el temporizador. Si el controlador usa un único búfer para ambos paquetes, el segundo RtPacketBuffer debe tener un WDF_MEMORY_DESCRIPTOR con Type = WdfMemoryDescriptorTypeInvalid y con un RtPacketOffset que se alinee con el final del primer paquete (packet[2].RtPacketOffset = packet[1].RtPacketOffset+packet[1].RtPacketSize).
EvtAcxStreamFreeRtPackets
El evento EvtAcxStreamFreeRtPackets indica al controlador que libere los RtPackets asignados en una llamada anterior a EvtAcxStreamAllocateRtPackets. Se incluyen los mismos paquetes de esa llamada.
EvtAcxStreamGetHwLatency
El evento EvtAcxStreamGetHwLatency indica al controlador que da latencia de transmisión para el circuito específico de esta transmisión (la latencia general será la suma de la latencia de los distintos circuitos). FifoSize está en bytes y el Delay se representa en unidades de 100 nanosegundos.
EvtAcxStreamSetRenderPacket
El evento EvtAcxStreamSetRenderPacket indica al controlador qué paquete acaba de liberar el cliente. Si no hay problemas, este paquete debe ser (CurrentRenderPacket + 1), donde CurrentRenderPacket es el paquete desde el que el controlador está transmitiendo en ese momento.
Los indicadores pueden ser 0 o KSSTREAM_HEADER_OPTIONSF_ENDOFSTREAM = 0x200
, lo que indica que Packet es el último paquete de la transmisión y EosPacketLength es una longitud válida en bytes para el paquete. Para obtener más información, consulte OptionsFlags en la Estructura KSSTREAM_HEADER (ks.h).
El controlador debe seguir aumentando el CurrentRenderPacket a medida que se procesan los paquetes en lugar de cambiar su CurrentRenderPacket para que coincida con este valor.
EvtAcxStreamGetCurrentPacket
El EvtAcxStreamGetCurrentPacket indica al controlador que señale qué paquete (basado en 0) se está procesando actualmente en el hardware o está rellenado en ese momento el hardware de captura.
EvtAcxStreamGetCapturePacket
El EvtAcxStreamGetCapturePacket indica al controlador que señale qué paquete (basado en 0) se ha llenado completamente más recientemente, incluido el valor QPC en el momento en que el controlador comenzó a llenar el paquete.
EvtAcxStreamGetPresentationPosition
El EvtAcxStreamGetPresentationPosition indica al controlador que señale la posición actual junto con el valor QPC en el momento en que se calculó la posición actual.
EVENTOS DE ESTADO DE TRANSMISIÓN
El estado de streaming de ACXSTREAM se gestiona mediante las siguientes API.
EVT_ACX_STREAM_PREPARE_HARDWARE
EVT_ACX_STREAM_RELEASE_HARDWARE
API de streaming de ACX
AcxStreamCreate
AcxStreamCreate crea una transmisión de ACX que se puede usar para controlar el rendimiento del streaming.
AcxRtStreamCreate
AcxRtStreamCreate crea una transmisión de ACX que se puede usar para controlar el rendimiento del streaming y gestionar la asignación de paquetes y comunicar el estado del streaming.
AcxRtStreamNotifyPacketComplete
El controlador llama a esta API de ACX cuando se ha completado un paquete. El tiempo de finalización del paquete y el índice de paquetes basado en 0 se incluyen para mejorar el rendimiento del cliente. La plataforma de ACX creará los eventos de notificación asociados a la transmisión.
Consulte también
Información general sobre las extensiones de clase de audio de ACX