Chuông âm thanh

Nội dung này mô tả cách phát chuông trong trình kết xuất có độ tin cậy cao (HAR). Thùng Audio hiển thị AudioManager cho ứng dụng HAR, ứng dụng này kiểm soát việc phát chuông.

Để duy trì độ trễ thấp, các luồng phát sẽ chạy trong suốt vòng đời của ứng dụng, ở trạng thái rảnh và nhường quyền khi không có âm thanh nào phát.

Thuật ngữ

thành phần
AudioAsset liên quan đến âm thanh có thể phát. Các thành phần thường được biết đến và tồn tại trong thời gian chạy ứng dụng.
thiết bị
AudioDevice đề cập đến một bus riêng biệt để phát âm thanh. Thiết bị là đơn vị chi tiết nhất liên quan đến phần cứng mà hệ thống truy cập. Trong quá trình triển khai SDVM tiêu chuẩn, AudioDevice đề cập đến một PCM (Kiến trúc âm thanh nâng cao cho Linux) duy nhất.
luồng
Một thực thể phát lại một thành phần trên thiết bị. Các luồng tồn tại từ thời điểm được lên lịch cho đến khi hoàn tất, bị huỷ hoặc kết thúc do lỗi.

Thành phần

Hình 1 cho thấy sơ đồ thành phần của chuông:

Sơ đồ thành phần

Hình 1. Sơ đồ thành phần.

Thiết bị âm thanh và PCM

Cấu hình phần cứng âm thanh tuân theo thiết kế lớp trừu tượng nền tảng HAR tiêu chuẩn và har-platform-api chứa cấu hình đó.

Thùng HAR Audio xác định một cấu trúc mới cho AudioDevice, cấu trúc này xác định các trường cho tất cả cấu trúc dữ liệu ảnh hưởng đến thùng HAR Audio và quá trình phát. AudioDevice cũng sử dụng các thành phần chung để bao bọc các tham số bổ sung tiềm ẩn dành riêng cho nền tảng. Trong trường hợp tinyalsa, PlatformAudioDevice chứa các bộ mô tả và thuộc tính của 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
}

Nội dung âm thanh

Phần này mô tả cách định cấu hình và triển khai nội dung âm thanh.

Cấu hình

Quá trình triển khai âm thanh HAR ban đầu hỗ trợ các thành phần âm thanh được định cấu hình tĩnh. Cấu hình JSON xác định những thành phần nào có sẵn và những thành phần nào được xác định là tệp WAV.

Quá trình triển khai này cũng hỗ trợ các thành phần âm thanh được tổng hợp và phát trực tuyến thông qua quá trình triển khai thành phần chung hơn, chấp nhận một hàm để tạo dữ liệu âm thanh.

Triển khai

Triển khai các thành phần bằng cách sử dụng 2 cấu trúc riêng biệt là AudioAssetAudioStream.

AudioAsset xác định các thuộc tính tĩnh của một thành phần và một vùng chứa cho dữ liệu nội bộ tiềm ẩn liên quan đến thành phần đó. Từ AudioAsset AudioStream, bạn có thể lấy được một thực thể có thể phát trực tuyến duy nhất của thành phần đó. AudioStream chứa một trạng thái nội bộ liên quan đến quá trình phát luồng đơn lẻ.

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

Phát chuông

Phần này mô tả API và quy trình phát chuông. Quá trình phát chuông đơn lẻ được gọi là luồng.

Vòng đời của một luồng

Hình 2 minh hoạ vòng đời của một luồng:

Phát và sự kiện phát trực tiếp

Hình 2. Phát luồng và sự kiện.

