このコンテンツでは、高可用性レンダラ(HAR)でのチャイム再生について説明します。
Audio クレートは、チャイム
再生を制御する AudioManager を HAR アプリに公開します。
レイテンシを低く抑えるため、再生スレッドはアプリのライフサイクル全体にわたって実行され、音声が再生されないときはアイドル状態になり、処理を譲ります。
用語
- アセット
AudioAssetは再生可能な音声に関連します。アセットは一般に知られており、アプリのランタイムに存在します。- デバイス
AudioDeviceは、音声の再生用の個別のバスを指します。デバイスは、システムがアクセスするハードウェアに関連する最も粒度の細かい単位です。標準の SDVM 実装では、AudioDeviceは単一の Advanced Linux Sound Architecture(ALSA)PCM を指します。- ストリーム
- デバイス上のアセットの再生のインスタンス。ストリームは、スケジュールされた時点から、完了、キャンセル、エラー終了するまで存続します。
コンポーネント
図 1 に、チャイムのコンポーネント図を示します。
図 1 。コンポーネント図。
オーディオ機器と PCM
オーディオ ハードウェア構成は標準の HAR プラットフォーム抽象化レイヤ設計に準拠しており、har-platform-api に含まれています。
HAR Audio クレートは、AudioDevice の新しい構造を定義します。この構造では、内部の HAR Audio クレート
と再生に影響するすべてのデータ構造のフィールドを定義します。AudioDevice は、ジェネリクスを使用して、プラットフォーム固有の追加パラメータをラップすることもできます。tinyalsa の場合、PlatformAudioDevice には 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
}
自動アセット
このセクションでは、オーディオ アセットの構成と実装について説明します。
構成
最初の HAR オーディオ実装では、静的に構成されたオーディオ アセットがサポートされています。JSON 構成では、使用可能なアセットと WAV ファイルとして定義されているアセットを定義します。
この実装では、オーディオ データを生成する関数を受け入れる、より汎用的なアセット実装を通じて、合成されたオーディオ アセットとストリーミングされたオーディオ アセットもサポートしています。
実装
2 つの別々の構造体 AudioAsset と AudioStream を使用してアセットを実装します。
AudioAsset は、アセットの静的プロパティと、アセットに関連する可能性のある内部データのコンテナを定義します。AudioAsset AudioStream から
を派生させることができます。これは、アセットの単一のストリーミング可能なインスタンスです。AudioStream
には、単一のストリーム再生に関連する内部状態が含まれます。
/// 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.
...
}
チャイム再生
このセクションでは、チャイムの再生に使用する API と手順について説明します。単一のチャイム再生はストリームと呼ばれます。
ストリームのライフサイクル
図 2 に、ストリームのライフサイクルを示します。
図 2 。ストリームの再生とイベント。
図 2 に、次の手順を示します。
再生: 再生するストリームをスケジュールします。
優先順位付け: 再生の優先順位付けにより、次のいずれを行うかが決まります。
- チャイムをすぐに再生する(最初のバイトの開始イベント)
- チャイムを後で再生する(一時停止または再開イベント)
- チャイムの優先順位を下げる(キャンセル イベント)
ミキサー コントロール: 必要に応じて、構成された動作に基づいてミキサー コントロールを更新します。
バイトを書き込む: バイトのチャンクを
AudioDeviceに書き込みます。追加データ: ストリームにデータが追加されている場合は、ステップ 2 に戻ります。
繰り返し: ストリームを繰り返す場合は、リセットしてステップ 2 に戻ります(再起動イベント)。
完了: ストリームが正常に完了しました(
FinishedSuccessfullyイベント)。
チャイムは、一時停止、再開、停止の呼び出しでいつでも中断できます。
チャイムの優先度
このロジックでは、チャイムの優先度を設定します。
再生モードのオーバーライド。たとえば、フェードアウト モードのチャイムは、フェードアウトが完了するまで常に最優先されます。
指定された優先度。
優先度が同じで、より新しい場合は、チャイムが最初に再生されます。
チャイムの優先度が同じ場合、AudioManager は enum 値でインスタンス化されます。
API
イベント
チャイムの開始時にイベント チャネルが提供されると、HAR Audio は再生中に多数のイベントを生成します。サポートされているイベントを次の例に示します。
/// 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)
}
ミキサー コントロール
このセクションでは、音量と空間化を制御する方法について説明します。
巻
HAR は、音量をミリベル単位で一貫して定義します。har-platform-api クレートは、ミリベルから制御信号への変換を処理します。
ミリベルとハードウェアの電力出力の関係は対数であり、ハードウェアとスピーカーの設定によって大きく異なります。そのため、
AudioDevice
(オーディオ デバイスと PCM)構成の一部として値間の構成を指定する必要があります。また、
プラットフォーム レイヤを呼び出す前に変換を行う必要があります。
そのため、PAL API の実装では 2 つの関数が定義されています。
fn set_volume_millibel(AudioDeviceID, Millibel) {
/// Default implementation with conversion using DeviceConfig.
}
fn set_volume_control(AudioDeviceID, ControlValue);
set_volume_millibel のデフォルト実装では、AudioDevice に指定された構成を使用します。これには、参照ミリベル - 制御の Key-Value ペアのセットが含まれます。ミリベルを制御値に変換し、変換された値で set_volume_control 関数を呼び出します。
この設計ではデフォルトが提供され、後続の実装でデフォルト マッピングをオーバーライドできます。
図 3 。HAR オーディオ フロー。
空間化
Audio API は、音声データを再生する空間領域を制御する機能を公開します。これらのパラメータは PAL レイヤに渡され、ハードウェア コントロールを使用してダウンストリームに適用されます。オプションは、PAL API の一部として次のように定義されます。
/// 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
}
ミキサー コントロール階層
アセットとストリームの音量と空間化を定義できます。ストリームの優先度を定義すると、ストリームはアセットで定義されたコントロールをオーバーライドします。
スレッドの管理
オーディオ マネージャーは、AudioDevice インスタンスごとに 1 つのスレッドを維持します。各スレッドは独立して動作します。AudioManager と再生スレッド間のやり取りでは、優先度で並べ替えられた共有ストリーム キューが使用されます。
ALSA 呼び出しでは、ポーリングで ASYNC 書き込みを使用して、データが処理されるタイミングを判断します。
図 4 。スレッド管理シーケンス。
ポーリング中の制御信号
サウンドカードがバイトを処理するのを待っているときに、制御信号を発行できます。たとえば、音声のフェードや空間化を変更する場合などです。オーディオ機器の状態を取得するポーリングは、AudioManager レベルで構成するか、デフォルトで 1 ミリ秒に設定します。ポーリング サイクルごとに、再生スレッドはタイミング付きの制御コマンドを処理して発行します。
バッファ管理
中断レイテンシを最小限に抑えるため、デバイスに書き込まれるバッファサイズは小さく保たれます。TinyALSA をデフォルトとして使用する場合、バッファサイズは startup_threshold パラメータと同じになるように構成されます。
TinyALSA は、デフォルトを割り当てられたデバイス バッファ全体を
2 で割った値として定義します。
ストリームの中断
ストリームが中断されると、カードに書き込まれたデータがドレインされるまで、ストリームはスレッドの優先度を維持します。そのため、中断と新しいストリームの間には移行期間が発生します。
たとえば、 HAR のオーディオ サンプルで次のものを使用する場合:
- サイズ 3,072
- レート 48,000
- サンプルサイズ 2
保留中のバッファは 3,072 フレームと 6,144 フレームとして計算され、中断遅延は 64 ~ 128 ミリ秒になります。本番環境の実装では、より小さいバッファが必要になります。
エラー管理とリスク
このセクションでは、エラーの管理方法と潜在的なリスクについて説明します。
古いストリームとキューの飢餓
AudioStream は一時停止できます。また、再生は最優先の AudioStream インスタンスからのみ行われるため、キューが大きくなり、優先度の低いストリームが飢餓状態になるリスクがあります。
これを回避するため、各キューの上限は構成可能なサイズに設定されます。この値を超えると、優先度の最も低いストリームが破棄されます。
モニタリングとアラート
本番環境では、安全モニターがオーディオ機能を追跡し、再生が想定どおりに行われるようにします。
AudioManager は、レイテンシに固有の内部統計情報と、ロギング パフォーマンスを定義するフラグをモニタリングします。これらのしきい値を設定すると、次の条件に該当する場合に、すべてのデバッグビルドで警告ログが生成されます。
- スケジューリングから再生開始までの時間が
xミリ秒を超えている。 - (中断されていないストリームの場合)アセットの長さと再生時間の差が
y% を超えている。
デバイスをブロックしました
オーディオ機器が応答しなくなるリスクは常にあります。たとえば、システム内の別のプロセスによって割り当てられて書き込まれてしまう場合などです。再生は別々のスレッドで非同期で実行され、チャイムは後で再生するためにキューに入れることができるため、呼び出し元のアプリには完全に透過的です。
これを検出するために、新しいチャイムの再生がスケジュールされるたびにスレッドの健全性チェックが行われます。再生スレッドにキューが設定されていて、過去 1 秒間に新しいバイトが処理されていない場合は、エラーが返されます。
今後、デバイスの再起動やオープンを試みる必要があるかもしれませんが、最初の実装ではエラーが表示されないようにする必要があります。
コードの構造
大まかに言うと、チャイム再生に関連するコードは次のクレートに存在します。
CRATE: display-safety/crates/(harry-app|harry)
チャイムを再生する呼び出しを発行する既存の HAR アプリ。
NEW CRATE: display-safety/crates/audio
新規: オーディオ コントロールと再生を管理するクレート(ほとんどの機能がここにあります)。
CRATE: display-safety/crates/har-platform-api/audio
オーディオに必要なすべてのシステム呼び出しを含む PAL。
CRATE: display-safety/crates/har-platform-(android|linux)/audio
TinyALSA を使用した再生用の tinyalsa-rs の呼び出し。最初のソリューションでは QNX のサポートは実装されていません。これは、サポートされるプラットフォームが増えるにつれて拡張されます。
TINYALSA PAL: display-safety/crates/tinyalsa-audio
再生用の TinyALSA 固有のコード。これは、Android プラットフォームと Linux プラットフォームの実装で使用されます。
CRATE: display-safety/crates/tinyalsa-rs
TinyALSA C 実装用の Rust バインディング
Rust 実装の詳細
具体的な実装の詳細を次に示します。
- すべての API 関数は
Result<X, AudioError>を返します。Xは () または 戻り値です。 - API 関数は
unsafeとしてマークされていません。 - Mutex と同期メカニズムは内部にあり、
AudioManagerAPI では公開されません。
所有権モデルと AudioManager
オーディオ システムとのアプリのやり取りはすべて、
AudioManagerまたはAudioManagerから返されたオブジェクトを介して行われます。AudioManagerはスレッドセーフです。AudioManagerは HARry アプリで 1 回インスタンス化され、Looperが所有権を持つようにMovedされます。AudioManagerはtokio_util::CancellationTokenトークンを使用して、開始された再生スレッドを管理します。これにより、スレッドが終了し、 リソースが解放されます。AudioManagerDroppedAudioManagerは、複数のインスタンスの作成を明示的に禁止しません。複数のインスタンスが存在する場合は、warnレベルでログに記録されます。
当事者意識を共有する
多くのオブジェクトは、排他アクセスでラップされ、同期された共有所有権を持ちます。これらのメカニズムは AudioManager API では公開されませんが、オーディオと PAL の実装の内部にあります。
AudioDevice- 開かれている(ハンドルがある)ハードウェア参照(TinyALSA PCM など)は、排他アクセス権を持ちます。SMP 設計をご覧ください。AudioStreamインスタンスは、再生がスケジュールされた後、排他アクセス権を持ちます。これは、アプリによって制御され、再生スレッドによって同時にアクセスされる可能性があるためです。再生スレッドは再生中にロックを保持しませんが、再生する次のバッファの不変スナップショットを作成し、次のバッファが処理されるまで変更を考慮しません。
各再生スレッドには、再生キューがあります。これは、
AudioManagerと再生スレッド間の共有参照です。そのため、スレッドには変更に対する排他アクセス権が必要です。ストリームのないスレッドは、
Condvar変数を使用して、 新しいデータが検出されたときにウェイクアップ イベントを受信するためにアイドル状態になります。このメカニズムは共有所有権を持ちます。
依存関係
クレートとオーディオ クレートは、Android ソースツリーでビルドすることが承認されていないクレートへの依存関係を減らすように設計されています。含まれている クレートのリストをご覧ください。
Android と Linux のダウンストリーム プラットフォームの実装は、
TinyALSA と既存のディスプレイ安全 tinyalsa-rs クレートに依存しています。
品質属性
信頼性
音声再生は安全性が重要ですが、この設計では安全モニタリングの実装は対象外です。ハードウェアと本番環境での音声再生の信頼性を検証するために、別途実装してください。
スケーラビリティ
デバイスごとに 1 つのスレッドを使用するアプローチは、さまざまなハードウェア構成にスケーリングすることを目的としています。各スレッドは主にアイドル状態であり、データを待機しているか、デバイスが書き込まれたデータを処理するのを待機しているため、プロセッサに負荷がかかったり、システムでパフォーマンスが低下したりすることはありません。
データを 1 つのデバイスにのみ再生するという設計上の決定と、それ以降のすべての出力制御に対するミキサー コントロール コマンドを組み合わせることで、正確な出力がサウンド ハードウェアによって処理され、将来のシステムにも対応できるスケーラビリティが確保されます。
レイテンシ
レイテンシはオーディオ システムにとって重要であるため、実装後、システムのレイテンシに対して一連のサービスレベル目標(SLO)が定義されます。レイテンシの健全性を継続的にモニタリングするため、すべてのデバッグビルドで定義された SLO を満たしていないシステムログをモニタリングします。
本番環境バージョンでは、モニタリング データはログに依存するのではなく、オーディオ実装の外部のシステムに渡されます。
テストとテスト戦略
クレートとオーディオ クレートは、テスト カバレッジを考慮して設計されています。すべての機能がテストされることを確認するために、モック プラットフォームの実装を追加しました。
ハードウェアとバインディングが複雑なため、プラットフォーム実装の広範なテスト カバレッジは除外されます。ハードウェアと Cuttlefish エミュレータでソリューションを手動でテストするためのサンプル実装を提供します。
ドキュメント
Audio crates/audio の README.md ファイルでは、AudioManager の使用方法について説明しています。crates/audio/examples には、次の例が含まれています。
- プラットフォームを実装する。
AudioManagerのインスタンスを作成する。WavAssetを再生する。- カスタム関数アセットを繰り返し再生する。
- 再生イベントをロギングする。