BAP Part 3 — Streaming Procedures

 

BAP Part 3 — Streaming Procedures
ASE State Machine, Unicast Audio Streaming Step-by-Step, and Broadcast Audio from Source to Sink
6
ASE States
3
Broadcast States
BASE
Broadcast Structure

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

ASE State Machine ASE Control Point Config Codec Config QoS Enable Receiver Start Ready Streaming State Disable Release BASE Structure BIG BIS Broadcast_ID Scan Offloading

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

Step 1 — Service & Capability Discovery

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.

Discover PACS Read Sink/Source PAC Read ASE characteristics Subscribe to notifications

Step 2 — Config Codec (Idle → Codec Configured)

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).

ASE Control Point write Codec_ID = 0x06 (LC3) LTV parameters

Step 3 — Config QoS (Codec Configured → QoS Configured)

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.

HCI LE Set CIG Parameters CIG_ID + CIS_ID QoS parameters

Step 4 — Enable (QoS Configured → Enabling)

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.

Enable opcode Streaming_Audio_Contexts HCI LE Create CIS

Step 5 — Audio Data Path Setup + Receiver Start Ready (Enabling → Streaming)

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.

LE Setup ISO Data Path (HCI) Receiver Start Ready Streaming state

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

Disable

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.

Release

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.

Link Loss / CIS Loss: If the LE ACL link drops unexpectedly, the Unicast Server immediately moves all ASEs to Releasing state. When the client reconnects, it can read the ASE characteristics to find them in Codec Configured state (server remembers the last settings), and the client can re-run steps 3–5 to resume streaming quickly.

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.

Level 1 — Group (BIG)
Presentation_Delay — microseconds before rendering audio
Num_Subgroups — how many subgroups exist
Level 2 — Subgroup (codec + language)
Codec_ID — LC3 or vendor-specific
Codec_Specific_Configuration — LTV: freq, frame duration, octets/frame
Metadata — LTV: Streaming_Audio_Contexts, Language
Num_BIS — BIS count in this subgroup
Level 3 — BIS (channel allocation)
BIS_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

1
Scan for Extended Advertising — The Broadcast Sink or a Broadcast Assistant scans for ADV_EXT_IND PDUs containing the Broadcast Audio Announcement Service UUID. The Broadcast_ID field helps identify the right source.
2
Synchronize to Periodic Advertising — Using the SyncInfo in the EA, the Sink synchronizes to the Periodic Advertising train and reads the BASE structure to understand the codec parameters and available streams.
3
Detect BIGInfo in PA — When the Broadcast Source is in the Streaming state, the BIGInfo data appears in the ACAD field of AUX_SYNC_IND PDUs. This is what the Sink needs to synchronize to the actual audio stream.
4
Synchronize to BIG/BIS — The Sink uses the BIG Sync Establishment procedure to start receiving ISO packets from the selected BIS. Audio data is now flowing directly to the Sink’s audio data path.

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

Encrypted Broadcast Streams (Broadcast_Code): A Broadcast Source can encrypt its BIG. The Broadcast Sink needs a 16-octet Broadcast_Code to decrypt it. The Broadcast Assistant delivers this code to the Scan Delegator via the BASS Set Broadcast_Code operation. The Scan Delegator passes it to the Broadcast Sink. This is how private broadcast audio works — like a company PA system where only employees know the code.

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.

Explore More BLE Audio Posts Bluetooth Classic Series

Leave a Reply

Your email address will not be published. Required fields are marked *