Questi contenuti descrivono la riproduzione del suono di notifica nel renderer ad alta affidabilità (HAR).
Un crate Audio espone AudioManager all'app HAR, che controlla la riproduzione
del suono.
Per mantenere bassa la latenza, i thread di riproduzione vengono eseguiti per tutta la durata dell'app, rimanendo inattivi e cedendo il controllo quando non viene riprodotto alcun audio.
Terminologia
- asset
AudioAssetsi riferisce all'audio riproducibile. Gli asset sono comunemente noti e esistono nel runtime dell'app.- dispositivo
AudioDevicesi riferisce a un bus separato per la riproduzione dell'audio. Il dispositivo è l'unità più granulare relativa all'hardware a cui accede il sistema. Nell'implementazione SDVM standard,AudioDevicesi riferisce a un singolo PCM ALSA (Advanced Linux Sound Architecture).- flusso
- Un'istanza di riproduzione di un asset su un dispositivo. Gli stream vengono mantenuti dal momento della pianificazione fino al completamento, all'annullamento o alla fine con errore.
Componenti
La figura 1 mostra il diagramma dei componenti per il suono di notifica:
Figura 1. Diagramma dei componenti.
Dispositivo audio e PCM
La configurazione dell'hardware audio segue la progettazione standard dell'Hardware Abstraction Layer (HAL) della piattaforma
e har-platform-api lo contiene.
Il crate HAR Audio definisce una nuova struttura per AudioDevice, che definisce
i campi per tutte le strutture di dati che influiscono sul crate HAR Audio interno
e sulla riproduzione. AudioDevice utilizza anche i generici per includere potenziali parametri aggiuntivi specifici della piattaforma. Nel caso di tinyalsa,
PlatformAudioDevice contiene i descrittori e le proprietà di un ALSA PCM.
/// 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
}
Asset audio
Questa sezione descrive come vengono configurati e implementati gli asset audio.
Configurazione
L'implementazione audio HAR iniziale supporta gli asset audio configurati staticamente. Una configurazione JSON definisce quali asset sono disponibili e quali sono definiti come file WAV.
L'implementazione supporta anche asset audio sintetizzati e in streaming tramite un'implementazione più generica, che accetta una funzione per generare dati audio.
Implementazione
Implementa gli asset utilizzando due costrutti separati, AudioAsset e AudioStream.
AudioAsset definisce le proprietà statiche di una risorsa e un contenitore per
potenziali dati interni correlati alla risorsa. Da AudioAsset AudioStream può
essere derivato, ovvero una singola istanza dell'asset riproducibile in streaming. AudioStream
contiene uno stato interno relativo alla riproduzione di un singolo stream.
/// 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.
...
}
Riproduzione del suono
Questa sezione descrive l'API e la procedura per la riproduzione di un suono di notifica. La riproduzione di un singolo suono è chiamata stream.
Ciclo di vita di uno stream
La figura 2 illustra il ciclo di vita di uno stream:
Figura 2. Riproduzione e eventi dello stream.
La Figura 2 descrive questi passaggi:
Play:programma lo stream da riprodurre.
Priorità:la priorità di riproduzione decide se:
- Riproduci suono ora (evento avviato quando i primi byte)
- Riproduci suono in un secondo momento (evento in pausa o ripreso)
- Riduzione della priorità del suono (evento annullato)
Controlli del mixer: se necessario, aggiorna i controlli del mixer in base ai comportamenti configurati.
Byte di scrittura:scrivi un blocco di byte in
AudioDevice.Altri dati:se lo stream contiene altri dati, torna al passaggio 2.
Ripeti:se lo stream deve essere ripetuto, ripristina e torna al passaggio 2 (evento riavviato).
Completato:lo stream è stato completato correttamente (evento
FinishedSuccessfully).
Il suono può essere interrotto con la messa in pausa, la ripresa o l'interruzione delle chiamate in qualsiasi momento.
Priorità dei suoni
Questa logica imposta le priorità dei segnali acustici:
Override della modalità di riproduzione. Ad esempio, un suono in modalità dissolvenza ha sempre la massima priorità finché la dissolvenza non è completata.
Priorità specificata.
Se la priorità uguale è più recente, la suoneria viene riprodotta per prima.
Quando i segnali acustici hanno la stessa priorità, AudioManager viene istanziato con un valore enum.
API
Eventi
Se viene fornito un canale di eventi all'avvio del suono, HAR Audio emette
un numero di eventi durante la riproduzione. Gli eventi supportati sono mostrati in questo
esempio:
/// 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)
}
Controllo mixer
Questa sezione descrive come vengono controllati il volume e la spazializzazione.
Volume
HAR definisce il volume in modo coerente in millibel. Il crate har-platform-api
gestisce la conversione da millibel a segnale di controllo.
La relazione tra i millibel e la potenza di uscita dell'hardware è logaritmica e
varia notevolmente tra diverse configurazioni di hardware e speaker. Di conseguenza,
fornisci la configurazione tra i valori nell'ambito della configurazione AudioDevice
(Dispositivo audio e PCM) e la conversione deve avvenire prima
di chiamare il livello della piattaforma.
Di conseguenza, l'implementazione nell'API PAL definisce due funzioni.
fn set_volume_millibel(AudioDeviceID, Millibel) {
/// Default implementation with conversion using DeviceConfig.
}
fn set_volume_control(AudioDeviceID, ControlValue);
L'implementazione predefinita per set_volume_millibel utilizza la configurazione fornita
per AudioDevice, incluso un insieme di coppie chiave-valore per il millibel di riferimento -
controlla, trasforma il millibel in valori di controllo e poi chiama la
funzione set_volume_control con il valore convertito.
Questo design fornisce un valore predefinito e consente alle implementazioni successive di ignorare la mappatura predefinita.
Figura 3. Flusso audio HAR.
Spatializzazione
L'API Audio espone funzionalità per controllare in quale area spaziale devono essere riprodotti i dati audio. Questi parametri vengono passati al livello PAL e applicati a valle utilizzando i controlli hardware. Le opzioni sono definite nell'API PAL come:
/// 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
}
Livelli di controllo del mixer
Puoi definire il volume e la spazializzazione per una risorsa e per uno stream. Se definisci una priorità per lo stream, lo stream sostituisce i controlli definiti dall'asset.
Gestione dei thread
Audio Manager gestisce un thread per ogni istanza di AudioDevice. Ogni thread
opera in modo indipendente. L'interazione tra AudioManager e il thread di riproduzione
utilizza una coda di stream condivisa ordinata in base alla priorità.
Le chiamate ALSA utilizzano scritture ASYNC con polling per determinare quando i dati vengono elaborati.
Figura 4. Sequenza di gestione dei thread.
Segnali di controllo durante il polling
Durante l'attesa che la scheda audio elabori i byte, è possibile emettere segnali di controllo. Ad
esempio, per modificare la dissolvenza o la spazializzazione dell'audio. Il polling per ottenere lo stato
del dispositivo audio è configurato a livello di AudioManager o
è impostato su 1 millisecondo per impostazione predefinita. Dopo ogni ciclo di polling, il thread di riproduzione
elabora ed esegue i comandi di controllo temporizzati.
Gestione del buffer
Per ridurre al minimo la latenza di interruzione, le dimensioni dei buffer scritti sul dispositivo
sono ridotte. Quando utilizzi TinyALSA come impostazione predefinita, la dimensione del buffer è configurata in modo che sia uguale al parametro startup_threshold.
TinyALSA definisce il valore predefinito come l'intero buffer del dispositivo allocato
diviso per due.
Interruzione dello streaming
Quando i flussi vengono interrotti, mantengono la priorità del thread finché i dati che hanno scritto sulla scheda non vengono svuotati. Di conseguenza, tra l'interruzione e il nuovo flusso si verifica un periodo di transizione.
Ad esempio,se un campione audio in HAR utilizza:
- Dimensioni di 3072
- Tariffa di 48.000
- Dimensione del campione di due
Il buffer in attesa viene calcolato come 3072 e 6144 frame, il che comporta un ritardo di interruzione da 64 a 128 millisecondi. Un'implementazione di produzione richiederebbe un buffer più piccolo.
Gestione degli errori e rischi
Questa sezione descrive come vengono gestiti gli errori e i potenziali rischi.
Stream obsoleti e carenza di coda
Dato che AudioStream può essere messo in pausa e la riproduzione può avvenire solo dall'istanza AudioStream con priorità più alta, esiste il rischio di una coda crescente che priva di risorse gli stream a bassa priorità.
Per evitare che ciò accada, ogni coda ha una dimensione configurabile. Quando questo valore viene superato, il flusso con la priorità più bassa viene eliminato.
Monitoraggio e avvisi
In produzione, il monitor di sicurezza tiene traccia delle funzionalità audio per verificare che la riproduzione avvenga come previsto.
AudioManager monitora le statistiche interne specifiche per le latenze e un flag
che definisce le prestazioni di logging. Dopo aver impostato queste soglie, vengono generati log di avviso per tutte le build di debug quando:
- La durata tra la programmazione e l'inizio della riproduzione supera i
xmillisecondi. - (Per uno stream non interrotto) la durata della risorsa e il tempo di riproduzione differiscono di oltre il
y%.
Dispositivo bloccato
Esiste sempre un piccolo rischio che un dispositivo audio non risponda, ad esempio se viene allocato e scritto da un altro processo nel sistema. Poiché la riproduzione viene eseguita in modo asincrono in thread separati e i segnali acustici possono essere messi in coda per essere riprodotti in un secondo momento, questo è completamente trasparente per l'app di chiamate.
Per rilevarlo, viene eseguito un controllo di integrità del thread ogni volta che viene pianificata la riproduzione di un nuovo suono, restituendo un errore se un thread di riproduzione ha una coda popolata e non ha elaborato nuovi byte nell'ultimo secondo.
Per scopi futuri potrebbe essere necessario tentare di riavviare / aprire i dispositivi, ma per l'implementazione iniziale gli errori non devono essere invisibili.
Struttura del codice
A livello generale, il codice relativo alla riproduzione dei segnali acustici esiste nei seguenti crate:
CRATE: display-safety/crates/(harry-app|harry)
L'app HAR esistente, che effettua chiamate per riprodurre i suoni.
NEW CRATE: display-safety/crates/audio
NOVITÀ: Crate per gestire il controllo e la riproduzione dell'audio (qui si trova la maggior parte delle funzionalità).
CRATE: display-safety/crates/har-platform-api/audio
PAL, comprese tutte le chiamate di sistema necessarie per l'audio.
CRATE: display-safety/crates/har-platform-(android|linux)/audio
Chiamate a tinyalsa-rs per la riproduzione utilizzando TinyALSA. Il supporto di QNX non è
implementato nella soluzione iniziale e aumenterà man mano che verranno supportate
più piattaforme.
TINYALSA PAL: display-safety/crates/tinyalsa-audio
Codice specifico di TinyALSA per la riproduzione. Viene utilizzato dalle implementazioni delle piattaforme Android e Linux.
CRATE: display-safety/crates/tinyalsa-rs
Binding Rust per l'implementazione C di TinyALSA
Dettagli di implementazione di Rust
Alcuni dettagli di implementazione specifici:
- Tutte le funzioni API restituiscono
Result<X, AudioError>, doveXè () o un valore restituito. - Nessuna funzione API è contrassegnata come
unsafe. - I meccanismi di sincronizzazione e mutex sono interni e non sono esposti nell'API
AudioManager.
Modello di proprietà e AudioManager
Tutta l'interazione dell'app con il sistema audio avviene tramite
AudioManagero oggetti restituiti daAudioManager.AudioManagerè thread-safe.AudioManagerviene istanziato una volta nell'app HARry eMoved, perLooperper avere la proprietà.AudioManagerutilizza un tokentokio_util::CancellationTokenper gestire i thread di riproduzione avviati, assicurando che i thread vengano terminati e le risorse rilasciate seAudioManagerèDropped.AudioManagernon impedisce esplicitamente la creazione di più istanze. Se esiste più di un'istanza, il log viene registrato con il livellowarn.
Proprietà condivisa
Un certo numero di oggetti ha la proprietà condivisa e sincronizzata con
accesso esclusivo. Questi meccanismi non sono esposti nell'API AudioManager, ma
sono interni alle implementazioni audio e PAL.
AudioDevice: ogni riferimento hardware (ad esempio TinyALSA PCM) aperto (con un handle) ha accesso esclusivo. Vedi Progettazione SMP.Le istanze
AudioStreamhanno accesso esclusivo dopo la pianificazione della riproduzione perché possono essere controllate dall'app e contemporaneamente accessibili dal thread di riproduzione.Il thread di riproduzione non mantiene i blocchi durante la riproduzione, ma crea uno snapshot immutabile del buffer successivo da riprodurre e non prende in considerazione le modifiche finché il buffer successivo non viene elaborato.
Ogni thread di riproduzione ha una coda di riproduzione, un riferimento condiviso tra
AudioManagere il thread di riproduzione. Di conseguenza, il thread richiede l'accesso esclusivo per le mutazioni.I thread senza stream diventano inattivi con la variabile
Condvarper ricevere eventi di riattivazione quando vengono rilevati nuovi dati. Questo meccanismo ha una proprietà condivisa.
Dipendenze
I crate e il crate audio sono progettati per ridurre le dipendenze dai crate che non sono approvati per essere integrati nell'albero dei sorgenti di Android. Consulta questo elenco di crate inclusi.
Le implementazioni della piattaforma downstream per Android e Linux dipendono da
TinyALSA e dalla crate tinyalsa-rs esistente per la sicurezza del display.
Attributi di qualità
Affidabilità
Sebbene la riproduzione audio sia fondamentale per la sicurezza, questo progetto non copre l'implementazione di un monitoraggio della sicurezza. Implementa questa funzionalità in un'attività separata per verificare l'affidabilità della riproduzione audio su hardware e in produzione.
Scalabilità
L'approccio di un thread per dispositivo è pensato per scalare a diverse configurazioni hardware. Poiché ogni thread è principalmente inattivo, in attesa di dati o in attesa che il dispositivo elabori i dati scritti, non dovrebbe essere impegnativo per il processore o richiedere molte risorse di sistema.
La decisione di progettazione di riprodurre i dati su un singolo dispositivo, combinata con i comandi di controllo del mixer per tutti gli ulteriori controlli dell'output, garantisce che l'output esatto venga gestito dall'hardware audio e dovrebbe essere scalabile per i sistemi futuri.
Latenza
La latenza è fondamentale per il sistema audio, quindi dopo l'implementazione viene definito un insieme di obiettivi del livello di servizio (SLO) per la latenza del sistema. Per monitorare continuamente lo stato della latenza, il monitoraggio nei log di sistema non soddisfa gli SLO definiti in tutte le build di debug.
Per le versioni di produzione, i dati di monitoraggio vengono trasmessi a un sistema esterno all'implementazione audio, anziché basarsi sui log.
Test e strategia di test
Le casse e la cassa audio sono progettate con la copertura dei test. Abbiamo aggiunto un'implementazione della piattaforma simulata per verificare che tutte le funzionalità vengano testate.
La complessità dell'hardware e dei binding impedisce un'ampia copertura dei test per le implementazioni della piattaforma. Forniamo implementazioni di esempio per testare manualmente la soluzione sull'hardware e sull'emulatore Cuttlefish.
Documentazione
Il file README.md in Audio crates/audio descrive come utilizzare
AudioManager. crates/audio/examples contiene esempi per:
- Implementa una piattaforma.
- Crea un'istanza di
AudioManager. - Riproduci
WavAsset. - Riproduci un asset di funzione personalizzata in loop.
- Registra gli eventi di riproduzione dei log.