BLE Audio course : BLE Audio Announcements, CCID & Commanders

BLE Audio course: Ble Audio Announcements, CCID & Commanders
Acceptor-initiated discovery · Stream-to-control binding · Remote control topology
2
Announcement Types
CCID
Links Control to Stream
CAP
Commander Role

BLE Audio gives more autonomy to the Acceptor. Rather than always waiting for the phone to initiate, an earbud can broadcast its readiness using Announcements. When multiple audio streams run simultaneously, CCIDs bind each stream to its correct control service. And for hearing aid users who need a dedicated remote, the Commander role provides a full-featured controller.

1. Announcements — Acceptor-Initiated Discovery

General vs Targeted Announcements

An Acceptor uses Service Data AD Type in its advertising PDU to announce readiness. This shifts control toward the device in your ear, rather than always requiring the phone to scan first.

Two Unicast Announcement Types (ASCS UUID = 0x184E)
General Announcement (0x00)
“I am available but not requesting a specific connection.”

Acceptor is advertising with its capabilities. An Initiator in RAP mode (Ready for Audio Peripheral) scans at low rate and will respond if it recognises this device from a prior pairing.

Use case: Earbuds powered on, waiting for phone to auto-reconnect.
Targeted Announcement (0x01)
“I need a connection RIGHT NOW — please connect to me.”

An Initiator in INAP mode (Immediate Need for Audio Peripheral) scans at high rate. Upon discovering this, it connects immediately or presents the user a choice.

Use case: User presses button on hearing aid to initiate a call. Should only be used briefly — not for continuous advertising.

Unicast Announcement AD Type Structure (Service Data UUID = ASCS 0x184E)
Field Size Value / Description
Length 1 byte Total length of Type + Value fields
Type 1 byte Service Data UUID (16-bit) AD Type
ASCS UUID 2 bytes 0x184E (ASCS Service UUID)
Announcement Type 1 byte 0x00 = General  |  0x01 = Targeted
Available_Audio_Contexts 4 bytes Current context availability bitfield from PACS. Initiator uses this to decide whether to connect.
Metadata Length 1 byte ≥ 1 if additional metadata follows, else 0
Metadata varies LTV format — e.g., Streaming Audio Contexts LTV
BAP vs CAP Announcements: If the announcement relates to an audio stream — it’s a BAP Announcement (includes Available_Audio_Contexts). If it’s for control or Scan Delegation only (no audio stream) — it’s a CAP Announcement (no context field).

2. Broadcast Announcements — Two Types

Broadcast Audio Announcement vs Basic Audio Announcement
Broadcast Discovery Chain
Step 1
📡
Broadcast Audio Announcement
In Extended Advertising
UUID: 0x1852
Contains: Broadcast_ID (3 bytes, random, fixed for BIG lifetime)
→ “I’m broadcasting — here’s my ID”
Step 2
📋
Basic Audio Announcement (BASE)
In Periodic Advertising
Contains: Codec config, QoS, Audio Location, Metadata for each BIS
→ “Here are the stream parameters to decide if you want to receive”
Step 3
🎧
BIS Sync
Broadcast Sink synchronises to the BIG and receives BIS audio
→ “I’ll receive BIS 0 (left) and BIS 1 (right)”

3. CCID — Content Control ID

Binding Audio Streams to Their Control Service

BLE Audio separates the control plane (phone calls, media control) from the data plane (audio stream). This is great for flexibility but creates a question: if you have a cellular call AND a Teams meeting active simultaneously, which control service governs which stream?

The Content Control ID (CCID) is a single-byte integer included in the audio stream’s metadata that says “this stream is controlled by service instance #N on the Initiator.”

CCID — Two Concurrent Calls Scenario
📱 Phone (Initiator)
TBS Instance 1 (CCID=0x01)
Cellular call with Mum
TBS Instance 2 (CCID=0x02)
Teams meeting (VoIP)
CIS 0
metadata: CCID=0x01
→→→→→
CIS 1
metadata: CCID=0x02
→→→→→
🎧 Earbuds (Acceptor)
Stream CIS 0 → CCID=0x01
“To hold/end this stream, use TBS instance 1”
Stream CIS 1 → CCID=0x02
“To hold/end this stream, use TBS instance 2”
Where CCIDs are used today: Only in Telephone Bearer Service (TBS) and Media Control Service (MCS). Not applicable to rendering control (volume) or audio capture control. The CCID value is a single octet — unique across all Content Control service instances on one device.
CCID in broadcast: Even though broadcast streams have no ACL connection, a CCID can still be used if an ACL link is present alongside the broadcast (e.g., using your phone’s media player to control a music broadcast you’re sharing with friends). The ACL carries the control; the BIG carries the audio.

4. Commanders — Dedicated Audio Remote Controls

The Commander Role (CAP) — More Than Just Volume Buttons

Hearing aids are tiny — no room for buttons. Getting your phone out of your pocket to adjust volume during a busy street is impractical. The Commander role defines a dedicated device for audio control.

