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 artículo se explica el streaming y el almacenamiento en búfer de ACX, que son fundamentales para una experiencia de audio sin problemas. Describe cómo el controlador comunica el estado del flujo y administra 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.
ACX admite dos tipos de flujo. El primer tipo de secuencia, el flujo de paquetes RT, le permite asignar paquetes RT y usarlos para transferir datos de audio hacia o desde el hardware del dispositivo, junto con transiciones de estado de flujo. El segundo tipo de secuencia, el flujo básico, solo admite transiciones de estado de flujo.
En un único punto de conexión de circuito, el circuito es un circuito de streaming que crea una secuencia de paquetes RT. Si dos o más circuitos se conectan 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 crean 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. Los DDIs para secuencias se definen en el encabezado acxstreams.h .
Pila de comunicaciones de streaming de ACX
Hay dos tipos de comunicaciones para el streaming ACX. Una ruta de comunicación controla el comportamiento de streaming. Por ejemplo, comandos como Start, Create y Allocate, que usan comunicaciones ACX estándar. El marco de ACX usa colas de E/S y pasa las solicitudes WDF mediante las colas. El comportamiento de la cola está oculto del código de controlador real mediante devoluciones de llamada de eventos y funciones de ACX. El controlador también tiene la oportunidad de preprocesar todas las solicitudes WDF.
La segunda y más interesante ruta de comunicación controla la señalización de streaming de audio. La señalización implica indicar al controlador cuándo un paquete está listo y recibir datos y cuando el controlador termina de procesar un paquete.
Requisitos principales para la señalización de streaming:
- Soporte para reproducción sin interrupciones
- 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. Estas técnicas se describen a continuación.
Búfer compartido
Un búfer compartido y un evento se comunican desde el controlador al cliente. El evento y el búfer compartido garantizan que el cliente no necesita esperar ni sondear. El cliente puede determinar todo lo que necesita para seguir transmitiendo al mismo 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 uno) del último paquete completado junto con el valor QPC (QueryPerformanceCounter) del tiempo de finalización. Para el controlador del dispositivo, debe indicar esta información llamando a AcxRtStreamNotifyPacketComplete. Cuando el controlador de dispositivo llama a AcxRtStreamNotifyPacketComplete, el marco de ACX actualiza el búfer compartido con el nuevo recuento de paquetes y QPC y señala un evento compartido con el cliente para indicar que el cliente puede leer el nuevo recuento de paquetes.
Llamadas IRP directas
Las llamadas IRP directas se comunican desde el cliente al controlador.
El cliente puede solicitar el recuento de paquetes actual o indicar el número de paquetes actual al controlador de dispositivo en cualquier momento. Estas solicitudes llaman a los controladores de eventos de dispositivos EvtAcxStreamGetCurrentPacket y EvtAcxStreamSetRenderPacket. El cliente también puede solicitar el paquete de captura actual, lo que llama al controlador de eventos del dispositivo EvtAcxStreamGetCapturePacket.
Similitudes con PortCls
La combinación de llamadas IRP directas y búfer compartido que usa ACX es similar a cómo PortCls comunica el control de finalización del búfer.
Para evitar fallos, los controladores deben asegurarse de que no hagan nada que requiera acceso a los bloqueos que también se usan en las rutas de control de flujo.
Compatibilidad con búferes de gran tamaño para la reproducción de baja potencia
Para reducir el consumo de energía durante la reproducción, reduzca el tiempo que la APU pasa en un estado de alta potencia. Dado que la reproducción de audio normal usa búferes de 10 ms, la APU permanece activa. Los controladores ACX pueden anunciar compatibilidad con búferes más grandes, en el intervalo de 1 a 2 segundos, para permitir que la APU entre en un estado de energía inferior.
En los modelos de streaming existentes, la reproducción de descarga admite una reproducción de bajo consumo. Un controlador de audio anuncia la compatibilidad con la reproducción por descarga exponiendo un nodo de 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 características:
- Descripción del motor de audio indica a la pila de audio qué patillas del filtro de onda proporcionan soporte para descarga, bucle invertido y soporte para la reproducción del host.
- El rango de tamaño de búfer indica al conjunto de audio los tamaños de búfer mínimo y máximo que se pueden admitir para la descarga de tareas. 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, incluido el soporte para aumento progresivo, ya que con el volumen del software usándose búferes más grandes no será sensible.
- 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 volumen, FX local y protección de retrollamada.
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 agrega un elemento ACXAUDIOENGINE a las secuencias creadas para la descarga para permitir el control sobre el volumen, silenciar y medir el 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, que puede provenir de un proveedor diferente. ACX proporciona una interfaz de streaming de kernel compatible para que las aplicaciones se ejecuten sin cambios.
Patillas de secuencia
Cada ACXCIRCUIT tiene al menos un pin receptor y un pin de origen. El marco ACX usa estos pines para exponer las conexiones del circuito a la pila 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 la ruta de acceso de audio del punto de conexión en el momento de la creación del circuito y registrar los ACXPIN con ACX. AcXCIRCUIT solo crea los pines de representación o captura para el circuito. El marco de ACX crea 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, 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, ACXCOMPOSITETEMPLATE que describe el punto de conexión compuesto determina el orden específico que conecta los circuitos. 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 permite 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 permiten a los controladores configurar el hardware en respuesta a los cambios de estado de flujo, como Pausar o Ejecutar.
El streaming ACXCIRCUIT recibe la primera solicitud para crear un flujo. La solicitud incluye el dispositivo, el pin y el formato de datos (incluido el modo).
Cada ACXCIRCUIT en la ruta de audio crea un objeto ACXSTREAM que representa el stream del circuito. El marco de ACX vincula los objetos ACXSTREAM juntos, de forma similar a cómo vincula objetos ACXCIRCUIT.
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 framework ACX crea uno o varios pines de puente para un circuito si el controlador no los agrega en el momento de la creación del circuito.
Para cada circuito, comenzando con el circuito de streaming, el puente de pin AcxPinTypeSource se conecta al siguiente circuito posterior. El circuito final tiene 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 un 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 conecta 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 ACXPIN de streaming creados por el circuito de streaming determinan los formatos de secuencia admitidos para el punto de conexió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 framework de ACX crea un ACXSTREAMBRIDGE entre cada circuito de un punto de conexión de múltiples circuitos. AcXSTREAMBRIDGE predeterminado usa el formato predeterminado del modo RAW del pin de puente del circuito ascendente al reenviar la solicitud de creación del flujo al circuito de bajada. Si el pin de puente del circuito ascendente no tiene ningún formato, se usa el formato de secuencia original. Si el pin conectado del circuito de bajada no admite el formato que se está usando, se produce un error en la creación del 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 llama a EvtAcxCircuitCreateStream de cada circuito. ACX comienza con el circuito inicial y llama al 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. Después de que todos los circuitos creen un objeto de secuencia, los objetos de secuencia controlan la lógica de streaming.
La solicitud de creación de flujos se envía al PIN adecuado generado como parte de la generación de topologías del circuito de cabecera, llamando a la función EvtAcxCircuitCreateStream especificada durante la creación del circuito de cabecera.
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 del flujo (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. Cada circuito tiene 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. La ruta de audio del endpoint es lineal y bidireccional. El framework ACX gestiona el procesamiento real de la cola de E/S.
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 se filtra a través de ACX y finaliza llamando 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 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.
Grandes flujos de búfer
Los flujos de búfer grandes se crean en el ACXPIN designado para la descarga por el elemento ACXAUDIOENGINE de ACXCIRCUIT.
Para admitir flujos de descarga, el controlador de dispositivo debe realizar las siguientes acciones durante la creación de circuitos de transmisión:
- 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 un flujo en el pin de 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. El segundo paquete tiene WdfMemoryDescriptorTypeInvalid y el desplazamiento del segundo paquete se encuentra en el búfer descrito por el primer paquete.
Si se asigna un único paquete, el controlador debe asignar un búfer alineado a la página con una longitud divisible por página. El desplazamiento del paquete único también debe ser cero. La plataforma ACX asigna este paquete al modo de usuario dos veces, consecutivamente.
| paquete 0 | paquete 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:
| paquete 0 | paquete 1 |
Con el streaming inicial de paquetes ACX, solo hay dos paquetes asignados al principio. Una vez realizada la asignación y el mapeo, la asignación de memoria virtual del cliente sigue siendo válida y no cambia durante la vida útil de la secuencia. Hay un evento asociado al flujo para indicar la finalización de ambos paquetes. También hay un búfer compartido que usa el marco de ACX para comunicar qué paquete finalizó con el evento.
Para PacketCount=1, si la aplicación solicita 10 ms de datos, la pila de audio envía una solicitud de un único búfer de 10 ms al controlador (no duplica el tamaño del búfer enviado al controlador).
El controlador asigna un búfer alineado a la página que tiene al menos 10 ms de longitud. Para un flujo de 48 k, 2 canales, 2 bytes por muestra, el búfer, impulsado por temporizador, más pequeño que se puede asignar es de 1,024 muestras (una página de memoria), lo que equivale a 21.333 ms. Para un flujo de 48k 8ch 2 bytes por muestra, el búfer más pequeño controlado por temporizador que se puede asignar es de 512 muestras (una página de memoria) o 10.667 ms. Para un flujo de 48 kHz 6 canales, 2 bytes por muestra, el búfer más pequeño controlado por temporizador sigue siendo de 1.024 muestras (tres páginas de memoria, para asegurarse de que el final de una muestra se alinea con el final del búfer), lo que equivale a 21,333 ms.
El framework de ACX asigna este buffer alineado a página en el proceso de modo de usuario dos veces, de manera sucesiva. Después, el proceso de modo de usuario puede escribir datos equivalentes al tamaño de un búfer en el mapeo en modo de usuario a partir de cualquier lugar del búfer sin necesidad de realizar ningún ajuste.
El controlador llama a NotifyPacketComplete después de leer todo el paquete de la memoria del sistema, por lo que el sistema sabe que puede escribir el siguiente paquete de datos de audio en el búfer del paquete.
Hay un retraso entre NotifyPacketComplete y el momento en que se renderiza la última muestra de ese paquete. Este retraso se expresa como resultado de EvtAcxStreamGetHwLatency.
Búferes de ping-pong
Se pueden usar búferes de ping-pong, donde se lee un búfer (ping), mientras que el otro se rellena (pong). Esto permite procesar un búfer mientras el otro recopila el siguiente conjunto de datos. En ACX, el controlador se encarga internamente del cambio cuando se rellena un búfer. Una vez rellenado el búfer de ping, se notifica mediante un callback registrado. En el callback, se obtiene la dirección del búfer procesado y se envía de nuevo el búfer. Mientras tanto, el búfer de pong recopila datos en segundo plano. Este mecanismo garantiza el procesamiento continuo de datos sin interrupciones.
Para un búfer de ping-pong, el tamaño de paquete solicitado es para un solo búfer (ping o pong) y el recuento de paquetes es dos.
Al compartir un único búfer entre dos paquetes, configure el segundo paquete como se describe en la función de devolución de llamada EVT_ACX_STREAM_ALLOCATE_RTPACKETS. La parte del búfer descrito por el primer paquete (memoria, desplazamiento y longitud) es el búfer de ping, mientras que la parte descrita por el segundo paquete (sin memoria para indicar que el búfer se comparte con el primer paquete, más el desplazamiento que apunta al búfer justo después del primer paquete) es el búfer de pong.
Adición de información adicional al encabezado de paquete
Solo es posible agregar información adicional a la información del encabezado del paquete, por ejemplo, para registrar o contabilidad, al comienzo del paquete para flujos basados en eventos de ping/pong (donde el recuento de paquetes = 2). En el caso de las secuencias impulsadas por temporizador con un solo paquete, el paquete debe estar totalmente alineado con las páginas (comenzando y terminando en una frontera de página) porque el paquete se asigna al modo de usuario dos veces.
En este caso, la aplicación puede escribir más allá del final de la primera asignación en la segunda asignación, escribiendo primero al final del búfer del sistema y luego al principio del mismo búfer del sistema.
El único búfer asignado debe estar alineado en la página ya que la asignación de memoria virtual en el modo de usuario ocurre por cada página.
Búferes controlados por temporizador
Los búferes controlados por temporizadores en ACX se pueden usar para garantizar una experiencia de audio sin problemas manteniendo un tiempo y una sincronización precisos. Para los búferes controlados por temporizadores en ACX:
- El cliente usa el valor de EvtAcxStreamGetPresentationPosition para determinar cuántos fotogramas se pueden escribir.
- La posición de presentación debe actualizarse más de una vez durante cada pasada por el búfer. El cliente escribe en el búfer comenzando en la posición donde escribió por última vez hasta la posición que informa el controlador (lo cual debería ser los datos que el hardware ha consumido desde la última vez que se consultó la posición).
- Cuanto más granular sea la posición, menos probable es que experimente fallos.
- En los búferes controlados por temporizador, el DSP no debe consumir todo el búfer antes de actualizar la posición.
- En el modo basado en temporizador, el controlador podría dividir un único búfer basado en temporizador en varios búferes DSP, actualizando la posición a medida que el DSP procesa cada búfer. Por ejemplo, un búfer de 20 ms basado en temporizador, dividido en 10 búferes de 2 ms, funcionaría razonablemente bien en modo basado en temporizador.
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 hay cambios en los tamaños de búfer mínimo y máximo.
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 secuencia, cada objeto de secuencia en la Ruta de Audio del Punto Final de la secuencia recibe un evento de notificación del marco 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 uno más activo, el circuito de streaming (que registró el SINK) recibe primero el evento. Una vez que el circuito gestiona el evento, el siguiente circuito en la ruta de audio del punto final recibe el evento.
En el caso de las secuencias de renderizado que pasan de un estado más activo a uno menos activo, el circuito de transmisión recibe el evento en último lugar.
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 recibe el último evento.
En El caso de las secuencias de captura que van de un estado más activo a un estado menos activo, el circuito de streaming recibe primero el evento.
El orden es el valor predeterminado proporcionado por el framework 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
Después de crear la secuencia y asignar los búferes adecuados, la secuencia se encuentra en el estado Pausar y espera a que se inicie la secuencia. Cuando el cliente coloca la secuencia en estado Play, el marco de ACX llama 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 coloca en el estado Play y los datos comienzan a fluir.
Representación de datos de audio
Después de crear la secuencia y asignar los recursos, la aplicación llama a Start en la secuencia para iniciar la reproducción. La aplicación debe llamar a GetBuffer/ReleaseBuffer antes de iniciar la secuencia para asegurarse de que el primer paquete que comienza a reproducir tiene datos de audio válidos.
El cliente comienza prerollando un búfer. Cuando el cliente invoca ReleaseBuffer, esto se traduce en una llamada dentro de AudioKSE que a su vez invoca a la capa ACX, la cual llama a EvtAcxStreamSetRenderPacket en el ACXSTREAM activo. La propiedad incluye el índice de paquetes (indexación basada en cero) y, si procede, una bandera EOS con el desplazamiento en bytes del final de la secuencia en el paquete actual.
Una vez que el circuito de streaming finaliza con un paquete, desencadena la notificación de finalización de búfer que libera a los clientes que esperan para rellenar el siguiente paquete con datos de audio para renderizar.
El modo de transmisión controlado por temporizador se admite y se indica mediante el uso de un valor PacketCount de 1 cuando se llama a la devolución de llamada del controlador EvtAcxStreamAllocateRtPackets.
Captura de datos de audio
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 framework de ACX indica el evento de notificación de flujo.
Una vez que se haya señalado la notificación de flujo, el cliente puede enviar KSPROPERTY_RTAUDIO_GETREADPACKET para obtener el índice (con base cero) del paquete que ha finalizado la captura. Cuando el cliente envía GETCAPTUREPACKET, el controlador puede suponer que se procesan 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 framework ACX actualiza esta estructura antes de que se complete la señalización del paquete.
Comportamiento del streaming del kernel KS heredado
A veces, como cuando un controlador implementa la captura de ráfaga (como un spotter de palabra clave), debe usar el comportamiento de control de paquetes de streaming de kernel heredado en lugar de PacketVRegister. Para usar el comportamiento anterior basado en paquetes, el controlador devuelve 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 marco ACX llama a la función de callback EvtAcxStreamGetPresentationPosition para obtener la posición actual de la secuencia. La posición actual de la secuencia incluye 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 admite 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 cero y el valor de QPC tomado lo más cerca posible de la finalización del paquete (por ejemplo, la rutina de servicio de interrupción puede calcular el valor QPC). Los clientes pueden obtener esta información 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 (por lo que el cliente puede comprobar que CompletedPacketCount y CompletedPacketQPC son 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 realiza la transición de la secuencia desde el estado AcxStreamStateStop al estado AcxStreamStatePause. El controlador debe reservar hardware necesario, como motores DMA, cuando recibe EvtAcxStreamPrepareHardware.
- EvtAcxStreamRun realiza la transición de la secuencia desde el estado AcxStreamStatePause al estado AcxStreamStateRun.
- EvtAcxStreamPause realiza la transición de la secuencia del estado AcxStreamStateRun al estado AcxStreamStatePause.
- EvtAcxStreamReleaseHardware realiza la transición de la secuencia desde el estado AcxStreamStatePause al estado AcxStreamStateStop. El controlador debe liberar el hardware necesario, como los motores DMA, cuando recibe EvtAcxStreamReleaseHardware.
La secuencia puede recibir el callback EvtAcxStreamPrepareHardware después de recibir el callback EvtAcxStreamReleaseHardware. Esto hace que la secuencia vuelva al estado AcxStreamStatePause.
La asignación de paquetes con EvtAcxStreamAllocateRtPackets normalmente se produce antes de la primera llamada a EvtAcxStreamPrepareHardware. Los paquetes asignados se liberan normalmente con EvtAcxStreamFreeRtPackets después de la última llamada a EvtAcxStreamReleaseHardware. Esta ordenación no está garantizada.
El estado AcxStreamStateAcquire no se utiliza. ACX quita la necesidad de que el controlador tenga el estado de adquisición porque este estado está implícito con las devoluciones de llamada de hardware de preparación (EvtAcxStreamPrepareHardware) y hardware de lanzamiento (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 recibe EvtAcxStreamPause y EvtAcxStreamReleaseHardware antes de que el marco de ACX 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 llama a EvtCleanupCallback cuando el marco intenta eliminar el objeto. No utilice EvtDestroyCallback, al que solo se llama después de liberar 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 están limpios en EvtAcxStreamReleaseHardware.
El controlador no debe limpiar los recursos que admiten el flujo hasta que el cliente lo solicite.
No se utiliza el estado AcxStreamStateAcquire. ACX elimina la necesidad de que el controlador mantenga el estado de adquisición, ya que este se encuentra implícito en las funciones de devolución de llamada para la preparación (EvtAcxStreamPrepareHardware) y liberación de hardware (EvtAcxStreamReleaseHardware).
Eliminación e invalidación de secuencias sorpresas
Si el controlador determina que la secuencia no es válida (por ejemplo, el conector está desconectado), el circuito apaga 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). No coloque la eliminación de cualquier elemento compartido en el contexto de destrucción de un objeto en 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, que es indeterminada.
En general, cuando se cierra el identificador, se invoca la llamada de devolución para la limpieza de la secuencia. Una excepción es cuando el controlador genera la secuencia dentro de su función de devolución de llamada. Si ACX no puede agregar esta secuencia a su puente de flujo justo antes de volver de la operación de creación de flujos, la secuencia se cancela de forma asincrónica y el subproceso actual devuelve un error al cliente de creación de secuencias. El stream no debería tener asignaciones de memoria en este momento. Para obtener más información, consulte EVT_ACX_STREAM_RELEASE_HARDWARE callback.
Secuencia de limpieza de memoria de transmisión
El búfer de flujo es un recurso del sistema y debe liberarse solo cuando el cliente en modo de usuario cierre el controlador de flujo. El búfer (que es diferente de los recursos de hardware del dispositivo) tiene la misma duración que el gestor de la secuencia. Cuando el cliente cierra el manejador, ACX invoca el callback de limpieza del objeto de secuencia y luego el callback de eliminación del objeto de secuencia cuando el recuento de referencias del objeto llega a cero.
Es posible que ACX aplazara 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. Esta retrollamada se produce cuando el usuario cierra los manipuladores de flujo.
Dado que los búferes de RT se asignan en modo usuario, su duración coincide con la del identificador. El controlador no debe liberar ni desbloquear los búferes de audio antes de que ACX invoque esta devolución de llamada.
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 porque el cliente de modo usuario puede retener sus identificadores durante mucho tiempo. El conductor no debe esperar a que estos controladores desaparezcan. Esta acción crea una comprobación de errores 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 llamada a AcxDeviceRemoveCircuit y, a continuación, libera 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:
- Dispositivo WDF de liberación de hardware: libera los recursos de hardware del dispositivo.
- AcxStreamFreeRtPackets: libera el búfer de audio asociado al controlador.
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 recibe 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 in KSSTREAM_HEADER structure (ks.h).
El controlador sigue aumentando CurrentRenderPacket a medida que se representan los paquetes en lugar de cambiar CurrentRenderPacket para que coincida con este valor.
EvtAcxStreamGetCurrentPacket
EvtAcxStreamGetCurrentPacket indica al controlador que señale qué paquete (basado en cero) se está procesando actualmente en el hardware o se está llenando actualmente por el hardware de captura.
EvtAcxStreamGetCapturePacket
EvtAcxStreamGetCapturePacket indica al controlador que indique qué paquete (basado en cero) se llenó más recientemente, incluido el valor QPC en el momento en que el controlador comenzó a rellenar 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
Las SIGUIENTES API administran el estado de streaming de acXSTREAM.
- EVT_ACX_STREAM_PREPARE_HARDWARE
- EVT_ACX_STREAM_RELEASE_HARDWARE
- EVT_ACX_STREAM_RUN
- EVT_ACX_STREAM_PAUSE
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 cero se incluyen para mejorar el rendimiento del cliente. El marco de ACX establece los eventos de notificación asociados a la secuencia.