En este contenido, se describe la reproducción de campanillas en el renderizador de alta disponibilidad (HAR).
Un crate Audio expone AudioManager a la app de HAR, que controla la reproducción de campanillas.
Para mantener la latencia baja, los subprocesos de reproducción se ejecutan durante todo el ciclo de vida de la app, y permanecen inactivos y ceden cuando no se reproduce audio.
Terminología
- recurso
AudioAssetpertenece al audio reproducible. Los recursos son conocidos y existen en el tiempo de ejecución de la app.- dispositivo
AudioDevicehace referencia a un bus independiente para la reproducción de audio. El dispositivo es la unidad más detallada relacionada con el hardware al que accede el sistema. En la implementación estándar de SDVM,AudioDevicehace referencia a un solo PCM de la Arquitectura Avanzada para el Sonido en Linux (ALSA).- transmisión
- Una instancia de reproducción de un recurso en un dispositivo. Las transmisiones persisten desde el momento en que se programan hasta que se completan, se cancelan o terminan con un error.
Componentes
En la Figura 1, se muestra el diagrama de componentes de la campanilla:
Figura 1: Diagrama de componentes
Dispositivo de audio y PCM
La configuración del hardware de audio sigue el diseño estándar de la capa de abstracción de la plataforma HAR, y har-platform-api la contiene.
El crate Audio de HAR define una nueva estructura para AudioDevice, que define
campos para todas las estructuras de datos que afectan el crate Audio interno de HAR
y la reproducción. AudioDevice también usa genéricos para encapsular parámetros adicionales potenciales específicos de la plataforma. En el caso de tinyalsa, PlatformAudioDevice contiene los descriptores y las propiedades de un PCM de ALSA.
/// NOTE: The following code is a sample definition to help understanding, it is not a
/// representation of the final code/implementation.
AudioDevice<PlatformAudioDevice> {
/// Internal HAR Identifier for the device.
AudioDeviceID,
/// The size (in bytes) for chunks of audio data to stream to the device.
ChunkSize,
/// Properties necessary to control volume (details in "Mixer control" section).
VolumeControl,
/// Properties necessary to control spatialization (details in "Mixer control"
/// section).
SpatialControl,
/// Platform specific data for the AudioDevice.
/// E.g. ALSA properties and reference to opened PCM.
PlatformAudioDevice
}
/// Elaboration of the previously mentioned VolumeControl
VolumeControl {
/// Identifier for the control used to change volume.
ControlID,
/// Mapping between Decibel and control values. (see Mixer control section)
VolumeOutputIndex
}
Elementos de audio
En esta sección, se describe cómo se configuran y se implementan los elementos de audio.
Configuración
La implementación inicial de audio de HAR admite elementos de audio configurados de forma estática. Una configuración JSON define qué elementos están disponibles y cuáles se definen como archivos WAV.
La implementación también admite elementos de audio sintetizados y transmitidos a través de una implementación de elementos más genérica, que acepta una función para generar datos de audio.
Implementación
Implementa elementos con dos construcciones separadas, AudioAsset y AudioStream.
AudioAsset define las propiedades estáticas de un elemento y un contenedor para los posibles datos internos relacionados con el elemento. De AudioAsset AudioStream se puede derivar, que es una sola instancia de transmisión del elemento. AudioStream
contiene un estado interno relacionado con la reproducción de transmisión singular.
/// NOTE: The following code is a sample definition to help understanding, it is not a
/// representation of the final code/implementation.
/// Static properties and definition of an Asset.
AudioAsset {
/// Perform optional initialization steps, e.g. load bytes from file into memory.
/// Can also define lazy loading, to load data at first playback instead.
fn initialize(LazyLoad);
/// Create a new AudioStream from the asset.
fn create_stream() -> AudioStream;
/// More functions for metadata etc. of the asset.
...
}
/// Single streamable instance of an AudioAsset
AudioStream {
/// Gets the next bytes to play from the Asset together with if the current chunk of
/// bytes contains any control signals (e.g. fade-out).
fn get_playback(num_bytes: usize) -> ([u8], ControlSignals);
/// Gets playback Mode details used to handle special states of playback
/// e.g. when a chime gets is interrupted and put in "fade-out" mode.
fn playback_mode() -> PlaybackMode;
/// [0.0, 1.0] indication of how much of the stream was played.
fn progress() -> f32;
/// Reset the stream, e.g. if it should play again.
fn reset();
/// Time of which the stream was created.
fn created_at() -> Instant;
/// Additional metadata etc. for the stream.
...
}
Reproducción de campanillas
En esta sección, se describe la API y el procedimiento para la reproducción de una campanilla. La reproducción de una campanilla singular se conoce como transmisión.
Ciclo de vida de una transmisión
En la Figura 2, se ilustra el ciclo de vida de una transmisión:
Figura 2: Reproducción y eventos de transmisión
En la Figura 2, se describen estos pasos:
Reproducir: Programa la transmisión para que se reproduzca.
Priorizar: La priorización de la reproducción decide si se debe hacer lo siguiente:
- Reproducir la campanilla ahora (evento iniciado cuando se reciben los primeros bytes)
- Reproducir la campanilla más tarde (evento pausado o reanudado)
- Quitar la prioridad de la campanilla (evento cancelado)
Controles del mezclador: Si es necesario, actualiza los controles del mezclador en función de los comportamientos configurados.
Escribir bytes: Escribe un fragmento de bytes en
AudioDevice.Más datos: Si la transmisión tiene más datos, vuelve al paso 2.
Repetir: Si se debe repetir la transmisión, restablece y vuelve al paso 2 (evento reiniciado).
Completado: La transmisión se completó correctamente (evento
FinishedSuccessfully).
La campanilla se puede interrumpir con llamadas de pausa, reanudación o detención en cualquier momento.
Prioridades de las campanillas
Esta lógica establece las prioridades de las campanillas:
Anulaciones del modo de reproducción. Por ejemplo, a una campanilla en el modo de atenuación siempre se le otorga la máxima prioridad hasta que se completa la atenuación.
Prioridad especificada.
Si la prioridad igual es más reciente, la campanilla se reproduce primero.
Cuando las campanillas tienen la misma prioridad, se crea una instancia de AudioManager con un valor enum.
API
Eventos
Si se proporciona un canal de eventos cuando se inicia la campanilla, Audio de HAR emite varios eventos durante la reproducción. Los eventos admitidos se muestran en este ejemplo:
/// NOTE: The following code is a sample definition to help understanding, it is not a
/// representation of the final code/implementation.
StreamBehaviors<PlatformStreamBehaviors> {
/// What should happen if the stream is interrupted for a higher priority stream.
/// e.g. pause-and-resume or cancel, will also define preference for fade-out.
OverrunBehavior,
/// Urgency, if interrupted streams are allowed to "fade-out", or if the stream should
/// urgently disrupt any other playback.
Optional<Urgency>,
/// Priority for the stream (or minimum if not specified).
Optional<StreamPriority>
/// Descriptor if a stream should be played on repeat.
Optional<RepeatBehavior>
/// Volume, if the stream should play at a specific volume.
Optional<Volume>
/// Spatialization, if the stream should play with specific spatialization.
Optional<Spatialization>
/// Optional generic for future expandability of the API, or pass-through of platform
/// specific Stream Behaviors
Optional<PlatformStreamBehaviors>
}
/// Plays a chime on specified device with given behaviors. StreamEvents are delivered
/// using the provided event transmitter. This method won't wait for any events.
fn play(AudioDeviceID, AssetID, StreamBehaviors, Option<EventTransmitter>) -> StreamController
/// Object used to control a Stream.
StreamController {
/// Gets the current state/metadata of a stream (e.g. ID, progress, playback_state).
fn metadata() -> StreamMetadata
/// Stops the stream.
fn stop()
/// Pauses a given stream, if the specified duration expires the stream is cancelled.
/// Timeout is required to make sure there are no paused streams left indefinitely
/// pending resumption.
fn pause(TimeoutDuration)
/// Resumes a paused stream.
fn resume()
/// Updates the spatialization of a playing stream.
fn set_spatialization(Spatialization)
/// Updates the volume of a playing stream.
fn set_volume(Volume)
}
Control del mezclador
En esta sección, se describe cómo se controlan el volumen y la espacialización.
Volumen
HAR define el volumen de manera coherente en milibelios. El crate har-platform-api controla la conversión de milibelios a señal de control.
La relación entre los milibelios y la potencia de salida del hardware es logarítmica y varía mucho entre las diferentes configuraciones de hardware y bocinas. Como resultado,
proporciona la configuración entre los valores como parte de AudioDevice
(dispositivo de audio y PCM) configuración, y la conversión debe realizarse antes de
llamar a la capa de la plataforma.
Como resultado, la implementación en la API de PAL define dos funciones.
fn set_volume_millibel(AudioDeviceID, Millibel) {
/// Default implementation with conversion using DeviceConfig.
}
fn set_volume_control(AudioDeviceID, ControlValue);
La implementación predeterminada para set_volume_millibel usa la configuración proporcionada para AudioDevice, incluido un conjunto de pares clave-valor para el milibelio de referencia - control, transforma el milibelio para controlar los valores y, luego, llama a la función set_volume_control con el valor convertido.
Este diseño proporciona un valor predeterminado y permite que las implementaciones posteriores anulen la asignación predeterminada.
Figura 3: Flujo de audio de HAR
Espacialización
La API de Audio expone la funcionalidad para controlar en qué área espacial deben reproducirse los datos de audio. Estos parámetros se pasan a la capa de PAL y se aplican de forma descendente con los controles de hardware. Las opciones se definen como parte de la API de PAL de la siguiente manera:
/// NOTE: The following code is a sample definition to help understanding, it is not a
/// representation of the final code/implementation.
enum Spatialization {
Front,
FrontLeft,
FrontRight,
Center, // No spatialization
Rear,
RearLeft,
RearRight,
Right,
Left
}
Niveles de control del mezclador
Puedes definir el volumen y la espacialización en un elemento y para una transmisión. Si defines una prioridad de transmisión, la transmisión anula los controles definidos por el elemento.
Administración de subprocesos
El administrador de audio mantiene un subproceso por instancia de AudioDevice. Cada subproceso funciona de forma independiente. La interacción entre AudioManager y el subproceso de reproducción usa una cola de transmisión compartida ordenada por prioridad.
Las llamadas a ALSA usan escrituras ASYNC con sondeo para determinar cuándo se procesan los datos.
Figura 4: Secuencia de administración de subprocesos
Señales de control durante el sondeo
Cuando se espera que la tarjeta de sonido procese bytes, se pueden emitir señales de control. Por ejemplo, para cambiar la atenuación o la espacialización del audio. El sondeo para obtener el estado del dispositivo de audio se configura en el nivel AudioManager o se establece de forma predeterminada en 1 milisegundo. Después de cada ciclo de sondeo, el subproceso de reproducción procesa y emite cualquier comando de control temporizado.
Administración de búfer
Para minimizar la latencia de interrupción, los tamaños de búfer escritos en el dispositivo se mantienen pequeños. Cuando se usa TinyALSA como valor predeterminado, el tamaño del búfer se configura para que sea el mismo que el parámetro startup_threshold.
TinyALSA define el valor predeterminado como todo el búfer de dispositivo asignado
dividido por dos.
Interrupción de la transmisión
Cuando se interrumpen las transmisiones, estas mantienen la prioridad del subproceso hasta que se vacían los datos que escribieron en la tarjeta. Como resultado, se produce un período de transición entre la interrupción y la transmisión nueva.
Por ejemplo, si una muestra de audio en HAR usa lo siguiente:
- Tamaño de 3,072
- Velocidad de 48,000
- Tamaño de muestra de dos
El búfer pendiente se calcula como 3,072 y 6,144 fotogramas, lo que genera un retraso de interrupción de 64 a 128 milisegundos. Una implementación de producción requeriría un búfer más pequeño.
Administración de errores y riesgos
En esta sección, se describe cómo se administran los errores y los posibles riesgos.
Transmisiones obsoletas y falta de recursos en la cola
Dado que AudioStream se puede pausar y que la reproducción solo puede ocurrir desde la instancia AudioStream de máxima prioridad, surge el riesgo de que una cola en crecimiento prive de recursos a las transmisiones de baja prioridad.
Para evitar que esto suceda, cada cola tiene un tamaño configurable. Cuando se supera este valor, se descarta la transmisión de menor prioridad.
Supervisa y alerta
En producción, el monitor de seguridad hace un seguimiento de las funciones de audio para garantizar que la reproducción se realice según lo previsto.
AudioManager supervisa las estadísticas internas específicas de las latencias y una marca que define el rendimiento del registro. Después de establecer estos umbrales, se generan registros de advertencia para todas las compilaciones de depuración cuando sucede lo siguiente:
- La duración entre la programación y el inicio de la reproducción supera los
xmilisegundos. - (Para una transmisión no interrumpida) la longitud del elemento y el tiempo de reproducción difieren en más de
ypor ciento.
Dispositivo bloqueado
Siempre existe un pequeño riesgo de que un dispositivo de audio deje de responder, por ejemplo, si otro proceso del sistema lo asigna y escribe en él. Dado que la reproducción se ejecuta de forma asíncrona en subprocesos separados y que las campanillas se pueden poner en cola para reproducirse más tarde, esto es completamente transparente para la app que realiza la llamada.
Para detectar esto, se realiza una verificación de estado del subproceso cada vez que se programa la reproducción de una campanilla nueva, y se muestra un error si un subproceso de reproducción tiene una cola propagada y no procesó ningún byte nuevo durante el último segundo.
Para fines futuros, puede ser necesario intentar reiniciar o abrir dispositivos, pero, para la implementación inicial, los errores no deben ser invisibles.
Estructura del código
En un nivel alto, el código relacionado con la reproducción de campanillas existe en los siguientes crates:
CRATE: display-safety/crates/(harry-app|harry)
La app de HAR existente, que emite llamadas para reproducir campanillas.
NEW CRATE: display-safety/crates/audio
NUEVO: Crate para administrar el control y la reproducción de audio (aquí es donde existe la mayor parte de la funcionalidad).
CRATE: display-safety/crates/har-platform-api/audio
PAL, incluidas todas las llamadas al sistema necesarias para el audio.
CRATE: display-safety/crates/har-platform-(android|linux)/audio
Llamadas a tinyalsa-rs para la reproducción con TinyALSA. La compatibilidad con QNX no se implementa en la solución inicial, y esto aumentará a medida que se admitan más plataformas.
TINYALSA PAL: display-safety/crates/tinyalsa-audio
Código específico de TinyALSA para la reproducción. Las implementaciones de las plataformas Android y Linux usan esta función.
CRATE: display-safety/crates/tinyalsa-rs
Vinculaciones de Rust para la implementación en C de TinyALSA
Detalles de la implementación de Rust
Estos son algunos detalles de implementación específicos:
- Todas las funciones de la API muestran
Result<X, AudioError>, dondeXes () o un valor de retorno. - Ninguna función de la API está marcada como
unsafe. - Los mecanismos de mutex y sincronización son internos y no se exponen en la API de
AudioManager.
Modelo de propiedad y AudioManager
Toda la interacción de la app con el sistema de audio se realiza a través de
AudioManagero los objetos que muestraAudioManager.AudioManageres seguro para subprocesos.AudioManagerse crea una vez en la app de HARry yMoved, para queLoopertenga la propiedad.AudioManagerusa un tokentokio_util::CancellationTokenpara administrar sus subprocesos de reproducción iniciados, lo que garantiza que los subprocesos finalicen y se liberen los recursos siAudioManageresDropped.AudioManagerno impide explícitamente que se creen varias instancias. Si existe más de una instancia, se registra con el nivelwarn.
Propiedad compartida
Varios objetos tienen una propiedad compartida encapsulada y sincronizada con acceso exclusivo. Estos mecanismos no se exponen en la API de AudioManager, pero son internos para las implementaciones de audio y PAL.
AudioDevice- Cada referencia de hardware (por ejemplo, TinyALSA PCM) que se abre (tiene un controlador) tiene acceso exclusivo. Consulta Diseño de SMP.Las instancias de
AudioStreamtienen acceso exclusivo después de que se programan para la reproducción, ya que la app puede controlarlas y el subproceso de reproducción puede acceder a ellas de forma simultánea.El subproceso de reproducción no mantiene bloqueos durante la reproducción, pero crea una instantánea inmutable del siguiente búfer para reproducir y no considera los cambios hasta que se procesa el siguiente búfer.
Cada subproceso de reproducción tiene una cola de reproducción, una referencia compartida entre
AudioManagery el subproceso de reproducción. Como resultado, el subproceso necesita acceso exclusivo para las mutaciones.Los subprocesos sin transmisiones permanecen inactivos con la
Condvarvariable para recibir eventos de activación cuando se detectan datos nuevos. Este mecanismo tiene propiedad compartida.
Dependencias
Los crates y el crate de audio están diseñados para reducir las dependencias de los crates que no están aprobados para compilarse en el árbol de origen de Android. Consulta esta lista de crates incluidos crates.
Las implementaciones de plataformas descendentes para Android y Linux dependen de
TinyALSA y del crate tinyalsa-rs de seguridad de pantalla existente.
Atributos de calidad
Confiabilidad
Si bien la reproducción de audio es fundamental para la seguridad, este diseño no abarca la implementación de una supervisión de seguridad. Implementa esto en un esfuerzo independiente para verificar la confiabilidad de la reproducción de audio en el hardware y en la producción.
Escalabilidad
El enfoque de un subproceso por dispositivo está diseñado para escalarse a diferentes configuraciones de hardware. Dado que cada subproceso está principalmente inactivo, espera datos o espera que el dispositivo procese los datos escritos, no debería ser exigente con el procesador ni intensivo en el rendimiento del sistema.
La decisión de diseño de reproducir datos solo en un dispositivo, combinada con los comandos de control del mezclador para todo el control de salida adicional, garantiza que el hardware de sonido controle la salida exacta y que se escale para los sistemas futuros.
Latencia
La latencia es fundamental para el sistema de audio, por lo que, después de la implementación, se define un conjunto de objetivos de nivel de servicio (SLO) para la latencia del sistema. Para supervisar continuamente el estado de la latencia, supervisa en los registros del sistema que no cumplan con los SLO definidos en todas las compilaciones de depuración.
Para las versiones de producción, los datos de supervisión se pasan a algún sistema externo a la implementación de audio, en lugar de depender de los registros.
Prueba y estrategia de prueba
Los crates y el crate de audio están diseñados con cobertura de pruebas. Agregamos una implementación de plataforma simulada para confirmar que se prueban todas las capacidades.
La complejidad del hardware y las vinculaciones impiden una cobertura de pruebas extensa para las implementaciones de plataformas. Proporcionamos implementaciones de muestra para probar manualmente la solución en el hardware y en el emulador de Cuttlefish.
Documentación
En el archivo README.md de Audio crates/audio, se describe cómo usar AudioManager. crates/audio/examples contiene ejemplos de lo siguiente:
- Implementar una plataforma
- Crear una instancia de
AudioManager - Reproducir
WavAsset - Reproducir un elemento de función personalizada en repetición
- Registrar eventos de reproducción