오디오 차임벨

이 콘텐츠에서는 고가용성 렌더러 (HAR)의 차임 재생을 설명합니다. Audio 크레이트는 차임 재생을 제어하는 HAR 앱에 AudioManager를 노출합니다.

지연 시간을 짧게 유지하기 위해 재생 스레드는 오디오가 재생되지 않을 때 유휴 상태로 전환되고 양보하면서 앱의 수명 동안 실행됩니다.

용어

애셋
AudioAsset 은 재생 가능한 오디오와 관련이 있습니다. 애셋은 일반적으로 알려져 있으며 앱 런타임에 존재합니다.
기기
AudioDevice 는 오디오 재생을 위한 별도의 버스를 나타냅니다. 기기는 시스템에서 액세스하는 하드웨어와 관련된 가장 세분화된 단위입니다. 표준 SDVM 구현에서 AudioDevice는 단일 ALSA (Advanced Linux Sound Architecture) PCM을 나타냅니다.
스트림
기기에서 애셋을 재생하는 인스턴스입니다. 스트림은 예약된 순간부터 완료, 취소 또는 오류로 종료될 때까지 유지됩니다.

구성요소

그림 1은 차임의 구성요소 다이어그램을 보여줍니다.

구성요소 다이어그램

그림 1. 구성요소 다이어그램

오디오 기기 및 PCM

오디오 하드웨어 구성은 표준 HAR 플랫폼 추상화 레이어 디자인을 따르며 har-platform-api에 포함되어 있습니다.

HAR Audio 크레이트는 내부 HAR Audio 크레이트 및 재생에 영향을 미치는 모든 데이터 구조의 필드를 정의하는 AudioDevice의 새 구조를 정의합니다. 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 파일로 정의된 애셋을 정의합니다.

이 구현은 오디오 데이터를 생성하는 함수를 허용하는 보다 일반적인 애셋 구현을 통해 합성된 오디오 애셋과 스트리밍된 오디오 애셋도 지원합니다.

구현

두 개의 별도 구조인 AudioAssetAudioStream을 사용하여 애셋을 구현합니다.

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에서는 다음 단계를 설명합니다.

  1. 재생: 스트림을 재생하도록 예약합니다.

  2. 우선순위 지정: 재생 우선순위 지정은 다음을 결정합니다.

    • 지금 차임 재생 (첫 번째 바이트 시 시작된 이벤트)
    • 나중에 차임 재생 (일시중지 또는 재개된 이벤트)
    • 차임 우선순위 낮추기 (취소된 이벤트)
  3. 믹서 컨트롤: 필요한 경우 구성된 동작에 따라 믹서 컨트롤을 업데이트합니다.

  4. 바이트 쓰기: 바이트 청크를 AudioDevice에 씁니다.

  5. 추가 데이터: 스트림에 추가 데이터가 있는 경우 2단계로 돌아갑니다.

  6. 반복: 스트림을 반복해야 하는 경우 재설정하고 2단계로 돌아갑니다(다시 시작된 이벤트).

  7. 완료됨: 스트림이 성공적으로 완료되었습니다 (FinishedSuccessfully 이벤트).

언제든지 일시중지, 재개 또는 중지 호출로 차임을 중단할 수 있습니다.

차임 우선순위

이 로직은 차임 우선순위를 설정합니다.

  1. 재생 모드 재정의. 예를 들어 페이드 아웃 모드의 차임은 페이드 아웃이 완료될 때까지 항상 최우선순위가 부여됩니다.

  2. 지정된 우선순위.

  3. 우선순위가 동일한 경우 차임이 먼저 재생됩니다.

차임의 우선순위가 동일하면 AudioManagerenum 값으로 인스턴스화됩니다.

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의 구현은 두 함수를 정의합니다.

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

fn set_volume_control(AudioDeviceID, ControlValue);

set_volume_millibel의 기본 구현은 참조 밀리벨 - 제어의 키-값 쌍 집합을 포함하여 AudioDevice에 제공된 구성을 사용하고 밀리벨을 제어 값으로 변환한 다음 변환된 값으로 set_volume_control 함수를 호출합니다.

이 디자인은 기본값을 제공하며 후속 구현에서 기본 매핑을 재정의할 수 있도록 합니다.

HAR 오디오 흐름

그림 3. HAR 오디오 흐름

공간화

오디오 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 인스턴스당 하나의 스레드를 유지합니다. 각 스레드는 독립적으로 작동합니다. AudioManager와 재생 스레드 간의 상호작용은 우선순위로 정렬된 공유 스트림 큐를 사용합니다.

ALSA 호출은 폴링이 있는 비동기 쓰기를 사용하여 데이터가 소화되는 시점을 결정합니다.

스레드 관리 시퀀스

그림 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로 표시되지 않습니다.
  • 뮤텍스 및 동기화 메커니즘은 내부적이며 AudioManager API에 노출되지 않습니다.

