An implementation of the Basic Audio Profile 1.0.2.
It is assumed that a developer implementing this feature will have access to the BAP specification to reference specific tables and values not fully detailed in this document.
Note: For automated tools and AI agents, specialized skills are available in the repository (under .agents/skills/) to read and reference the specifications.
We are designing a mid-level library to simplify the establishment and management of Bluetooth LE Audio Unicast streams (BAP 1.0.2), which currently requires complex and repetitive interactions with PACS and ASCS state machines. The “Simple Control” feature aims to automate the mapping of varied peer capabilities to standard audio configurations and handle the stream setup state machine, providing a clean API that yields data stream handles to the client while retaining flexibility for testing unsupported configurations.
In our design, we strive to make this library easy to use for clients to connect and perform audio stream control operations.
This library uses the bt-ascs and bt-pacs client libraries, with the goal that the client of this library should not need to use these libraries directly. In some advanced situations, we may provide a way to use the ASCS and PACS clients directly.
This library is focused on the Unicast roles defined in the spec: Unicast Client and Unicast Server.
The current implementation status can be found in TODO.md.
The Specification defines a number of specific capabilities which must or may be supported by the Unicast Server:
It also defines a set of audio configurations for multiple channel audio in section 4.4.
This version of the library provides simple control for audio streams. Fine-grained control is deferred to a later version; clients requiring fine-grained control should use the underlying libraries directly for now.
In either control method, once audio is started, the set of CIS along with the codec configurations can be retrieved and provided to the client, so it can be passed to an audio system which may need to send/receive audio.
The simple control method is managed by a BapUnicastManager which can track multiple peers (identified by PeerId). It provides a high-level interface for clients to establish and control audio streams based on spec-defined configurations.
The manager is initialized without side effects. Calling the asynchronous add_peer(PeerId) triggers the connection and discovery of PACS and ASCS on the remote peer. This method returns the initial mapping of supported spec-defined Configurations to the set of valid Codec Capability Settings (e.g., LC3_48_2) that the peer supports, along with a stream of events for dynamic updates. If discovery fails, the method returns an error immediately.
The manager provides the following asynchronous operations:
start_streaming(PeerId, Configuration, CodecSetting): Initiates the ASCS state machine to configure and enable the stream. It returns the established CIS objects (from bt_gatt::connected_isochronous) to the client, handing over ownership of the data plane.pause_streaming(PeerId, Option<ConnectedIsochronousStream>): Moves the stream to a paused state (typically mapping to QoS Configured in ASCS). The client can indicate it expects to close the CIS to save power by returning the CIS object to the library. If omitted, the library attempts to keep the CIS connected to minimize resume latency.resume_streaming(PeerId): Re-enables a paused stream. If the CIS was closed during pause, the library will re-establish it.stop_streaming(PeerId, ConnectedIsochronousStream): Disables and releases the streams, returning the ASEs to the Idle state. The client must return the CIS object to the library so it can handle the teardown.The manager provides a stream of events for each peer to notify the client of dynamic changes:
CapabilitiesChanged: Emitted when the peer's available audio contexts or capabilities change (discovered via PACS notifications). It provides the updated mapping of supported configurations.StreamStateChanged: Emitted when a stream's state changes (e.g., due to peer-initiated transitions or autonomous release).sequenceDiagram participant Client participant Manager participant Peer (ASCS/PACS) Client->>Manager: add_peer(PeerId) Manager->>Peer: Discover PACS & ASCS Peer-->>Manager: Capabilities & ASEs Manager-->>Client: Map<Configuration, Set<CodecSetting>> Client->>Manager: start_streaming(PeerId, Config, Setting) Manager->>Peer: Config Codec LTVs Manager->>Peer: Config QoS Manager->>Peer: Enable Peer-->>Manager: CIS Established Manager-->>Client: CIS Handles
Fine-grained control is deferred to a later version of this library. Clients requiring fine-grained control over endpoints and specific state transitions should use the bt-ascs and bt-pacs libraries directly.
The library will define a centralized Error enum to represent failures in stream management and control operations.
src/error.rs or src/types.rs#[derive(Debug, thiserror::Error)] pub enum Error { /// An error occurred in the underlying GATT interaction. #[error("GATT error: {0}")] Gatt(bt_gatt::Error), /// An error occurred in the ASCS client. #[error("ASCS error: {0}")] Ascs(bt_ascs::Error), /// The requested peer was not found in the manager. #[error("Peer {0} not found")] PeerNotFound(PeerId), /// The operation requested is invalid in the current state. #[error("Invalid state: {0}")] InvalidState(String), /// The configuration requested is not valid according to the spec. #[error("Invalid configuration: {0}")] InvalidConfiguration(String), /// The peer rejected the operation (e.g. QoS or Codec configuration rejected). #[error("Peer rejected operation: {0}")] PeerRejected(String), /// An internal error occurred. #[error("Internal error: {0}")] Internal(String), }
Gatt: Used when reading/writing characteristics fails during discovery or control point operations.Ascs: Used when the underlying bt-ascs library returns an error, e.g., failure to parse ASCS notifications.PeerNotFound: Returned by start_streaming, pause_streaming, etc., if the provided PeerId has not been added to the manager via add_peer.InvalidState: Returned if the client attempts to resume_streaming on a stream that is not paused, or start_streaming on a peer that is already streaming.InvalidConfiguration: Returned if the client requests a spec configuration that is known to be invalid or unsupported by the library's current implementation level.PeerRejected: Returned when the peer explicitly rejects a state transition in the ASCS control point (e.g., returning a failure response code for Config Codec or Config QoS).To implement the Simple Control feature, we will create the following files in the src/ directory:
src/lib.rsmanager, client, types, mapping) and re-export the public-facing API (like the BapUnicastManager and the configuration enums).src/manager.rsBapUnicastManager struct. It will handle the collection of peers, mapped by their PeerId. It will contain the add_peer function and delegate the actual stream control operations to the specific client instance for that peer.src/client.rsBapUnicastClient struct representing a connection to a single peer. This is where the core logic lives:bt-pacs and bt-ascs.src/types.rsMonoAudioSource, Conversational).Lc3_48_2).src/mapping.rsbt-pacs (frequencies, frame durations) and the endpoints available in bt-ascs, and calculates which of our strongly-typed spec configurations are actually possible.tests/tests.rsThe library relies on bt-pacs and bt-ascs to interact with the remote peer's GATT services, and bt-common for shared types.
bt-commonbt-common provides shared types and structures, including the LTV (Length-Type-Value) structures used for codec capabilities and configurations.
bt-pacsbt-pacs provides types for parsing Published Audio Capabilities (PAC) records and Audio Locations. The library reads these from the remote peer's PACS service.
Key types used from bt-pacs:
PacRecord: Represents a capability record.pub struct PacRecord { pub codec_id: CodecId, pub codec_specific_capabilities: Vec<CodecCapability>, pub metadata: Vec<Metadata>, }
CodecCapability (from bt-common) contains the supported parameters like sampling frequencies and frame durations.bt-ascsThe library uses AudioStreamControlServiceClient from bt-ascs::client to drive the ASCS state machine.
API Contract for AudioStreamControlServiceClient:
impl<T: bt_gatt::GattTypes> AudioStreamControlServiceClient<T> { /// Discover the control point and all endpoints on the server. pub async fn create(gatt_client: T::PeerService) -> Result<Self, Error>; /// Configure the codec for a set of ASEs. pub async fn configure_codec( &mut self, codec_configurations: Vec<CodecConfiguration>, ) -> Result<OperationResult<(AseId, QosParameters)>, Error>; /// Configure QoS for a set of ASEs. pub async fn configure_qos( &mut self, qos_configurations: Vec<QosConfiguration>, ) -> Result<OperationResult<AseId>, Error>; /// Enable a set of ASEs. pub async fn enable( &mut self, ases_with_metadata: Vec<AseIdWithMetadata>, ) -> Result<OperationResult<AseId>, Error>; }
Key Parameter Types:
CodecConfiguration:
pub struct CodecConfiguration { pub ase_id: AseId, pub target_latency: TargetLatency, pub target_phy: TargetPhy, pub codec_id: CodecId, pub codec_specific_configuration: Vec<u8>, // LTV encoded }
[!NOTE] The LTV structures used for
codec_specific_configurationare provided by thebt-commoncrate.
QosConfiguration:
pub struct QosConfiguration { pub ase_id: AseId, pub cig_id: CigId, pub cis_id: CisId, pub sdu_interval: SduInterval, pub framing: Framing, pub phy: Vec<Phy>, pub max_sdu: MaxSdu, pub retransmission_number: u8, pub max_transport_latency: MaxTransportLatency, pub presentation_delay: PresentationDelay, }
AseIdWithMetadata:
pub struct AseIdWithMetadata { pub ase_id: AseId, pub metadata: Vec<Metadata>, }
The library automates the mapping of raw peer capabilities and endpoints to standard BAP configurations. This logic resides in src/mapping.rs.
bt-pacs, including supported sampling frequencies, frame durations, channel counts, and max supported codec frames per SDU.bt-ascs, specifically the number of available Sink and Source ASEs (Audio Stream Endpoints).Audio Configuration 1) to a set of compatible Codec Capability Settings (e.g., LC3_48_2).For each standard BAP configuration defined in the specification (e.g., Section 4.4; see also Tables 7, 13, and 56 for specific capability and QoS parameters):
Audio Configuration 1 requires 1 Sink ASE.CodecSetting.CodecSetting and sufficient ASEs, it is included in the returned map for the client to choose from.BapUnicastManager structure to support it in the future.src/mapping.rs with fake data).tests/tests.rs will be created to test the public API of the BapUnicastManager with mock peer connections.