Commander Topology — Multiple Roles in One Device
Commander
(Smart Watch / Keyfob / Phone App)
✅ VCP — Volume Control
✅ MCP — Media Control (play/pause)
✅ CCP — Call Control (answer/end)
✅ Broadcast Assistant — scan & filter broadcasts
✅ HAP — Hearing Aid Presets
ACL
🦻🦻
Acceptors
(Hearing Aid Pair / TWS Earbuds)
VCS — Volume Control Service
MCS — Media Control Service
ASCS — Audio Stream Endpoint
BASS — Broadcast Audio Scan Service
🔍
Scan
📡
Broadcasters
(PA System / TV / Venue)
Commander scans for broadcasts, delivers keys for encrypted streams, lets user choose which BIG to receive
First-come, first-served: Multiple Commanders can coexist. A phone app AND a smartwatch can both be Commanders for the same hearing aids. Whichever acts first on a given operation wins. This is by design — no pairing/priority system between Commanders.

5. BlueZ: Announcements & Broadcast Assistant

BlueZ: Targeted Announcement in LE Advertising + Broadcast Assistant Scan
/* ================================================================ * Acceptor side: Targeted Announcement in LE Advertising PDU * Using BlueZ HCI directly (hcitool or btmgmt equivalent in code) * The Service Data AD type carries the ASCS UUID + announcement data * ================================================================ */ import struct import subprocess ASCS_UUID = 0x184E # ASCS Service UUID (little-endian in packet) def build_targeted_announcement(available_contexts: int) -> bytes: “”” Build Service Data AD Type for Targeted Announcement. available_contexts: 4-byte bitmask of available Context Types “”” announcement_type = 0x01 # 0x00=General, 0x01=Targeted metadata_length = 0x00 # no additional metadata # Service Data AD format: # [AD Length][AD Type=0x16][UUID 2B LE][Announcement Type][Available_Audio_Contexts 4B][Metadata Len] uuid_bytes = struct.pack(‘<H’, ASCS_UUID) # Little-endian UUID ctx_bytes = struct.pack(‘<I’, available_contexts) # 4-byte context bitfield value = uuid_bytes + bytes([announcement_type]) + ctx_bytes + bytes([metadata_length]) ad_length = 1 + len(value) # AD Type byte + value ad = bytes([ad_length, 0x16]) + value # 0x16 = Service Data – 16-bit UUID return ad # Context Types: Media (0x04) | Conversational (0x02) AVAILABLE_CTX = 0x00060006 # source + sink available contexts announcement_ad = build_targeted_announcement(AVAILABLE_CTX) print(“Targeted Announcement AD bytes:”, announcement_ad.hex()) # Set this in LE advertising data via BlueZ RegisterAdvertisement D-Bus call /* ================================================================ * Commander / Broadcast Assistant — BASS (Broadcast Audio Scan Service) * Commander scans for Broadcast Audio Announcements (UUID 0x1852), * then writes to Acceptor’s BASS to instruct it to sync to a BIG. * ================================================================ */ # Commander scans for Broadcast Audio Announcements # BlueZ: Register Advertisement Monitor for specific UUID import dbus BROADCAST_AUDIO_ANNOUNCEMENT_UUID = “00001852-0000-1000-8000-00805f9b34fb” bus = dbus.SystemBus() adapter = dbus.Interface( bus.get_object(“org.bluez”, “/org/bluez/hci0”), “org.bluez.Adapter1” ) # Start discovery filtered to Broadcast Audio Announcement UUID adapter.SetDiscoveryFilter({ “UUIDs”: dbus.Array([BROADCAST_AUDIO_ANNOUNCEMENT_UUID], signature=”s”), “Transport”: dbus.String(“le”), }) adapter.StartDiscovery() # When Broadcaster found, read its Periodic Advertising (BASE structure) # then write to Acceptor’s BASS BRS (Broadcast Receive State) characteristic: # – Broadcast_ID (3 bytes from the announcement) # – PA_Sync_State: request acceptor to sync to periodic advertising # – BIS_Sync: bitmask of which BISes to receive (e.g., 0x03 = BIS 0 + BIS 1) BASS_UUID = “00001852-0000-1000-8000-00805f9b34fb” # Write Add Source operation to BASS on Acceptor (via GATT Write) # OpCode 0x02 = Add Source add_source_op = bytes([ 0x02, # Add Source opcode 0x00, # Advertiser_Address_Type (Public) 0xAA,0xBB,0xCC, # Advertiser_Address (6 bytes) 0xDD,0xEE,0xFF, 0x01, # Advertising_SID 0x01,0x02,0x03, # Broadcast_ID (from announcement) 0x02, # PA_Sync: 0x02 = sync to PA (request PA train sync) 0x00,0x00, # PA_Interval (unknown) 0x01, # Num_Subgroups 0x03,0x00,0x00,0x00,# BIS_Sync bitmask: BIS 0 + BIS 1 0x00, # Metadata_Length ]) # gatt_write(acceptor_bass_char_handle, add_source_op)
Key Takeaways
General = available but passive | Targeted = needs connection NOW
Initiator: INAP = high scan rate | RAP = low scan rate, responds to Targeted
Broadcast Audio Announcement (ext adv) → BASE in Periodic Advertising → BIS sync
CCID = single byte linking stream metadata to its control service instance
Commander = phone / watch / keyfob; first-come-first-served among multiple Commanders
CCID currently only used by TBS and MCS — not volume or capture control

Leave a Reply

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