소유권 모델 및 AudioManager

  • 오디오 시스템과의 모든 앱 상호작용은 AudioManager 또는 AudioManager에서 반환된 객체를 통해 이루어집니다.

  • AudioManager는 스레드로부터 안전합니다.

  • AudioManager 는 HARry 앱에서 한 번 인스턴스화되고 Looper가 소유권을 갖도록 Moved됩니다.

  • AudioManagertokio_util::CancellationToken 토큰을 사용하여 시작된 재생 스레드를 관리하며, 스레드가 종료되고 리소스가 해제되도록 합니다. AudioManagerDropped되면

  • AudioManager 는 여러 인스턴스가 생성되는 것을 명시적으로 방지하지 않습니다. 인스턴스가 두 개 이상 있는 경우 warn 수준으로 로깅합니다.

공유 소유권

여러 객체에는 독점 액세스로 래핑되고 동기화된 공유 소유권이 있습니다. 이러한 메커니즘은 AudioManager API에 노출되지 않지만 오디오 및 PAL 구현에 내부적으로 적용됩니다.

  • AudioDevice - 열린 (핸들이 있는) 각 하드웨어 참조 (예: TinyALSA PCM)에는 독점 액세스 권한이 있습니다. SMP 디자인을 참고하세요.

  • AudioStream 인스턴스는 앱에서 제어할 수 있고 재생 스레드에서 동시에 액세스할 수 있으므로 재생을 위해 예약된 후 독점 액세스 권한을 갖습니다.

    재생 스레드는 재생 중에 잠금을 유지하지 않지만 재생할 다음 버퍼의 변경 불가능한 스냅샷을 만들고 다음 버퍼가 소화될 때까지 변경사항을 고려하지 않습니다.

  • 각 재생 스레드에는 재생 큐가 있으며 AudioManager와 재생 스레드 간의 공유 참조가 있습니다. 따라서 스레드에는 변경을 위한 독점 액세스 권한이 필요합니다.

  • 스트림이 없는 스레드는 새 데이터가 감지될 때 웨이크업 이벤트를 수신하기 위해 Condvar 변수를 사용하여 유휴 상태가 됩니다. 이 메커니즘에는 공유 소유권이 있습니다.

종속 항목

크레이트 및 오디오 크레이트는 Android 소스 트리에서 빌드하도록 승인되지 않은 크레이트에 대한 종속 항목을 줄이도록 설계되었습니다. 포함된 크레이트 목록을 참고하세요.

Android 및 Linux의 다운스트림 플랫폼 구현은 TinyALSA 및 기존 디스플레이 안전 tinyalsa-rs 크레이트에 종속됩니다.

품질 속성

안정성

오디오 재생은 안전에 중요하지만 이 디자인은 안전 모니터링 구현을 다루지 않습니다. 하드웨어 및 프로덕션에서 오디오 재생 안정성을 확인하기 위해 별도의 노력으로 이를 구현합니다.

확장성

기기당 하나의 스레드 접근 방식은 다양한 하드웨어 설정으로 확장하기 위한 것입니다. 각 스레드는 기본적으로 유휴 상태이거나 데이터를 기다리거나 기기가 작성된 데이터를 소화할 때까지 기다리므로 프로세서에 부담을 주거나 시스템에서 성능 집약적이지 않아야 합니다.

단일 기기에서만 데이터를 재생하도록 설계한 결정은 모든 추가 출력 제어를 위한 믹서 제어 명령어와 결합되어 정확한 출력이 사운드 하드웨어에서 처리되도록 하며 향후 시스템에 맞게 확장되어야 합니다.

지연 시간

지연 시간은 오디오 시스템에 중요하므로 구현 후 시스템의 지연 시간에 대한 서비스 수준 목표 (SLO) 집합이 정의됩니다. 지연 시간 상태를 지속적으로 모니터링하려면 모든 디버그 빌드에서 정의된 SLO를 충족하지 않는 시스템 로그의 모니터링을 수행합니다.

프로덕션 버전의 경우 모니터링 데이터는 로그에 의존하는 대신 오디오 구현 외부의 일부 시스템으로 전달됩니다.

테스트 및 테스트 전략

크레이트와 오디오 크레이트는 테스트 범위로 설계되었습니다. 모든 기능이 테스트되었는지 확인하기 위해 모의 플랫폼 구현을 추가했습니다.

하드웨어 및 바인딩의 복잡성으로 인해 플랫폼 구현에 대한 광범위한 테스트 범위가 불가능합니다. 하드웨어 및 Cuttlefish 에뮬레이터에서 솔루션을 수동으로 테스트할 수 있는 샘플 구현을 제공합니다.

문서

Audio crates/audioREADME.md 파일은 AudioManager를 사용하는 방법을 설명합니다. crates/audio/examples에는 다음 예가 포함되어 있습니다.

  • 플랫폼 구현
  • AudioManager 인스턴스 만들기
  • WavAsset 재생
  • 맞춤 함수 애셋을 반복해서 재생
  • 재생 이벤트 로깅