Bunyi audio

Konten ini menjelaskan pemutaran bel di perender ketersediaan tinggi (HAR). Crate Audio mengekspos AudioManager ke aplikasi HAR, yang mengontrol pemutaran bel.

Untuk menjaga latensi tetap rendah, thread pemutaran berjalan selama masa aktif aplikasi, menganggur dan melepaskan saat tidak ada audio yang diputar.

Terminologi

aset
AudioAsset berkaitan dengan audio yang dapat diputar. Aset biasanya diketahui dan ada di runtime aplikasi.
perangkat
AudioDevice mengacu pada bus terpisah untuk pemutaran audio. Perangkat adalah unit paling terperinci yang terkait dengan hardware yang diakses oleh sistem. Dalam penerapan SDVM standar, AudioDevice merujuk ke satu PCM Advanced Linux Sound Architecture (ALSA).
aliran
Instance pemutaran aset di perangkat. Aliran tetap ada sejak dijadwalkan hingga selesai, dibatalkan, atau berakhir karena error.

Komponen

Gambar 1 menampilkan diagram komponen untuk chime:

Diagram komponen

Gambar 1. Diagram komponen.

Perangkat audio dan PCM

Konfigurasi hardware audio mengikuti desain lapisan abstraksi platform HAR standar, dan har-platform-api memuatnya.

Crate HAR Audio menentukan struktur baru untuk AudioDevice, yang menentukan kolom untuk semua struktur data yang memengaruhi crate Audio HAR internal dan pemutaran. AudioDevice juga menggunakan generik untuk membungkus parameter tambahan khusus platform yang potensial. Dalam kasus tinyalsa, PlatformAudioDevice berisi deskripsi dan properti 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
}

Aset audio

Bagian ini menjelaskan cara aset audio dikonfigurasi dan diterapkan.

Konfigurasi

Implementasi audio HAR awal mendukung aset audio yang dikonfigurasi secara statis. Konfigurasi JSON menentukan aset mana yang tersedia dan aset mana yang ditentukan sebagai file WAV.

Implementasi ini juga mendukung aset audio yang disintesis dan di-streaming melalui implementasi aset yang lebih umum, yang menerima fungsi untuk menghasilkan data audio.

Penerapan

Terapkan aset menggunakan dua konstruksi terpisah, AudioAsset dan AudioStream.

AudioAsset menentukan properti statis aset, dan penampung untuk potensi data internal yang terkait dengan aset. Dapat diturunkan dari AudioAsset AudioStream, yang merupakan satu instance aset yang dapat di-streaming. AudioStream berisi status internal yang terkait dengan pemutaran streaming tunggal.

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

Pemutaran bel

Bagian ini menjelaskan API dan prosedur untuk pemutaran bel. Pemutaran lonceng tunggal disebut sebagai stream.

Siklus proses streaming

Gambar 2 menggambarkan siklus proses aliran:

Pemutaran dan peristiwa streaming

Gambar 2. Pemutaran dan acara streaming.

Gambar 2 menjelaskan langkah-langkah ini:

  1. Putar: Jadwalkan streaming untuk diputar.

  2. Prioritaskan: Prioritas pemutaran memutuskan apakah akan:

    • Putar bunyi lonceng sekarang (memulai acara saat byte pertama)
    • Putar bunyi bel nanti (acara dijeda atau dilanjutkan)
    • Membatalkan prioritas bel (acara dibatalkan)
  3. Kontrol Mixer: Jika diperlukan, perbarui kontrol Mixer berdasarkan perilaku yang dikonfigurasi.

  4. Menulis byte: Menulis chunk byte ke AudioDevice.

  5. Data lainnya: Jika aliran memiliki lebih banyak data, kembali ke Langkah 2.

  6. Ulangi: Jika streaming harus diulang, reset dan kembali ke Langkah 2 (peristiwa dimulai ulang).

  7. Selesai: Streaming berhasil diselesaikan (peristiwa FinishedSuccessfully ).

Bel dapat dihentikan dengan panggilan jeda, lanjutkan, atau hentikan kapan saja.

Prioritas bunyi bel

