Overview
This part covers the actual streaming procedures you will implement as an embedded engineer. There are two distinct paths: unicast audio (connection-based, phone to headset) and broadcast audio (connectionless, one source to many sinks).
Unicast audio uses the ASE (Audio Stream Endpoint) state machine — a sequence of GATT operations that move an ASE from idle all the way to streaming. Broadcast audio uses a simpler state machine on the Broadcast Source side, combined with Extended Advertising and Periodic Advertising to carry stream parameters.
Key Terms
Part A — Unicast Audio Streaming
The ASE State Machine
Every audio stream in unicast BAP is controlled through an ASE (Audio Stream Endpoint). The ASE is a GATT characteristic on the Unicast Server. Moving an ASE through its states is how a stream gets established, used, and torn down.
| ← Unicast Client writes to ASE Control Point | Unicast Server notifies ASE characteristic → | ||||
| Idle |
Config Codec
⟶
|
Codec Configured |
Config QoS
⟶
|
QoS Configured |
| ↑ Release (Server autonomous) ↓ Enable | ||||
| Released |
Release op
⟵⟵⟵⟵⟵
|
Enabling | ||
| ↑ Disable ↓ Receiver Start Ready | ||||
| Streaming ★ | ||||
Step-by-Step: Establishing a Unicast Stream
The Unicast Client connects to the Unicast Server over LE ACL. It then does GATT discovery to find PACS and ASCS. It reads the Sink PAC characteristic to learn what codec settings the server supports, and reads the Supported/Available Audio Contexts characteristics.
The Unicast Client writes a Config Codec opcode to the ASE Control Point characteristic. It includes the ASE_ID, Codec_ID (LC3 = 0x06), and Codec_Specific_Configuration LTV structures (sampling frequency, frame duration, octets per frame, channel allocation).
The Unicast Server responds by notifying the ASE Control Point (success/failure), then notifying the ASE characteristic with its Codec Configured state — which also includes the server’s preferred QoS range (Presentation_Delay_Min, Presentation_Delay_Max).
Before writing the Config QoS opcode, the Unicast Client must first configure the CIG in its local Bluetooth Controller using the HCI LE Set CIG Parameters command. This reserves the isochronous resources.
Then the Unicast Client writes Config QoS to the ASE Control Point including CIG_ID, CIS_ID, SDU_Interval, Framing, PHY, Max_SDU, Retransmission_Number, Max_Transport_Latency, and Presentation_Delay. All ASEs within the same CIG must be configured in a single Config QoS write.
The Unicast Client writes the Enable opcode. At this point it can also include Metadata LTV structures — specifically Streaming_Audio_Contexts to tell the server what kind of audio is about to flow (e.g., Media, Conversational).
The ASE moves to the Enabling state. The Unicast Client must now attempt CIS establishment using the HCI LE Create CIS command. The Unicast Server must accept the incoming CIS connection in the Enabling state.
After CIS establishment, both devices set up their audio data paths using HCI LE Setup ISO Data Path. For a Sink ASE (server receives audio), the server autonomously writes a Receiver Start Ready notification. For a Source ASE (server sends audio), the Unicast Client writes Receiver Start Ready when it is ready to receive.
When Receiver Start Ready completes, the ASE moves to the Streaming state. Audio data now flows over the CIS. The Audio Source starts sending LC3 Media Packets as isochronous SDUs.
BlueZ — Setup ISO Data Path (Transparent Codec, Host-side Codec)
#include <bluetooth/hci.h> #include <bluetooth/hci_lib.h> /* * Setup ISO Data Path — tells the controller which direction * audio flows and which codec is in use. * When the LC3 codec runs in the Host (not the controller), * we use Coding_Format = 0x03 (Transparent) so the controller * just passes raw bytes through without decoding. */ void setup_iso_data_path(int hci_fd, uint16_t conn_handle, uint8_t direction) { struct { uint16_t conn_handle; uint8_t data_path_dir; /* 0x00=Input(TX), 0x01=Output(RX) */ uint8_t data_path_id; /* 0x00 = HCI transport */ uint8_t codec_id[5]; /* coding format + company + vendor */ uint32_t controller_delay; uint8_t codec_cfg_len; } cmd = { .conn_handle = htobs(conn_handle), .data_path_dir = direction, .data_path_id = 0x00, /* codec_id[0] = 0x03 = Transparent (LC3 runs in Host) */ .codec_id = { 0x03, 0x00, 0x00, 0x00, 0x00 }, .controller_delay = 0, .codec_cfg_len = 0, }; /* Send HCI_LE_Setup_ISO_Data_Path (OCF 0x006E) */ hci_send_cmd(hci_fd, OGF_LE_CTL, 0x006E, sizeof(cmd), (uint8_t *)&cmd); }
Stopping the Stream — Disable and Release
Stops audio streaming but keeps the CIS and QoS configuration. The Sink ASE goes directly to QoS Configured. The Source ASE goes to Disabling — the client then writes Receiver Stop Ready to complete the transition.
Tears down everything — ASE transitions to Releasing, both sides remove their audio data paths, and the Unicast Client terminates the CIS. The server then autonomously transitions the ASE to either Idle or Codec Configured.
Part B — Broadcast Audio Streaming
The Broadcast Audio Stream State Machine
Broadcast audio is entirely driven by the Broadcast Source. There is no connection and no GATT involved. The state machine has only three states:
| Idle No BIG, no advertising |
Configure
(start EA + PA) ⟶
|
Configured EA + PA active No BIG yet (no audio) |
Establish
(create BIG) ⟶
|
Streaming BIG active, audio flowing |
| Streaming ⟶ Disable (terminate BIG) ⟶ Configured | Configured ⟶ Release (stop PA) ⟶ Idle | ||||
How EA, PA and BIG Fit Together
A Broadcast Source transmits three layers of packets. Each one serves a specific purpose and points to the next:
| Layer 1 Extended Advertising (EA) |
Layer 2 Periodic Advertising (PA) |
Layer 3 BIG / BIS |
| ADV_EXT_IND contains Broadcast Audio Announcement Service UUID and a random Broadcast_ID. It also contains a SyncInfo pointer that leads to the PA. | AUX_SYNC_IND carries the BASE structure — the full codec and stream parameters. When the BIG is active, it also carries BIGInfo in the Extended Header ACAD field. | BIGInfo enables the Broadcast Sink to synchronize to the BIG and receive audio data from one or more BIS channels. |
| PDUs: ADV_EXT_IND → AUX_ADV_IND | PDUs: AUX_SYNC_IND → AUX_CHAIN_IND | Audio ISO data packets |
The BASE Structure — How Stream Parameters Are Announced
The BASE (Broadcast Audio Source Endpoint) is the data structure carried in Periodic Advertising. It describes every stream in a BIG using a three-level hierarchy.
Presentation_Delay — microseconds before rendering audioNum_Subgroups — how many subgroups existCodec_ID — LC3 or vendor-specificCodec_Specific_Configuration — LTV: freq, frame duration, octets/frameMetadata — LTV: Streaming_Audio_Contexts, LanguageNum_BIS — BIS count in this subgroupBIS_index — which BIS in the BIG (1-based)Codec_Specific_Configuration — LTV: Audio_Channel_Allocation (FL / FR)A practical example: a TV broadcasting stereo audio with two language options would have 2 subgroups (English and Spanish), each with 2 BIS (Front Left and Front Right). A total of 4 BIS in one BIG.
| Level | Parameter | Value in TV example |
|---|---|---|
| 1 (BIG) | Presentation_Delay | 40 ms |
| 1 | Num_Subgroups | 2 |
| 2 (Subgroup 0) | Codec_ID | 0x06 (LC3) |
| 2 | Sampling_Freq + Frame_Duration | 48 kHz, 10 ms |
| 2 | Metadata: Language | Spanish |
| 3 (BIS index 1) | Audio_Channel_Allocation | Front Left |
| 3 (BIS index 2) | Audio_Channel_Allocation | Front Right |
How a Broadcast Sink Finds and Receives a Stream
Broadcast Assistant — Helping Power-Constrained Sinks
A hearing aid or a small earbud may not have the power budget to constantly scan for Broadcast Sources on its own. The Scan Delegator role on the sink device allows it to ask a phone (acting as a Broadcast Assistant) to scan on its behalf.
| Scan Delegator (Hearing Aid) |
1. Solicitation
(EA with BASS UUID) ⟶
|
Broadcast Assistant (Phone) |
2. Scans & finds
Broadcast Source ⟶
|
Broadcast Source (TV) |
| 3. Assistant connects to Delegator (LE ACL) and writes Add Source + Broadcast_Code to BASS Control Point | ||||
| 4. Assistant performs PAST (Periodic Advertising Sync Transfer) — passes SyncInfo to Delegator over LL_PERIODIC_SYNC_IND | ||||
| 5. Hearing Aid (Broadcast Sink) synchronizes to PA and BIG using the SyncInfo — audio streaming begins | ||||
BlueZ — Creating a BIS Broadcast Source
Setting up a Broadcast Source in BlueZ uses ISO sockets with the BT_ISO_BC_SRC flag. The socket bind creates the BIG and the connect creates the BIS channels.
#include <bluetooth/bluetooth.h> #include <bluetooth/iso.h> /* * BIS Broadcast Source using BlueZ ISO socket API * Creates a BIG with one BIS for mono broadcast audio * Codec: LC3 16_2_1 (16kHz, 10ms frames, 40 bytes/frame) */ int create_bis_source(void) { int fd; /* BIS QoS configuration — broadcast version */ struct bt_iso_qos qos = { .bcast = { .big = BT_ISO_QOS_BIG_UNSET, .bis = BT_ISO_QOS_BIS_UNSET, .sync_interval = 6, /* units of 1.25 ms */ .packing = 0x00, /* sequential */ .framing = 0x00, /* unframed */ .encryption = 0x00, /* no encryption */ .bcode = { 0 }, /* broadcast code (used if encrypted) */ .options = 0, .skip = 0, .sync_timeout = 0x4000, .out = { .interval = 10000, /* 10 ms SDU interval */ .latency = 10, /* max transport latency ms */ .sdu = 40, /* 40 octets = LC3 16_2 */ .phy = BT_ISO_PHY_2M, .rtn = 2, /* retransmissions */ }, }, }; struct sockaddr_iso src = { .sa_family = AF_BLUETOOTH, .v.bis.dev_id = 0, /* hci0 */ }; fd = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO); if (fd < 0) { perror("socket"); return -1; } setsockopt(fd, SOL_BLUETOOTH, BT_ISO_QOS, &qos, sizeof(qos)); /* Bind creates the BIG */ bind(fd, (struct sockaddr *)&src, sizeof(src)); /* Connect to broadcast address creates the BIS */ struct sockaddr_iso dst = { .sa_family = AF_BLUETOOTH, .v.bis.bdaddr = *BDADDR_ANY, /* broadcast target */ }; connect(fd, (struct sockaddr *)&dst, sizeof(dst)); /* Now write() sends LC3-encoded SDUs into the BIS */ return fd; }
Summary
| Topic | Unicast | Broadcast |
|---|---|---|
| Transport | CIS (Connected Isochronous) | BIS (Broadcast Isochronous) |
| Connection required? | Yes (LE ACL first) | No |
| Control mechanism | GATT writes to ASE Control Point | Source-driven state machine |
| Direction | Bidirectional | Unidirectional (Source → Sinks) |
| Key parameters | CIG_ID, CIS_ID, ASE_ID, Presentation_Delay | BASE, BIG, BIS_index, Broadcast_ID |
| BlueZ tool | ISO socket + BT_ISO_QOS (cis member) | ISO socket + BT_ISO_QOS (bcast member) |
You’ve Covered the Essentials of BAP
You now understand the six roles, the LC3 codec and QoS settings, and both unicast and broadcast streaming procedures. The next step is exploring the upper-layer profiles — CAP (Coordinated Audio Profile), TMAP (Telephony and Media), and HAP (Hearing Access Profile) — which all build on top of BAP.
