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,59 +1,105 @@
| Supported Targets | ESP32-H4 | ESP32-S31 |
| ----------------- | -------- | --------- |
# BLE BIG Receiver Example
# BIG Receiver
(See the README.md file in the upper level `examples` directory for more information about examples.)
This example demonstrates the **Bluetooth LE Synchronized Receiver** functionality. It acts as a BIS (Broadcast Isochronous Stream) sink: it scans for extended advertising, establishes periodic advertising synchronization with a broadcaster, receives BIGInfo from the periodic advertising, and synchronizes to the BIG to receive isochronous data on the BIS channels. Use it together with the [big_broadcaster](../big_broadcaster) example on another device as the source.
## Overview
The implementation uses the NimBLE host stack with ISO support and the ESP-BLE-ISO APIs (periodic sync, BIG sync, data path, channel operations). It is intended for chips that support BLE 5.2 ISO (e.g. ESP32-H4). The target broadcaster name is hardcoded as `BIG Broadcaster` and the broadcast code is hardcoded as `1234`; these must match the [big_broadcaster](../big_broadcaster) defaults.
This example demonstrates a raw BLE Isochronous Broadcast Receiver — it joins 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 **receiver (BIG sync sink)**: it scans for an extended advertiser by device name, synchronises to the peer's periodic advertising train, parses the BIGInfo report, and then issues `LE BIG Create Sync` to join two BIS sub-events. An HCI output data path is installed on each BIS and incoming SDUs are accounted for by a small RX-metrics helper.
The payloads received are **application-supplied dummy data** produced by `big_broadcaster`; this example does not decode LE-Audio / LC3 — the coding format on the data path is `ESP_BLE_ISO_CODING_FORMAT_TRANSPARENT`.
## Requirements
* A board with Bluetooth LE 5.2 and ISO support (e.g. ESP32-H4)
* Another device running the [big_broadcaster](../big_broadcaster) example, which performs periodic advertising and creates 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`:
* `TARGET_DEVICE_NAME` — `"BIG Broadcaster"` (matched against the AD complete-name field)
* `TARGET_BROADCAST_CODE` — `"1234"` (must match the broadcaster)
* `SCAN_INTERVAL` / `SCAN_WINDOW` — 100 ms / 100 ms (passive)
* `PA_SYNC_TIMEOUT` — 10 s
* `BIG_SYNC_TIMEOUT` — 1 s
* `BIS_ISO_CHAN_COUNT` — 2
### 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 — the receiver passively syncs to PA + BIS without ATT or pairing.
## 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`) with GAP callback for scan, periodic sync, and BIGInfo events.
2. **Extended scan**: Start passive extended scanning. On extended scan report, parse advertising data for the complete local name.
3. **Periodic advertising sync**: When the advertised name matches the hardcoded target name `BIG Broadcaster` and the advertiser has periodic advertising, create a periodic advertising synchronization to that advertiser.
4. **BIGInfo and BIG sync**: When BIGInfo is received in the periodic advertising, create a BIG sync (`esp_ble_iso_big_sync`) with two BIS channels (hardcoded in this example; must match the [big_broadcaster](../big_broadcaster)) and the hardcoded broadcast code `1234`.
5. **Receive ISO data**: When each BIS channel is connected, set up the output data path. Incoming ISO SDUs are reported in the receive callback; the example counts valid, error, and lost packets and logs periodically.
1. NVS, NimBLE host, and the ISO common layer are initialised; a GAP application callback is registered for ISO-related GAP events.
2. Passive extended scanning is started via `ble_gap_disc()`.
3. On each `EXT_SCAN_RECV` the AD payload is parsed; if the complete-name field equals `"BIG Broadcaster"` and the report carries a non-zero periodic-advertising interval, `ble_gap_periodic_adv_sync_create()` is called.
4. On `PA_SYNC` success the extended scan is cancelled (BIGInfo arrives over the PA channel anyway).
5. On the first `BIGINFO_RECV` event the receiver fills `esp_ble_iso_big_sync_param_t` (both BIS in `bis_bitfield`, broadcast code `"1234"`, MSE = `nse` from BIGInfo) and calls `esp_ble_iso_big_sync()` — the HCI `LE BIG Create Sync` command.
6. When each BIS becomes ready, the connected callback resets that BIS's RX metrics and installs an HCI output data path (`ESP_BLE_ISO_DATA_PATH_DIR_OUTPUT`, transparent coding).
7. Each `recv` callback updates the per-BIS counters; one log line is emitted every `LOG_INTERVAL_PACKETS` SDUs.
8. On BIS disconnect or PA-sync-lost the receiver clears its sync state and restarts extended scanning.
## Example Output
## Expected Log
Tag: `BIG_SNC`.
Discovery and PA sync:
```
I (xxx) BIG_SNC: Extended scan started
I (xxx) BIG_SNC: ISO channel 0x0001 connected
I (xxx) BIG_SNC: ISO channel 0x0002 connected
I (xxx) BIG_SNC: Received 1000(1000/0/0) ISO data packets (chan 0x...)
...
I (xxx) BIG_SNC: Scanning for broadcaster...
I (xxx) BIG_SNC: PA synced: handle <h> sid <s> phy <p> peer xx:xx:xx:xx:xx:xx
```
If periodic sync is lost:
BIS bring-up:
```
I (xxx) BIG_SNC: PA sync lost, reason ...
I (xxx) BIG_SNC: [BIS #0] Connected
I (xxx) BIG_SNC: [BIS #1] Connected
```
Steady-state (logged every `LOG_INTERVAL_PACKETS` SDUs by the shared RX helper):
```
I (xxx) BIG_SNC: [BIS #0] RX: <N> packets
I (xxx) BIG_SNC: [BIS #1] RX: <N> packets
```
Teardown / re-sync (broadcaster disappears or restarts; reason `0x08` is the typical timeout, `0x3D` is the rare MIC-fail race documented in source):
```
I (xxx) BIG_SNC: [BIS #0] Disconnected, reason 0x08
I (xxx) BIG_SNC: [BIS #1] Disconnected, reason 0x08
I (xxx) BIG_SNC: PA sync lost: sync_handle <h> reason 0x<rr>
I (xxx) BIG_SNC: Scanning for broadcaster...
```
## Peer Pairing
Run [big_broadcaster](../big_broadcaster/) on a second board.
1. Flash and run `big_broadcaster` on board A; it begins extended + periodic advertising and creates the BIG.
2. Flash and run `big_receiver` on board B; it logs `Scanning for broadcaster...` and waits.
3. Board B matches the AD name `"BIG Broadcaster"`, creates a PA sync, logs `PA synced`, and stops the extended scan.
4. On the first BIGInfo report, board B issues `LE BIG Create Sync` with broadcast code `"1234"` and both BIS selected; both sides log `[BIS #0/1] Connected`.
5. Per-BIS RX-packet milestones are logged on B at the same cadence that A logs TX milestones.
6. Resetting board A causes B to log a BIS disconnect and `PA sync lost`, then automatically restart scanning to re-pair.
@@ -5,6 +5,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <assert.h>
@@ -55,27 +56,54 @@ 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);
/* New BIS session — reset RX counters so milestones reflect
* this session only. Matches the session-start reset pattern
* used by cis_peripheral and the audio examples.
*/
if (chan_idx >= 0) {
example_iso_rx_metrics_reset(&rx_metrics[chan_idx]);
}
err = esp_ble_iso_setup_data_path(chan, ESP_BLE_ISO_DATA_PATH_DIR_OUTPUT, &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;
}
}
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);
/* Common BIS disconnect reasons during broadcaster restart:
*
* 0x08 CONN_TIMEOUT - no subevent received within
* BIG_Sync_Timeout (broadcaster is gone
* or out of range). This is the common
* case.
*
* 0x3D TERM_DUE_TO_MIC_FAIL - packets arrived but MIC check
* failed repeatedly. Only possible on
* encrypted BIGs; typically means the
* broadcaster restarted with a new
* session before we timed out, so the
* old session key no longer decrypts.
*
* !!! LOW PROBABILITY !!!
* Requires the broadcaster to come
* back on air within the BIG_Sync_
* Timeout window — a narrow race.
*
* Both are normal: receiver will drop PA sync and re-discover.
*/
ESP_LOGI(TAG, "[BIS #%d] Disconnected, reason 0x%02x",
bis_chan_index_get(chan), reason);
big_synced = false;
out_big = NULL;
for (size_t i = 0; i < BIS_ISO_CHAN_COUNT; i++) {
example_iso_rx_metrics_reset(&rx_metrics[i]);
}
}
static void iso_recv_cb(esp_ble_iso_chan_t *chan,
@@ -83,14 +111,16 @@ static void iso_recv_cb(esp_ble_iso_chan_t *chan,
const uint8_t *data, uint16_t len)
{
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);
rx_metrics[chan_idx].last_sdu_len = len;
example_iso_rx_metrics_on_recv(info, &rx_metrics[chan_idx], TAG, "chan", chan);
example_iso_rx_metrics_on_recv(info, &rx_metrics[chan_idx], TAG, name);
}
static esp_ble_iso_chan_ops_t iso_ops = {
@@ -150,7 +180,7 @@ static void ext_scan_start(void)
return;
}
ESP_LOGI(TAG, "Extended scan started");
ESP_LOGI(TAG, "Scanning for broadcaster...");
}
static int pa_sync_create(uint8_t addr_type, uint8_t addr[6], uint8_t sid)
@@ -213,26 +243,33 @@ static void ext_scan_recv(esp_ble_iso_gap_app_event_t *event)
static void pa_sync(esp_ble_iso_gap_app_event_t *event)
{
int err;
if (event->pa_sync.status) {
ESP_LOGE(TAG, "PA sync failed, status %d", event->pa_sync.status);
per_adv_synced = false;
return;
}
ESP_LOGI(TAG, "PA sync established:");
ESP_LOGI(TAG, "sync_handle 0x%04x status 0x%02x addr %02x:%02x:%02x:%02x:%02x:%02x "
"sid %u adv_phy %u per_adv_itvl 0x%04x adv_ca %u",
event->pa_sync.sync_handle, event->pa_sync.status,
ESP_LOGI(TAG, "PA synced: handle %u sid %u phy %u peer %02x:%02x:%02x:%02x:%02x:%02x",
event->pa_sync.sync_handle, event->pa_sync.sid, event->pa_sync.adv_phy,
event->pa_sync.addr.val[5], event->pa_sync.addr.val[4],
event->pa_sync.addr.val[3], event->pa_sync.addr.val[2],
event->pa_sync.addr.val[1], event->pa_sync.addr.val[0],
event->pa_sync.sid, event->pa_sync.adv_phy,
event->pa_sync.per_adv_itvl, event->pa_sync.adv_ca);
event->pa_sync.addr.val[1], event->pa_sync.addr.val[0]);
/* PA sync is established; the BIGInfo report will arrive via the
* PA sync channel, so the extended scanner is no longer needed.
* Stop it now — pa_sync_lost() will restart it on loss.
*/
err = ble_gap_disc_cancel();
if (err) {
ESP_LOGW(TAG, "Failed to stop scanning, err %d", err);
}
}
static void pa_sync_lost(esp_ble_iso_gap_app_event_t *event)
{
ESP_LOGI(TAG, "PA sync lost: sync_handle 0x%04x reason 0x%02x",
ESP_LOGI(TAG, "PA sync lost: sync_handle %u reason 0x%02x",
event->pa_sync_lost.sync_handle, event->pa_sync_lost.reason);
per_adv_synced = false;