Notifications sonores

Ce contenu décrit la lecture des carillons dans le moteur de rendu haute disponibilité (HAR, High Availability Renderer). Une caisse Audio expose AudioManager à l'application HAR, qui contrôle la lecture du carillon.

Pour maintenir une faible latence, les threads de lecture s'exécutent tout au long du cycle de vie de l'application, en restant inactifs et en cédant la place lorsqu'aucun son n'est lu.

Terminologie

actif [Pls check definition]
AudioAsset concerne les contenus audio lisibles. Les composants sont connus et existent dans l'exécution de l'application.
appareil
AudioDevice fait référence à un bus distinct pour la lecture audio. L'appareil est l'unité la plus précise concernant le matériel auquel le système accède. Dans l'implémentation SDVM standard, AudioDevice fait référence à un seul PCM ALSA (Advanced Linux Sound Architecture).
flux
Instance de lecture d'un élément sur un appareil. Les flux persistent à partir du moment où ils sont planifiés jusqu'à ce qu'ils soient terminés, annulés ou qu'ils se terminent par une erreur.

Composants

La figure 1 affiche le diagramme des composants pour Chime :

Diagramme de composants

Figure 1 : Diagramme de composants.

Appareil audio et PCM

La configuration du matériel audio suit la conception standard de la couche d'abstraction de la plate-forme HAR et har-platform-api la contient.

La crate HAR Audio définit une nouvelle structure pour AudioDevice, qui définit les champs pour toutes les structures de données qui affectent la crate HAR Audio interne et la lecture. AudioDevice utilise également des génériques pour encapsuler les éventuels paramètres supplémentaires spécifiques à la plate-forme. Dans le cas de tinyalsa, PlatformAudioDevice contient les descripteurs et les propriétés d'un PCM 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
}

Composants audio

Cette section décrit comment les composants audio sont configurés et implémentés.

Configuration

L'implémentation audio HAR initiale est compatible avec les composants audio configurés de manière statique. Une configuration JSON définit les composants disponibles et ceux définis comme fichiers WAV.

L'implémentation est également compatible avec les éléments audio synthétisés et diffusés en streaming via une implémentation d'éléments plus générique, qui accepte une fonction permettant de générer des données audio.

Implémentation

Implémentez les composants à l'aide de deux constructions distinctes, AudioAsset et AudioStream.

AudioAsset définit les propriétés statiques d'un élément et un conteneur pour les données internes potentielles associées à l'élément. AudioAsset AudioStream peut être dérivé, ce qui correspond à une seule instance de l'asset pouvant être diffusée. AudioStream contient un état interne lié à la lecture du flux unique.

/// 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.
  ...
}

Lecture du carillon

Cette section décrit l'API et la procédure de lecture d'un carillon. La lecture d'un carillon unique est appelée flux.

Cycle de vie d'un flux

La figure 2 illustre le cycle de vie d'un flux :

Lecture et événements de flux

Figure 2 : Lecture et événements de flux

La figure 2 décrit ces étapes :

  1. Lire : programmez la lecture du flux.

  2. Prioriser : la priorisation de la lecture détermine si l'appareil doit :

    • Jouer la sonnerie maintenant (événement de début lorsque les premiers octets)
    • Lire le carillon plus tard (événement mis en pause ou repris)
    • Priorité faible pour la notification (événement annulé)
  3. Commandes du mixeur : si nécessaire, mettez à jour les commandes du mixeur en fonction des comportements configurés.

  4. Écrire des octets : écrire un bloc d'octets dans AudioDevice.

  5. Plus de données : si le flux contient plus de données, revenez à l'étape 2.

  6. Répéter : si le flux doit être répété, réinitialisez-le et revenez à l'étape 2 (événement redémarré).

  7. Terminé : le flux s'est terminé avec succès (événement FinishedSuccessfully).

Le carillon peut être interrompu à tout moment en mettant en pause, en reprenant ou en arrêtant les appels.

Priorités des carillons

