Bluetooth LE Audio – Real World Applications

Bluetooth LE Audio – Real World Applications
How LE Audio changes the way we hear, share, and experience sound
Chapter
12
Level
Intermediate
Topics
8 Sections
Code
BlueZ C + HCI

Keywords You Will Learn

BIG BIS Broadcast Source Broadcast Sink Broadcast Assistant Broadcast_Code PAST Public Broadcast Profile Audio Sharing LC3 CIS QoS Telecoil Tap 2 Hear

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.

1. The Core Building Blocks: BIG and BIS

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 / BIS Hierarchy – Broadcast Transmitter
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.

2. Scanning for a Broadcast – The Sink Side

A Broadcast Sink (your earbuds, hearing aid, or headphones) needs to go through three steps before it can receive audio:

Broadcast Sink Sync Process (3 Steps)
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(&params, 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), &params);
    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;
}

3. Use Case – Public Transport (Bus Stops and Trains)

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 Broadcast – How it Flows
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 Settings at a Glance – Public Broadcast
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.

4. Use Case – Coffee Shops and Restaurants (Silent Music)

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.

Silent Restaurant – Broadcast Music Setup
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. Use Case – TVs (Public, Home, and Hotels)

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 – Multiple Language BIS Streams in One BIG
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.

Home TV – Encrypted Broadcast with PAST for Code Distribution
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.

6. Use Case – Phones Sharing Music with Friends

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

Phone Music Sharing – Unicast to Broadcast Transition
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.

7. Audio Sharing – Public vs Personal Broadcast

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

Audio Sharing – Two Categories of Broadcast Transmitter
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. Wearables, Battery Boxes, and Tap 2 Hear

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.

Tap 2 Hear – NFC-triggered Broadcast Access
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.

9. Reading ISO Audio Data in BlueZ (Broadcast Sink Side)

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;
}

10. Classic Audio vs LE Audio – Coexistence Period

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.

Classic Audio vs LE Audio – Feature Comparison
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.

Quick Summary – What You Should Remember
Chapter 12 – Key Takeaways
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.

Back to Home Bluetooth Series

Leave a Reply

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