Hình 2 mô tả các bước sau:

  1. Phát: Lên lịch phát luồng.

  2. Ưu tiên: Việc ưu tiên phát sẽ quyết định có nên:

    • Phát chuông ngay (sự kiện bắt đầu khi có các byte đầu tiên)
    • Phát chuông sau (sự kiện tạm dừng hoặc tiếp tục)
    • Giảm mức độ ưu tiên của chuông (sự kiện bị huỷ)
  3. Các chế độ điều khiển bộ trộn: Nếu cần, hãy cập nhật các chế độ điều khiển bộ trộn dựa trên hành vi đã định cấu hình.

  4. Ghi byte: Ghi một khối byte vào AudioDevice.

  5. Thêm dữ liệu: Nếu luồng có thêm dữ liệu, hãy quay lại Bước 2.

  6. Lặp lại: Nếu luồng cần được lặp lại, hãy đặt lại và quay lại Bước 2 (sự kiện khởi động lại).

  7. Đã hoàn tất: Luồng đã hoàn tất thành công (sự kiện FinishedSuccessfully).

Bạn có thể tạm dừng, tiếp tục hoặc ngừng phát chuông bất cứ lúc nào.

Mức độ ưu tiên của chuông

Logic này đặt mức độ ưu tiên của chuông:

  1. Ghi đè chế độ phát. Ví dụ: chuông ở chế độ mờ dần luôn được cấp mức độ ưu tiên cao nhất cho đến khi quá trình mờ dần hoàn tất.

  2. Mức độ ưu tiên đã chỉ định.

  3. Nếu mức độ ưu tiên bằng nhau gần đây hơn, thì chuông sẽ phát trước.

Khi chuông có mức độ ưu tiên bằng nhau, AudioManager sẽ được tạo thực thể với giá trị enum.

API

Sự kiện

Nếu một kênh sự kiện được cung cấp khi chuông bắt đầu, HAR Audio sẽ phát ra một số sự kiện trong quá trình phát. Các sự kiện được hỗ trợ sẽ xuất hiện trong ví dụ này:

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

Điều khiển bộ trộn

Phần này mô tả cách kiểm soát âm lượng và không gian hoá.

Tập

HAR xác định âm lượng một cách nhất quán theo đơn vị milibel. Thùng har-platform-api xử lý việc chuyển đổi từ milibel sang tín hiệu điều khiển.

Mối quan hệ giữa milibel và công suất đầu ra của phần cứng là theo hàm logarit và khác nhau rất nhiều giữa các cấu hình phần cứng và loa. Do đó, hãy cung cấp cấu hình giữa các giá trị trong cấu hình AudioDevice (Thiết bị âm thanh và PCM) và quá trình chuyển đổi phải diễn ra trước khi gọi lớp nền tảng.

Do đó, quá trình triển khai trong API PAL sẽ xác định 2 hàm.

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

fn set_volume_control(AudioDeviceID, ControlValue);

Quá trình triển khai mặc định cho set_volume_millibel sử dụng cấu hình được cung cấp cho AudioDevice, bao gồm một tập hợp các cặp khoá-giá trị cho milibel tham chiếu – điều khiển, chuyển đổi milibel để điều khiển các giá trị, sau đó gọi hàm set_volume_control với giá trị đã chuyển đổi.

Thiết kế này cung cấp giá trị mặc định và cho phép các quá trình triển khai tiếp theo ghi đè ánh xạ mặc định.

Luồng âm thanh HAR

Hình 3. Luồng âm thanh HAR.

Không gian hoá

Audio API hiển thị chức năng để kiểm soát dữ liệu âm thanh của vùng không gian nào sẽ phát. Các tham số này được truyền đến lớp PAL và được áp dụng ở hạ nguồn bằng các chế độ điều khiển phần cứng. Các tuỳ chọn được xác định trong API PAL như sau:

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

Các cấp điều khiển bộ trộn

Bạn có thể xác định âm lượng và không gian hoá trên một thành phần và cho một luồng. Nếu bạn xác định mức độ ưu tiên của luồng, thì luồng sẽ ghi đè các chế độ điều khiển do thành phần xác định.

Quản lý luồng

