12
Intermediate
8 Sections
BlueZ C + HCI
Keywords You Will Learn
Why LE Audio Was Built
Bluetooth Classic Audio (A2DP, HFP) was fine for the early smartphone era, but it had real limits. You could only stream to one sink at a time, it consumed significant power, and sharing audio between multiple people was simply impossible over a standard Bluetooth link.
Bluetooth LE Audio, introduced with the Bluetooth 5.2 specification, was not designed just to be a power-efficient version of Classic Audio. The goal was to open entirely new ways to use wireless audio — in public venues, hospitals, cinemas, restaurants, and everyday personal listening.
The biggest new capability is Broadcast. One transmitter can send an audio stream that any number of receivers can listen to simultaneously, without any unicast pairing. This is what replaces the old inductive telecoil loop used in theatres and public halls for hearing aid users — and then extends it far beyond.
Before jumping into use cases, you need to understand two key structures that make LE Audio broadcast work.
- BIG (Broadcast Isochronous Group) — A container for one or more broadcast audio streams, all time-aligned and sent from the same transmitter.
- BIS (Broadcast Isochronous Stream) — A single audio channel inside a BIG. A BIG can carry several BIS channels, for example separate left/right stereo streams or separate language tracks.
| BIG – Broadcast Isochronous Group (one transmitter) | ||
| BIS 0 English (24 kHz mono) |
BIS 1 Hindi (24 kHz mono) |
BIS 2 Stereo Music (48 kHz) |
| All BIS streams share the same timing. A sink subscribes only to the BIS it needs. | ||
From a BlueZ/HCI perspective, creating a BIG means sending the HCI_LE_Create_BIG command to the controller. Here is the raw HCI structure and a minimal BlueZ C snippet to do that:
/*
* ble_audio_big_create.c
* Creates a Broadcast Isochronous Group (BIG) using HCI sockets.
* Requires: BlueZ headers, CAP_NET_RAW capability (run as root or with proper caps)
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
/* HCI opcode for LE Create BIG (BT 5.2 Vol 4, Part E, 7.8.103) */
#define HCI_OP_LE_CREATE_BIG 0x2068
/* Parameters for LE_Create_BIG command */
struct hci_cp_le_create_big {
uint8_t big_handle; /* 0x00 – 0xEF, identifies this BIG */
uint8_t adv_handle; /* Periodic advertising set handle */
uint8_t num_bis; /* Number of BIS streams in this BIG */
uint8_t sdu_interval[3]; /* SDU interval in microseconds (3 bytes, little-endian) */
uint16_t max_sdu; /* Max size of each SDU in bytes */
uint16_t max_transport_latency; /* Max allowed latency in ms */
uint8_t rtn; /* Retransmission number (0 = no retransmit) */
uint8_t phy; /* PHY: 0x02 = LE 2M PHY */
uint8_t packing; /* 0 = sequential, 1 = interleaved */
uint8_t framing; /* 0 = unframed, 1 = framed */
uint8_t encryption; /* 0 = unencrypted, 1 = encrypted */
uint8_t broadcast_code[16]; /* 128-bit code (used only if encryption=1) */
} __attribute__((packed));
int create_big(int hci_fd)
{
struct hci_cp_le_create_big cp;
memset(&cp, 0, sizeof(cp));
cp.big_handle = 0x00; /* Handle for this BIG */
cp.adv_handle = 0x00; /* Must match the periodic adv set handle */
cp.num_bis = 2; /* Two BIS: left + right stereo */
/* SDU interval = 10 ms = 10000 µs = 0x002710 little-endian */
cp.sdu_interval[0] = 0x10;
cp.sdu_interval[1] = 0x27;
cp.sdu_interval[2] = 0x00;
cp.max_sdu = 100; /* Max SDU size in bytes (LC3 frame at 24kHz) */
cp.max_transport_latency = 10; /* 10 ms */
cp.rtn = 2; /* 2 retransmissions */
cp.phy = 0x02; /* 2M PHY */
cp.packing = 0x00; /* Sequential */
cp.framing = 0x00; /* Unframed */
cp.encryption = 0x01; /* Encrypted broadcast */
/* Set a simple Broadcast_Code (16 bytes) – use a secure random value in production */
memcpy(cp.broadcast_code, "EmbPathshala2024", 16);
int ret = hci_send_cmd(hci_fd,
OGF_LE_CTL, /* 0x08 */
HCI_OP_LE_CREATE_BIG,
sizeof(cp),
&cp);
if (ret < 0) {
perror("HCI_LE_Create_BIG failed");
return -1;
}
printf("HCI_LE_Create_BIG sent successfully\n");
return 0;
}
Once the controller confirms the BIG was created (via HCI_LE_BIG_Complete event), it starts broadcasting Isochronous data. Any LE Audio sink nearby can scan for it using Periodic Advertising scan without any pairing.
A Broadcast Sink (your earbuds, hearing aid, or headphones) needs to go through three steps before it can receive audio:
| Step 1 Extended Advertising Scan Sink scans for Extended Adv packets to find the Broadcaster |
→ | Step 2 Periodic Advertising Sync Sink syncs to the PA train to get BASE (Audio Stream Info) |
→ | Step 3 BIG Sync Sink calls LE_BIG_Create_Sync to join the BIG and receive BIS audio |
/*
* ble_audio_sink_scan.c
* Step 1: Enable Extended Advertising scanning to find a Broadcast Source.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
/* HCI opcodes for extended scanning */
#define HCI_OP_LE_SET_EXT_SCAN_PARAMS 0x2041
#define HCI_OP_LE_SET_EXT_SCAN_ENABLE 0x2042
/* Extended scan parameters for 1M + coded PHY */
struct hci_cp_le_set_ext_scan_params {
uint8_t own_addr_type; /* 0x00 = public address */
uint8_t filter_policy; /* 0x00 = accept all */
uint8_t scanning_phys; /* Bitmask: bit0=1M, bit2=coded */
/* PHY[0]: 1M scan */
uint8_t scan_type_1m;
uint16_t scan_interval_1m; /* 0x0060 = 60 ms */
uint16_t scan_window_1m; /* 0x0030 = 30 ms */
} __attribute__((packed));
int enable_ext_scan(int hci_fd)
{
struct hci_cp_le_set_ext_scan_params params;
memset(¶ms, 0, sizeof(params));
params.own_addr_type = 0x00; /* Public address */
params.filter_policy = 0x00; /* Accept all advertisers */
params.scanning_phys = 0x01; /* Scan on 1M PHY */
params.scan_type_1m = 0x00; /* Passive scan (no scan request) */
params.scan_interval_1m = htobs(0x0060); /* 60 ms */
params.scan_window_1m = htobs(0x0030); /* 30 ms */
int ret = hci_send_cmd(hci_fd, OGF_LE_CTL,
HCI_OP_LE_SET_EXT_SCAN_PARAMS,
sizeof(params), ¶ms);
if (ret < 0) {
perror("Set ext scan params failed");
return -1;
}
/* Enable scanning: filter_duplicates=1, duration=0 (continuous) */
uint8_t enable_cp[6] = {
0x01, /* enable */
0x01, /* filter duplicates */
0x00, 0x00, /* duration: 0 = indefinite */
0x00, 0x00 /* period: 0 */
};
ret = hci_send_cmd(hci_fd, OGF_LE_CTL,
HCI_OP_LE_SET_EXT_SCAN_ENABLE,
sizeof(enable_cp), enable_cp);
if (ret < 0) {
perror("Enable ext scan failed");
return -1;
}
printf("Extended scan enabled – listening for Broadcast Sources\n");
return 0;
}
/*
* Step 2: Create Periodic Advertising Sync (PA Sync).
* Call this after receiving an Extended Adv report with the broadcaster's address.
*/
#define HCI_OP_LE_PERIODIC_ADV_CREATE_SYNC 0x2044
struct hci_cp_le_pa_create_sync {
uint8_t options; /* 0x00 = use advertiser info from this command */
uint8_t sid; /* Advertising SID from the ext adv report */
uint8_t addr_type; /* 0x00 = public */
bdaddr_t addr; /* Broadcaster's BD_ADDR */
uint16_t skip; /* Max PA events to skip = 0 */
uint16_t sync_timeout; /* Timeout: N * 10ms. 0x00A0 = 1600ms */
uint8_t sync_cte_type; /* 0x00 = sync to all PA */
} __attribute__((packed));
int create_pa_sync(int hci_fd, bdaddr_t *broadcaster_addr, uint8_t sid)
{
struct hci_cp_le_pa_create_sync cp;
memset(&cp, 0, sizeof(cp));
cp.options = 0x00;
cp.sid = sid; /* From Extended Adv report */
cp.addr_type = 0x00;
bacpy(&cp.addr, broadcaster_addr);
cp.skip = htobs(0x0000);
cp.sync_timeout = htobs(0x00A0); /* 1.6 sec timeout */
cp.sync_cte_type = 0x00;
int ret = hci_send_cmd(hci_fd, OGF_LE_CTL,
HCI_OP_LE_PERIODIC_ADV_CREATE_SYNC,
sizeof(cp), &cp);
if (ret < 0) {
perror("PA Create Sync failed");
return -1;
}
/*
* Now wait for HCI_LE_Periodic_Advertising_Sync_Established event.
* That event gives us the sync_handle we need for BIG sync.
* The PA data itself contains the BASE (Basic Audio Announcements),
* which tells us the BIG handle, BIS count, codec config, and language LTVs.
*/
printf("PA sync in progress – waiting for Sync Established event\n");
return 0;
}
/*
* Step 3: Synchronise to the BIG (join the broadcast to receive audio).
* Call after PA Sync is established and BASE is parsed.
*/
#define HCI_OP_LE_BIG_CREATE_SYNC 0x206B
struct hci_cp_le_big_create_sync {
uint8_t big_handle; /* Assigned locally by the sink */
uint16_t sync_handle; /* From PA Sync Established event */
uint8_t encryption; /* 0x01 if broadcast is encrypted */
uint8_t broadcast_code[16]; /* Must match broadcaster's code */
uint8_t mse; /* Max Subevents: 0x00 = controller decides */
uint16_t big_sync_timeout; /* N * 10ms. 0x00C8 = 2 seconds */
uint8_t num_bis; /* How many BIS to receive */
uint8_t bis[2]; /* BIS indices to sync (e.g., {1, 2} for stereo) */
} __attribute__((packed));
int sync_to_big(int hci_fd, uint16_t sync_handle)
{
struct hci_cp_le_big_create_sync cp;
memset(&cp, 0, sizeof(cp));
cp.big_handle = 0x00;
cp.sync_handle = htobs(sync_handle);
cp.encryption = 0x01;
memcpy(cp.broadcast_code, "EmbPathshala2024", 16);
cp.mse = 0x00;
cp.big_sync_timeout = htobs(0x00C8);
cp.num_bis = 2;
cp.bis[0] = 0x01; /* BIS index 1: left channel */
cp.bis[1] = 0x02; /* BIS index 2: right channel */
int ret = hci_send_cmd(hci_fd, OGF_LE_CTL,
HCI_OP_LE_BIG_CREATE_SYNC,
sizeof(cp), &cp);
if (ret < 0) {
perror("BIG Create Sync failed");
return -1;
}
/*
* Wait for HCI_LE_BIG_Sync_Established event.
* After that, the controller hands ISO data up via HCI ISO data packets.
* Each packet is one LC3-encoded SDU (audio frame) per BIS.
*/
printf("BIG sync requested – waiting for BIG Sync Established\n");
return 0;
}
Think about a crowded bus stop. The electronic display board shows your bus is arriving in 3 minutes. But what if your hearing aids or earbuds could just tell you that — in your preferred language — without you reading anything?
This is one of the first real-world deployments of LE Audio broadcast. A small transmitter module at the bus stop broadcasts route information over a BIS. Your transport app picks up the Extended Advertising packet, identifies the stream by its Local Name AD type and Program_Info LTV, and presents it in your phone’s UI.
| Bus Stop Broadcaster BIS: “London Bus Stop J” 16_2_2 QoS (16 kHz mono) No encryption |
→→→ | User’s Phone Transport app scans, detects PA, parses BASE, presents “Listen?” dialog |
→→→ | Earbuds / Hearing Aid Phone mixes BIS audio into CIS stream to earbuds via LC3 decode |
Key technical detail: The announcement audio itself is a mono voice stream at 16 kHz. The QoS preset 16_2_2 means 16 kHz sample rate, 10 ms SDU interval, RTN=2. This uses only about 13% airtime for mono — extremely efficient. Most public broadcasts will use exactly this setting.
When the user boards the bus, the app should automatically drop the bus stop BIS and switch to the BIS on the bus itself. The app does this by monitoring for PA sync loss (which fires when the bus stop transmitter is out of range) and scanning for the next best match using the ProgramInfo LTV in the BASE.
| QoS Preset | Sample Rate | Mono Airtime | Stereo Airtime | Universal (all devices)? |
|---|---|---|---|---|
| 16_2_2 | 16 kHz | <13% | N/A | ✓ Yes |
| 24_2_1 | 24 kHz | 13% | 26% | ✓ Yes |
| 32_2_1 | 32 kHz | 15% | 30% | ✗ No |
| 48_2_1 | 48 kHz | 30% | 60% | ✗ No |
Why does “Universal” matter? Resource-constrained hearing aids support only LC3 decode at 16 kHz and 24 kHz. If a transmitter broadcasts only at 48 kHz, hearing aid users simply cannot receive it — defeating the original purpose. The Public Broadcast Profile (PBP) mandates that any public transmitter must provide at least a 16 kHz or 24 kHz stream.
Restaurant noise is a known problem. Music plays through ceiling speakers, everyone talks louder, staff turn the music up further — a feedback loop that makes conversation painful.
LE Audio broadcast offers a clean solution. The existing audio system keeps its amplifier and speakers — you just attach a small Bluetooth LE Audio broadcast module to the audio output (3.5 mm jack or RCA). That module broadcasts the music. Customers with earbuds or hearing aids can tune in if they want; everyone else gets silence.
| Restaurant Audio System | ||||
| Existing Audio Player/PC |
→ | BLE Audio Broadcast Module (plugs into 3.5mm jack) |
)))) | Customer’s Earbuds or Hearing Aid (subscribes to BIS) Music plays privately |
| No pairing needed. No ceiling speakers. Room stays quiet. | ||||
Practical QoS choice for this scenario: Since there are no visual cues to sync to (no video), latency is not critical. The book recommends transmitting two simultaneous streams in one BIG — 32 kHz stereo and 24 kHz mono — using about 45% total airtime. This gives high-quality listeners the stereo experience while ensuring hearing aid users can still join on 24 kHz mono.
The transmitter should be mounted high on a wall or ceiling — not inside a cabinet — so signals are not absorbed. This is a hardware design guideline that manufacturers need to document clearly, since venue owners are not expected to know about RF propagation.
5.1 Public TVs (Gyms, Airports, Pubs)
Most gym TVs are muted. Multiple screens show different channels, and running audio from all of them simultaneously would be unbearable. With LE Audio broadcast, each TV can transmit its audio as a separate unencrypted BIS. A gym member syncs to the BIS for the screen they are watching and hears it in their earbuds — privately and with no disruption to others.
Airports can add a twist: flight announcements get mixed into the TV audio stream in real time. A passenger listening to CNN on their earbuds never misses a boarding call — the airport’s audio management system detects the announcement, mixes it into each BIS stream, waits for it to finish, then continues the TV audio.
| Airport TV Broadcaster – 1 BIG, 6 BIS streams (24 kHz mono each) | |||||
| BIS 1 English |
BIS 2 Hindi |
BIS 3 Arabic |
BIS 4 French |
BIS 5 Mandarin |
BIS 6 German |
| Each BIS identified by Language LTV in BASE. User’s app filters to preferred language automatically. | |||||
5.2 Personal TVs at Home
At home the use case flips — you want encryption so your neighbours cannot listen. The TV broadcasts an encrypted stream. Family members pair with the TV once. After that, whenever they come within range, the TV uses PAST (Periodic Advertising Sync Transfer) to push the current Broadcast_Code to their earbuds via an existing LE connection. No app needed after first setup.
| TV (Broadcast Source) + Broadcast Assistant Encrypted BIG Broadcast_Code refreshed on power-off |
─── Advertising ───>
<─── LE ACL (PAST) ─── |
User’s Phone (Broadcast Assistant) Receives Broadcast_Code via PAST from bonded TV |
─── CIS ───> | Earbuds (Broadcast Sink) Decrypts audio using Broadcast_Code received via PAST |
What is PAST? Periodic Advertising Sync Transfer. It lets one device tell another device exactly where to find a PA train — the SyncInfo — over an existing LE ACL connection. The Broadcast Assistant in the phone uses PAST to hand the Broadcast Sink (earbuds) both the sync information and the Broadcast_Code in one go, so the earbuds can lock on to the TV’s BIG directly.
5.3 Hotel TVs
Hotel TVs are a natural fit. Neighbours in the next room cannot hear your encrypted TV. The best implementation integrates the Broadcast_Code into the hotel’s check-in app — when you check in digitally, the app receives the code for your room’s TV. The same code stays valid for your entire stay, automatically pushed to your earbuds whenever you enter the room.
This is the feature most consumer users will relate to immediately. Think of the old cassette Walkman days — two people sharing a single pair of wired earbuds. Bluetooth took that away when headphone jacks disappeared from phones.
LE Audio brings it back, properly. Your phone starts as a unicast source streaming to your own earbuds (using a CIS). When friends join, the phone transitions to a broadcast source — sets up a BIG, encrypts it, and shares the Broadcast_Code with each friend via a short-term LE pairing (no bonding required).
| Before friends arrive — Unicast | |||
|---|---|---|---|
| Your Phone (Unicast Source) |
──CIS──> | Your Earbuds (Unicast Sink) |
|
| After friends arrive — Broadcast | |||
| Your Phone (Broadcast Source) Encrypted BIG |
)))) | Your Earbuds | Friend 1 Earbuds Code shared via short-term pairing |
Once all friends disconnect, the phone automatically reverts to unicast (CIS), because unicast is more power-efficient for a single listener — the CIS acknowledgement means the transmitter only retransmits when actually needed, whereas a broadcast transmitter must always transmit every scheduled retransmission subevent, even if nobody missed a packet.
Silent disco is the unencrypted version: you just broadcast without a code and anyone nearby with the right app can join. Great for group exercise classes or outdoor events.
The Bluetooth SIG formalised the idea of Audio Sharing to resolve the interoperability problem between high-end consumer earbuds (which want 48 kHz LC3) and resource-constrained hearing aids (which can only decode up to 24 kHz LC3).
| Property | Public Broadcast Transmitter | Personal Broadcast Transmitter |
|---|---|---|
| Examples | Bus stop, airport, cinema, cafe | TV, phone, PC, tablet |
| Mandatory stream | 16 kHz or 24 kHz mono (PBP) | Any; must switch to 16/24 kHz when sharing |
| Encryption | Optional (usually off) | Strongly recommended |
| Higher quality optional? | Yes (additional BIS at 48 kHz) | Yes (simultaneous BIS streams) |
| Discovery mechanism | Public Broadcast Service UUID in adv | Broadcast Assistant + PAST |
A Public Broadcast Transmitter announces itself using the Public Broadcast Service UUID in its Extended Advertising data. A scanning device that sees this UUID knows it can synchronise without any pre-arrangement. This is exactly like how phones show available Wi-Fi networks — just scan and connect.
8.1 Tap 2 Hear – NFC-based Access
For personal communication desks (bank counters, airport check-in, supermarket checkout), the user just taps their phone or Commander device onto an NFC tag at the counter. That single tap transfers both the BIG sync information and the Broadcast_Code to the user’s device, which relays it to the hearing aids or earbuds via PAST. The conversation begins immediately — zero friction.
| Counter Broadcaster BIS: staff voice Encrypted |
))))) | NFC Tag at Counter Stores: BIG sync info + Broadcast_Code |
─NFC tap─> | User’s Phone Receives code, sends via PAST to earbuds |
─PAST─> | Earbuds / Hearing Aid Decrypts + plays staff voice |
8.2 Battery Charging Cases as Controllers
Earbud charging cases (battery boxes) are underrated devices. They are always with the user, large enough for physical buttons, and can hold a Bluetooth chip cheaply. With LE Audio, a battery box can act as a Broadcast Assistant — it scans for available Public Broadcast streams and shows them on a small display or presents them via audio cues. The user picks a stream by pressing a button. No phone needed.
The battery box can also handle volume control, stream switching, and playback commands — all things that currently require pulling out a phone and opening an app. This brings LE Audio control to a form factor that fits in a shirt pocket.
8.3 Wearables Gain a Real Purpose
Smartwatches and wristbands can implement the Broadcast Assistant role using LE Audio’s AICS (Audio Input Control Service) and VCS (Volume Control Service) profiles. Since these are standard LE profiles, any LE Audio earbud must interoperate with any LE Audio controller — no proprietary extensions needed.
Once the BIG sync is established (Section 2, Step 3), the kernel delivers ISO data packets through a dedicated socket type added in Linux 5.19 and BlueZ 5.64 — BTPROTO_ISO. Here is how to open an ISO socket to receive audio frames:
/*
* ble_audio_iso_recv.c
* Reads LC3-encoded audio frames from an ISO socket (Broadcast Sink).
*
* Requires: Linux kernel >= 5.19, BlueZ >= 5.64
* Build: gcc ble_audio_iso_recv.c -lbluetooth -o iso_recv
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/iso.h> /* BlueZ ISO socket header */
#define LC3_FRAME_BYTES 40 /* Typical LC3 frame at 24kHz, 10ms SDU interval */
int main(void)
{
int sk;
struct sockaddr_iso addr;
uint8_t buf[LC3_FRAME_BYTES * 2]; /* 2x for safety margin */
ssize_t len;
/* 1. Create ISO socket */
sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO);
if (sk < 0) {
perror("socket(BTPROTO_ISO)");
return 1;
}
/*
* 2. Bind to local adapter.
* For broadcast sink, BlueZ handles BIG/BIS sync setup via ioctls
* (BT_ISO_QOS, BT_ISO_BIG_INFO) before we reach the data loop.
* At this point the socket is already connected to the BIS.
*/
memset(&addr, 0, sizeof(addr));
addr.iso_family = AF_BLUETOOTH;
bacpy(&addr.iso_bdaddr, BDADDR_ANY); /* Use default adapter */
addr.iso_bdaddr_type = BDADDR_LE_PUBLIC;
if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(sk);
return 1;
}
printf("ISO socket bound. Waiting for LC3 audio frames...\n");
/* 3. Read loop – each recv() call returns one LC3-encoded SDU */
while (1) {
len = recv(sk, buf, sizeof(buf), 0);
if (len < 0) {
perror("recv ISO");
break;
}
printf("Received ISO SDU: %zd bytes (LC3 frame)\n", len);
/*
* Here you would pass buf[] to an LC3 decoder.
* liblc3 (by Google, used by BlueZ internally) provides:
*
* lc3_decode(decoder, buf, len, LC3_PCM_FORMAT_S16,
* pcm_buffer, 1);
*
* Decoded PCM frames go to ALSA or PulseAudio for playback.
*/
}
close(sk);
return 0;
}
/*
* Setting QoS for a Broadcast Sink ISO socket.
* This must be done before the socket is connected/synced.
* BlueZ exposes this via setsockopt(BT_ISO_QOS).
*/
#include <bluetooth/iso.h>
int set_broadcast_sink_qos(int sk)
{
struct bt_iso_qos qos;
memset(&qos, 0, sizeof(qos));
/*
* For a Broadcast Sink we configure the RX path only.
* The QoS parameters must match what the Broadcaster advertised
* in the BASE (Basic Audio Announcement Structure inside PA data).
*/
qos.bcast.sync_factor = 0x07; /* PA sync factor */
qos.bcast.packing = 0x00; /* Sequential */
qos.bcast.framing = 0x00; /* Unframed */
qos.bcast.encryption = 0x01; /* Encrypted */
memcpy(qos.bcast.bcode, "EmbPathshala2024", 16); /* Broadcast_Code */
qos.bcast.options = 0x00;
qos.bcast.skip = htobs(0x0000);
qos.bcast.sync_timeout = htobs(0x4000); /* 163.84 sec */
qos.bcast.sync_cte_type = 0x00;
qos.bcast.mse = 0x00;
qos.bcast.timeout = htobs(0x4000);
/* RX path: SDU interval, max SDU, PHY, RTN, latency */
qos.bcast.in.interval = 10000; /* 10 ms in microseconds */
qos.bcast.in.latency = 10; /* Max latency in ms */
qos.bcast.in.sdu = 100; /* Max SDU bytes */
qos.bcast.in.phy = BT_ISO_PHY_2M;
qos.bcast.in.rtn = 2;
if (setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS,
&qos, sizeof(qos)) < 0) {
perror("setsockopt BT_ISO_QOS");
return -1;
}
printf("ISO QoS set for broadcast sink\n");
return 0;
}
Bluetooth Classic Audio (A2DP, HFP, AVRCP) is not disappearing overnight. Most audio devices shipped before 2024 use pre-5.2 chipsets that cannot be upgraded to LE Audio. Cars, TVs, and some speakers have 10+ year product lifespans.
| Feature | Bluetooth Classic Audio | Bluetooth LE Audio |
|---|---|---|
| Codec | SBC, AAC, aptX (optional) | LC3 (mandatory) |
| Topology | Unicast only (A2DP) | Unicast (CIS) + Broadcast (BIS) |
| Multiple simultaneous sinks | No | Yes (unlimited with BIS) |
| Hearing aid support | Limited / proprietary | First-class (HAS, PACS) |
| Audio quality at low bitrate | SBC degrades quickly | LC3 stays clean at low rates |
| Power consumption | Higher | Lower (LE PHY + LC3 efficiency) |
For the next 5 years, phones and earbuds will support both stacks. When both ends support LE Audio, the stack implementation decides which to use — for instance, if a bonded earbud supports LE Audio, the phone may prefer it for lower power. Developers building audio apps today should handle both scenarios gracefully.
| Topic | One-Line Takeaway |
|---|---|
| BIG / BIS | One BIG can carry many BIS channels; sinks pick only what they need. |
| Telecoil replacement | LE Audio broadcast replaces inductive loops with higher quality, wider range, multi-language support. |
| Public QoS rule | Always include a 16 or 24 kHz stream for universal hearing aid compatibility (PBP mandate). |
| Broadcast_Code | Encrypts a BIG so only authorised listeners can decode it; distributed via PAST, NFC, or app. |
| PAST | Lets a Broadcast Assistant push PA sync info + Broadcast_Code to a sink over LE ACL. |
| Audio Sharing | SIG standard distinguishing Public (PBP-compliant, 16/24 kHz) vs Personal (any rate, must support sharing mode). |
| ISO socket | Linux 5.19+ exposes BTPROTO_ISO for reading LC3-encoded audio frames from BIS streams. |
| Battery box / Wearable | Can act as Broadcast Assistant — scan, select, and relay streams without touching a phone. |
Explore More on EmbeddedPathashala
This was Chapter 12 of the Bluetooth LE Audio series. Explore the earlier chapters to build the full picture from the physical layer up.
