fix(ble_audio): Miscellaneous fixes for ISO & LE Audio examples

This commit is contained in:
Liu Linyan
2026-05-06 09:31:07 +08:00
parent 92288a2d84
commit 325b08e023
108 changed files with 3051 additions and 1269 deletions
@@ -1,52 +1,107 @@
| Supported Targets | ESP32-H4 | ESP32-S31 |
| ----------------- | -------- | --------- |
# BLE BIG Broadcaster Example
# BIG Broadcaster
(See the README.md file in the upper level `examples` directory for more information about examples.)
This example demonstrates the **Bluetooth LE Isochronous Broadcaster** functionality. It acts as a Broadcast Isochronous Stream (BIS) source: it starts extended advertising and periodic advertising, creates a Broadcast Isochronous Group (BIG), and sends isochronous data on the BIS channels. Receivers can synchronize to this BIG using the [big_receiver](../big_receiver) example.
## Overview
The implementation uses the NimBLE host stack with ISO support and the ESP-BLE-ISO APIs (CIG/BIG, data path, channel operations). It is intended for chips that support BLE 5.2 ISO (e.g. ESP32-H4). The advertised device name is hardcoded as `BIG Broadcaster` and the broadcast code is hardcoded as `1234`; these can be changed by editing the source code constants.
This example demonstrates a raw BLE Isochronous Broadcaster — it creates a Broadcast Isochronous Group (BIG) directly at the ISO transport layer over the NimBLE host, without any BLE Audio profile (BAP/CAP) on top.
The device acts as the **broadcaster**: it starts extended + periodic advertising, creates a BIG carrying two BIS streams, sets up an HCI input data path on each BIS, and then transmits SDUs on a fixed 10 ms cadence using a software TX scheduler. The peer (`big_receiver`) discovers the broadcaster by name, syncs to the periodic advertising train, decodes BIGInfo, and joins the BIS sub-events.
The audio data on the wire is **application-supplied dummy payload** (each SDU is filled with the byte value of its sequence number). The coding format is `ESP_BLE_ISO_CODING_FORMAT_TRANSPARENT`, so no LC3 / LE-Audio framing is involved.
## Requirements
* A board with Bluetooth LE 5.2 and ISO support (e.g. ESP32-H4)
* Optionally, a second device running the [big_receiver](../big_receiver) example to receive and sync to the BIG
* A board with BLE 5.2 and ISO support (e.g. ESP32-H4, ESP32-S31)
* Peer device running the paired example
## How to Use Example
## Configuration
Before project configuration and build, set the correct chip target:
```bash
idf.py menuconfig
```
No build-time options — runtime defaults are baked into source.
Notable hard-coded parameters in `main/main.c`:
* `LOCAL_DEVICE_NAME` — `"BIG Broadcaster"` (advertised in extended adv)
* `LOCAL_BROADCAST_CODE` — `"1234"` (BIG is encrypted)
* `BIG_SDU_INTERVAL_US` — 10 000 us
* `BIG_LATENCY_MS` — 10 ms
* `BIG_PHY` — 2M
* `BIG_RTN` — 2 (retransmissions)
* `BIS_ISO_CHAN_COUNT` — 2
* `BIS_SDU_SIZE` — 120 bytes
### Security & Pairing
The shared init at `../common_components/example_init/ble_iso_example_init.c` configures Just-Works pairing (LE Secure Connections, no MITM, `BLE_SM_IO_CAP_NO_IO`) with bonding enabled, and leaves `gatts_register_cb = NULL` (no GATT services). These settings are not exercised by this example — BIG broadcast traffic is non-connectable and BIS payload encryption is driven by the broadcast code, independent of host SMP.
## Build & Flash
```bash
idf.py set-target esp32h4
```
### Build and Flash
Run the following to build, flash and monitor:
```bash
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF.
(Exit serial monitor with `Ctrl-]`.)
## Example Flow
1. **Initialization**: NVS, Bluetooth stack (NimBLE), and ISO common layer (`esp_ble_iso_common_init`).
2. **Extended and periodic advertising**: Configure and start extended advertising with a fixed interval; attach periodic advertising with name `BIG Broadcaster` for receivers to discover.
3. **Create BIG**: Register the advertising set for BIG, then create a BIG with two BIS channels (hardcoded as `BIS_ISO_CHAN_COUNT` in the source; `CONFIG_BT_ISO_MAX_CHAN` must be >= 2), SDU interval 10 ms, latency 10 ms, sequential packing, unframed, and encryption with the hardcoded broadcast code `1234`. The [big_receiver](../big_receiver) must use the same broadcast code.
4. **Send ISO data**: Once all BIS channels are connected and their data paths are set up, a periodic TX scheduler based on `k_work_delayable` sends the same SDU on all BIS channels at the configured interval in the ISO task context; sequence numbers and drift handling are applied. Note that the scheduler timer resolution is in milliseconds, which may not match the exact SDU interval for all configurations.
1. NVS, NimBLE host, and the ISO common layer are initialised.
2. A TX scheduler is initialised for each of the two BIS channels.
3. Extended advertising parameters / data and periodic advertising parameters / data are configured for handle 0 (non-connectable, non-scannable, primary 1M / secondary 2M).
4. Periodic advertising and extended advertising are started.
5. `esp_ble_iso_big_ext_adv_add()` ties the adv handle to the BIG, then `esp_ble_iso_big_create()` issues the HCI `LE Create BIG` with two BIS, encryption on, and broadcast code `"1234"`.
6. As each BIS becomes ready, the connected callback installs an HCI input data path (`ESP_BLE_ISO_DATA_PATH_DIR_INPUT`, transparent coding).
7. Once both BIS are connected, the per-channel TX schedulers start firing every 10 ms, calling `esp_ble_iso_chan_send()` with a 120-byte dummy SDU and an incrementing sequence number.
8. On disconnect of all BIS the schedulers are stopped and the BIG handle is cleared.
## Example Output
## Expected Log
Tag: `BIG_BRD`.
Startup / advertising:
```
I (xxx) BIG_BRD: Extended adv instance 0 started
I (xxx) BIG_BRD: ISO channel 0x0001 connected
I (xxx) BIG_BRD: ISO channel 0x0002 connected
I (xxx) BIG_BRD: Transmitted 1000 ISO data packets (chan 0x...)
...
I (xxx) BIG_BRD: Advertising started (handle 0)
```
BIS bring-up (first BIS, then second, then TX start):
```
I (xxx) BIG_BRD: [BIS #0] Connected
I (xxx) BIG_BRD: Waiting for remaining BIS channels (1/2)
I (xxx) BIG_BRD: [BIS #1] Connected
I (xxx) BIG_BRD: All 2 BIS channels connected, starting TX
```
Steady-state (logged every `LOG_INTERVAL_PACKETS` SDUs by the shared TX helper):
```
I (xxx) BIG_BRD: [BIS #0] TX: <N> packets
I (xxx) BIG_BRD: [BIS #1] TX: <N> packets
```
Teardown:
```
I (xxx) BIG_BRD: [BIS #0] Disconnected, reason 0x<rr>
I (xxx) BIG_BRD: [BIS #1] Disconnected, reason 0x<rr>
I (xxx) BIG_BRD: All BIS channels disconnected, TX stopped
```
## Peer Pairing
Run [big_receiver](../big_receiver/) on a second board.
1. Flash and run `big_broadcaster` on board A; it begins extended + periodic advertising and creates the BIG immediately.
2. Flash and run `big_receiver` on board B; it starts passive extended scanning.
3. Board B matches on the device name `"BIG Broadcaster"` and creates a periodic advertising sync to the broadcaster's PA train.
4. Once PA-synced, board B receives the BIGInfo report and calls `LE BIG Create Sync` with broadcast code `"1234"` and both BIS in the bitfield.
5. Board A logs `[BIS #0/1] Connected` as the controller reports the BIS as established; board B logs `PA synced` followed by per-BIS `Connected` and starts counting received SDUs.
6. Resetting either side causes the receiver to drop and (via `pa_sync_lost`) restart its scan, re-establishing the link automatically.
@@ -5,6 +5,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <assert.h>
@@ -73,20 +74,21 @@ static void iso_connected_cb(esp_ble_iso_chan_t *chan)
.pid = ESP_BLE_ISO_DATA_PATH_HCI,
.format = ESP_BLE_ISO_CODING_FORMAT_TRANSPARENT,
};
int chan_idx = bis_chan_index_get(chan);
esp_err_t err;
ESP_LOGI(TAG, "ISO channel %p connected", chan);
ESP_LOGI(TAG, "[BIS #%d] Connected", chan_idx);
err = esp_ble_iso_setup_data_path(chan, ESP_BLE_ISO_DATA_PATH_DIR_INPUT, &data_path);
if (err) {
ESP_LOGE(TAG, "Failed to setup ISO data path, err %d", err);
ESP_LOGE(TAG, "[BIS #%d] Failed to setup data path, err %d", chan_idx, err);
return;
}
connected_bis_count++;
if (connected_bis_count < BIS_ISO_CHAN_COUNT) {
ESP_LOGI(TAG, "Waiting for all BIS channels (%u/%u)",
ESP_LOGI(TAG, "Waiting for remaining BIS channels (%u/%u)",
connected_bis_count, BIS_ISO_CHAN_COUNT);
return;
}
@@ -100,7 +102,7 @@ static void iso_connected_cb(esp_ble_iso_chan_t *chan)
err = example_iso_tx_scheduler_start(&chan_tx[i].scheduler, BIG_SDU_INTERVAL_US);
if (err) {
ESP_LOGE(TAG, "Failed to start tx scheduler[%u], err %d", i, err);
ESP_LOGE(TAG, "[BIS #%zu] Scheduler start failed, err %d", i, err);
continue;
}
@@ -110,7 +112,8 @@ static void iso_connected_cb(esp_ble_iso_chan_t *chan)
static void iso_disconnected_cb(esp_ble_iso_chan_t *chan, uint8_t reason)
{
ESP_LOGI(TAG, "ISO channel %p disconnected, reason 0x%02x", chan, reason);
ESP_LOGI(TAG, "[BIS #%d] Disconnected, reason 0x%02x",
bis_chan_index_get(chan), reason);
if (connected_bis_count > 0) {
connected_bis_count--;
@@ -124,7 +127,7 @@ static void iso_disconnected_cb(esp_ble_iso_chan_t *chan, uint8_t reason)
for (size_t i = 0; i < BIS_ISO_CHAN_COUNT; i++) {
err = example_iso_tx_scheduler_stop(&chan_tx[i].scheduler);
if (err) {
ESP_LOGE(TAG, "Failed to stop tx scheduler[%u], err %d", i, err);
ESP_LOGE(TAG, "[BIS #%zu] Scheduler stop failed, err %d", i, err);
}
}
@@ -135,14 +138,16 @@ static void iso_disconnected_cb(esp_ble_iso_chan_t *chan, uint8_t reason)
static void iso_sent_cb(esp_ble_iso_chan_t *chan, void *user_data)
{
int chan_idx = bis_chan_index_get(chan);
char name[24];
if (chan_idx < 0) {
ESP_LOGW(TAG, "Unknown BIS channel %p", chan);
ESP_LOGW(TAG, "Unknown BIS channel");
return;
}
snprintf(name, sizeof(name), "BIS #%d", chan_idx);
example_iso_tx_scheduler_on_sent(&chan_tx[chan_idx].scheduler,
user_data, TAG, "chan", chan);
user_data, TAG, name);
}
static esp_ble_iso_chan_ops_t iso_ops = {
@@ -281,7 +286,7 @@ static int ext_adv_start(void)
return err;
}
ESP_LOGI(TAG, "Extended adv instance %u started", ADV_HANDLE);
ESP_LOGI(TAG, "Advertising started (handle %u)", ADV_HANDLE);
return 0;
}
@@ -334,7 +339,7 @@ static void iso_chan_send(int chan_idx)
}
if (bis_iso_chan[chan_idx].iso == NULL) {
ESP_LOGW(TAG, "No channel to transmit data, %u", chan_idx);
ESP_LOGW(TAG, "[BIS #%d] No channel to transmit", chan_idx);
return;
}
@@ -347,8 +352,7 @@ static void iso_chan_send(int chan_idx)
sizeof(chan_tx[chan_idx].data),
chan_tx[chan_idx].seq_num);
if (err) {
ESP_LOGD(TAG, "Failed to transmit data on channel 0x%04x",
bis_iso_chan[chan_idx].iso->handle);
ESP_LOGD(TAG, "[BIS #%d] send failed, err %d", chan_idx, err);
return;
}
@@ -397,7 +401,7 @@ void app_main(void)
tx_scheduler_cb,
&chan_tx[i]);
if (err) {
ESP_LOGE(TAG, "Failed to init tx scheduler[%u], err %d", i, err);
ESP_LOGE(TAG, "[BIS #%zu] Scheduler init failed, err %d", i, err);
return;
}
}