Trình quản lý âm thanh duy trì một luồng cho mỗi thực thể AudioDevice. Mỗi luồng hoạt động độc lập. Hoạt động tương tác giữa AudioManager và luồng phát sử dụng một hàng đợi luồng dùng chung được sắp xếp theo mức độ ưu tiên.

Các lệnh gọi ALSA sử dụng các lượt ghi ASYNC với tính năng thăm dò ý kiến để xác định thời điểm dữ liệu được xử lý.

Trình tự quản lý luồng

Hình 4. Trình tự quản lý luồng.

Tín hiệu điều khiển trong quá trình thăm dò ý kiến

Khi chờ thẻ âm thanh xử lý các byte, bạn có thể đưa ra tín hiệu điều khiển. Ví dụ: để thay đổi độ mờ hoặc không gian hoá của âm thanh. Việc thăm dò ý kiến để lấy trạng thái của thiết bị âm thanh được định cấu hình ở cấp AudioManager hoặc mặc định là 1 mili giây. Sau mỗi chu kỳ thăm dò ý kiến, luồng phát sẽ xử lý và đưa ra mọi lệnh điều khiển theo thời gian.

Quản lý bộ đệm

Để giảm thiểu độ trễ gián đoạn, kích thước bộ đệm được ghi vào thiết bị sẽ được giữ ở mức nhỏ. Khi sử dụng TinyALSA làm giá trị mặc định, dung lượng bộ nhớ đệm sẽ được định cấu hình giống như tham số startup_threshold. TinyALSA xác định giá trị mặc định là toàn bộ bộ đệm thiết bị được phân bổ chia cho 2.

Gián đoạn luồng

Khi các luồng bị gián đoạn, các luồng sẽ duy trì mức độ ưu tiên của luồng cho đến khi dữ liệu mà chúng đã ghi vào thẻ được sử dụng hết. Do đó, sẽ có một giai đoạn chuyển đổi giữa quá trình gián đoạn và luồng mới.

Ví dụ: nếu mẫu âm thanh trong HAR sử dụng:

  • Kích thước là 3.072
  • Tốc độ là 48.000
  • Kích thước mẫu là 2

Bộ đệm đang chờ xử lý được tính là 3.072 và 6.144 khung, dẫn đến độ trễ gián đoạn từ 64 đến 128 mili giây. Quá trình triển khai sản xuất sẽ yêu cầu bộ đệm nhỏ hơn.

Quản lý lỗi và rủi ro

Phần này mô tả cách quản lý lỗi và các rủi ro tiềm ẩn.

Các luồng lỗi thời và tình trạng thiếu hàng đợi

Với việc AudioStream có thể bị tạm dừng và vì quá trình phát chỉ có thể diễn ra từ thực thể AudioStream có mức độ ưu tiên cao nhất, nên sẽ có nguy cơ hàng đợi ngày càng tăng làm thiếu các luồng có mức độ ưu tiên thấp.

Để tránh trường hợp này, mỗi hàng đợi sẽ bị giới hạn ở một kích thước có thể định cấu hình. Khi vượt quá giá trị này, luồng có mức độ ưu tiên thấp nhất sẽ bị loại bỏ.

Theo dõi và cảnh báo

Trong quá trình sản xuất, trình giám sát an toàn sẽ theo dõi các tính năng âm thanh để đảm bảo quá trình phát diễn ra như dự kiến.

AudioManager theo dõi các số liệu thống kê nội bộ dành riêng cho độ trễ và một cờ xác định hiệu suất ghi nhật ký. Sau khi đặt các ngưỡng này, nhật ký cảnh báo sẽ được tạo cho tất cả các bản dựng gỡ lỗi khi:

  • Thời lượng giữa thời điểm lên lịch và thời điểm bắt đầu phát vượt quá x mili giây.
  • Đối với luồng không bị gián đoạn, độ dài thành phần và thời gian phát khác nhau hơn y phần trăm.

Đã chặn thiết bị

