Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este tema se describe el streaming de ACX y el almacenamiento en búfer asociado, que es fundamental para una experiencia de audio sin problemas. Se describen los mecanismos utilizados por el controlador para comunicarse sobre el estado del flujo y gestionar el búfer del flujo. 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 de ACX.
Tipos de streaming de ACX
AcxStream representa una secuencia de audio en el hardware de un circuito específico. AcxStream puede agregar uno o varios objetos similares a AcxElements.
El marco de ACX admite dos tipos de flujo. El primer tipo de secuencia, la secuencia de paquetes RT, proporciona compatibilidad para 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 secuencia. El segundo tipo de secuencia, la secuencia básica, solo proporciona compatibilidad con transiciones de estado de flujo.
En un único extremo de circuito, el circuito debe ser de streaming que cree una secuencia de paquetes RT. Si dos o más 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 secuencia de paquetes RT; Los circuitos conectados crearán secuencias básicas para recibir eventos relacionados con las transiciones de estado de flujo.
Para obtener más información, vea Flujo de ACX en Resumen de objetos ACX. Las DDIs para el flujo se definen en el encabezado acxstreams.h.
Pila de comunicaciones de streaming de ACX
Hay dos tipos de comunicaciones para ACX Streaming. Usar una ruta de comunicaciones para controlar el comportamiento del streaming con comandos como Start, Create y Allocate, que utilizarán las comunicaciones ACX estándar. El marco ACX utiliza colas de E/S y pasa las solicitudes de WDF a través de las colas. El comportamiento de la cola está oculto en el código del controlador real mediante el uso de callbacks de Evt y funciones de ACX, aunque al controlador también se le da la oportunidad de preprocesar todas las solicitudes WDF.
La segunda y más interesante ruta de comunicación se usa para la señalización de streaming de audio. Esto implica indicar al controlador cuándo un paquete está listo y recibir datos cuando el controlador ha terminado de procesar un paquete.
Los principales requisitos para la señalización de streaming:
- Soporte para la reproducción de Glitch-Free
- Baja latencia
- Los bloqueos necesarios se limitan a la secuencia en cuestión.
- Facilidad de uso para el desarrollador de controladores
Para comunicarse con el controlador para indicar el estado de streaming, ACX usa eventos con un búfer compartido y llamadas IRP directas. Estos se describen a continuación.
Búfer compartido
Para comunicarse desde el controlador al cliente, se usa un búfer compartido y un evento. Esto garantiza que el cliente no necesita esperar o sondear, y que el cliente puede determinar todo lo que necesita para continuar transmitiendo al tiempo que reduce o elimina la necesidad de llamadas IRP directas.
El controlador de dispositivo usa un búfer compartido para comunicarse con el cliente al que se representa o captura el paquete. Este búfer compartido incluye el recuento de paquetes (basado en 1) del último paquete completado junto con el valor QPC (QueryPerformanceCounter) del tiempo de finalización. El controlador del dispositivo debe indicar esta información llamando a AcxRtStreamNotifyPacketComplete. Cuando el controlador de dispositivo llama a AcxRtStreamNotifyPacketComplete, el marco de ACX actualizará el búfer compartido con el nuevo recuento de paquetes y QPC y indicará un evento compartido con el cliente para indicar que el cliente puede leer el nuevo recuento de paquetes.
Llamadas IRP directas
Para comunicarse desde el cliente al controlador, se usan llamadas IRP directas. Esto reduce las complejidades en torno a garantizar que las solicitudes de WDF se administran de forma oportuna y se ha demostrado que funcionan bien en la arquitectura existente.
El cliente puede solicitar en cualquier momento el recuento de paquetes actual o indicar el número de paquetes actual al controlador del dispositivo. Estas solicitudes invocarán los controladores de eventos del controlador de dispositivo EvtAcxStreamGetCurrentPacket y EvtAcxStreamSetRenderPacket. El cliente también puede solicitar el paquete de captura actual, lo que activará el manejador de eventos del dispositivo EvtAcxStreamGetCapturePacket.
Similitudes con PortCls
La combinación de llamadas IRP directas y búfer compartido usado por ACX es similar a cómo se comunica el control de finalización del búfer en PortCls. Los IRP son muy similares y el búfer compartido presenta la capacidad de que el controlador comunique directamente el número de paquetes y el tiempo sin depender de IRP. Los controladores de dispositivos deberán asegurarse de que no hagan nada que requiera acceso a las cerraduras que también se usan en las rutas de control de flujo; esto es necesario para evitar fallos.
Compatibilidad con búferes de gran tamaño para la reproducción de baja potencia
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 alta potencia. 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 ACX pueden anunciar compatibilidad con búferes significativamente mayores, en el intervalo de tamaño de 1 a 2 segundos. Esto significa que la APU puede reactivarse una vez cada 1 a 2 segundos, realizar las operaciones necesarias a la velocidad máxima para preparar el siguiente búfer de 1 a 2 segundos y, a continuación, ir al estado de energía 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 tecnología Offload Playback. Un controlador de audio anuncia la compatibilidad con Offload Playback exponiendo un nodo AudioEngine en el filtro de onda de un punto de conexión. El nodo AudioEngine proporciona un medio para controlar el motor DSP que usa el controlador para representar el audio de los búferes grandes con el procesamiento deseado.
El nodo AudioEngine proporciona estas instalaciones:
- Descripción del motor de audio, que indica a la pila de audio qué patillas del filtro de onda proporcionan compatibilidad con descarga y bucle invertido (y compatibilidad con la reproducción del host).
- Intervalo de tamaño de búfer, que indica a la pila de audio los tamaños de búfer mínimo y máximo que se pueden admitir para la descarga. reproducción. El intervalo 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 actual de mezcla de dispositivos y el formato del dispositivo.
- Volumen, incluyendo el soporte para aumento gradual, ya que con el control de volumen del software y búferes más grandes no tendrán capacidad de respuesta.
- Loopback Protection, que indica al controlador que silencie el pin de bucle de retorno de AudioEngine si uno o más de los flujos delegados contienen contenido protegido.
- Estado FX global, para habilitar o deshabilitar GFX en AudioEngine.
Cuando se crea una secuencia en el Pin de descarga, la secuencia admite Volume, Local FX y Loopback Protection.
Reproducción de baja energía con ACX
El marco ACX usa el mismo modelo para la reproducción de baja potencia. El controlador crea tres objetos ACXPIN independientes para el streaming de host, descarga y bucle invertido, junto con un elemento ACXAUDIOENGINE que describe cuáles de estos pines se usan para host, descarga y bucle invertido. El controlador agrega los pines y el elemento ACXAUDIOENGINE al ACXCIRCUIT durante la creación del circuito.
Creación de flujos descargados
El controlador también agregará un elemento ACXAUDIOENGINE a las secuencias creadas para la descarga para permitir el control sobre el volumen, silenciar y medir pico.
Diagrama de streaming
En este diagrama se muestra un controlador ACX de varias pilas.
Cada controlador ACX controla una parte independiente del hardware de audio y podría ser proporcionada por un proveedor diferente. ACX proporciona una interfaz de streaming de kernel compatible para permitir que las aplicaciones se ejecuten tal como está.
Pines de flujo
Cada ACXCIRCUIT tiene al menos un pin receptor y un pin de origen. El framework ACX utiliza estos pines para proporcionar acceso a las conexiones del circuito al stack de audio. Para un circuito Render, el Pin de origen se usa para controlar el comportamiento de representación de cualquier flujo creado a partir del circuito. En el caso de un circuito capture, el pin receptor se usa para controlar el comportamiento de captura de cualquier flujo creado a partir del circuito. ACXPIN es el objeto utilizado para controlar el streaming en la ruta de acceso de audio. El ACXCIRCUIT de streaming es responsable de crear los objetos ACXPIN adecuados para el Endpoint Audio Path en el momento de la creación del circuito y registrar los ACXPINs en ACX. El ACXCIRCUIT solo necesita crear los pines de representación o captura para el circuito; el entorno ACX creará los otros pines necesarios para conectarse y comunicarse con el circuito.
Circuito de streaming
Cuando un punto de conexión se compone de un único circuito, ese circuito 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 específico 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 una secuencia de paquetes RT 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 flujo de streaming en respuesta a las necesidades de cliente y hardware.
Los siguientes circuitos del punto de conexión deben usar AcxStreamCreate para crear una secuencia básica 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 flujo, como Pausar o Ejecutar.
El circuito de transmisión ACXCIRCUIT es el primero en recibir las solicitudes para crear una secuencia. La solicitud incluye el dispositivo, el pin y el formato de datos (incluido el modo).
Cada ACXCIRCUIT de la ruta de audio creará un objeto ACXSTREAM que representa la instancia de flujo del circuito. El marco de ACX vincula los objetos ACXSTREAM juntos (de la misma manera que los objetos ACXCIRCUIT están vinculados).
Circuitos ascendentes y descendentes
La creación de flujos se inicia en el circuito de streaming y se reenvía a cada circuito de bajada en el orden en que los circuitos están conectados. Las conexiones se realizan entre patillas de puente creadas con Communication igual a AcxPinCommunicationNone. El entorno ACX creará uno o varios pines de puente para un circuito si el controlador no los agrega durante la creación del circuito.
Para cada circuito, comenzando con el circuito de transmisión por secuencias, el pin de puente AcxPinTypeSource se conectará al siguiente circuito subsiguiente. El circuito final tendrá un pin de punto de conexión que describa 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).
Para cada circuito que sigue al circuito de streaming, el pin de puente AcxPinTypeSink se conectará al siguiente circuito ascendente.
Negociación de formato de secuencia
El controlador anuncia los formatos admitidos para la creación de flujos agregando los formatos admitidos por modo al ACXPIN usado para la creación de flujos con AcxPinAssignModeDataFormatList y AcxPinGetRawDataFormatList. Para los puntos de conexión multircuito, se puede utilizar ACXSTREAMBRIDGE para coordinar el modo y la compatibilidad con el formato entre los circuitos ACX. Los formatos de flujo admitidos para el punto de conexión se determinan mediante los ACXPIN de transmisión creados por el circuito de transmisión. 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, el marco 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 ascendente al reenviar la solicitud de creación del flujo al circuito descendente. Si el punto de puente del circuito ascendente no tiene ningún formato, se usará el formato de flujo original. Si el pin conectado del circuito de bajada no admite el formato que se está usando, se producirá un error en la creación de flujo.
Si un circuito de dispositivo realiza un cambio de formato de flujo, el controlador de dispositivo debe agregar el formato de flujo de salida al pin de puente de salida.
Creación de flujos
El primer paso de creación de secuencias consiste en crear la instancia de ACXSTREAM para cada ACXCIRCUIT en la ruta de acceso de audio del punto de conexión. ACX llamará a EvtAcxCircuitCreateStream de cada circuito. ACX comenzará con el circuito de cabeza y llamará a EvtAcxCircuitCreateStream de cada circuito en orden, finalizando con el circuito de cola. El orden se puede invertir especificando la marca AcxStreamBridgeInvertChangeStateSequence (definida en ACX_STREAM_BRIDGE_CONFIG_FLAGS) para el Stream Bridge. Una vez que todos los circuitos hayan creado un objeto de secuencia, los objetos de secuencia controlarán la lógica de streaming.
La solicitud de creación de stream se envía al PIN adecuado generado dentro del proceso de creación de la topología del circuito principal, llamando al método EvtAcxCircuitCreateStream especificado durante la creación del circuito principal.
El circuito de streaming es el circuito ascendente que controla inicialmente la solicitud de creación de flujos.
- Actualiza la estructura de ACXSTREAM_INIT, asignando AcxStreamCallbacks y AcxRtStreamCallbacks
- Crea el objeto ACXSTREAM mediante AcxRtStreamCreate
- Crea cualquier elemento específico de la secuencia (por ejemplo, ACXVOLUME o ACXAUDIOENGINE)
- Agrega los elementos al objeto ACXSTREAM.
- Devuelve el objeto ACXSTREAM que se creó en el marco de ACX.
A continuación, ACX reenvía la creación del flujo al siguiente circuito de bajada.
- Actualiza la estructura ACXSTREAM_INIT, asignando AcxStreamCallbacks.
- Crea el objeto ACXSTREAM mediante AcxStreamCreate
- Se generan los elementos específicos de cada flujo
- Agrega los elementos al objeto ACXSTREAM.
- Devuelve el objeto ACXSTREAM que se creó en el marco de ACX.
El canal de comunicación entre circuitos en una trayectoria de audio utiliza 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 acceso de audio del punto de conexión. Además, el trayecto de audio de un punto de conexión es lineal y bidireccional. El control real de colas de E/S se realiza mediante el marco de ACX. Al crear el objeto ACXSTREAM, cada circuito puede agregar información de contexto al objeto ACXSTREAM para almacenar y realizar un seguimiento de los datos privados de la secuencia.
Ejemplo de flujo de renderizado
Crear una secuencia de renderizado en una ruta de audio de punto final compuesta por tres circuitos: DSP, CODEC y AMP. El circuito DSP funciona como circuito de streaming y ha proporcionado un controlador EvtAcxPinCreateStream. El circuito DSP también funciona como un circuito de filtro: dependiendo del modo de flujo y la configuración, puede aplicar el procesamiento de señales a los datos de audio. El circuito CODEC representa el DAC, proporcionando la funcionalidad de destino 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 detalles de hardware del punto de conexión.
- AudioKSE llama a NtCreateFile para crear una secuencia.
- Esto filtra a través de ACX y termina con la llamada al 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 asegurarse de que puede controlar la secuencia creada.
- El circuito DSP crea el objeto ACXSTREAM para representar la secuencia.
- El circuito DSP asigna una estructura de contexto privado y la asocia a ACXSTREAM.
- El circuito DSP retorna el flujo de ejecución al marco ACX, el cual luego llama al siguiente circuito en la Ruta de Audio del Punto Final, el circuito CODEC.
- El circuito CODEC valida la información del formato de datos para confirmar que puede controlar la representació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 flujo al ACXSTREAM.
- El circuito CODEC devuelve el flujo de ejecución al framework de ACX, que luego llama al siguiente circuito en la ruta de audio del extremo, 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 al marco de ACX. En este punto, la creación del flujo está completa.
Flujos de memoria intermedia grandes
Los flujos de búfer grandes se crean en el ACXPIN designado para la descarga por el elemento ACXAUDIOENGINE de ACXCIRCUIT.
Para admitir secuencias de descarga, el controlador de dispositivo debe hacer lo siguiente durante la creación del circuito de streaming:
- Cree los objetos Host, Offload y Loopback ACXPIN y agréguelos al ACXCIRCUIT.
- Cree 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 deben realizar pasos similares para agregar un elemento ACXSTREAMAUDIOENGINE al crear una secuencia en el pin Offload.
Asignación de recursos de flujo
El modelo de streaming para ACX se basa en paquetes, con compatibilidad con uno o dos paquetes para una secuencia. La representación o captura de ACXPIN para el circuito de streaming recibe una solicitud para asignar los paquetes de memoria que se usan en la secuencia. Para admitir Rebalance, la memoria asignada debe ser 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 requiere 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 a página y se mapea dos veces en el modo de usuario.
| paquete 0 | paquete 0 |
Esto permite que GetBuffer devuelva un puntero a un único búfer de memoria contiguo que puede extenderse desde el final del búfer hasta el inicio sin que la aplicación necesite gestionar el ajuste del acceso a la memoria.
Si se asignan dos paquetes, se asignan al modo de usuario :
| paquete 0 | paquete 1 |
Con el streaming inicial de paquetes ACX, solo hay dos paquetes asignados al principio. La asignación de memoria virtual del cliente seguirá siendo válida sin cambios durante la vida útil de la secuencia una vez que la asignación y el mapeo hayan sido realizados. Hay un evento asociado con el flujo para indicar la finalización de ambos paquetes. También habrá un búfer compartido que usará el marco de ACX para comunicar qué paquete finalizó con el evento.
Tamaños de paquete de flujos de búfer grandes
Al exponer la compatibilidad con los Búferes Grandes, el controlador también proporcionará un callback que se utiliza para determinar los tamaños de paquete mínimos y máximos para la reproducción de Búferes Grandes. El tamaño del paquete para la asignación del búfer de flujo se determina en función del mínimo y máximo.
Dado que los tamaños de búfer mínimo y máximo pueden ser volátiles, el controlador puede producir un error en la llamada de asignación de paquetes si el mínimo y el máximo han cambiado.
Especificación de restricciones de búfer de ACX
Para especificar restricciones de búfer de ACX, los controladores ACX pueden usar las propiedades KS/PortCls KSAUDIO_PACKETSIZE_CONSTRAINTS2 y la estructura KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT.
En el ejemplo de código siguiente se muestra cómo establecer restricciones de tamaño de búfer para los búferes de WaveRT para 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 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 toma acxCircuitGetSymbolicLinkName para el circuito y, a continuación, llama a IoGetDeviceInterfaceAlias para localizar la interfaz de audio utilizada por el circuito.
A continuación, la función SetDeviceInterfacePropertyDataMultiple llama a la función IoSetDeviceInterfacePropertyData para modificar el valor actual de la propiedad de la interfaz de dispositivo, específicamente los valores de propiedad de audio KS en la interfaz de audio del 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 flujo
Cuando se produce un cambio de estado de flujo, cada objeto de flujo en la ruta de audio del punto de conexión recibirá un evento de notificación del framework de ACX. El orden en el que esto sucede depende del cambio de estado y del flujo de la corriente.
Para los flujos de renderizado que pasan de un estado menos activo a un estado más activo, el circuito de transmisión (que registró el SINK) recibirá el evento primero. Una vez que se ha gestionado el evento, el siguiente circuito en el camino de audio del endpoint recibirá el evento.
En el caso de las secuencias de representación que van de un estado más activo a un estado menos activo, el circuito de streaming recibirá el evento el último.
En el caso de las secuencias de captura que van de un estado menos activo a un estado más activo, el circuito de streaming recibirá el evento al final.
En el caso de las secuencias de captura que van de un estado más activo a uno menos activo, el circuito de streaming recibirá el evento primero.
El orden anterior es el valor predeterminado proporcionado por el marco de ACX. Un controlador puede solicitar el comportamiento opuesto estableciendo AcxStreamBridgeInvertChangeStateSequence (definido en ACX_STREAM_BRIDGE_CONFIG_FLAGS) al crear el ACXSTREAMBRIDGE que el controlador agrega al circuito de streaming.
Streaming de datos de audio
Una vez creada la secuencia y se asignan los búferes adecuados, la secuencia se encuentra en el estado Pausar en espera de que se inicie la secuencia. Cuando el cliente coloca la secuencia en estado Play, el marco ACX llamará a todos los objetos ACXSTREAM asociados a la secuencia para indicar que el estado de la secuencia está en Play. A continuación, ACXPIN se colocará en el estado De reproducción, en cuyo punto comenzarán a fluir los datos.
Representación de datos de audio
Una vez creada la secuencia y los recursos asignados, la aplicación llamará a Start en la secuencia para iniciar la reproducción. Tenga en cuenta que una aplicación debe llamar a GetBuffer/ReleaseBuffer antes de iniciar la secuencia para asegurarse de que el primer paquete que empezará a reproducirse inmediatamente tendrá datos de audio válidos.
El cliente se inicia mediante la puesta en marcha previa de 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, una marca EOS con el desplazamiento de bytes del final de la secuencia en el paquete actual. Una vez que el circuito de streaming termine con un paquete, desencadenará la notificación de búfer completado que liberará a los clientes que esperan completar el siguiente paquete con datos de audio de renderizado.
El modo de streaming controlado por temporizador se admite y se indica mediante el uso de un valor packetCount de 1 al llamar a la devolución de llamada evtAcxStreamAllocateRtPackets del controlador.
Captura de datos de audio
Una vez creada la secuencia y los recursos asignados, la aplicación llamará a Start en la secuencia para iniciar la reproducción.
Cuando se ejecuta la secuencia, el circuito de origen rellena el paquete de captura con datos de audio. Una vez rellenado el primer paquete, el circuito de origen libera el paquete en el marco de ACX. En este momento, el marco de ACX indica el evento de notificación de secuencia.
Una vez que se haya señalado la notificación de secuencia, el cliente puede 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 el framework ACX tan pronto como se haya llamado a GETREADPACKET.
El cliente también puede usar KSPROPERTY_RTAUDIO_PACKETVREGISTER para obtener un puntero a la estructura RTAUDIO_PACKETVREGISTER del flujo. El marco ACX actualizará esta estructura antes de que se complete el paquete de señalización.
Comportamiento del streaming del kernel KS heredado
Puede haber situaciones, como cuando un controlador implementa la captura de ráfagas (como en una implementación de detección de palabras clave), donde debe usarse el comportamiento heredado de gestión de paquetes de transmisión del núcleo en lugar del PacketVRegister. Para usar el comportamiento anterior basado en paquetes, el controlador debe devolver STATUS_NOT_SUPPORTED para KSPROPERTY_RTAUDIO_PACKETVREGISTER.
En el ejemplo siguiente se muestra cómo hacerlo en AcxStreamInitAssignAcxRequestPreprocessCallback para acXSTREAM. Para obtener más información, vea 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 flujo
El framework ACX llamará al callback EvtAcxStreamGetPresentationPosition para obtener la posición actual de la secuencia. La posición actual de la secuencia incluirá PlayOffset y WriteOffset.
El modelo de streaming waveRT permite al controlador de audio exponer un registro de posición HW al cliente. El modelo de streaming de ACX no admitirá la exposición de registros 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 de QPC que se toma lo más cerca posible de la finalización del paquete (como ejemplo, el valor QPC se puede calcular mediante la rutina del 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, 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 flujo
Una vez creada una secuencia, ACX transitará la secuencia a distintos estados mediante los siguientes callbacks:
- EvtAcxStreamPrepareHardware pasará la secuencia del estado AcxStreamStateStop al estado AcxStreamStatePause. El controlador debe reservar hardware necesario, como motores DMA, cuando recibe EvtAcxStreamPrepareHardware.
- EvtAcxStreamRun pasará la secuencia del estado AcxStreamStatePause al estado AcxStreamStateRun.
- EvtAcxStreamPause pasará la secuencia del estado AcxStreamStateRun al estado AcxStreamStatePause.
- EvtAcxStreamReleaseHardware pasará la secuencia 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 cambiará la secuencia 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. Esta ordenación no está garantizada.
No se usa el estado AcxStreamStateAcquire. ACX elimina la necesidad de que el controlador tenga el estado de adquisición, ya que este estado está implícito con las devoluciones de llamada para la preparación del hardware (EvtAcxStreamPrepareHardware) y la liberación del hardware (EvtAcxStreamReleaseHardware).
Compatibilidad con flujos de búfer grandes y compatibilidad con el motor de descarga
ACX usa el elemento ACXAUDIOENGINE para designar un ACXPIN que controlará la creación de flujos de descarga y los diferentes elementos necesarios para descargar el volumen de flujo, silenciar y el estado de medidor máximo. Esto es similar al nodo del motor de audio existente en los controladores waveRT.
Proceso de cierre de flujo
Cuando el cliente cierra la secuencia, el controlador recibirá EvtAcxStreamPause y EvtAcxStreamReleaseHardware antes de que el ACX Framework elimine el objeto ACXSTREAM. El controlador puede proporcionar la entrada WDF EvtCleanupCallback estándar en la estructura WDF_OBJECT_ATTRIBUTES al llamar a AcxStreamCreate para realizar la limpieza final de ACXSTREAM. WDF llamará a EvtCleanupCallback cuando el marco intente eliminar el objeto. No utilice EvtDestroyCallback, al que solo se llama una vez que se han liberado todas las referencias al objeto, lo cual es indeterminado.
El controlador debe limpiar los recursos de memoria del sistema asociados con el objeto ACXSTREAM en EvtCleanupCallback, si los recursos aún no se han limpiado en EvtAcxStreamReleaseHardware.
Es importante que el driver no libere los recursos que respaldan el flujo hasta que el cliente lo solicite.
No se usa el estado AcxStreamStateAcquire. ACX elimina la necesidad de que el controlador tenga el estado de adquisición, ya que este estado está implícito con las devoluciones de llamada de preparación del hardware (EvtAcxStreamPrepareHardware) y liberación del hardware (EvtAcxStreamReleaseHardware).
Eliminación e invalidación de flujos inesperados
Si el controlador determina que la secuencia no es válida (por ejemplo, el conector se desconecta), el circuito apagará todas las secuencias.
Limpieza de memoria de secuencias
La eliminación de los recursos de la secuencia se puede realizar en la limpieza del contexto de flujo del controlador (no destruir). Nunca coloque la eliminación de nada que se comparta en el contexto de un objeto destruye la devolución de llamada. Esta guía se aplica a todos los objetos ACX.
La devolución de llamada de destrucción se invoca después de que se haya perdido la última referencia, cuando se desconoce.
En general, cuando se cierra el identificador, se invoca la llamada de devolución para la limpieza de la secuencia. Una excepción a esto es cuando el controlador creó la secuencia en su devolución de llamada. Si ACX no pudo agregar esta secuencia a su puente de flujo justo antes de volver de la operación stream-create, la secuencia se cancela asincrónica y el subproceso actual devuelve un error al cliente create-stream. La secuencia no debe tener asignaciones de mem asignadas en este momento. Para obtener más información, consulte EVT_ACX_STREAM_RELEASE_HARDWARE callback.
Secuencia de limpieza de memoria de flujo
El búfer de flujo es un recurso del sistema y solo se debe liberar cuando el cliente en modo de usuario cierra el manejador del flujo. El búfer (que es diferente de los recursos de hardware del dispositivo) tiene la misma vida útil que el identificador de la secuencia. Cuando el cliente cierra el manejador, ACX invoca el callback de limpieza de los objetos del flujo y, a continuación, el callback de eliminación de los objetos del flujo cuando la referencia del objeto llega a cero.
Es posible que ACX aplazase una eliminación de STREAM obj a un elemento de trabajo cuando el controlador creó un stream-obj y, a continuación, produjo un error en la devolución de llamada de create-stream. Para evitar un interbloqueo con un subproceso WDF de apagado, ACX aplaza la eliminación a otro subproceso. Para evitar posibles efectos secundarios de este comportamiento (liberación diferida de recursos), el controlador puede liberar los recursos de flujo asignados antes de devolver un error 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 este callback cuando el usuario cierra los manejadores de flujo.
Dado que los búferes RT se asignan en modo de usuario, la duración del búfer es la misma que la del identificador. El controlador no debe intentar liberar los búferes de audio antes de que ACX invoque este callback.
EVT_ACX_STREAM_FREE_RTPACKETS callback debe invocarse después del EVT_ACX_STREAM_RELEASE_HARDWARE callback y concluir antes de EvtDeviceReleaseHardware.
Esta devolución de llamada puede ocurrir después de que el controlador procese la devolución de llamada de liberación de hardware de WDF, ya que el cliente en modo usuario puede retener sus identificadores durante mucho tiempo. El controlador no debe intentar esperar a que estos identificadores desaparezcan, ya que esto simplemente provocará una comprobación de errores de 0x9f DRIVER_POWER_STATE_FAILURE. Consulte la función de callback EVT_WDF_DEVICE_RELEASE_HARDWARE para obtener más información.
Este código EvtDeviceReleaseHardware del controlador ACX de ejemplo muestra un ejemplo de cómo llamar a AcxDeviceRemoveCircuit y posteriormente liberar la memoria de 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:
Liberación de hardware de dispositivo wdf:> libera los recursos de hardware del dispositivo.
AcxStreamFreeRtPackets -> liberar el búfer de audio asociado con el identificador
Para obtener más información sobre cómo administrar objetos WDF y circuit, vea AcX WDF Driver Lifetime Management.
Streaming DDIs
Estructuras de streaming
estructura de ACX_RTPACKET
Esta estructura representa un único paquete asignado. PacketBuffer puede ser un controlador WDFMEMORY, un MDL o un búfer. Tiene una función de inicialización asociada, ACX_RTPACKET_INIT.
ACX_STREAM_CALLBACKS
Esta estructura identifica los callbacks del controlador para transmitir al framework de ACX. Esta estructura forma parte de la estructura ACX_PIN_CONFIG.
Callbacks de streaming
EvtAcxStreamAllocateRtPackets
El evento EvtAcxStreamAllocateRtPackets indica al controlador que asigne RtPackets para streaming. AcxRtStream recibirá PacketCount = 2 para el streaming controlado por eventos o PacketCount = 1 para el streaming basado en temporizador. Si el controlador usa un único búfer para ambos paquetes, el segundo RtPacketBuffer debe tener un WDF_MEMORY_DESCRIPTOR con Type = WdfMemoryDescriptorTypeInvalid con un RtPacketOffset que se alinea con el final del primer paquete (paquete[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 proporcione latencia de flujo para el circuito específico de esta secuencia (la latencia general será una suma de la latencia de los distintos circuitos). FifoSize está en bytes y el retraso está 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 actualmente.
Las marcas pueden ser 0 o KSSTREAM_HEADER_OPTIONSF_ENDOFSTREAM = 0x200, lo que indica que El paquete es el último paquete de la secuencia y EosPacketLength es una longitud válida en bytes para el paquete. Para obtener más información, vea OptionsFlags en KSSTREAM_HEADER structure (ks.h).
El controlador debe seguir aumentando currentRenderPacket a medida que se representan los paquetes en lugar de cambiar su currentRenderPacket para que coincida con este valor.
EvtAcxStreamGetCurrentPacket
EvtAcxStreamGetCurrentPacket ordena al controlador que señale qué paquete (con base cero) se está representando actualmente en el hardware o se está rellenando actualmente por el hardware de captura.
EvtAcxStreamGetCapturePacket
El EvtAcxStreamGetCapturePacket indica al controlador que indique qué paquete (basado en 0) se llenó completamente más recientemente, incluido el valor QPC en el momento en que el controlador comenzó a llenar el paquete.
EvtAcxStreamGetPresentationPosition
EvtAcxStreamGetPresentationPosition indica al controlador que indique 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 una ACXSTREAM se administra mediante las siguientes API.
EVT_ACX_STREAM_PREPARE_HARDWARE
EVT_ACX_STREAM_RELEASE_HARDWARE
API de streaming de ACX
AcxStreamCreate
AcxStreamCreate crea una secuencia ACX que se puede usar para controlar el comportamiento de streaming.
AcxRtStreamCreate
AcxRtStreamCreate crea una secuencia ACX que se puede usar para controlar el comportamiento de streaming y controlar la asignación de paquetes y comunicar el estado de 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. El marco ACX establecerá los eventos de notificación asociados a la secuencia.
Consulte también
Información general sobre las extensiones de clase de audio de ACX