Cette logique définit les priorités des carillons :

  1. Remplacements du mode de lecture. Par exemple, un carillon en mode fondu est toujours prioritaire jusqu'à ce que le fondu soit terminé.

  2. Priorité spécifiée.

  3. Si la priorité égale est plus récente, la sonnerie est lue en premier.

Lorsque les carillons ont la même priorité, AudioManager est instancié avec une valeur enum.

API

Événements

Si un canal d'événements est fourni au début de la sonnerie, HAR Audio émet un certain nombre d'événements pendant la lecture. Les événements acceptés sont présentés dans cet exemple :

/// 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)
}

Contrôle du mixeur

Cette section décrit comment le volume et la spatialisation sont contrôlés.

Volume

HAR définit le volume de manière cohérente en millibels. La caisse har-platform-api gère la conversion des millibels en signal de contrôle.

La relation entre les millibels et la puissance de sortie du matériel est logarithmique et varie considérablement en fonction des configurations matérielles et des haut-parleurs. Par conséquent, fournissez une configuration entre les valeurs dans le cadre de la configuration AudioDevice (Périphérique audio et PCM). La conversion doit avoir lieu avant l'appel de la couche de plate-forme.

Par conséquent, l'implémentation dans l'API PAL définit deux fonctions.

fn set_volume_millibel(AudioDeviceID, Millibel) {
  /// Default implementation with conversion using DeviceConfig.
}

fn set_volume_control(AudioDeviceID, ControlValue);

L'implémentation par défaut de set_volume_millibel utilise la configuration fournie pour AudioDevice, y compris un ensemble de paires clé/valeur pour le millibel de référence. Elle contrôle et transforme les millibels en valeurs de contrôle, puis appelle la fonction set_volume_control avec la valeur convertie.

Cette conception fournit une valeur par défaut et permet aux implémentations ultérieures de remplacer le mappage par défaut.

Flux audio HAR

Figure 3. Flux audio HAR.

Spatialisation

L'API Audio expose des fonctionnalités permettant de contrôler la zone spatiale dans laquelle les données audio doivent être lues. Ces paramètres sont transmis à la couche PAL et appliqués en aval à l'aide de commandes matérielles. Les options sont définies dans l'API PAL comme suit :

/// 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
}

Niveaux de contrôle du mixeur

Vous pouvez définir le volume et la spatialisation pour un élément et pour un flux. Si vous définissez une priorité de flux, le flux remplace les contrôles définis par le composant.

Gestion des threads

Le gestionnaire audio conserve un thread par instance AudioDevice. Chaque thread fonctionne de manière indépendante. L'interaction entre AudioManager et le thread de lecture utilise une file d'attente de flux partagée, triée par priorité.

Les appels ALSA utilisent des écritures ASYNC avec interrogation pour déterminer quand les données sont traitées.

Séquence de gestion des threads

Figure 4. Séquence de gestion des threads.

Signaux de contrôle lors de l'interrogation

Des signaux de contrôle peuvent être émis en attendant que la carte son digère les octets. Par exemple, pour modifier le fondu ou la spatialisation de l'audio. L'interrogation pour obtenir l'état de l'appareil audio est configurée au niveau AudioManager ou est définie par défaut sur 1 milliseconde. Après chaque cycle d'interrogation, le thread de lecture traite et émet les commandes de contrôle temporisées.

Gestion du tampon

Pour minimiser la latence d'interruption, les tailles de mémoire tampon écrites sur l'appareil sont maintenues à une petite taille. Lorsque TinyALSA est utilisé par défaut, la taille de la mémoire tampon est configurée pour être identique au paramètre startup_threshold. TinyALSA définit la valeur par défaut comme la moitié de la mémoire tampon de l'appareil allouée.

Interruption de la diffusion

Lorsque les flux sont interrompus, ils conservent la priorité du thread jusqu'à ce que les données qu'ils ont écrites sur la carte soient épuisées. Par conséquent, une période de transition a lieu entre l'interruption et le nouveau flux.

Par exemple,si un échantillon audio dans HAR utilise :

  • Taille de 3 072
  • Fréquence de 48 000
  • Taille de l'échantillon de deux

