What You Will Learn
Part 1 showed why classic Bluetooth audio hit a wall. This part shows how LE Audio tears it down with a new transport, a better codec, and a topology that scales from one earbud to unlimited broadcast listeners.
- The four core innovations in Bluetooth LE Audio
- CIS — standard stereo earbuds without any proprietary chip
- BIS — broadcast audio to unlimited receivers, no pairing needed
- LC3 codec — better quality at lower bitrate than SBC
- Linux kernel and BlueZ version requirements
- Complete BlueZ ISO socket C code: CIS sender, CIS receiver, BIS source
The Four Core Innovations in LE Audio
|
① LC3 Codec
One universal codec for both voice and music. Better quality than SBC at half the bitrate. Standardized — all devices use the same codec, no optional-codec fragmentation.
|
② ISO Channels
New isochronous transport over BLE. Fixed-interval delivery like SCO, but flexible. Eliminates every proprietary TWS relay and sniff scheme.
|
③ Multi-Stream
A phone maintains independent synchronized streams to left and right earbuds simultaneously. Standard feature — any BT 5.2 device, no proprietary chip required.
|
④ Broadcast Audio
One device broadcasts audio to unlimited receivers. No pairing, no connection. Replaces hearing loop (telecoil) systems in theatres, airports, and public spaces.
|
The LE Audio Protocol Stack
LE Audio replaces A2DP and HFP with layered specifications all running over BLE. Control uses GATT services (PACS, ASCS). Audio data uses the new ISO channel layer.
| Audio Application — Music, Voice, Hearing Aid | |
| ↕ | |
| VCP — Volume Control Profile Standardized volume via GATT |
CCP / MCP — Call / Media Control Replaces HFP AT commands + AVRCP |
| ↕ | |
| PACS — Published Audio Capabilities Advertises supported codecs (GATT) |
ASCS — Audio Stream Control Stream setup and control (GATT) |
| ↕ | |
| BAP — Basic Audio Profile Defines unicast (CIS) and broadcast (BIS) streams |
|
| ↕ | |
| CIS — Connected Isochronous Stream Unicast: earbuds, hearing aids |
BIS — Broadcast Isochronous Stream Broadcast: unlimited listeners |
| ↕ | |
| ISO Layer — Isochronous Channels (BT 5.2) Fixed-interval guaranteed delivery over BLE link layer |
|
| ↕ | |
| BLE Link Layer — Bluetooth 5.2+ | |
| ↕ | |
| BLE Radio — 1M / 2M PHY | |
ISO Channels — How Guaranteed Timing Works
ISO channels deliver audio data with a fixed, repeating interval. Every device knows exactly when each packet will arrive. This predictability is what enables perfect multi-device synchronization without any relay or proprietary scheme.
| ISO Channel (LE Audio) — Fixed Interval | ACL Channel (A2DP) — Variable Timing | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
||||||||||||||||||||||||||||||
CIS — Connected Isochronous Stream (Unicast Audio)
A CIS is an ISO channel between two connected BLE devices. For earbuds, the phone sets up two separate CIS connections — one to each earbud. Both receive their own channel directly from the phone. No relay. No sniffing. No proprietary chip.
Multiple CIS connections are organized into a CIG (Connected Isochronous Group). A CIG allows the phone to synchronize all streams to the same timing reference — left and right play in perfect sync, guaranteed by the standard.
| CIG — Connected Isochronous Group (ID = 0x01) | ||||||
| CIS ID = 0x01 LC3 LEFT channel interval: 10 ms SDU: 40 bytes |
Same CIG →
synchronized |
CIS ID = 0x02 LC3 RIGHT channel interval: 10 ms SDU: 40 bytes |
||||
| ↓ | ↓ | |||||
| 🎧 Left Earbud Receives directly from phone No relay from right earbud |
✅ Perfect sync
guaranteed by CIG timing |
🎧 Right Earbud Receives directly from phone No proprietary protocol |
||||
|
||||||
BIS — Broadcast Isochronous Stream (Broadcast Audio)
A BIS broadcasts audio to any number of listeners with no pairing and no connection required. Listeners just scan and tune in. This is the standard replacement for hearing loop (telecoil) systems used in theatres and public spaces.
| BIG — Broadcast Isochronous Group (ID = 0x01) | ||||
| BIS ID = 0x01 — LEFT channel (LC3) | BIS ID = 0x02 — RIGHT channel (LC3) | |||
|
📺 Broadcast Source
TV / Stage PA / Airport Gate Sends BIG broadcast — no pairing needed |
||||
| ↙ | ↓ | ↓ | ↓ | ↘ |
| 🦻 Hearing Aid Listener 1 |
🎧 Earbuds Listener 2 |
🎧 Headphones Listener 3 |
🎧 Another Listener 4 |
… Unlimited |
|
No pairing. No connection. No setup. Any BLE 5.2 device scans for the BIG → syncs to BIS streams → receives audio.
Real-world use cases: Airport gate announcements in your own hearing aid | Cinema audio track in your language | Gym silent disco | Museum guided tour audio |
||||
The LC3 Codec
LC3 (Low Complexity Communications Codec) is the mandatory codec for all Bluetooth LE Audio devices. Developed by Fraunhofer IIS and Ericsson. Unlike A2DP where codec fragmentation is a problem, every LE Audio device supports LC3 — no fallback negotiation needed.
| Property | SBC (A2DP mandatory) | LC3 (LE Audio mandatory) |
|---|---|---|
| Bitrate range | 128–345 kbps | 16–320 kbps |
| Quality at 64 kbps | Unusable | Acceptable (telephone quality) |
| Quality at 128 kbps | Basic, noticeable artifacts | Better than SBC at 320 kbps |
| Frame duration | ~7.5 ms fixed | 7.5 ms or 10 ms (configurable) |
| Works for voice? | No — need mSBC/CVSD for HFP | Yes — same codec for voice + music |
| Fragmentation | Optional codecs create mismatches | Universal — all LE Audio devices use LC3 |
| Sample rates | 16, 32, 44.1, 48 kHz | 8, 16, 24, 32, 44.1, 48 kHz |
CIS vs BIS — Quick Reference
| Property | CIS — Connected Isochronous Stream | BIS — Broadcast Isochronous Stream |
|---|---|---|
| Use case | Earbuds, hearing aids, headsets | Public audio, hearing loops, silent disco |
| Topology | 1 source ↔ 1 sink (bidirectional) | 1 source → unlimited sinks (unidirectional) |
| Connection needed? | Yes — BLE connection first, then GATT (ASCS) | No — scan → sync → receive |
| Pairing needed? | Yes | No |
| Group type | CIG — Connected Isochronous Group | BIG — Broadcast Isochronous Group |
| Linux socket | connect() / accept() | listen() / write() |
| QoS union member | bt_iso_qos.ucast | bt_iso_qos.bcast |
| Min kernel | 5.10 | 5.19 |
Linux / BlueZ Requirements
| Feature | Min Kernel | Min BlueZ | Notes |
|---|---|---|---|
| BTPROTO_ISO socket | 5.10 | 5.56+ | Basic ISO socket API |
| CIS unicast audio | 5.10 | 5.64+ | Earbuds, hearing aids |
| BIS broadcast audio | 5.19 | 5.66+ | Broadcast source/sink |
| PACS + ASCS profiles | Any | 5.65+ | GATT-based stream control via D-Bus |
| Hardware adapter | — | — | Must support BT 5.2 + LE ISO feature bit |
Code Example 1 — Check System Readiness
Before writing any LE Audio code, run this script to verify kernel version, BlueZ version, adapter ISO feature support, and that ISO sockets can actually be created.
#!/bin/bash
# check_le_audio.sh — Verify system readiness for LE Audio development
# Usage: chmod +x check_le_audio.sh && sudo ./check_le_audio.sh
echo "========================================="
echo " Bluetooth LE Audio — System Check"
echo "========================================="
# ---- 1. Kernel version ----
KERNEL=$(uname -r)
KMAJ=$(echo "$KERNEL" | cut -d'.' -f1)
KMIN=$(echo "$KERNEL" | cut -d'.' -f2)
echo ""
echo "[1] Kernel: $KERNEL"
if [ "$KMAJ" -gt 5 ] || { [ "$KMAJ" -eq 5 ] && [ "$KMIN" -ge 19 ]; }; then
echo " OK CIS (unicast) + BIS (broadcast) support: YES"
elif [ "$KMAJ" -eq 5 ] && [ "$KMIN" -ge 10 ]; then
echo " OK CIS (unicast) support: YES"
echo " WARN BIS (broadcast): need kernel 5.19+"
else
echo " ERR Need kernel 5.10+ for any LE Audio support"
fi
# ---- 2. BlueZ version ----
echo ""
echo "[2] BlueZ:"
if command -v bluetoothd &>/dev/null; then
echo " Version: $(bluetoothd --version 2>&1)"
echo " (Recommended: 5.65+ for full LE Audio profile support)"
else
echo " ERR bluetoothd not found — install bluez package"
fi
# ---- 3. Adapter info ----
echo ""
echo "[3] Adapter info:"
if command -v hciconfig &>/dev/null; then
hciconfig 2>/dev/null | grep -E "hci[0-9]|BD Address" | sed 's/^/ /'
fi
# ---- 4. LE ISO feature flag via btmgmt ----
echo ""
echo "[4] LE Isochronous Channels feature flag:"
if command -v btmgmt &>/dev/null; then
FEAT=$(btmgmt info 2>/dev/null | grep -i "iso\|isochronous" | head -3)
if [ -n "$FEAT" ]; then
echo "$FEAT" | sed 's/^/ /'
else
echo " (Not found — adapter may not support BT 5.2 LE ISO)"
fi
fi
# ---- 5. BTPROTO_ISO socket test ----
echo ""
echo "[5] BTPROTO_ISO socket test:"
python3 - <<'PYEOF'
import socket
AF_BLUETOOTH = 31
SOCK_SEQPACKET = 5
BTPROTO_ISO = 6
try:
sk = socket.socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO)
sk.close()
print(" OK BTPROTO_ISO socket creation supported")
except PermissionError:
print(" WARN Run as root for ISO sockets (need CAP_NET_RAW)")
except OSError as e:
print(f" ERR BTPROTO_ISO not available: {e}")
print(" Need kernel 5.10+ for ISO socket support")
PYEOF
echo ""
echo "========================================="
Code Example 2 — CIS Sender (Phone / Source Side)
This is the core building block for LE Audio unicast development. BTPROTO_ISO replaces both BTPROTO_SCO (for classic voice) and BTPROTO_L2CAP (for A2DP) in the LE Audio world.
/* cis_sender.c — BlueZ ISO socket CIS unicast sender
* Build: gcc cis_sender.c -o cis_sender -lbluetooth
* Run: sudo ./cis_sender AA:BB:CC:DD:EE:FF
* Needs: kernel 5.10+, BlueZ 5.64+, BT 5.2 adapter
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/iso.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#define CIG_ID 0x01
#define CIS_ID 0x01
/* Step 1 — Create the ISO socket
* BTPROTO_ISO = 6 (defined in kernel bluetooth.h)
* SOCK_SEQPACKET = ordered, packet-based delivery */
static int create_iso_socket(void)
{
int sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO);
if (sk < 0) {
perror("socket(BTPROTO_ISO)");
fprintf(stderr, "Need: root, kernel 5.10+, BT 5.2 adapter\n");
return -1;
}
printf("[1] ISO socket fd=%d\n", sk);
return sk;
}
/* Step 2 — Bind to local adapter
* BDADDR_ANY = let kernel select adapter */
static int bind_iso(int sk)
{
struct sockaddr_iso src = { 0 };
src.iso_family = AF_BLUETOOTH;
bacpy(&src.iso_bdaddr, BDADDR_ANY);
src.iso_bdaddr_type = BDADDR_LE_PUBLIC;
if (bind(sk, (struct sockaddr *)&src, sizeof(src)) < 0) {
perror("bind"); return -1;
}
printf("[2] Bound to local adapter\n");
return 0;
}
/* Step 3 — Set CIS QoS parameters
*
* interval: ISO packet interval in microseconds (10000 = 10ms)
* latency: max transport latency in milliseconds
* sdu: max SDU (Service Data Unit) size in bytes
* phy: BLE PHY — 0x01=1M, 0x02=2M, 0x03=Coded
* rtn: retransmission number
*
* LC3 at 16kHz mono 10ms frame → sdu = 40 bytes
* LC3 at 48kHz mono 10ms frame → sdu = 120 bytes
*/
static int set_cis_qos(int sk)
{
struct bt_iso_qos qos;
memset(&qos, 0, sizeof(qos));
qos.ucast.cig = CIG_ID;
qos.ucast.cis = CIS_ID;
qos.ucast.framing = 0x00; /* unframed — fixed size SDUs */
qos.ucast.packing = 0x00; /* sequential */
/* TX (outgoing audio to earbud) */
qos.ucast.out.interval = 10000; /* 10ms in microseconds */
qos.ucast.out.latency = 10; /* max 10ms transport latency */
qos.ucast.out.sdu = 40; /* 40 bytes = LC3 16kHz mono 10ms */
qos.ucast.out.phy = 0x02; /* 2M PHY */
qos.ucast.out.rtn = 2; /* 2 retransmissions */
/* RX (microphone audio back from earbud — bidirectional) */
qos.ucast.in = qos.ucast.out;
if (setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS,
&qos, sizeof(qos)) < 0) {
perror("setsockopt(BT_ISO_QOS)"); return -1;
}
printf("[3] QoS set: CIG=0x%02x CIS=0x%02x "
"interval=%u latency=%u sdu=%u\n",
CIG_ID, CIS_ID,
qos.ucast.out.interval,
qos.ucast.out.latency,
qos.ucast.out.sdu);
return 0;
}
/* Step 4 — Connect CIS to peer (earbud / hearing aid)
* connect() sends HCI_LE_Create_CIG to controller,
* which negotiates CIS with peer.
* Blocks until CIS Established event received. */
static int connect_cis(int sk, const char *peer_str)
{
struct sockaddr_iso dst = { 0 };
bdaddr_t peer;
str2ba(peer_str, &peer);
dst.iso_family = AF_BLUETOOTH;
bacpy(&dst.iso_bdaddr, &peer);
dst.iso_bdaddr_type = BDADDR_LE_PUBLIC;
printf("[4] Connecting CIS to %s ...\n", peer_str);
if (connect(sk, (struct sockaddr *)&dst, sizeof(dst)) < 0) {
perror("connect(CIS)"); return -1;
}
printf("[4] CIS established\n");
return 0;
}
/* Step 5 — Read back negotiated QoS
* Controller may adjust parameters during negotiation.
* Always read back to get actual agreed values. */
static uint16_t read_qos(int sk)
{
struct bt_iso_qos qos;
socklen_t len = sizeof(qos);
if (getsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len) < 0) {
perror("getsockopt"); return 40;
}
printf("[5] Negotiated: interval=%u latency=%u sdu=%u phy=0x%02x\n",
qos.ucast.out.interval, qos.ucast.out.latency,
qos.ucast.out.sdu, qos.ucast.out.phy);
return qos.ucast.out.sdu;
}
/* Step 6 — Send SDUs (one per ISO interval)
* Each write() = one audio frame (one SDU).
* In real code: fill buf with LC3-encoded PCM here. */
static void send_loop(int sk, uint16_t sdu)
{
uint8_t *buf = calloc(1, sdu);
if (!buf) { perror("calloc"); return; }
printf("[6] Sending %u-byte SDUs (Ctrl+C to stop)\n", sdu);
for (int n = 0; ; n++) {
buf[0] = n & 0xFF; /* dummy data — replace with LC3 frame */
if (write(sk, buf, sdu) < 0) {
if (errno == EINTR) break;
perror("write"); break;
}
usleep(10000); /* pace at 10ms ISO interval */
}
free(buf);
}
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage: %s <peer-addr>\n"
"Example: %s AA:BB:CC:DD:EE:FF\n",
argv[0], argv[0]);
return 1;
}
int sk = create_iso_socket(); if (sk < 0) return 1;
if (bind_iso(sk) < 0) goto done;
if (set_cis_qos(sk) < 0) goto done;
if (connect_cis(sk, argv[1]) < 0) goto done;
send_loop(sk, read_qos(sk));
done:
close(sk);
return 0;
}
Code Example 3 — CIS Receiver (Earbud / Sink Side)
The sink side listens for incoming CIS connections using listen() and accept(), then reads SDUs (audio frames) in a loop.
/* cis_receiver.c — BlueZ ISO socket CIS receiver (sink side)
* Build: gcc cis_receiver.c -o cis_receiver -lbluetooth
* Run: sudo ./cis_receiver
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/iso.h>
int main(void)
{
int srv, cli;
struct sockaddr_iso src = { 0 }, peer;
socklen_t peer_len;
uint8_t buf[255];
ssize_t n;
char addr[18];
/* Create ISO socket */
srv = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO);
if (srv < 0) { perror("socket"); return 1; }
/* Bind to any local BLE adapter */
src.iso_family = AF_BLUETOOTH;
bacpy(&src.iso_bdaddr, BDADDR_ANY);
src.iso_bdaddr_type = BDADDR_LE_PUBLIC;
if (bind(srv, (struct sockaddr *)&src, sizeof(src)) < 0) {
perror("bind"); close(srv); return 1;
}
/* Listen — wait for incoming CIS from source */
if (listen(srv, 1) < 0) {
perror("listen"); close(srv); return 1;
}
printf("Waiting for incoming CIS connection...\n");
/* Accept — blocks until CIS is established by remote */
peer_len = sizeof(peer);
cli = accept(srv, (struct sockaddr *)&peer, &peer_len);
if (cli < 0) { perror("accept"); close(srv); return 1; }
ba2str(&peer.iso_bdaddr, addr);
printf("CIS connected from: %s\n", addr);
/* Read SDUs in a loop
* In production: pass each SDU to LC3 decoder,
* then send decoded PCM to ALSA / PipeWire. */
printf("Receiving audio frames (Ctrl+C to stop)...\n");
for (int frame = 0; ; frame++) {
n = read(cli, buf, sizeof(buf));
if (n <= 0) {
if (n == 0) printf("Remote disconnected\n");
else if (errno != EINTR) perror("read");
break;
}
/* For demo: print frame count and first byte */
printf("Frame %4d len=%zd byte0=0x%02x\n",
frame, n, buf[0]);
}
close(cli);
close(srv);
return 0;
}
Code Example 4 — bluetoothctl LE Audio Inspection
For quick testing without C code, bluetoothctl in BlueZ 5.65+ exposes LE Audio PACS/ASCS through its media menu.
$ bluetoothctl
# Scan with LE-only filter
[bluetooth]# power on
[bluetooth]# scan le
[NEW] Device AA:BB:CC:DD:EE:FF LE_Audio_Sink
# Connect
[bluetooth]# connect AA:BB:CC:DD:EE:FF
[CHG] Device AA:BB:CC:DD:EE:FF Connected: yes
# Check for LE Audio GATT services in device info
[bluetooth]# info AA:BB:CC:DD:EE:FF
UUIDs:
Published Audio Capabilities (0x184e) <-- PACS present
Audio Stream Control (0x184e) <-- ASCS present
Volume Control (0x1844) <-- VCP present
# Enter media submenu (BlueZ 5.65+)
[bluetooth]# menu media
# List audio endpoints (from PACS)
[media]# list-endpoints
Endpoint /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/pac_source0
UUID: Published Audio Capabilities
Codec: LC3
Capabilities: 16_2_1 (16kHz, 10ms frame, 40 bytes SDU)
Capabilities: 48_4_1 (48kHz, 10ms frame, 120 bytes SDU)
# ---- Check adapter for BT 5.2 LE ISO feature ----
$ sudo btmgmt info
Bluetooth Version: 12 # 12 = BT 5.3, 11 = BT 5.2
Supported Features:
...
LE Isochronous Channels <-- must be present for LE Audio
...
# ---- Read LE Supported Features from HCI ----
$ sudo hcitool cmd 0x08 0x0003
# Response byte 3 bit 5 = LE Isochronous Channels (BT 5.2 feature)
Code Example 5 — BIS Broadcast Source
BIS uses the same BTPROTO_ISO socket but with the bcast QoS union member. The source calls listen() to start the BIG broadcast — no connection, no pairing. Requires kernel 5.19+.
/* bis_source.c — BIS broadcast audio source
* Build: gcc bis_source.c -o bis_source -lbluetooth
* Run: sudo ./bis_source
* Needs: kernel 5.19+, BT 5.2 adapter
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/iso.h>
/* LC3 48kHz / 10ms / mono preset: 48_4_1
* SDU = 120 bytes, interval = 10ms */
#define BIG_ID 0x01
#define BIS_ID 0x01 /* one socket per BIS channel */
#define SDU_SIZE 120
#define ISO_US 10000 /* 10ms in microseconds */
int main(void)
{
int sk;
struct sockaddr_iso src = { 0 };
struct bt_iso_qos qos;
uint8_t frame[SDU_SIZE];
/* Create ISO socket */
sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO);
if (sk < 0) { perror("socket"); return 1; }
/* Bind to any local adapter */
src.iso_family = AF_BLUETOOTH;
bacpy(&src.iso_bdaddr, BDADDR_ANY);
src.iso_bdaddr_type = BDADDR_LE_PUBLIC;
if (bind(sk, (struct sockaddr *)&src, sizeof(src)) < 0) {
perror("bind"); close(sk); return 1;
}
/* Configure BIS QoS — note: bcast union member, not ucast */
memset(&qos, 0, sizeof(qos));
qos.bcast.big = BIG_ID;
qos.bcast.bis = BIS_ID;
qos.bcast.packing = 0x00; /* sequential */
qos.bcast.framing = 0x00; /* unframed */
qos.bcast.encryption = 0x00; /* no encryption */
qos.bcast.out.interval = ISO_US;
qos.bcast.out.latency = 20; /* 20ms max latency — broadcast needs more */
qos.bcast.out.sdu = SDU_SIZE;
qos.bcast.out.phy = 0x02; /* 2M PHY */
qos.bcast.out.rtn = 4; /* 4 retransmissions for broadcast */
if (setsockopt(sk, SOL_BLUETOOTH, BT_ISO_QOS,
&qos, sizeof(qos)) < 0) {
perror("setsockopt(BT_ISO_QOS bcast)");
close(sk); return 1;
}
/* listen() starts the BIG in BLE Extended Advertising.
* Any BLE 5.2+ scanner nearby can sync to this BIG
* and receive BIS streams — no pairing, no connection. */
if (listen(sk, 1) < 0) {
perror("listen (BIG start)"); close(sk); return 1;
}
printf("BIG 0x%02x started — BIS 0x%02x broadcasting...\n",
BIG_ID, BIS_ID);
printf("Any BT 5.2+ device nearby can now receive this stream.\n");
/* Send 1000 frames then exit */
for (int i = 0; i < 1000; i++) {
memset(frame, i & 0xFF, SDU_SIZE); /* fill with dummy data */
/* In production: fill frame with LC3-encoded PCM here */
if (write(sk, frame, SDU_SIZE) < 0) {
perror("write(BIS SDU)"); break;
}
usleep(ISO_US);
}
printf("Done. Closing BIG.\n");
close(sk);
return 0;
}
Key Takeaways — Part 2
Next: PACS and ASCS — Stream Control in Depth
The next post dives into the GATT layer — PACS PAC records, ASCS ASE state machine, and how D-Bus interfaces in BlueZ expose LE Audio stream management to applications.
