mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
fix(ble_audio): Miscellaneous fixes for ISO & LE Audio examples
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user