Sonidos de notificación

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
AudioAsset pertenece al audio reproducible. Los recursos son conocidos y existen en el tiempo de ejecución de la app.
dispositivo
AudioDevice hace 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, AudioDevice hace 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:

Diagrama de componentes

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:

Reproducción y eventos de transmisiones

Figura 2: Reproducción y eventos de transmisión

En la Figura 2, se describen estos pasos:

  1. Reproducir: Programa la transmisión para que se reproduzca.

  2. 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)
  3. Controles del mezclador: Si es necesario, actualiza los controles del mezclador en función de los comportamientos configurados.

  4. Escribir bytes: Escribe un fragmento de bytes en AudioDevice.

  5. Más datos: Si la transmisión tiene más datos, vuelve al paso 2.

  6. Repetir: Si se debe repetir la transmisión, restablece y vuelve al paso 2 (evento reiniciado).

  7. 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:

  1. 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.

  2. Prioridad especificada.

  3. 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.

Flujo de audio de HAR

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.

Secuencia de administración de subprocesos

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 x milisegundos.
  • (Para una transmisión no interrumpida) la longitud del elemento y el tiempo de reproducción difieren en más de y por 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>, donde X es () 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 AudioManager o los objetos que muestra AudioManager.

  • AudioManager es seguro para subprocesos.

  • AudioManager se crea una vez en la app de HARry y Moved, para que Looper tenga la propiedad.

  • AudioManager usa un token tokio_util::CancellationToken para administrar sus subprocesos de reproducción iniciados, lo que garantiza que los subprocesos finalicen y se liberen los recursos si AudioManager es Dropped.

  • AudioManager no impide explícitamente que se creen varias instancias. Si existe más de una instancia, se registra con el nivel warn.

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 AudioStream tienen 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 AudioManager y el subproceso de reproducción. Como resultado, el subproceso necesita acceso exclusivo para las mutaciones.

  • Los subprocesos sin transmisiones permanecen inactivos con la Condvar variable 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