La mémoire tampon en attente est calculée sur la base de 3 072 et 6 144 images, ce qui entraîne un délai d'interruption de 64 à 128 millisecondes. Une implémentation en production nécessiterait un tampon plus petit.

Gestion des erreurs et risques

Cette section décrit la façon dont les erreurs sont gérées et les risques potentiels.

Flux obsolètes et manque de ressources dans la file d'attente

Étant donné que AudioStream peut être mis en pause et que la lecture ne peut avoir lieu qu'à partir de l'instance AudioStream de priorité la plus élevée, le risque est qu'une file d'attente croissante affame les flux de faible priorité.

Pour éviter cela, chaque file d'attente est limitée à une taille configurable. Lorsque cette valeur est dépassée, le flux de priorité la plus faible est supprimé.

Surveillance et alertes

En production, le moniteur de sécurité suit les caractéristiques audio pour s'assurer que la lecture se déroule comme prévu.

AudioManager surveille les statistiques internes spécifiques aux latences et un indicateur qui définit les performances de journalisation. Une fois ces seuils définis, des journaux d'avertissement sont générés pour toutes les versions de débogage lorsque :

  • La durée entre la programmation et le début de la lecture dépasse x millisecondes.
  • (Pour un flux non interrompu) La durée de l'élément et la durée de lecture diffèrent de plus de y %.

Appareil bloqué

Il existe toujours un petit risque qu'un appareil audio ne réponde plus, par exemple s'il est alloué et écrit par un autre processus du système. Étant donné que la lecture s'exécute de manière asynchrone dans des threads distincts et que les carillons peuvent être mis en file d'attente pour être lus ultérieurement, cela est totalement transparent pour l'application appelante.

Pour détecter ce problème, une vérification de l'état du thread est effectuée chaque fois qu'un nouveau carillon est programmé pour être joué. Une erreur est renvoyée si un thread de lecture a une file d'attente remplie et n'a pas traité de nouveaux octets au cours de la dernière seconde.

À l'avenir, il pourra être nécessaire de tenter de redémarrer / d'ouvrir des appareils, mais pour l'implémentation initiale, les erreurs ne doivent pas être invisibles.

Structure du code

De manière générale, le code lié à la lecture des carillons existe dans les caisses suivantes :

CRATE: display-safety/crates/(harry-app|harry)

L'application HAR existante, qui émet des appels pour lire des carillons.

NEW CRATE: display-safety/crates/audio