Logika ini menetapkan prioritas bunyi lonceng:

  1. Penggantian mode pemutaran. Misalnya, bunyi bel dalam mode memudar selalu diberi prioritas utama hingga memudar selesai.

  2. Prioritas yang ditentukan.

  3. Jika prioritas yang sama lebih baru, bel akan berbunyi terlebih dahulu.

Jika bel memiliki prioritas yang sama, AudioManager akan di-instansiasi dengan nilai enum.

API

Acara

Jika saluran peristiwa disediakan saat bunyi lonceng dimulai, HAR Audio akan memancarkan sejumlah peristiwa selama pemutaran. Peristiwa yang didukung ditampilkan dalam contoh ini:

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

Kontrol mixer

Bagian ini menjelaskan cara mengontrol volume dan spasialisasi.

Volume

HAR menentukan volume secara konsisten dalam milibel. Crate har-platform-api menangani konversi dari milibel ke sinyal kontrol.

Hubungan antara milibel dan output daya hardware bersifat logaritmik, dan sangat bervariasi antara berbagai hardware dan konfigurasi speaker. Akibatnya, berikan konfigurasi antara nilai sebagai bagian dari konfigurasi AudioDevice (Perangkat Audio dan PCM), dan konversi harus dilakukan sebelum memanggil lapisan platform.

Akibatnya, penerapan di PAL API menentukan dua fungsi.

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

fn set_volume_control(AudioDeviceID, ControlValue);

Implementasi default untuk set_volume_millibel menggunakan konfigurasi yang disediakan untuk AudioDevice, termasuk serangkaian key-value pair untuk referensi millibel - kontrol, mengubah millibel menjadi nilai kontrol, lalu memanggil fungsi set_volume_control dengan nilai yang dikonversi.

Desain ini menyediakan default dan memungkinkan penerapan berikutnya untuk mengganti pemetaan default.

Alur audio HAR

Gambar 3. Alur audio HAR.

Spasialisasi

Audio API mengekspos fungsi untuk mengontrol area spasial tempat data audio harus diputar. Parameter ini diteruskan ke lapisan PAL, dan diterapkan di hilir menggunakan kontrol hardware. Opsi ditentukan sebagai bagian dari PAL API sebagai:

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

Tingkat kontrol mixer

Anda dapat menentukan volume dan spasialisasi pada aset dan untuk streaming. Jika Anda menentukan prioritas streaming, streaming akan menggantikan kontrol yang ditentukan oleh aset.

Pengelolaan thread

Pengelola audio mempertahankan satu thread per instance AudioDevice. Setiap thread beroperasi secara independen. Interaksi antara AudioManager dan thread pemutaran menggunakan antrean streaming bersama yang diurutkan berdasarkan prioritas.

Panggilan ALSA menggunakan penulisan ASYNC dengan polling untuk menentukan kapan data diproses.

Urutan pengelolaan thread

Gambar 4. Urutan pengelolaan thread.

Sinyal kontrol selama polling

Saat menunggu kartu suara mencerna byte, sinyal kontrol dapat dikeluarkan. Misalnya, untuk mengubah pudar atau spasialisasi audio. Polling untuk mendapatkan status perangkat audio dikonfigurasi pada level AudioManager atau secara default 1 milidetik. Setelah setiap siklus polling, thread pemutaran mencerna dan mengeluarkan perintah kontrol berwaktu.

Pengelolaan buffer

Untuk meminimalkan latensi gangguan, ukuran buffer yang ditulis ke perangkat dijaga agar tetap kecil. Saat menggunakan TinyALSA sebagai default, ukuran buffer dikonfigurasi agar sama dengan parameter startup_threshold. TinyALSA menentukan default sebagai seluruh buffer perangkat yang dialokasikan dibagi dua.

Gangguan streaming

Saat aliran terganggu, aliran mempertahankan prioritas thread hingga data yang telah ditulis ke kartu dikosongkan. Akibatnya, periode transisi terjadi antara gangguan dan streaming baru.

Misalnya, jika sampel audio di HAR menggunakan:

  • Ukuran 3.072
  • Kecepatan 48.000
  • Ukuran sampel dua