Luôn có một rủi ro nhỏ là thiết bị âm thanh không phản hồi, ví dụ: nếu thiết bị đó được phân bổ và ghi bởi một quy trình khác trong hệ thống. Với việc quá trình phát chạy không đồng bộ trong các luồng riêng biệt và chuông có thể được xếp hàng đợi để phát sau, điều này hoàn toàn minh bạch đối với ứng dụng gọi.

Để phát hiện điều này, hệ thống sẽ kiểm tra tình trạng luồng bất cứ khi nào một chuông mới được lên lịch phát, trả về lỗi nếu luồng phát có hàng đợi được điền sẵn và không xử lý bất kỳ byte mới nào trong giây cuối cùng.

Trong tương lai, bạn có thể cần thử khởi động lại / mở thiết bị, nhưng đối với quá trình triển khai ban đầu, lỗi không được ẩn.

Cấu trúc mã

Nhìn chung, mã liên quan đến việc phát chuông tồn tại trên các thùng sau:

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

Ứng dụng HAR hiện có, ứng dụng này đưa ra các lệnh gọi để phát chuông.

NEW CRATE: display-safety/crates/audio

MỚI: Thùng để quản lý việc điều khiển và phát âm thanh (đây là nơi có hầu hết các chức năng).

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

PAL bao gồm tất cả các lệnh gọi hệ thống cần thiết cho âm thanh.

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

Các lệnh gọi đến tinyalsa-rs để phát bằng TinyALSA. Hỗ trợ QNX không được triển khai trong giải pháp ban đầu và sẽ tăng lên khi có thêm nền tảng được hỗ trợ.

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

Mã dành riêng cho TinyALSA để phát. Mã này được dùng bởi các quá trình triển khai nền tảng Android và Linux.

CRATE: display-safety/crates/tinyalsa-rs

Các liên kết Rust cho quá trình triển khai TinyALSA C

Thông tin chi tiết về quá trình triển khai Rust

Một số thông tin chi tiết cụ thể về quá trình triển khai:

  • Tất cả các hàm API đều trả về Result<X, AudioError> trong đó X là () hoặc a giá trị trả về.
  • Không có hàm API nào được đánh dấu là unsafe.
  • Cơ chế Mutex và đồng bộ hoá là nội bộ và không được hiển thị trong API AudioManager.

Mô hình quyền sở hữu và AudioManager

  • Mọi hoạt động tương tác của ứng dụng với hệ thống âm thanh đều diễn ra thông qua AudioManager hoặc các đối tượng được trả về từ AudioManager.

  • AudioManager là luồng an toàn.

  • AudioManager được tạo thực thể một lần trong ứng dụng HARry và Moved để Looper có quyền sở hữu.

  • AudioManager sử dụng mã thông báo tokio_util::CancellationToken để quản lý các luồng phát đã bắt đầu, đảm bảo các luồng bị chấm dứt và tài nguyên được giải phóng nếu AudioManagerDropped.

  • AudioManager không ngăn chặn rõ ràng việc tạo nhiều thực thể. Nếu có nhiều thực thể, thì hệ thống sẽ ghi nhật ký ở cấp warn.

Quyền sở hữu dùng chung

Một số đối tượng có quyền sở hữu dùng chung được bao bọc và đồng bộ hoá với quyền truy cập độc quyền. Các cơ chế này không được hiển thị trong API AudioManager, nhưng là nội bộ đối với các quá trình triển khai âm thanh và PAL.

  • AudioDevice – Mỗi tham chiếu phần cứng (ví dụ: TinyALSA PCM) được mở (có một trình xử lý) đều có quyền truy cập độc quyền. Xem Thiết kế SMP.

  • Các thực thể AudioStream có quyền truy cập độc quyền sau khi được lên lịch phát vì chúng có thể được ứng dụng kiểm soát và được luồng phát truy cập đồng thời.

    Luồng phát không giữ các khoá trong quá trình phát, nhưng tạo ảnh chụp nhanh bất biến của bộ đệm tiếp theo để phát và không xem xét các thay đổi cho đến khi bộ đệm tiếp theo được xử lý.

  • Mỗi luồng phát có một hàng đợi phát, một tham chiếu dùng chung giữa AudioManager và luồng phát. Do đó, luồng cần có quyền truy cập độc quyền để đột biến.

  • Các luồng không có luồng sẽ ở trạng thái rảnh với biến Condvar để nhận các sự kiện đánh thức khi phát hiện dữ liệu mới. Cơ chế này có quyền sở hữu dùng chung.