NOUVEAU : Crate pour gérer le contrôle et la lecture audio (c'est là que se trouve la plupart des fonctionnalités).

CRATE: display-safety/crates/har-platform-api/audio

PAL, y compris tous les appels système requis pour l'audio.

CRATE: display-safety/crates/har-platform-(android|linux)/audio

Appels à tinyalsa-rs pour la lecture à l'aide de TinyALSA. La prise en charge de QNX n'est pas implémentée dans la solution initiale et s'étendra à mesure que d'autres plates-formes seront prises en charge.

TINYALSA PAL: display-safety/crates/tinyalsa-audio

Code spécifique à TinyALSA pour la lecture. Cette méthode est utilisée par les implémentations de plate-forme Android et Linux.

CRATE: display-safety/crates/tinyalsa-rs

Liaisons Rust pour l'implémentation C de TinyALSA

Détails de l'implémentation Rust

Voici quelques détails d'implémentation spécifiques :

  • Toutes les fonctions de l'API renvoient Result<X, AudioError>X est () ou une valeur renvoyée.
  • Aucune fonction d'API n'est marquée comme unsafe.
  • Les mécanismes de mutex et de synchronisation sont internes et ne sont pas exposés dans l'API AudioManager.

Modèle de propriété et AudioManager

  • Toutes les interactions de l'application avec le système audio se font via AudioManager ou les objets renvoyés par AudioManager.

  • AudioManager est thread-safe.

  • AudioManager est instancié une fois dans l'application HARry, et Moved, pour que Looper en soit propriétaire.

  • AudioManager utilise un jeton tokio_util::CancellationToken pour gérer ses threads de lecture démarrés, en s'assurant que les threads sont arrêtés et que les ressources sont libérées si AudioManager est Dropped.

  • AudioManager n'empêche pas explicitement la création de plusieurs instances. S'il existe plusieurs instances, la journalisation est effectuée au niveau warn.

Copropriété

Un certain nombre d'objets ont une propriété partagée encapsulée et synchronisée avec un accès exclusif. Ces mécanismes ne sont pas exposés dans l'API AudioManager, mais sont internes aux implémentations audio et PAL.

  • AudioDevice : chaque référence matérielle (par exemple, TinyALSA PCM) qui est ouverte (possède un handle) a un accès exclusif. Consultez Conception SMP.

  • Les instances AudioStream ont un accès exclusif une fois qu'elles sont planifiées pour la lecture, car elles peuvent être contrôlées par l'application et consultées simultanément par le thread de lecture.

    Le thread de lecture ne contient pas de verrouillages pendant la lecture, mais crée un instantané immuable du prochain tampon à lire et ne tient pas compte des modifications tant que le prochain tampon n'est pas traité.

  • Chaque thread de lecture dispose d'une file d'attente de lecture, une référence partagée entre AudioManager et le thread de lecture. Par conséquent, le thread a besoin d'un accès exclusif pour les mutations.

  • Les threads sans flux deviennent inactifs avec la variable Condvar pour recevoir des événements de réveil lorsque de nouvelles données sont détectées. Ce mécanisme est une propriété partagée.

Dépendances

Les caisses et les caisses audio sont conçues pour réduire les dépendances vis-à-vis des caisses qui ne sont pas approuvées pour la compilation dans l'arborescence source Android. Consultez la liste des crates incluses.

Les implémentations de plate-forme en aval pour Android et Linux dépendent de TinyALSA et du crate tinyalsa-rs existant pour la sécurité de l'affichage.

Attributs de qualité

Fiabilité

Bien que la lecture audio soit essentielle pour la sécurité, cette conception ne couvre pas l'implémentation d'une surveillance de la sécurité. Implémentez cela dans un effort distinct, pour vérifier la fiabilité de la lecture audio sur le matériel et en production.

Évolutivité

L'approche "un thread par appareil" est conçue pour s'adapter à différentes configurations matérielles. Étant donné que chaque thread est principalement inactif, en attente de données ou en attente que l'appareil digère les données écrites, il ne devrait pas être exigeant pour le processeur ni intensif en termes de performances pour le système.

La décision de conception de ne lire les données que sur un seul appareil, combinée aux commandes de contrôle du mixeur pour tout contrôle de sortie supplémentaire, garantit que la sortie exacte est gérée par le matériel audio et devrait évoluer pour les futurs systèmes.

Latence

La latence est essentielle pour le système audio. Par conséquent, après l'implémentation, un ensemble d'objectifs de niveau de service (SLO) sont définis pour la latence du système. Pour surveiller en continu l'état de la latence, surveillez les journaux système qui ne respectent pas les SLO définis dans toutes les versions de débogage.

Pour les versions de production, les données de surveillance sont transmises à un système externe à l'implémentation audio, plutôt que de s'appuyer sur les journaux.

Test et stratégie de test

Les caisses et la caisse audio sont conçues avec une couverture de test. Nous avons ajouté une implémentation de plate-forme fictive pour confirmer que toutes les fonctionnalités sont testées.

La complexité du matériel et des liaisons empêche une couverture de test étendue pour les implémentations de plate-forme. Nous fournissons des exemples d'implémentations pour tester manuellement la solution sur du matériel et sur l'émulateur Cuttlefish.

Documentation

Le fichier README.md dans Audio crates/audio décrit comment utiliser AudioManager. crates/audio/examples contient des exemples pour :

  • Implémentez une plate-forme.
  • Créez une instance de AudioManager.
  • Lire "WavAsset"
  • Lire un élément de fonction personnalisée en boucle.
  • Enregistrez les événements de lecture.