Buffer yang tertunda dihitung sebagai 3.072 dan 6.144 frame, yang menghasilkan penundaan gangguan 64 hingga 128 milidetik. Penerapan produksi akan memerlukan buffer yang lebih kecil.

Pengelolaan error dan risiko

Bagian ini menjelaskan cara pengelolaan error dan potensi risiko.

Streaming yang tidak aktif dan kekurangan antrean

Mengingat AudioStream dapat dijeda, dan karena pemutaran hanya dapat terjadi dari instance AudioStream dengan prioritas teratas, risiko munculnya antrean yang terus bertambah akan membuat streaming dengan prioritas rendah tidak dapat diputar.

Untuk menghindari kejadian ini, setiap antrean dibatasi pada ukuran yang dapat dikonfigurasi. Jika nilai ini terlampaui, aliran dengan prioritas terendah akan dibuang.

Memantau dan mengingatkan

Dalam produksi, monitor keamanan melacak fitur audio untuk melacak pemutaran yang berlangsung seperti yang diharapkan.

AudioManager memantau statistik internal khusus untuk latensi dan tanda yang menentukan performa logging. Setelah menetapkan nilai minimum ini, log peringatan dibuat untuk semua build debug saat:

  • Durasi antara penjadwalan dan memulai pemutaran melebihi x milidetik.
  • (Untuk streaming yang tidak terganggu) panjang aset dan waktu pemutaran berbeda lebih dari y persen.

Perangkat diblokir

Selalu ada sedikit risiko perangkat audio menjadi tidak responsif, misalnya, jika dialokasikan dan ditulis oleh proses lain dalam sistem. Mengingat pemutaran berjalan secara asinkron di thread terpisah, dan bunyi lonceng dapat diantrekan untuk diputar nanti, hal ini sepenuhnya transparan bagi aplikasi yang memanggil.

Untuk mendeteksinya, pemeriksaan kondisi thread dilakukan setiap kali bel baru dijadwalkan untuk diputar, yang akan menampilkan error jika thread pemutaran memiliki antrean yang terisi, dan belum memproses byte baru selama satu detik terakhir.

Untuk tujuan mendatang, mungkin perlu dicoba untuk memulai ulang / membuka perangkat, tetapi untuk penerapan awal, error tidak boleh tidak terlihat.

Struktur kode

Secara umum, kode yang terkait dengan pemutaran bel ada di seluruh crate berikut:

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

Aplikasi HAR yang ada, yang mengeluarkan panggilan untuk memutar bunyi lonceng.

NEW CRATE: display-safety/crates/audio

BARU: Crate untuk mengelola kontrol dan pemutaran audio (di sinilah sebagian besar fungsionalitas berada).

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

PAL termasuk semua panggilan sistem yang diperlukan untuk audio.

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

Panggilan ke tinyalsa-rs untuk pemutaran menggunakan TinyALSA. Dukungan QNX tidak diterapkan dalam solusi awal, dan dukungan ini akan berkembang seiring dengan makin banyaknya platform yang didukung.

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

Kode khusus TinyALSA untuk pemutaran. Ini digunakan oleh implementasi platform Android dan Linux.

CRATE: display-safety/crates/tinyalsa-rs

Binding Rust untuk penerapan C TinyALSA

Detail penerapan Rust

Beberapa detail penerapan khusus:

  • Semua fungsi API menampilkan Result<X, AudioError> dengan X adalah () atau nilai yang ditampilkan.
  • Tidak ada fungsi API yang ditandai sebagai unsafe.
  • Mekanisme mutex dan sinkronisasi bersifat internal dan tidak diekspos di API AudioManager.

Model kepemilikan dan AudioManager

  • Semua interaksi aplikasi dengan sistem audio terjadi melalui AudioManager atau objek yang ditampilkan dari AudioManager.

  • AudioManager aman untuk thread.

  • AudioManager di-instantiate satu kali di aplikasi HARry, dan Moved, agar Looper memiliki kepemilikan.

  • AudioManager menggunakan token tokio_util::CancellationToken untuk mengelola thread pemutaran yang dimulai, memastikan thread dihentikan dan resource dilepaskan jika AudioManager adalah Dropped.

  • AudioManager tidak secara eksplisit mencegah pembuatan beberapa instance. Jika ada lebih dari satu instance, instance tersebut akan dicatat dengan tingkat warn.