Phần phụ thuộc

Các thùng và thùng âm thanh được thiết kế để giảm sự phụ thuộc vào các thùng không được phê duyệt để xây dựng trong cây nguồn Android. Xem danh sách các thùng được đưa vào crates.

Các quá trình triển khai nền tảng ở hạ nguồn cho Android và Linux phụ thuộc vào TinyALSA và thùng tinyalsa-rs an toàn cho màn hình hiện có.

Thuộc tính chất lượng

Độ tin cậy

Mặc dù quá trình phát âm thanh là rất quan trọng đối với sự an toàn, nhưng thiết kế này không bao gồm việc triển khai tính năng giám sát an toàn. Triển khai tính năng này trong một nỗ lực riêng biệt để xác minh độ tin cậy của quá trình phát âm thanh trên phần cứng và trong quá trình sản xuất.

Khả năng mở rộng quy mô

Phương pháp một luồng cho mỗi thiết bị nhằm mục đích mở rộng quy mô cho các cấu hình phần cứng khác nhau. Với việc mỗi luồng chủ yếu ở trạng thái rảnh, chờ dữ liệu hoặc chờ thiết bị xử lý dữ liệu đã ghi, thì luồng đó không nên yêu cầu bộ xử lý hoặc hiệu suất cao trên hệ thống.

Quyết định thiết kế chỉ phát dữ liệu cho một thiết bị, kết hợp với các lệnh điều khiển bộ trộn cho tất cả các chế độ điều khiển đầu ra tiếp theo, đảm bảo đầu ra chính xác được xử lý bởi phần cứng âm thanh và sẽ mở rộng quy mô cho các hệ thống trong tương lai.

Độ trễ

Độ trễ là rất quan trọng đối với hệ thống âm thanh, vì vậy, sau khi triển khai, một tập hợp các mục tiêu ở cấp dịch vụ (SLO) sẽ được xác định cho độ trễ của hệ thống. Để liên tục theo dõi tình trạng độ trễ, hãy theo dõi trong nhật ký hệ thống không đáp ứng các SLO đã xác định trong tất cả các bản dựng gỡ lỗi.

Đối với các phiên bản sản xuất, dữ liệu giám sát được truyền đến một số hệ thống bên ngoài quá trình triển khai âm thanh, thay vì dựa vào nhật ký.

Chiến lược kiểm thử và kiểm thử

Các thùng và thùng âm thanh được thiết kế với phạm vi kiểm thử. Chúng tôi đã thêm một quá trình triển khai nền tảng mô phỏng để xác nhận rằng tất cả các chức năng đều được kiểm thử.

Độ phức tạp của phần cứng và các liên kết ngăn cản phạm vi kiểm thử rộng rãi cho các quá trình triển khai nền tảng. Chúng tôi cung cấp các quá trình triển khai mẫu để kiểm thử giải pháp theo cách thủ công trên phần cứng và trên trình mô phỏng Cuttlefish.

Tài liệu

Tệp README.md trong Audio crates/audio mô tả cách sử dụng AudioManager. crates/audio/examples chứa các ví dụ về:

  • Triển khai một nền tảng.
  • Tạo một thực thể của AudioManager.
  • Phát WavAsset.
  • Phát một thành phần hàm tuỳ chỉnh theo kiểu lặp lại.
  • Ghi nhật ký các sự kiện phát.