Kepemilikan bersama

Sejumlah objek memiliki kepemilikan bersama yang di-wrap dan disinkronkan dengan akses eksklusif. Mekanisme ini tidak diekspos di AudioManager API, tetapi bersifat internal untuk implementasi audio dan PAL.

  • AudioDevice - Setiap referensi hardware (misalnya, TinyALSA PCM) yang dibuka (memiliki handle) memiliki akses eksklusif. Lihat Desain SMP.

  • Instance AudioStream memiliki akses eksklusif setelah dijadwalkan untuk pemutaran karena dapat dikontrol oleh aplikasi dan diakses secara bersamaan oleh thread pemutaran.

    Thread pemutaran tidak menahan kunci selama pemutaran, tetapi membuat snapshot buffer berikutnya yang tidak dapat diubah untuk diputar, dan tidak mempertimbangkan perubahan hingga buffer berikutnya diproses.

  • Setiap thread pemutaran memiliki antrean pemutaran, referensi bersama antara AudioManager dan thread pemutaran. Akibatnya, thread memerlukan akses eksklusif untuk mutasi.

  • Thread tanpa aliran akan menjadi tidak aktif dengan variabel Condvar menjadi menerima peristiwa aktif saat data baru terdeteksi. Mekanisme ini memiliki kepemilikan bersama.

Dependensi

Crate dan crate audio didesain untuk mengurangi dependensi pada crate yang tidak disetujui untuk dibangun di pohon sumber Android. Lihat daftar peti yang disertakan di sini.

Implementasi platform downstream untuk Android dan Linux bergantung pada TinyALSA dan crate keamanan layar tinyalsa-rs yang ada.

Atribut kualitas

Keandalan

Meskipun pemutaran audio sangat penting untuk keselamatan, desain ini tidak mencakup penerapan pemantauan keselamatan. Terapkan ini dalam upaya terpisah untuk memverifikasi keandalan pemutaran audio di hardware dan dalam produksi.

Skalabilitas

Pendekatan satu thread per perangkat dimaksudkan untuk menskalakan ke berbagai konfigurasi hardware. Mengingat setiap thread sebagian besar dalam kondisi tidak ada aktivitas, menunggu data, atau menunggu perangkat mencerna data yang ditulis, thread tersebut tidak akan membebani prosesor atau performa sistem.

Keputusan desain untuk hanya memutar data ke satu perangkat, yang dikombinasikan dengan perintah kontrol mixer untuk semua kontrol output lebih lanjut, memastikan output yang tepat ditangani oleh hardware suara, dan harus diskalakan untuk sistem mendatang.

Latensi

Latensi sangat penting untuk sistem audio, jadi setelah penerapan, serangkaian tujuan tingkat layanan (SLO) ditentukan untuk latensi sistem. Untuk terus memantau kondisi latensi, pemantauan di log sistem tidak memenuhi SLO yang ditentukan di semua build debug.

Untuk versi produksi, data pemantauan diteruskan ke beberapa sistem eksternal ke implementasi audio, bukan mengandalkan log.

Pengujian dan strategi pengujian

Crate dan crate audio dirancang dengan cakupan pengujian. Kami menambahkan penerapan platform tiruan untuk mengonfirmasi bahwa semua kemampuan diuji.

Kompleksitas hardware dan binding menghalangi cakupan pengujian yang luas untuk implementasi platform. Kami menyediakan contoh penerapan untuk menguji solusi secara manual di hardware dan di emulator Cuttlefish.

Dokumentasi

File README.md di Audio crates/audio menjelaskan cara menggunakan AudioManager. crates/audio/examples berisi contoh untuk:

  • Terapkan platform.
  • Buat instance AudioManager.
  • Putar WavAsset.
  • Memutar aset fungsi kustom berulang kali.
  • Mencatat peristiwa pemutaran ke dalam log.