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
@@ -5,68 +5,91 @@
(See the README.md file in the upper level `examples` directory for more information about examples.)
This example demonstrates the **Basic Audio Profile (BAP) Broadcast Sink** functionality. It scans for BAP Broadcast Sources (advertising with the Broadcast Audio service data), establishes periodic advertising synchronization with the first matching source, creates a BAP Broadcast Sink, and synchronizes to the BIG to receive broadcast audio streams. It listens until the source stops or sync is lost. The scan filters by the hardcoded source name (`BAP Broadcast Source`). Optionally, you can run in a mode where a Broadcast Assistant (e.g. a phone app) controls when to sync via the Scan Delegator.
## Overview
The implementation uses the NimBLE host stack with ISO and BAP support, ESP-BLE-ISO, and ESP-BLE-AUDIO APIs (PACS, BAP broadcast sink, scan delegator). It is intended for chips that support BLE 5.2 ISO and LE Audio (e.g. ESP32-H4). Sink capabilities are registered with LC3 (16 kHz and 24 kHz, 10 ms frame duration, 1 channel). For encrypted broadcasts, the broadcast code is hardcoded as `1234` in the source, or can be provided by a Broadcast Assistant.
This example acts as a **BAP Broadcast Sink**. It scans for extended advertisements that carry the Broadcast Audio Announcement Service UUID and whose complete-name AD matches the hard-coded `"BAP Broadcast Source"` string; on a hit, it creates a periodic-advertising sync, builds a BAP broadcast sink for that PA handle and broadcast ID, decodes the BASE / BIGInfo from the PA channel, and then synchronizes to the BIG to receive BIS streams.
The build runs on top of the NimBLE host stack and the ESP BLE Audio component set. Sink-side APIs used: `esp_ble_audio_common_init` / `_start`, `esp_ble_audio_pacs_register` + `esp_ble_audio_pacs_cap_register` (sink PAC and sink location enabled), `esp_ble_audio_bap_scan_delegator_register` (so a Broadcast Assistant can drive PA-sync, broadcast-code, and BIS-sync requests via BASS), `esp_ble_audio_bap_broadcast_sink_register_cb`, `esp_ble_audio_bap_broadcast_sink_create` / `_sync` / `_stop` / `_delete`, and `esp_ble_audio_bap_base_get_subgroup_count` / `_get_bis_indexes`. PAC capabilities are LC3 with sample rates 16 kHz + 24 kHz, frame duration 10 ms, 1 channel, 40–60 octets/frame, 1 frame/SDU. The fallback broadcast code is `"1234"`; if a Broadcast Assistant has supplied one through BASS it is used instead.
After PA sync is established, `ble_gap_disc_cancel()` stops the extended scanner — BASE/BIGInfo arrive over the PA channel — and `pa_sync_lost()` re-arms the scanner.
## Requirements
* A board with Bluetooth LE 5.2, ISO, and LE Audio support (e.g. ESP32-H4)
* A BAP Broadcast Source (e.g. another device or sample running as broadcast source) that is advertising and sending broadcast audio
* A board with Bluetooth LE 5.2, ISO, and LE Audio support (e.g. ESP32-H4, ESP32-S31)
* A peer running the [broadcast_source](../broadcast_source) example (or another BAP Broadcast Source advertising the matching name)
## How to Use Example
## Configuration
Before project configuration and build, set the correct chip target:
```bash
idf.py set-target esp32h4
```
### Configure the Project
Open the project configuration menu:
Open menuconfig:
```bash
idf.py menuconfig
```
In the **Example: Broadcast Sink** menu:
No build-time options — runtime defaults are baked into source. The example always runs both: it scans for `"BAP Broadcast Source"` directly and registers a BASS scan delegator so a Broadcast Assistant could also drive sync (PAST is rejected — see `pa_sync_req_cb`).
* **Whether to wait for a Broadcast Assistant**: If enabled, the device advertises connectable and waits for a Broadcast Assistant to connect and send PA sync, broadcast code, and BIS sync requests via the Scan Delegator; the sink then syncs according to those requests.
### Security & Pairing
### Build and Flash
The example inherits a Just-Works pairing model (LE Secure Connections, no MITM, no I/O capability) with bonding enabled from the shared init at `../common_components/example_init/ble_audio_example_init.c`; change pairing/IO-cap there if needed.
Run the following to build, flash and monitor:
## Build & Flash
```bash
idf.py set-target esp32h4 # or esp32s31
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, and LE Audio common layer (`esp_ble_audio_common_init`). Register PACS with sink capabilities (LC3), register BAP scan delegator callbacks and broadcast sink callbacks, then start the audio stack. Set device name if needed.
2. **Scanning**: Start extended scanning. On scan report, look for Broadcast Audio service data (UUID + broadcast ID). Filter by the hardcoded target device name (`BAP Broadcast Source`). When a matching advertiser with periodic advertising is found and not already syncing, create periodic advertising synchronization.
3. **PA sync**: When periodic sync is established, create a BAP Broadcast Sink (`esp_ble_audio_bap_broadcast_sink_create`) for that sync handle and broadcast ID.
4. **BASE and syncable**: When the BASE is received, the sink gets subgroup count and BIS index bitfield. When BIGInfo indicates the sink is syncable, call `esp_ble_audio_bap_broadcast_sink_sync` with the chosen BIS bitfield and broadcast code (from menuconfig or from the scan delegator). Streams are bound to the registered stream array.
5. **Streams**: As each BIS stream starts, the stream started callback runs; when all requested streams have started, the sink is considered running. Incoming audio data is delivered in the stream receive callback; the example counts valid, error, and lost packets per stream and logs periodically. When streams stop or PA sync is lost, the sink is cleaned up.
1. `app_main` initializes NVS, calls `bluetooth_init()`, and calls `esp_ble_audio_common_init(&info)` with `info.gap_cb = iso_gap_app_cb`.
2. PACS is registered (`snk_pac` + `snk_loc`), each stream's `ops` field is wired to `stream_ops`, and the LC3 sink capability is registered via `esp_ble_audio_pacs_cap_register(ESP_BLE_AUDIO_DIR_SINK, ...)`.
3. The scan delegator (`scan_delegator_cbs`: `recv_state_updated`, `pa_sync_req`, `pa_sync_term_req`, `broadcast_code`, `bis_sync_req`) and broadcast-sink callbacks (`base_recv`, `syncable`) are registered, then `esp_ble_audio_common_start(NULL)` runs.
4. `ext_scan_start()` runs passive extended discovery (`itvl=window=160`). For each `ESP_BLE_AUDIO_GAP_EVENT_EXT_SCAN_RECV`, `data_cb` matches the complete/short/broadcast name AD type against `"BAP Broadcast Source"` and records the Broadcast ID from the Broadcast Audio Service Data.
5. On match (and only when not already PA-syncing and no scan-delegator state is pinned), `pa_sync_create()` calls `ble_gap_periodic_adv_sync_create` with `skip=0`, `sync_timeout=10s`.
6. `ESP_BLE_AUDIO_GAP_EVENT_PA_SYNC` clears `pa_syncing`, cancels discovery, stores `sync_handle`, and calls `esp_ble_audio_bap_broadcast_sink_create()`.
7. `base_recv_cb` extracts the subgroup count and BIS index bitfield (masked by `bis_index_mask`); when no Broadcast Assistant is connected, `requested_bis_sync` defaults to `ESP_BLE_AUDIO_BAP_BIS_SYNC_NO_PREF`.
8. `syncable_cb` AND-masks the BASE bitfield with the requested mask, copies `TARGET_BROADCAST_CODE` if the BIG is encrypted (unless BASS already supplied one), and calls `esp_ble_audio_bap_broadcast_sink_sync()` with the chosen mask and `streams_p`.
9. `stream_started_cb` resets per-stream RX metrics and increments `stream_count_started`; `stream_recv_cb` updates metrics via `example_audio_rx_metrics_on_recv()`. When all streams have stopped, `stream_stopped_cb` deletes the broadcast sink. `pa_sync_lost()` clears the cached `req_recv_state`, deletes any sink, and restarts the scanner.
## Example Output
## Expected Log
```
I (xxx) BAP_BSNK: Scanning for broadcast source...
I (xxx) BAP_BSNK: Broadcast source PA synced, creating Broadcast Sink
I (xxx) BAP_BSNK: Received BASE with 1 subgroups from broadcast sink 0x...
I (xxx) BAP_BSNK: Broadcast sink (0x...) is syncable, BIG not encrypted
I (xxx) BAP_BSNK: Stream 0x... started (1/1)
I (xxx) BAP_BSNK: Received 1000(1000/0/0) ISO data packets (stream 0x...)
...
I (xxx) BAP_BSNK: BASE received (... subgroup(s))
I (xxx) BAP_BSNK: bis_index_bitfield = 0x...
I (xxx) BAP_BSNK: Broadcast sink syncable, BIG encrypted
I (xxx) BAP_BSNK: Syncing to broadcast: BIS mask 0x... (... stream(s))
I (xxx) BAP_BSNK: [SNK #0] Stream started (.../...)
```
If PA sync is lost:
Per-stream RX metrics are then emitted under the `BAP_BSNK` tag, name `SNK #<idx>`. If a Broadcast Assistant connects via BASS, additional log lines appear:
```
I (xxx) BAP_BSNK: PA sync lost, reason ...
I (xxx) BAP_BSNK: Receive state updated, pa_sync 0x... encrypt 0x...
I (xxx) BAP_BSNK: Received request to sync to PA (PAST {not }available): ...
I (xxx) BAP_BSNK: Broadcast code received
I (xxx) BAP_BSNK: BIS sync req: broadcast_id 0x... BIS mask 0x... subgroup mask 0x... (...)
I (xxx) BAP_BSNK: Received request to terminate PA sync
```
On teardown / sync loss:
```
I (xxx) BAP_BSNK: [SNK #0] Stream stopped, reason 0x... (.../...)
I (xxx) BAP_BSNK: PA sync lost: sync_handle ... reason 0x...
I (xxx) BAP_BSNK: PA sync terminated
```
## Peer Pairing
Run [broadcast_source](../broadcast_source/) on a second board. Expected interaction sequence:
1. Source advertises extended PDU containing the Broadcast Audio Announcement Service UUID, Broadcast ID `0x123456`, and complete name `"BAP Broadcast Source"`; sink scans and matches by name + UUID.
2. Source's periodic advertising carries the encoded BASE; sink establishes PA sync and parses the BASE to recover subgroup count and BIS index bitfield.
3. Source's BIGInfo advertises the BIG as encrypted (broadcast code `"1234"`); sink reports `BIG encrypted`.
4. Source starts the BIG and the two BIS streams (`FRONT_LEFT`, `FRONT_RIGHT`); sink calls `esp_ble_audio_bap_broadcast_sink_sync()` with the chosen BIS bitfield and the matching broadcast code.
5. Source's TX scheduler keeps pushing SDUs at `preset_active.qos.interval`; sink stream `recv` callbacks deliver the data and update RX metrics.
6. Stopping the source (or losing PA sync) triggers `stream_stopped_cb` on the sink, which deletes the broadcast sink and resumes scanning.
@@ -1,5 +1,5 @@
# SPDX-FileCopyrightText: 2022 Nordic Semiconductor ASA
# SPDX-FileContributor: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
menu "Example: Broadcast Sink"
@@ -113,7 +113,7 @@ static void ext_scan_start(void)
return;
}
ESP_LOGI(TAG, "Extended scan started");
ESP_LOGI(TAG, "Scanning for broadcast source...");
}
static int pa_sync_create(const bt_addr_le_t *addr, uint8_t adv_sid)
@@ -213,7 +213,7 @@ static void broadcast_code_cb(esp_ble_conn_t *conn,
const esp_ble_audio_bap_scan_delegator_recv_state_t *recv_state,
const uint8_t broadcast_code[ESP_BLE_ISO_BROADCAST_CODE_SIZE])
{
ESP_LOGI(TAG, "Broadcast code received for %p", recv_state);
ESP_LOGI(TAG, "Broadcast code received");
req_recv_state = recv_state;
@@ -253,10 +253,10 @@ static int bis_sync_req_cb(esp_ble_conn_t *conn,
}
}
ESP_LOGI(TAG, "BIS sync req for %p: BIS indexes 0x%08x (subgroup indexes 0x%08x), "
"broadcast id: 0x%06x, (%s)", recv_state, requested_bis_sync,
requested_subgroup_sync, recv_state->broadcast_id,
stream_started ? "Stream started" : "Stream not started");
ESP_LOGI(TAG, "BIS sync req: broadcast_id 0x%06x BIS mask 0x%08x subgroup mask 0x%08x (%s)",
recv_state->broadcast_id, requested_bis_sync,
requested_subgroup_sync,
stream_started ? "streaming" : "not streaming");
if (stream_started && requested_bis_sync == 0) {
/* The stream stopped callback will be called as part of this, and
@@ -307,8 +307,7 @@ static void base_recv_cb(esp_ble_audio_bap_broadcast_sink_t *sink,
return;
}
ESP_LOGI(TAG, "Received BASE with %d subgroups from broadcast sink %p",
base_subgroup_count, sink);
ESP_LOGI(TAG, "BASE received (%d subgroup(s))", base_subgroup_count);
err = esp_ble_audio_bap_base_get_bis_indexes(base, &base_bis_index_bitfield);
if (err) {
@@ -334,8 +333,8 @@ static void syncable_cb(esp_ble_audio_bap_broadcast_sink_t *sink,
uint32_t sync_bitfield;
esp_err_t err;
ESP_LOGI(TAG, "Broadcast sink (%p) is syncable, BIG %s",
sink, biginfo->encryption ? "encrypted" : "not encrypted");
ESP_LOGI(TAG, "Broadcast sink syncable, BIG %s",
biginfo->encryption ? "encrypted" : "not encrypted");
sync_bitfield = (bis_index_bitfield & requested_bis_sync);
if (sync_bitfield == 0) {
@@ -353,9 +352,8 @@ static void syncable_cb(esp_ble_audio_bap_broadcast_sink_t *sink,
}
}
ESP_LOGI(TAG, "Syncing to broadcast with bitfield:");
ESP_LOGI(TAG, "0x%08x = 0x%08x (bis_index) & 0x%08x (req_bis_sync), stream_count %u",
sync_bitfield, bis_index_bitfield, requested_bis_sync, stream_count);
ESP_LOGI(TAG, "Syncing to broadcast: BIS mask 0x%08x (%u stream(s))",
sync_bitfield, stream_count);
if (biginfo->encryption) {
memset(sink_broadcast_code, 0, ESP_BLE_ISO_BROADCAST_CODE_SIZE);
@@ -376,13 +374,24 @@ static esp_ble_audio_bap_broadcast_sink_cb_t broadcast_sink_cbs = {
.syncable = syncable_cb,
};
static int stream_index(const esp_ble_audio_bap_stream_t *stream)
{
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
if (&streams[i].stream == stream) {
return (int)i;
}
}
return -1;
}
static void stream_started_cb(esp_ble_audio_bap_stream_t *stream)
{
struct broadcast_sink_stream *sink_stream = CONTAINER_OF(stream,
struct broadcast_sink_stream,
stream);
ESP_LOGI(TAG, "Stream %p started (%u/%u)", stream, stream_count_started, stream_count);
ESP_LOGI(TAG, "[SNK #%d] Stream started (%u/%u)",
stream_index(stream), stream_count_started, stream_count);
example_audio_rx_metrics_reset(&sink_stream->rx_metrics);
@@ -395,8 +404,8 @@ static void stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint8_t reason
{
esp_err_t err;
ESP_LOGI(TAG, "Stream %p stopped with reason 0x%02x (%u/%u)",
stream, reason, stream_count_stopped, stream_count);
ESP_LOGI(TAG, "[SNK #%d] Stream stopped, reason 0x%02x (%u/%u)",
stream_index(stream), reason, stream_count_stopped, stream_count);
if (++stream_count_stopped == stream_count) {
stream_started = false;
@@ -418,10 +427,11 @@ static void stream_recv_cb(esp_ble_audio_bap_stream_t *stream,
struct broadcast_sink_stream *sink_stream = CONTAINER_OF(stream,
struct broadcast_sink_stream,
stream);
char name[24];
snprintf(name, sizeof(name), "SNK #%d", stream_index(stream));
sink_stream->rx_metrics.last_sdu_len = len;
example_audio_rx_metrics_on_recv(info, &sink_stream->rx_metrics,
TAG, "stream", stream);
example_audio_rx_metrics_on_recv(info, &sink_stream->rx_metrics, TAG, name);
}
static esp_ble_audio_bap_stream_ops_t stream_ops = {
@@ -506,6 +516,7 @@ static void ext_scan_recv(esp_ble_audio_gap_app_event_t *event)
static void pa_sync(esp_ble_audio_gap_app_event_t *event)
{
esp_err_t err;
int rc;
pa_syncing = false;
@@ -518,6 +529,15 @@ static void pa_sync(esp_ble_audio_gap_app_event_t *event)
ESP_LOGI(TAG, "Broadcast source PA synced, creating Broadcast Sink");
/* PA sync is established; the BASE / BIGInfo reports 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.
*/
rc = ble_gap_disc_cancel();
if (rc) {
ESP_LOGW(TAG, "Failed to stop scanning, err %d", rc);
}
err = esp_ble_audio_bap_broadcast_sink_create(event->pa_sync.sync_handle,
broadcaster_broadcast_id,
&broadcast_sink);
@@ -529,7 +549,7 @@ static void pa_sync(esp_ble_audio_gap_app_event_t *event)
static void pa_sync_lost(esp_ble_audio_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);
if (sync_handle == event->pa_sync_lost.sync_handle) {
@@ -540,6 +560,12 @@ static void pa_sync_lost(esp_ble_audio_gap_app_event_t *event)
stream_count = 0;
stream_count_started = 0;
stream_count_stopped = 0;
/* Clear the stale scan-delegator state pointer captured by
* recv_state_updated_cb() while we were synced. Without this,
* ext_scan_recv() would keep rejecting new source ads because
* its gate is `pa_syncing == false && req_recv_state == NULL`.
*/
req_recv_state = NULL;
if (broadcast_sink != NULL) {
esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink);
@@ -6,6 +6,8 @@ CONFIG_BT_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_NVS_PERSIST=y
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
CONFIG_BT_NIMBLE_MAX_CCCDS=20
CONFIG_BT_NIMBLE_ISO=y
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y
@@ -5,57 +5,77 @@
(See the README.md file in the upper level `examples` directory for more information about examples.)
This example demonstrates the **Basic Audio Profile (BAP) Broadcast Source** functionality. It starts extended advertising with the Broadcast Audio service data (UUID and Broadcast ID) and device name, periodic advertising with the Broadcast Audio Source Endpoint (BASE), then starts the BAP Broadcast Source so that BIGInfo and (mock) audio data are sent over the BIG. Sinks such as the [broadcast_sink](../broadcast_sink) example can discover this source, establish periodic sync, and synchronize to the BIG to receive the streams.
## Overview
The implementation uses the NimBLE host stack with ISO and BAP support, ESP-BLE-ISO, and ESP-BLE-AUDIO APIs (BAP broadcast source create/start, stream send, LC3 presets). It is intended for chips that support BLE 5.2 ISO and LE Audio (e.g. ESP32-H4). The source is configured with the LC3 16_2_1 broadcast preset, a hardcoded broadcast ID (`0x123456`), and a hardcoded broadcast code (`1234`) for encrypted broadcasts. These values can be changed by editing the source code constants.
This example acts as a **BAP Broadcast Source**. It creates a BAP broadcast source from the LC3 `16_2_1` broadcast preset, encodes the BASE into the periodic advertising payload, places the Broadcast Audio Announcement Service UUID and a fixed 24-bit Broadcast ID (`0x123456`) in the extended advertising payload, and then starts the BIG so that BIS streams transmit synthetic SDU data on a fixed cadence.
The build runs on top of the NimBLE host stack and the ESP BLE Audio component set. Source-side APIs used: `esp_ble_audio_common_init` / `esp_ble_audio_common_start` (common layer), `esp_ble_audio_bap_broadcast_source_create` / `_get_base` / `_start` (BAP), `esp_ble_audio_bap_broadcast_adv_add` (BAP/ISO glue), and the BAP stream callbacks (`started`, `stopped`, `sent`, `disconnected`). PACS, GAP scanner, and the scan delegator are **not** used on this side. Encryption is enabled because the broadcast code `"1234"` is non-empty; packing is `ESP_BLE_ISO_PACKING_SEQUENTIAL`. Channel allocation per stream is hard-coded as `FRONT_LEFT` for stream 0 and `FRONT_RIGHT` for stream 1.
The TX scheduler is built on `example_audio_tx_scheduler_*` helpers; the source comment notes that ESP timer resolution is not accurate enough for the SDU interval, so a `k_work_delayable`-based scheduler is used instead.
## Requirements
* A board with Bluetooth LE 5.2, ISO, and LE Audio support (e.g. ESP32-H4)
* Optionally, a device running the [broadcast_sink](../broadcast_sink) example to receive and play the broadcast streams
* A board with Bluetooth LE 5.2, ISO, and LE Audio support (e.g. ESP32-H4, ESP32-S31)
* A peer running the [broadcast_sink](../broadcast_sink) example to receive and decode the BIS streams
## How to Use Example
## Configuration
Before project configuration and build, set the correct chip target:
Open menuconfig:
```bash
idf.py set-target esp32h4
idf.py menuconfig
```
### Build and Flash
No build-time options — runtime defaults are baked into source. The device name (`"BAP Broadcast Source"`), broadcast ID (`0x123456`), broadcast code (`"1234"`), advertising/periodic intervals, stream and subgroup counts (via `CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT` / `_SUBGROUP_COUNT` from the BAP component), and LC3 preset (`16_2_1`) are compile-time constants in `main.c`.
Run the following to build, flash and monitor:
### Security & Pairing
The example inherits a Just-Works pairing model (LE Secure Connections, no MITM, no I/O capability) with bonding enabled from the shared init at `../common_components/example_init/ble_audio_example_init.c`; change pairing/IO-cap there if needed.
## Build & Flash
```bash
idf.py set-target esp32h4 # or esp32s31
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, and LE Audio common layer (`esp_ble_audio_common_init`). No PACS or GAP callback needed for the source role.
2. **Broadcast source setup**: Register broadcast source callbacks (started/stopped). Configure subgroups and streams using the selected LC3 preset (codec config, QoS, channel allocation). Create the BAP Broadcast Source (`esp_ble_audio_bap_broadcast_source_create`) with optional encryption and sequential packing. Register stream callbacks (started, stopped, sent) for each stream.
3. **Extended and periodic advertising**: Set extended advertising data: Broadcast Audio UUID + Broadcast ID (static or random) + complete device name. Set periodic advertising data: Broadcast Audio UUID + encoded BASE from the broadcast source. Start periodic advertising then extended advertising.
4. **Start BIG**: Add the advertising set for BIG and start the BAP Broadcast Source (`esp_ble_audio_bap_broadcast_source_start`) with the same adv handle. BIGInfo is sent in the periodic advertising; BIS streams are created.
5. **Stream and send**: When each BIS stream starts, the stream started callback allocates an SDU buffer and starts a periodic TX scheduler based on `k_work_delayable`. The scheduler posts work items to the ISO task at the stream QoS interval to send mock audio data; sent and drift are reported in the stream sent callback. When a stream stops, resources are freed and the scheduler is stopped. Note that the scheduler timer resolution is in milliseconds, which may not match the exact SDU interval for all configurations.
1. `app_main` initializes NVS, calls `bluetooth_init()`, and calls `esp_ble_audio_common_init(NULL)` (no GAP callback for this role).
2. `broadcast_source_setup()` registers `source_started` / `source_stopped` callbacks, populates per-stream channel-allocation BIS metadata (left/right), registers `stream_ops` on each stream, and calls `esp_ble_audio_bap_broadcast_source_create()` with the encrypted preset.
3. `esp_ble_audio_common_start(NULL)` starts the audio stack; each stream's `example_audio_tx_scheduler_init()` is wired with `tx_scheduler_cb`.
4. `ext_adv_start()` configures non-connectable extended adv (1M primary / 2M secondary, 200 ms interval), writes Broadcast Audio Service Data + Broadcast ID + complete name, configures periodic adv (100 ms), writes the BASE returned by `esp_ble_audio_bap_broadcast_source_get_base()`, then starts periodic and extended advertising.
5. `broadcast_start()` calls `esp_ble_audio_bap_broadcast_adv_add()` and `esp_ble_audio_bap_broadcast_source_start()` on the same `ADV_HANDLE`, which kicks off BIGInfo + BIS creation.
6. When each BIS stream goes streaming, `stream_started_cb` allocates an SDU-sized buffer and calls `example_audio_tx_scheduler_start()` at `preset_active.qos.interval`; the scheduler invokes `broadcast_source_tx()`, which fills the buffer with the low byte of `seq_num` and calls `esp_ble_audio_bap_stream_send()`.
7. `stream_sent_cb` forwards completions to `example_audio_tx_scheduler_on_sent()` for drift accounting; `stream_stopped_cb` and `stream_disconnected_cb` stop the scheduler; `source_stopped_cb` frees all per-stream buffers.
## Example Output
## Expected Log
```
I (xxx) BAP_BSRC: Creating broadcast source with 1 subgroups & 2 streams per subgroup
I (xxx) BAP_BSRC: Extended adv instance 0 started
I (xxx) BAP_BSRC: Broadcast source 0x... started
I (xxx) BAP_BSRC: Stream 0x... started
I (xxx) BAP_BSRC: Transmitted 1000 ISO data packets (stream 0x...)
...
I (xxx) BAP_BSRC: Creating broadcast source: ... subgroup(s), ... stream(s)/subgroup
I (xxx) BAP_BSRC: Advertising started (handle 0)
I (xxx) BAP_BSRC: Broadcast source started
I (xxx) BAP_BSRC: [SRC #0] Stream started
I (xxx) BAP_BSRC: [SRC #1] Stream started
```
If the broadcast source stops (e.g. stream stopped):
Periodic TX accounting (emitted by `example_audio_tx_scheduler_on_sent` under the `BAP_BSRC` tag, name `SRC #<idx>`) follows. On teardown:
```
I (xxx) BAP_BSRC: Stream 0x... stopped, reason 0x...
I (xxx) BAP_BSRC: Broadcast source 0x... stopped, reason 0x...
I (xxx) BAP_BSRC: [SRC #0] Stream stopped, reason 0x...
I (xxx) BAP_BSRC: [SRC #0] ISO disconnected, reason 0x...
I (xxx) BAP_BSRC: Broadcast source stopped, reason 0x...
```
## Peer Pairing
Run [broadcast_sink](../broadcast_sink/) on a second board. Expected interaction sequence:
1. Source advertises extended PDU containing the Broadcast Audio Announcement Service UUID, Broadcast ID `0x123456`, and complete name `"BAP Broadcast Source"`; sink scans and matches by name + UUID.
2. Source's periodic advertising carries the encoded BASE; sink establishes PA sync and parses the BASE to recover subgroup count and BIS index bitfield.
3. Source's BIGInfo advertises the BIG as encrypted (broadcast code `"1234"`); sink reports `BIG encrypted`.
4. Source starts the BIG and the two BIS streams (`FRONT_LEFT`, `FRONT_RIGHT`); sink calls `esp_ble_audio_bap_broadcast_sink_sync()` with the chosen BIS bitfield and the matching broadcast code.
5. Source's TX scheduler keeps pushing SDUs at `preset_active.qos.interval`; sink stream `recv` callbacks deliver the data and update RX metrics.
6. Stopping the source (or losing PA sync) triggers `stream_stopped_cb` on the sink, which deletes the broadcast sink and resumes scanning.
@@ -66,24 +66,36 @@ static esp_ble_audio_bap_broadcast_source_t *broadcast_source;
static void broadcast_source_tx(struct broadcast_source_stream *source_stream);
static int stream_index(const esp_ble_audio_bap_stream_t *stream)
{
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
if (&streams[i].stream == stream) {
return (int)i;
}
}
return -1;
}
static void stream_started_cb(esp_ble_audio_bap_stream_t *stream)
{
struct broadcast_source_stream *source_stream = CONTAINER_OF(stream,
struct broadcast_source_stream,
stream);
int idx = stream_index(stream);
esp_err_t err;
ESP_LOGI(TAG, "Stream %p started", stream);
ESP_LOGI(TAG, "[SRC #%d] Stream started", idx);
if (source_stream->stream.qos == NULL || source_stream->stream.qos->sdu == 0) {
ESP_LOGE(TAG, "Invalid stream qos");
ESP_LOGE(TAG, "[SRC #%d] Invalid QoS", idx);
return;
}
if (source_stream->data == NULL) {
source_stream->data = calloc(1, source_stream->stream.qos->sdu);
if (source_stream->data == NULL) {
ESP_LOGE(TAG, "Failed to alloc tx buffer, sdu %u", source_stream->stream.qos->sdu);
ESP_LOGE(TAG, "[SRC #%d] Failed to alloc TX buffer (sdu %u)",
idx, source_stream->stream.qos->sdu);
return;
}
}
@@ -94,7 +106,7 @@ static void stream_started_cb(esp_ble_audio_bap_stream_t *stream)
/* Note: esp timer is not accurate enough */
err = example_audio_tx_scheduler_start(&source_stream->scheduler, preset_active.qos.interval);
if (err) {
ESP_LOGE(TAG, "Failed to start tx scheduler, err %d", err);
ESP_LOGE(TAG, "[SRC #%d] Scheduler start failed, err %d", idx, err);
return;
}
@@ -106,13 +118,14 @@ static void stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint8_t reason
struct broadcast_source_stream *source_stream = CONTAINER_OF(stream,
struct broadcast_source_stream,
stream);
int idx = stream_index(stream);
esp_err_t err;
ESP_LOGI(TAG, "Stream %p stopped, reason 0x%02x", stream, reason);
ESP_LOGI(TAG, "[SRC #%d] Stream stopped, reason 0x%02x", idx, reason);
err = example_audio_tx_scheduler_stop(&source_stream->scheduler);
if (err) {
ESP_LOGE(TAG, "Failed to stop tx scheduler, err %d", err);
ESP_LOGE(TAG, "[SRC #%d] Scheduler stop failed, err %d", idx, err);
}
}
@@ -121,13 +134,14 @@ static void stream_disconnected_cb(esp_ble_audio_bap_stream_t *stream, uint8_t r
struct broadcast_source_stream *source_stream = CONTAINER_OF(stream,
struct broadcast_source_stream,
stream);
int idx = stream_index(stream);
esp_err_t err;
ESP_LOGI(TAG, "Stream %p disconnected, reason 0x%02x", stream, reason);
ESP_LOGI(TAG, "[SRC #%d] ISO disconnected, reason 0x%02x", idx, reason);
err = example_audio_tx_scheduler_stop(&source_stream->scheduler);
if (err) {
ESP_LOGE(TAG, "Failed to stop tx scheduler, err %d", err);
ESP_LOGE(TAG, "[SRC #%d] Scheduler stop failed, err %d", idx, err);
}
}
@@ -136,8 +150,10 @@ static void stream_sent_cb(esp_ble_audio_bap_stream_t *stream, void *user_data)
struct broadcast_source_stream *source_stream = CONTAINER_OF(stream,
struct broadcast_source_stream,
stream);
char name[24];
example_audio_tx_scheduler_on_sent(&source_stream->scheduler, user_data, TAG, "stream", stream);
snprintf(name, sizeof(name), "SRC #%d", stream_index(stream));
example_audio_tx_scheduler_on_sent(&source_stream->scheduler, user_data, TAG, name);
}
static esp_ble_audio_bap_stream_ops_t stream_ops = {
@@ -170,13 +186,16 @@ static void broadcast_source_tx(struct broadcast_source_stream *source_stream)
return;
}
int idx = stream_index(&source_stream->stream);
if (source_stream->stream.qos == NULL || source_stream->stream.qos->sdu == 0) {
ESP_LOGE(TAG, "Invalid stream qos");
ESP_LOGE(TAG, "[SRC #%d] Invalid QoS", idx);
return;
}
if (source_stream->data == NULL) {
ESP_LOGE(TAG, "Tx buffer unavailable, sdu %u", source_stream->stream.qos->sdu);
ESP_LOGE(TAG, "[SRC #%d] TX buffer unavailable (sdu %u)",
idx, source_stream->stream.qos->sdu);
return;
}
@@ -187,8 +206,7 @@ static void broadcast_source_tx(struct broadcast_source_stream *source_stream)
source_stream->stream.qos->sdu,
source_stream->seq_num);
if (err) {
ESP_LOGD(TAG, "Failed to broadcast data on stream %p, err %d",
&source_stream->stream, err);
ESP_LOGD(TAG, "[SRC #%d] send failed, err %d", idx, err);
return;
}
@@ -208,12 +226,12 @@ static void tx_scheduler_cb(void *arg)
static void source_started_cb(esp_ble_audio_bap_broadcast_source_t *source)
{
ESP_LOGI(TAG, "Broadcast source %p started", source);
ESP_LOGI(TAG, "Broadcast source started");
}
static void source_stopped_cb(esp_ble_audio_bap_broadcast_source_t *source, uint8_t reason)
{
ESP_LOGI(TAG, "Broadcast source %p stopped, reason 0x%02x", source, reason);
ESP_LOGI(TAG, "Broadcast source stopped, reason 0x%02x", reason);
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
if (streams[i].data != NULL) {
@@ -275,7 +293,7 @@ static esp_err_t broadcast_source_setup(void)
memcpy(create_param.broadcast_code, LOCAL_BROADCAST_CODE, strlen(LOCAL_BROADCAST_CODE));
}
ESP_LOGI(TAG, "Creating broadcast source with %u subgroups & %u streams per subgroup",
ESP_LOGI(TAG, "Creating broadcast source: %u subgroup(s), %u stream(s)/subgroup",
ARRAY_SIZE(subgroup_param), streams_per_subgroup);
err = esp_ble_audio_bap_broadcast_source_create(&create_param, &broadcast_source);
@@ -453,7 +471,7 @@ static int ext_adv_start(void)
goto end;
}
ESP_LOGI(TAG, "Extended adv instance %u started", ADV_HANDLE);
ESP_LOGI(TAG, "Advertising started (handle %u)", ADV_HANDLE);
end:
if (ext_data) {
@@ -525,7 +543,7 @@ void app_main(void)
tx_scheduler_cb,
&streams[i]);
if (err) {
ESP_LOGE(TAG, "Failed to initialize tx scheduler[%u], err %d", i, err);
ESP_LOGE(TAG, "[SRC #%zu] Scheduler init failed, err %d", i, err);
return;
}
}
@@ -5,60 +5,119 @@
(See the README.md file in the upper level `examples` directory for more information about examples.)
This example demonstrates the **Basic Audio Profile (BAP) Unicast Client** functionality. It scans for a BAP Unicast Server (advertising with the ASCS service data), connects to it, performs GATT service discovery and MTU exchange, then discovers sink and source ASEs, configures streams with an LC3 unicast preset, sets QoS, enables and connects the streams, and establishes unicast audio. For source (TX) streams the client starts the stream and can send audio data; sink streams are started by the server. Run it together with the [unicast_server](../unicast_server) example on another device as the peer.
## Overview
The implementation uses the NimBLE host stack with ISO and BAP support, ESP-BLE-ISO, and ESP-BLE-AUDIO APIs (BAP unicast client discover, config, QoS, enable, connect, start; stream send). It is intended for chips that support BLE 5.2 ISO and LE Audio (e.g. ESP32-H4). The client uses the LC3 16_2_1 unicast preset and initiates pairing after the ACL connection.
This example implements a **BAP Unicast Client** on top of the NimBLE host stack. It scans for a peer advertising the ASCS service UUID with a complete local name of `BAP Unicast Server`, opens an ACL connection, initiates pairing, exchanges MTU, and starts GATT service discovery.
Once the peer is reachable, the client uses the BAP unicast client API (`esp_ble_audio_bap_unicast_client_discover`, `esp_ble_audio_bap_stream_config`, `esp_ble_audio_bap_unicast_group_create`, `esp_ble_audio_bap_stream_qos`, `esp_ble_audio_bap_stream_enable`, `esp_ble_audio_bap_stream_connect`, `esp_ble_audio_bap_stream_start`) to discover sink and source ASEs, configure each with the LC3 `16_2_1` unicast preset, build a unicast group, set QoS, enable, and ISO-connect the streams. Source (TX-from-server) streams are started by this client; sink (TX-to-server) streams are started by the server.
The TX path in `stream_tx.c` registers each newly started sink stream with `example_audio_tx_scheduler` and feeds it SDU-sized buffers driven by `esp_ble_audio_bap_stream_send`. The PACS callbacks (`pac_record`, `endpoint`, `location`, `available_contexts`) print the discovered peer capabilities.
## Requirements
* A board with Bluetooth LE 5.2, ISO, and LE Audio support (e.g. ESP32-H4)
* Another device running the [unicast_server](../unicast_server) example, which advertises with ASCS and acts as BAP Unicast Server
* A board with BLE 5.2, ISO, and LE Audio 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:
Open menuconfig:
```bash
idf.py menuconfig
```
No build-time options — runtime defaults are baked into source.
### Security & Pairing
This example inherits a Just-Works pairing model (LE Secure Connections, no MITM) with bonding enabled from the shared init at `../common_components/example_init/ble_audio_example_init.c`.
## 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, and LE Audio common layer (`esp_ble_audio_common_init`) with GAP and GATT callbacks. Register stream ops for sink and source streams, register BAP unicast client callbacks, then start the audio stack.
2. **Scanning**: Start passive extended scanning. On scan report, parse advertising for connectable packets containing the ASCS (Audio Stream Control Service) service data; when found, initiate an ACL connection to that device.
3. **Connection and security**: On ACL connection, initiate pairing. After MTU exchange and GATT service discovery complete, start BAP discovery (sinks first, then sources).
4. **Discovery**: Discover sink ASEs then source ASEs on the server. For each discovered endpoint, the endpoint callback records the EP; when source discovery completes, start configuring streams.
5. **Configure, QoS, enable**: For each sink and source stream, configure with the LC3 16_2_1 codec config. When all streams are configured, create a unicast group, set QoS for the group, then enable each stream with metadata. When all streams are enabled, connect each stream.
6. **Connect and start**: When each stream is connected, connect the next; when all are connected, start the source (TX) streams. Sink streams are started by the server. When a TX stream is started, it is registered for sending; the example can then send audio on that stream.
1. `app_main` initializes NVS, NimBLE, and the LE Audio common layer, registers stream ops on every sink/source slot, registers the BAP unicast client callbacks, sets the device name `BAP Unicast Client`, and calls `ext_scan_start`.
2. Passive extended scanning runs forever; `scan_data_cb` matches the complete local name `BAP Unicast Server` and parses the ASCS service-data AD for announcement type plus a 32-bit available-contexts field.
3. On a connectable match `conn_create` cancels scanning and issues `ble_gap_connect`; the resulting `ESP_BLE_AUDIO_GAP_EVENT_ACL_CONNECT` saves the handle and calls `ble_gap_security_initiate`.
4. After the security-change event the client triggers `ble_gattc_exchange_mtu`; `gatt_mtu_change` then calls `esp_ble_audio_gattc_disc_start`, and once both MTU and discovery are reported `discover_sinks` runs.
5. `discover_cb` chains sink discovery into `discover_sources`; `endpoint_cb` records each EP into the next free `sinks[]`/`sources[]` slot, and source completion kicks `configure_stream`, which configures every EP with `unicast_preset.codec_cfg`.
6. After the last `config_cb`, `create_group` builds `pair_params` (sinks first, then sources) with `ESP_BLE_ISO_PACKING_SEQUENTIAL` and calls `esp_ble_audio_bap_unicast_group_create`, then `set_stream_qos`.
7. When `is_all_stream_qos_set` returns true `enable_stream` walks every stream; on the final `enable_cb`, `connect_stream` ISO-connects each pair (sink first if available); when all are up `start_stream` starts only source streams. Sink streams are started by the server, and `stream_started_cb` calls `stream_tx_register` to begin TX.
## Example Output
## Expected Log
TAG: `BAP_UCL`.
Startup and scanning:
```
I (xxx) BAP_UCL: Unicast server found, type ...
I (xxx) BAP_UCL: connection established, status 0
I (xxx) BAP_UCL: Sink #0: ep 0x...
I (xxx) BAP_UCL: Source #0: ep 0x...
I (xxx) BAP_UCL: Discover sinks complete
I (xxx) BAP_UCL: Discover sources complete
I (xxx) BAP_UCL: Sink stream[0] configured
I (xxx) BAP_UCL: Source stream[0] configured
I (xxx) BAP_UCL: Stream 0x... QoS set
I (xxx) BAP_UCL: Stream 0x... enabled
I (xxx) BAP_UCL: Stream 0x... connected
I (xxx) BAP_UCL: Stream 0x... started
...
I (xxx) BAP_UCL: Scanning for unicast server...
I (xxx) BAP_UCL: Unicast server found: type ... contexts 0x...
```
If connection or discovery fails, relevant error messages are logged.
ACL, security, MTU, discovery:
```
I (xxx) BAP_UCL: Connected: handle ... role ... peer ...
I (xxx) BAP_UCL: Security: handle ... level ... bonded ...
I (xxx) BAP_UCL: MTU updated: handle ... mtu ...
I (xxx) BAP_UCL: Service discovery started: handle ...
I (xxx) BAP_UCL: Service discovery complete: handle ...
```
ASE discovery:
```
I (xxx) BAP_UCL: [SNK] Discovering...
I (xxx) BAP_UCL: [SNK #...] Endpoint discovered
I (xxx) BAP_UCL: [SNK] Discovery complete
I (xxx) BAP_UCL: [SRC] Discovering...
I (xxx) BAP_UCL: [SRC #...] Endpoint discovered
I (xxx) BAP_UCL: [SRC] Discovery complete
```
Config / group / QoS / enable / connect / start:
```
I (xxx) BAP_UCL: [SNK #...] Config ACK'd
I (xxx) BAP_UCL: [SNK #...] Stream configured, QoS preference:
I (xxx) BAP_UCL: [SNK #...] Configured
I (xxx) BAP_UCL: Pair[...]: SNK=... SRC=...
I (xxx) BAP_UCL: Unicast group created (... pair(s))
I (xxx) BAP_UCL: [SNK #...] QoS ACK'd
I (xxx) BAP_UCL: [SNK #...] QoS set
I (xxx) BAP_UCL: [SNK #...] Enable ACK'd
I (xxx) BAP_UCL: [SNK #...] Enabled
I (xxx) BAP_UCL: Pair[...] connecting (SNK first)
I (xxx) BAP_UCL: [SNK #...] ISO connected (pair[...])
I (xxx) BAP_UCL: [SNK #...] Connected
I (xxx) BAP_UCL: [SRC] Starting all source streams
I (xxx) BAP_UCL: [SRC #...] Stream started
I (xxx) BAP_UCL: [SNK #...] Stream started
I (xxx) BAP_UCL: [SNK #...] Started (SDU ..., interval ... us)
```
Disconnect:
```
I (xxx) BAP_UCL: [SNK #...] ISO disconnected, reason 0x...
I (xxx) BAP_UCL: Disconnected: handle ... reason 0x...
```
## Peer Pairing
Run [unicast_server](../unicast_server/) on a second board.
1. Server boots and starts connectable extended advertising with the ASCS UUID and `BAP Unicast Server` name.
2. Client boots and scans; on matching the name and ASCS service data it cancels scanning and issues `ble_gap_connect`.
3. After ACL is up the client initiates pairing/security; both sides log `Connected:` and the client logs `Security:`.
4. Client exchanges MTU and runs GATT discovery, then discovers sink ASEs followed by source ASEs on the server.
5. Client drives ASCS Config -> QoS -> Enable -> ISO Connect on every ASE; the server returns its QoS preference and accepts each operation.
6. Client starts source streams; server auto-starts each sink stream from `stream_enabled_cb`, after which the client's TX scheduler feeds SDUs and the server's `stream_recv_cb` accumulates RX metrics.
@@ -8,6 +8,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include "nvs_flash.h"
#include "esp_system.h"
@@ -168,6 +169,11 @@ static void reset_stream_state(void)
reset_stream_pair_state();
for (size_t i = 0; i < ARRAY_SIZE(sinks); i++) {
esp_ble_audio_bap_stream_ops_t *ops = sinks[i].stream.ops;
memset(&sinks[i].stream, 0, sizeof(sinks[i].stream));
sinks[i].stream.ops = ops;
sinks[i].configured = ASCS_RSP_NONE;
sinks[i].qos_set = ASCS_RSP_NONE;
sinks[i].enabled = ASCS_RSP_NONE;
@@ -176,6 +182,11 @@ static void reset_stream_state(void)
}
for (size_t i = 0; i < ARRAY_SIZE(sources); i++) {
esp_ble_audio_bap_stream_ops_t *ops = sources[i].stream.ops;
memset(&sources[i].stream, 0, sizeof(sources[i].stream));
sources[i].stream.ops = ops;
sources[i].configured = ASCS_RSP_NONE;
sources[i].qos_set = ASCS_RSP_NONE;
sources[i].enabled = ASCS_RSP_NONE;
@@ -199,17 +210,42 @@ static bool stream_is_sink(const esp_ble_audio_bap_stream_t *stream)
return false;
}
static const char *dir_str(esp_ble_audio_dir_t dir)
{
return dir == ESP_BLE_AUDIO_DIR_SINK ? "SNK" : "SRC";
}
static const char *stream_dir_str(const esp_ble_audio_bap_stream_t *stream)
{
return stream_is_sink(stream) ? "SNK" : "SRC";
}
static int stream_index(const esp_ble_audio_bap_stream_t *stream)
{
for (size_t i = 0; i < ARRAY_SIZE(sinks); i++) {
if (&sinks[i].stream == stream) {
return (int)i;
}
}
for (size_t i = 0; i < ARRAY_SIZE(sources); i++) {
if (&sources[i].stream == stream) {
return (int)i;
}
}
return -1;
}
static int discover_sinks(void)
{
int err;
err = esp_ble_audio_bap_unicast_client_discover(default_conn_handle, ESP_BLE_AUDIO_DIR_SINK);
if (err) {
ESP_LOGE(TAG, "Failed to discover sinks, err %d", err);
ESP_LOGE(TAG, "[SNK] Discover failed, err %d", err);
return err;
}
ESP_LOGI(TAG, "Discovering sinks");
ESP_LOGI(TAG, "[SNK] Discovering...");
return 0;
}
@@ -220,10 +256,12 @@ static int discover_sources(void)
err = esp_ble_audio_bap_unicast_client_discover(default_conn_handle, ESP_BLE_AUDIO_DIR_SOURCE);
if (err) {
ESP_LOGE(TAG, "Failed to discover sources, err %d", err);
ESP_LOGE(TAG, "[SRC] Discover failed, err %d", err);
return err;
}
ESP_LOGI(TAG, "[SRC] Discovering...");
return 0;
}
@@ -240,7 +278,7 @@ static bool configure_stream(void)
&sinks[i].stream, sinks[i].ep,
&unicast_preset.codec_cfg);
if (err) {
ESP_LOGE(TAG, "Failed to configure sink stream[%u], err %d", i, err);
ESP_LOGE(TAG, "[SNK #%zu] Config request failed, err %d", i, err);
sinks[i].configured = ASCS_RSP_FAILURE;
/* Try to configure the next sink stream */
@@ -259,7 +297,7 @@ static bool configure_stream(void)
&sources[i].stream, sources[i].ep,
&unicast_preset.codec_cfg);
if (err) {
ESP_LOGE(TAG, "Failed to configure source stream[%u], err %d", i, err);
ESP_LOGE(TAG, "[SRC #%zu] Config request failed, err %d", i, err);
sources[i].configured = ASCS_RSP_FAILURE;
/* Try to configure the next source stream */
@@ -278,11 +316,11 @@ static void stream_configured(esp_ble_audio_bap_stream_t *stream, bool success)
if (sinks[i].configured == ASCS_RSP_NONE &&
&sinks[i].stream == stream) {
if (success) {
ESP_LOGI(TAG, "Sink stream[%u] configure succeeded", i);
ESP_LOGI(TAG, "[SNK #%zu] Configured", i);
sinks[i].configured = ASCS_RSP_SUCCESS;
configured_sink_stream_count++;
} else {
ESP_LOGE(TAG, "Sink stream[%u] configure failed", i);
ESP_LOGE(TAG, "[SNK #%zu] Configure failed", i);
sinks[i].configured = ASCS_RSP_FAILURE;
}
return;
@@ -293,18 +331,18 @@ static void stream_configured(esp_ble_audio_bap_stream_t *stream, bool success)
if (sources[i].configured == ASCS_RSP_NONE &&
&sources[i].stream == stream) {
if (success) {
ESP_LOGI(TAG, "Source stream[%u] configure succeeded", i);
ESP_LOGI(TAG, "[SRC #%zu] Configured", i);
sources[i].configured = ASCS_RSP_SUCCESS;
configured_source_stream_count++;
} else {
ESP_LOGE(TAG, "Source stream[%u] configure failed", i);
ESP_LOGE(TAG, "[SRC #%zu] Configure failed", i);
sources[i].configured = ASCS_RSP_FAILURE;
}
return;
}
}
ESP_LOGW(TAG, "No matching stream found for configured stream %p", stream);
ESP_LOGW(TAG, "No matching stream for configured callback");
}
static int create_group(void)
@@ -335,8 +373,9 @@ static int create_group(void)
pair_params[i].rx_param = NULL;
}
ESP_LOGI(TAG, "Stream pair[%u]: sink %p source %p", i,
stream_pairs[i].sink_stream, stream_pairs[i].source_stream);
ESP_LOGI(TAG, "Pair[%zu]: SNK=%s SRC=%s", i,
stream_pairs[i].sink_stream ? "yes" : "-",
stream_pairs[i].source_stream ? "yes" : "-");
}
group_param.params = pair_params;
@@ -349,7 +388,7 @@ static int create_group(void)
return err;
}
ESP_LOGI(TAG, "Created unicast group");
ESP_LOGI(TAG, "Unicast group created (%zu pair(s))", params_count);
return 0;
}
@@ -374,10 +413,10 @@ static void stream_qos_set(esp_ble_audio_bap_stream_t *stream, bool success)
sinks[i].qos_set == ASCS_RSP_NONE &&
&sinks[i].stream == stream) {
if (success) {
ESP_LOGI(TAG, "Sink stream[%u] QoS set", i);
ESP_LOGI(TAG, "[SNK #%zu] QoS set", i);
sinks[i].qos_set = ASCS_RSP_SUCCESS;
} else {
ESP_LOGE(TAG, "Sink stream[%u] QoS set failed", i);
ESP_LOGE(TAG, "[SNK #%zu] QoS set failed", i);
sinks[i].qos_set = ASCS_RSP_FAILURE;
}
return;
@@ -389,17 +428,17 @@ static void stream_qos_set(esp_ble_audio_bap_stream_t *stream, bool success)
sources[i].qos_set == ASCS_RSP_NONE &&
&sources[i].stream == stream) {
if (success) {
ESP_LOGI(TAG, "Source stream[%u] QoS set", i);
ESP_LOGI(TAG, "[SRC #%zu] QoS set", i);
sources[i].qos_set = ASCS_RSP_SUCCESS;
} else {
ESP_LOGE(TAG, "Source stream[%u] QoS set failed", i);
ESP_LOGE(TAG, "[SRC #%zu] QoS set failed", i);
sources[i].qos_set = ASCS_RSP_FAILURE;
}
return;
}
}
ESP_LOGW(TAG, "No matching stream found for QoS set stream %p", stream);
ESP_LOGW(TAG, "No matching stream for QoS callback");
}
static bool is_all_stream_qos_set(void)
@@ -433,7 +472,7 @@ static bool enable_stream(void)
unicast_preset.codec_cfg.meta,
unicast_preset.codec_cfg.meta_len);
if (err) {
ESP_LOGE(TAG, "Failed to enable sink stream[%u], err %d", i, err);
ESP_LOGE(TAG, "[SNK #%zu] Enable request failed, err %d", i, err);
sinks[i].enabled = ASCS_RSP_FAILURE;
/* Try to enable the next sink stream */
@@ -452,7 +491,7 @@ static bool enable_stream(void)
unicast_preset.codec_cfg.meta,
unicast_preset.codec_cfg.meta_len);
if (err) {
ESP_LOGE(TAG, "Failed to enable source stream[%u], err %d", i, err);
ESP_LOGE(TAG, "[SRC #%zu] Enable request failed, err %d", i, err);
sources[i].enabled = ASCS_RSP_FAILURE;
/* Try to enable the next source stream */
@@ -474,10 +513,10 @@ static void stream_enabled(esp_ble_audio_bap_stream_t *stream, bool success)
sinks[i].enabled == ASCS_RSP_NONE &&
&sinks[i].stream == stream) {
if (success) {
ESP_LOGI(TAG, "Sink stream[%u] enabled", i);
ESP_LOGI(TAG, "[SNK #%zu] Enabled", i);
sinks[i].enabled = ASCS_RSP_SUCCESS;
} else {
ESP_LOGE(TAG, "Sink stream[%u] enabled failed", i);
ESP_LOGE(TAG, "[SNK #%zu] Enable failed", i);
sinks[i].enabled = ASCS_RSP_FAILURE;
}
return;
@@ -490,17 +529,17 @@ static void stream_enabled(esp_ble_audio_bap_stream_t *stream, bool success)
sources[i].enabled == ASCS_RSP_NONE &&
&sources[i].stream == stream) {
if (success) {
ESP_LOGI(TAG, "Source stream[%u] enabled", i);
ESP_LOGI(TAG, "[SRC #%zu] Enabled", i);
sources[i].enabled = ASCS_RSP_SUCCESS;
} else {
ESP_LOGE(TAG, "Source stream[%u] enabled failed", i);
ESP_LOGE(TAG, "[SRC #%zu] Enable failed", i);
sources[i].enabled = ASCS_RSP_FAILURE;
}
return;
}
}
ESP_LOGW(TAG, "No matching stream found for enabled stream %p", stream);
ESP_LOGW(TAG, "No matching stream for enable callback");
}
static int connect_stream(void)
@@ -546,7 +585,7 @@ static int connect_stream(void)
err = esp_ble_audio_bap_stream_connect(stream);
if (err) {
ESP_LOGE(TAG, "Failed to connect stream pair[%u], err %d", i, err);
ESP_LOGE(TAG, "Pair[%zu] connect failed, err %d", i, err);
if (sink_ready) {
sinks[sink_index].connected = ASCS_RSP_FAILURE;
@@ -564,7 +603,8 @@ static int connect_stream(void)
sources[source_index].connected = ASCS_RSP_CONNECT;
}
ESP_LOGI(TAG, "Connecting stream pair[%u], sink %p source %p", i, sink_stream, source_stream);
ESP_LOGI(TAG, "Pair[%zu] connecting (%s)", i,
sink_ready ? "SNK first" : "SRC first");
return false;
}
@@ -603,12 +643,11 @@ static void stream_connected(esp_ble_audio_bap_stream_t *stream, bool success)
sinks[i].connected == ASCS_RSP_CONNECT &&
&sinks[i].stream == stream) {
if (success) {
ESP_LOGI(TAG, "Sink stream[%u] connect succeeded", i);
sinks[i].connected = state;
ESP_LOGI(TAG, "[SNK #%zu] Connected", i);
} else {
ESP_LOGE(TAG, "Sink stream[%u] connect failed", i);
sinks[i].connected = state;
ESP_LOGE(TAG, "[SNK #%zu] Connect failed", i);
}
sinks[i].connected = state;
return;
}
}
@@ -620,17 +659,16 @@ static void stream_connected(esp_ble_audio_bap_stream_t *stream, bool success)
sources[i].connected == ASCS_RSP_CONNECT &&
&sources[i].stream == stream) {
if (success) {
ESP_LOGI(TAG, "Source stream[%u] connect succeeded", i);
sources[i].connected = state;
ESP_LOGI(TAG, "[SRC #%zu] Connected", i);
} else {
ESP_LOGE(TAG, "Source stream[%u] connect failed", i);
sources[i].connected = state;
ESP_LOGE(TAG, "[SRC #%zu] Connect failed", i);
}
sources[i].connected = state;
return;
}
}
ESP_LOGW(TAG, "No matching stream found for connected stream %p", stream);
ESP_LOGW(TAG, "No matching stream for connected callback");
}
static int start_stream(void)
@@ -639,7 +677,7 @@ static int start_stream(void)
/* Note: sink streams are started by the unicast server */
ESP_LOGI(TAG, "Starting source streams");
ESP_LOGI(TAG, "[SRC] Starting all source streams");
for (size_t i = 0; i < ARRAY_SIZE(sources); i++) {
if (sources[i].configured == ASCS_RSP_SUCCESS &&
@@ -648,7 +686,7 @@ static int start_stream(void)
sources[i].connected == ASCS_RSP_SUCCESS) {
err = esp_ble_audio_bap_stream_start(&sources[i].stream);
if (err) {
ESP_LOGE(TAG, "Failed to start stream[%u], err %d", i, err);
ESP_LOGE(TAG, "[SRC #%zu] Start failed, err %d", i, err);
}
}
}
@@ -659,19 +697,21 @@ static int start_stream(void)
static void stream_configured_cb(esp_ble_audio_bap_stream_t *stream,
const esp_ble_audio_bap_qos_cfg_pref_t *pref)
{
ESP_LOGI(TAG, "Stream %p configured", stream);
example_print_qos_pref(pref);
ESP_LOGI(TAG, "[%s #%d] Stream configured, QoS preference:",
stream_dir_str(stream), stream_index(stream));
example_print_qos_pref(TAG, pref);
}
static void stream_qos_set_cb(esp_ble_audio_bap_stream_t *stream)
{
ESP_LOGI(TAG, "Stream %p QoS set", stream);
/* QoS set is also reported by qos_cb; skip the duplicate log here. */
(void)stream;
}
static void stream_enabled_cb(esp_ble_audio_bap_stream_t *stream)
{
ESP_LOGI(TAG, "Stream %p enabled", stream);
/* Enabled is also reported by enable_cb; skip the duplicate log here. */
(void)stream;
}
static void stream_connected_cb(esp_ble_audio_bap_stream_t *stream)
@@ -680,23 +720,17 @@ static void stream_connected_cb(esp_ble_audio_bap_stream_t *stream)
int pair_index;
bool ret;
ESP_LOGI(TAG, "Stream %p connected", stream);
pair_index = get_stream_pair_index(stream);
paired_stream = get_paired_stream(stream);
if (pair_index >= 0) {
ESP_LOGI(TAG, "Stream %p in pair[%d], paired stream %p, same_pair %u",
stream, pair_index, paired_stream,
streams_are_same_pair(stream, paired_stream));
} else {
ESP_LOGW(TAG, "Failed to find stream pair for stream %p", stream);
}
ESP_LOGI(TAG, "[%s #%d] ISO connected (pair[%d])",
stream_dir_str(stream), stream_index(stream), pair_index);
stream_connected(stream, true);
if (streams_are_same_pair(stream, paired_stream) &&
stream_is_connecting(paired_stream)) {
ESP_LOGI(TAG, "Waiting paired stream %p connected before next pair", paired_stream);
/* Wait for the paired stream before moving on */
return;
}
@@ -710,7 +744,8 @@ static void stream_connected_cb(esp_ble_audio_bap_stream_t *stream)
static void stream_disconnected_cb(esp_ble_audio_bap_stream_t *stream, uint8_t reason)
{
ESP_LOGI(TAG, "Stream %p disconnected, reason 0x%02x", stream, reason);
ESP_LOGI(TAG, "[%s #%d] ISO disconnected, reason 0x%02x",
stream_dir_str(stream), stream_index(stream), reason);
/* Reset the connected state to allow reconnection */
for (size_t i = 0; i < ARRAY_SIZE(sinks); i++) {
@@ -728,36 +763,41 @@ static void stream_disconnected_cb(esp_ble_audio_bap_stream_t *stream, uint8_t r
}
}
ESP_LOGW(TAG, "No matching stream found for disconnected stream %p", stream);
ESP_LOGW(TAG, "No matching stream for disconnected callback");
}
static void stream_started_cb(esp_ble_audio_bap_stream_t *stream)
{
int err;
ESP_LOGI(TAG, "Stream %p started", stream);
ESP_LOGI(TAG, "[%s #%d] Stream started",
stream_dir_str(stream), stream_index(stream));
if (stream_is_sink(stream)) {
err = stream_tx_register(stream);
if (err) {
ESP_LOGE(TAG, "Failed to register stream %p for TX, err %d", stream, err);
ESP_LOGE(TAG, "[SNK #%d] Failed to register TX, err %d",
stream_index(stream), err);
}
}
}
static void stream_metadata_updated_cb(esp_ble_audio_bap_stream_t *stream)
{
ESP_LOGI(TAG, "Stream %p metadata updated", stream);
ESP_LOGI(TAG, "[%s #%d] Metadata updated",
stream_dir_str(stream), stream_index(stream));
}
static void stream_disabled_cb(esp_ble_audio_bap_stream_t *stream)
{
ESP_LOGI(TAG, "Stream %p disabled", stream);
ESP_LOGI(TAG, "[%s #%d] Stream disabled",
stream_dir_str(stream), stream_index(stream));
}
static void stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint8_t reason)
{
ESP_LOGI(TAG, "Stream %p stopped, reason 0x%02x", stream, reason);
ESP_LOGI(TAG, "[%s #%d] Stream stopped, reason 0x%02x",
stream_dir_str(stream), stream_index(stream), reason);
if (stream_is_sink(stream)) {
(void)stream_tx_unregister(stream);
@@ -766,7 +806,8 @@ static void stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint8_t reason
static void stream_released_cb(esp_ble_audio_bap_stream_t *stream)
{
ESP_LOGI(TAG, "Stream %p released", stream);
ESP_LOGI(TAG, "[%s #%d] Stream released",
stream_dir_str(stream), stream_index(stream));
}
static void stream_sent_cb(esp_ble_audio_bap_stream_t *stream, void *user_data)
@@ -792,14 +833,28 @@ static void location_cb(esp_ble_conn_t *conn,
esp_ble_audio_dir_t dir,
esp_ble_audio_location_t loc)
{
ESP_LOGI(TAG, "Location, dir %u loc 0x%08lx", dir, loc);
ESP_LOGI(TAG, "[%s] Location: 0x%08lx", dir_str(dir), loc);
}
static void available_contexts_cb(esp_ble_conn_t *conn,
esp_ble_audio_context_t snk_ctx,
esp_ble_audio_context_t src_ctx)
{
ESP_LOGI(TAG, "Available contexts, sink 0x%04x source 0x%04x", snk_ctx, src_ctx);
ESP_LOGI(TAG, "Available contexts: SNK=0x%04x SRC=0x%04x", snk_ctx, src_ctx);
}
static void log_rsp(const char *op, esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_code_t rsp_code,
esp_ble_audio_bap_ascs_reason_t reason)
{
if (rsp_code == ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_SUCCESS) {
ESP_LOGI(TAG, "[%s #%d] %s ACK'd",
stream_dir_str(stream), stream_index(stream), op);
} else {
ESP_LOGW(TAG, "[%s #%d] %s rejected (rsp 0x%02x reason 0x%02x)",
stream_dir_str(stream), stream_index(stream), op,
rsp_code, reason);
}
}
static void config_cb(esp_ble_audio_bap_stream_t *stream,
@@ -810,7 +865,7 @@ static void config_cb(esp_ble_audio_bap_stream_t *stream,
bool ret;
int err;
ESP_LOGI(TAG, "Config, stream %p rsp_code 0x%02x reason 0x%02x", stream, rsp_code, reason);
log_rsp("Config", stream, rsp_code, reason);
stream_configured(stream, rsp_code == ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_SUCCESS);
@@ -853,7 +908,7 @@ static void qos_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_code_t rsp_code,
esp_ble_audio_bap_ascs_reason_t reason)
{
ESP_LOGI(TAG, "Qos, stream %p rsp_code 0x%02x reason 0x%02x", stream, rsp_code, reason);
log_rsp("QoS", stream, rsp_code, reason);
stream_qos_set(stream, rsp_code == ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_SUCCESS);
@@ -868,7 +923,7 @@ static void enable_cb(esp_ble_audio_bap_stream_t *stream,
{
bool ret;
ESP_LOGI(TAG, "Enable, stream %p rsp_code 0x%02x reason 0x%02x", stream, rsp_code, reason);
log_rsp("Enable", stream, rsp_code, reason);
stream_enabled(stream, rsp_code == ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_SUCCESS);
@@ -884,35 +939,35 @@ static void start_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_code_t rsp_code,
esp_ble_audio_bap_ascs_reason_t reason)
{
ESP_LOGI(TAG, "Start, stream %p rsp_code 0x%02x reason 0x%02x", stream, rsp_code, reason);
log_rsp("Start", stream, rsp_code, reason);
}
static void stop_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_code_t rsp_code,
esp_ble_audio_bap_ascs_reason_t reason)
{
ESP_LOGI(TAG, "Stop, stream %p rsp_code 0x%02x reason 0x%02x", stream, rsp_code, reason);
log_rsp("Stop", stream, rsp_code, reason);
}
static void disable_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_code_t rsp_code,
esp_ble_audio_bap_ascs_reason_t reason)
{
ESP_LOGI(TAG, "Disable, stream %p rsp_code 0x%02x reason 0x%02x", stream, rsp_code, reason);
log_rsp("Disable", stream, rsp_code, reason);
}
static void metadata_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_code_t rsp_code,
esp_ble_audio_bap_ascs_reason_t reason)
{
ESP_LOGI(TAG, "Metadata, stream %p rsp_code 0x%02x reason 0x%02x", stream, rsp_code, reason);
log_rsp("Metadata", stream, rsp_code, reason);
}
static void release_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_code_t rsp_code,
esp_ble_audio_bap_ascs_reason_t reason)
{
ESP_LOGI(TAG, "Release, stream %p rsp_code 0x%02x reason 0x%02x", stream, rsp_code, reason);
log_rsp("Release", stream, rsp_code, reason);
}
static void pac_record_cb(esp_ble_conn_t *conn,
@@ -923,7 +978,7 @@ static void pac_record_cb(esp_ble_conn_t *conn,
return;
}
example_print_codec_cap(codec_cap);
example_print_codec_cap(TAG, codec_cap);
}
static void endpoint_cb(esp_ble_conn_t *conn,
@@ -933,23 +988,23 @@ static void endpoint_cb(esp_ble_conn_t *conn,
if (dir == ESP_BLE_AUDIO_DIR_SOURCE) {
for (size_t i = 0; i < ARRAY_SIZE(sources); i++) {
if (sources[i].ep == NULL) {
ESP_LOGI(TAG, "Source #%u: ep %p", i, ep);
ESP_LOGI(TAG, "[SRC #%zu] Endpoint discovered", i);
sources[i].ep = ep;
return;
}
}
ESP_LOGW(TAG, "Could not add source ep");
ESP_LOGW(TAG, "[SRC] Endpoint dropped (no free slot)");
} else if (dir == ESP_BLE_AUDIO_DIR_SINK) {
for (size_t i = 0; i < ARRAY_SIZE(sinks); i++) {
if (sinks[i].ep == NULL) {
ESP_LOGI(TAG, "Sink #%u: ep %p", i, ep);
ESP_LOGI(TAG, "[SNK #%zu] Endpoint discovered", i);
sinks[i].ep = ep;
return;
}
}
ESP_LOGW(TAG, "Could not add sink ep");
ESP_LOGW(TAG, "[SNK] Endpoint dropped (no free slot)");
}
}
@@ -961,17 +1016,17 @@ static void discover_cb(esp_ble_conn_t *conn, int err, esp_ble_audio_dir_t dir)
if (dir == ESP_BLE_AUDIO_DIR_SINK) {
if (err) {
ESP_LOGE(TAG, "Discovery sinks failed, err %d", err);
ESP_LOGE(TAG, "[SNK] Discovery failed, err %d", err);
} else {
ESP_LOGI(TAG, "Discover sinks complete");
ESP_LOGI(TAG, "[SNK] Discovery complete");
discover_sources();
}
} else if (dir == ESP_BLE_AUDIO_DIR_SOURCE) {
if (err) {
ESP_LOGE(TAG, "Discovery sources failed, err %d", err);
ESP_LOGE(TAG, "[SRC] Discovery failed, err %d", err);
} else {
ESP_LOGI(TAG, "Discover sources complete");
ESP_LOGI(TAG, "[SRC] Discovery complete");
configure_stream();
}
@@ -1120,8 +1175,8 @@ static void ext_scan_recv(esp_ble_audio_gap_app_event_t *event)
return;
}
ESP_LOGI(TAG, "Unicast server found, type %u, contexts 0x%08x, meta_len %u",
adv.announcement_type, adv.audio_contexts, adv.meta_len);
ESP_LOGI(TAG, "Unicast server found: type %u contexts 0x%08x",
adv.announcement_type, adv.audio_contexts);
dst.type = event->ext_scan_recv.addr.type;
memcpy(dst.val, event->ext_scan_recv.addr.val, sizeof(dst.val));
@@ -1142,30 +1197,28 @@ static void acl_connect(esp_ble_audio_gap_app_event_t *event)
int err;
if (event->acl_connect.status) {
ESP_LOGE(TAG, "connection failed, status %d", event->acl_connect.status);
ESP_LOGE(TAG, "Connection failed, status %d", event->acl_connect.status);
return;
}
ESP_LOGI(TAG, "Conn established:");
ESP_LOGI(TAG, "conn_handle 0x%04x status 0x%02x role %u peer %02x:%02x:%02x:%02x:%02x:%02x",
event->acl_connect.conn_handle, event->acl_connect.status,
event->acl_connect.role, event->acl_connect.dst.val[5],
event->acl_connect.dst.val[4], event->acl_connect.dst.val[3],
event->acl_connect.dst.val[2], event->acl_connect.dst.val[1],
event->acl_connect.dst.val[0]);
ESP_LOGI(TAG, "Connected: handle %u role %u peer %02x:%02x:%02x:%02x:%02x:%02x",
event->acl_connect.conn_handle, event->acl_connect.role,
event->acl_connect.dst.val[5], event->acl_connect.dst.val[4],
event->acl_connect.dst.val[3], event->acl_connect.dst.val[2],
event->acl_connect.dst.val[1], event->acl_connect.dst.val[0]);
default_conn_handle = event->acl_connect.conn_handle;
err = pairing_start(event->acl_connect.conn_handle);
if (err) {
ESP_LOGE(TAG, "Failed to initiate security, err %d", err);
return;
}
default_conn_handle = event->acl_connect.conn_handle;
}
static void acl_disconnect(esp_ble_audio_gap_app_event_t *event)
{
ESP_LOGI(TAG, "Conn terminated: conn_handle 0x%04x reason 0x%02x",
ESP_LOGI(TAG, "Disconnected: handle %u reason 0x%02x",
event->acl_disconnect.conn_handle, event->acl_disconnect.reason);
default_conn_handle = CONN_HANDLE_INIT;
@@ -1173,13 +1226,17 @@ static void acl_disconnect(esp_ble_audio_gap_app_event_t *event)
disc_cancelled = false;
mtu_exchanged = false;
reset_stream_state();
/* Delete the group before reset_stream_state() memsets the streams:
* the library walks stream->iso / stream->group to return ISO slots
* to the pool, and zeroing them first leaks the slot.
*/
if (unicast_group != NULL) {
esp_ble_audio_bap_unicast_group_delete(unicast_group);
unicast_group = NULL;
}
reset_stream_state();
ext_scan_start();
}
@@ -1188,19 +1245,14 @@ static void security_change(esp_ble_iso_gap_app_event_t *event)
int err;
if (event->security_change.status) {
ESP_LOGE(TAG, "security change failed, status %d", event->security_change.status);
ESP_LOGE(TAG, "Security change failed, status %d", event->security_change.status);
return;
}
ESP_LOGI(TAG, "Security change:");
ESP_LOGI(TAG, "conn_handle 0x%04x status 0x%02x role %u sec_level %u bonded %u "
"peer %02x:%02x:%02x:%02x:%02x:%02x",
event->security_change.conn_handle, event->security_change.status,
event->security_change.role, event->security_change.sec_level,
event->security_change.bonded, event->security_change.dst.val[5],
event->security_change.dst.val[4], event->security_change.dst.val[3],
event->security_change.dst.val[2], event->security_change.dst.val[1],
event->security_change.dst.val[0]);
ESP_LOGI(TAG, "Security: handle %u level %u bonded %u",
event->security_change.conn_handle,
event->security_change.sec_level,
event->security_change.bonded);
err = exchange_mtu(event->security_change.conn_handle);
if (err) {
@@ -1233,22 +1285,23 @@ static void gatt_mtu_change(esp_ble_audio_gatt_app_event_t *event)
{
int err;
ESP_LOGI(TAG, "gatt mtu change, conn_handle %u, mtu %u",
ESP_LOGI(TAG, "MTU updated: handle %u mtu %u",
event->gatt_mtu_change.conn_handle, event->gatt_mtu_change.mtu);
if (event->gatt_mtu_change.mtu < ESP_BLE_AUDIO_ATT_MTU_MIN) {
ESP_LOGW(TAG, "Invalid new mtu %u, shall be at least %u",
ESP_LOGW(TAG, "MTU %u below minimum %u",
event->gatt_mtu_change.mtu, ESP_BLE_AUDIO_ATT_MTU_MIN);
return;
}
err = esp_ble_audio_gattc_disc_start(event->gatt_mtu_change.conn_handle);
if (err) {
ESP_LOGE(TAG, "Failed to start svc disc, err %d", err);
ESP_LOGE(TAG, "Failed to start service discovery, err %d", err);
return;
}
ESP_LOGI(TAG, "Start discovering gatt services");
ESP_LOGI(TAG, "Service discovery started: handle %u",
event->gatt_mtu_change.conn_handle);
/* Note:
* MTU exchanged event may arrived after discover completed event.
@@ -1262,14 +1315,15 @@ static void gatt_mtu_change(esp_ble_audio_gatt_app_event_t *event)
static void gattc_disc_cmpl(esp_ble_audio_gatt_app_event_t *event)
{
ESP_LOGI(TAG, "gattc disc cmpl, status %u, conn_handle %u",
event->gattc_disc_cmpl.status, event->gattc_disc_cmpl.conn_handle);
if (event->gattc_disc_cmpl.status) {
ESP_LOGE(TAG, "gattc disc failed, status %u", event->gattc_disc_cmpl.status);
ESP_LOGE(TAG, "Service discovery failed, status %u",
event->gattc_disc_cmpl.status);
return;
}
ESP_LOGI(TAG, "Service discovery complete: handle %u",
event->gattc_disc_cmpl.conn_handle);
/* Note:
* Discover completed event may arrived before MTU exchanged event.
*/
@@ -1317,7 +1371,7 @@ static int ext_scan_start(void)
return err;
}
ESP_LOGI(TAG, "Extended scan started");
ESP_LOGI(TAG, "Scanning for unicast server...");
return 0;
}
@@ -8,6 +8,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -46,13 +47,13 @@ static void stream_tx_send(struct tx_stream *tx_stream)
}
if (tx_stream->stream->qos == NULL || tx_stream->stream->qos->sdu == 0) {
ESP_LOGE(TAG, "Invalid stream qos");
ESP_LOGE(TAG, "[SNK TX] Invalid QoS");
return;
}
if (tx_stream->data == NULL) {
ESP_LOGE(TAG, "TX buffer unavailable, SDU %u (stream %p)",
tx_stream->stream->qos->sdu, tx_stream->stream);
ESP_LOGE(TAG, "[SNK TX] Buffer unavailable (SDU %u)",
tx_stream->stream->qos->sdu);
return;
}
@@ -64,8 +65,7 @@ static void stream_tx_send(struct tx_stream *tx_stream)
tx_stream->stream->qos->sdu,
tx_stream->seq_num);
if (err) {
ESP_LOGD(TAG, "Failed to transmit data on stream %p, err %d",
tx_stream->stream, err);
ESP_LOGD(TAG, "[SNK TX] send failed, err %d", err);
return;
}
@@ -76,8 +76,11 @@ void stream_tx_sent(esp_ble_audio_bap_stream_t *stream, void *user_data)
{
for (size_t i = 0; i < ARRAY_SIZE(tx_streams); i++) {
if (tx_streams[i].stream == stream) {
char name[24];
snprintf(name, sizeof(name), "SNK #%zu", i);
example_audio_tx_scheduler_on_sent(&tx_streams[i].scheduler, user_data,
TAG, "stream", stream);
TAG, name);
break;
}
}
@@ -100,18 +103,16 @@ int stream_tx_register(esp_ble_audio_bap_stream_t *stream)
for (size_t i = 0; i < ARRAY_SIZE(tx_streams); i++) {
if (tx_streams[i].stream == NULL) {
ESP_LOGI(TAG, "Registered stream %p for TX", stream);
if (stream->qos == NULL || stream->qos->sdu == 0) {
ESP_LOGE(TAG, "Invalid stream qos");
ESP_LOGE(TAG, "[SNK #%zu] Invalid QoS", i);
return -EINVAL;
}
if (tx_streams[i].data == NULL) {
tx_streams[i].data = calloc(1, stream->qos->sdu);
if (tx_streams[i].data == NULL) {
ESP_LOGE(TAG, "Failed to alloc TX buffer, SDU %u (stream %p)",
stream->qos->sdu, stream);
ESP_LOGE(TAG, "[SNK #%zu] Failed to alloc buffer (SDU %u)",
i, stream->qos->sdu);
return -ENOMEM;
}
}
@@ -123,17 +124,20 @@ int stream_tx_register(esp_ble_audio_bap_stream_t *stream)
err = example_audio_tx_scheduler_start(&tx_streams[i].scheduler,
stream->qos->interval);
if (err) {
ESP_LOGE(TAG, "Failed to start tx scheduler, err %d", err);
ESP_LOGE(TAG, "[SNK #%zu] Scheduler start failed, err %d", i, err);
tx_streams[i].stream = NULL;
return err;
}
ESP_LOGI(TAG, "[SNK #%zu] Started (SDU %u, interval %u us)",
i, stream->qos->sdu, stream->qos->interval);
stream_tx_send(&tx_streams[i]);
return 0;
}
}
ESP_LOGE(TAG, "No free TX stream slot");
ESP_LOGE(TAG, "[SNK] No free TX slot");
return -ENOMEM;
}
@@ -148,11 +152,9 @@ int stream_tx_unregister(esp_ble_audio_bap_stream_t *stream)
for (size_t i = 0; i < ARRAY_SIZE(tx_streams); i++) {
if (tx_streams[i].stream == stream) {
ESP_LOGI(TAG, "Unregistered stream %p for TX", stream);
err = example_audio_tx_scheduler_stop(&tx_streams[i].scheduler);
if (err) {
ESP_LOGE(TAG, "Failed to stop tx scheduler, err %d", err);
ESP_LOGE(TAG, "[SNK #%zu] Scheduler stop failed, err %d", i, err);
return err;
}
@@ -162,6 +164,7 @@ int stream_tx_unregister(esp_ble_audio_bap_stream_t *stream)
tx_streams[i].data = NULL;
}
ESP_LOGI(TAG, "[SNK #%zu] Stopped", i);
return 0;
}
}
@@ -178,7 +181,7 @@ void stream_tx_init(void)
tx_scheduler_cb,
&tx_streams[i]);
if (err) {
ESP_LOGE(TAG, "Failed to init tx scheduler[%u], err %d", i, err);
ESP_LOGE(TAG, "[SNK #%zu] Scheduler init failed, err %d", i, err);
return;
}
}
@@ -6,6 +6,7 @@ CONFIG_BT_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_NVS_PERSIST=y
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
CONFIG_BT_NIMBLE_MAX_CCCDS=20
CONFIG_BT_NIMBLE_ISO=y
@@ -5,57 +5,105 @@
(See the README.md file in the upper level `examples` directory for more information about examples.)
This example demonstrates the **Basic Audio Profile (BAP) Unicast Server** functionality. It starts connectable extended advertising with the ASCS (Audio Stream Control Service) UUID and service data (targeted announcement, sink and source audio contexts), then waits for a BAP Unicast Client to connect. After connection, the server responds to client requests: codec configuration, QoS, enable, and start. The server starts sink (RX) streams when the client enables them; source (TX) streams are started by the client. The server receives audio on sink streams and can send on source streams. Run it together with the [unicast_client](../unicast_client) example on another device as the client.
## Overview
The implementation uses the NimBLE host stack with ISO and BAP support, ESP-BLE-ISO, and ESP-BLE-AUDIO APIs (PACS, BAP unicast server register and callbacks, stream start, stream recv). It is intended for chips that support BLE 5.2 ISO and LE Audio (e.g. ESP32-H4). PACS advertises sink and source capabilities with LC3 (any frequency, 10 ms frame, up to 2 channels, various contexts). The number of sink and source ASEs is set via `CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT` and `CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT`.
This example implements a **BAP Unicast Server** on top of the NimBLE host stack. It registers PACS sink and source capabilities (LC3, any frequency, 10 ms frame duration, 2-channel support, 40-120 octets per frame, up to 2 frames per SDU) and a hard-coded location/context set (front-left + front-right; UNSPECIFIED | CONVERSATIONAL | MEDIA), then starts connectable extended advertising on 1M/2M PHY whose payload carries the Flags AD, the ASCS UUID, the targeted unicast announcement service data with sink/source contexts, and the device name `BAP Unicast Server`.
The unicast server callbacks (`config`, `reconfig`, `qos`, `enable`, `start`, `metadata`, `disable`, `stop`, `release`) react to ASCS PDUs from a peer client. `config_cb` allocates a free `sink_streams[]`/`source_streams[]` slot and returns a fixed `qos_pref` (unframed PDUs, 2M PHY preference, RTN 2, 10 ms max latency, 20-40 ms presentation delay). `enable_cb` decodes the codec config to sample rate, frame duration, and frame-blocks-per-SDU before accepting the request. Reconfig is rejected unconditionally.
The stream ops handle ISO data: when the client enables a sink ASE, `stream_enabled_cb` calls `esp_ble_audio_bap_stream_start` so the server starts sink streams locally (the client starts source streams). Inbound ISO SDUs flow through `stream_recv_cb` into `example_audio_rx_metrics_on_recv`, which logs RX metrics periodically.
## Requirements
* A board with Bluetooth LE 5.2, ISO, and LE Audio support (e.g. ESP32-H4)
* Another device running the [unicast_client](../unicast_client) example, which scans for ASCS and connects to this server
* A board with BLE 5.2, ISO, and LE Audio 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:
Open menuconfig:
```bash
idf.py menuconfig
```
No build-time options — runtime defaults are baked into source.
### Security & Pairing
This example inherits a Just-Works pairing model (LE Secure Connections, no MITM) with bonding enabled from the shared init at `../common_components/example_init/ble_audio_example_init.c`.
## 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, and LE Audio common layer (`esp_ble_audio_common_init`) with GAP and GATT callbacks. Register PACS (sink and source PAC and location), register the BAP unicast server (sink and source ASE counts), register unicast server callbacks (config, reconfig, QoS, enable, start, metadata, disable, stop, release), register PACS capabilities (LC3 sink and source), register stream callbacks for sink and source streams, set PACS location and supported/available contexts, then start the audio stack and set the device name.
2. **Advertising**: Start connectable extended advertising with flags, ASCS UUID, ASCS service data (targeted, sink/source contexts), and device name `bap_unicast_server`.
3. **Client connection**: When a client connects, ACL and optional security change are handled. On MTU exchange the server may start GATT service discovery (as needed for the stack).
4. **ASCS operations**: The client discovers ASEs, then sends codec config, QoS, enable, and start. The server allocates a stream per config (sink or source), returns QoS preference, validates enable (codec params), and on start resets stream counters. For sink streams, when the client enables the stream the server calls `esp_ble_audio_bap_stream_start`; for source streams the client starts them.
5. **Streaming**: When a sink stream is started, received ISO SDUs are delivered in the stream recv callback; the example counts valid, error, and lost packets and logs periodically. Source streams can send audio when started by the client.
1. `app_main` initializes NVS, NimBLE, and the LE Audio common layer, then calls `esp_ble_audio_pacs_register` with `snk_pac/snk_loc/src_pac/src_loc` all true.
2. It registers the unicast server (`esp_ble_audio_bap_unicast_server_register` with `CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT` / `CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT`) and its callback table, registers sink and source PACS capabilities, and attaches `stream_ops` to every preallocated stream slot.
3. PACS location is set per direction (`SINK_LOCATION` / `SOURCE_LOCATION`) and supported plus available contexts are pushed for sink and source.
4. `esp_ble_audio_common_start` and `ble_svc_gap_device_name_set("BAP Unicast Server")` run, then `ext_adv_start` builds `ext_adv_data` with Flags, ASCS UUID, ASCS service data (targeted announcement + contexts), and the device name; it configures and starts the extended advertising set on `ADV_HANDLE` 0.
5. On `ESP_BLE_AUDIO_GAP_EVENT_ACL_CONNECT` the connection is logged; after the peer client triggers MTU exchange the server logs the new MTU and calls `esp_ble_audio_gattc_disc_start`.
6. ASCS callbacks fire as the client drives the state machine: `config_cb` allocates a stream and returns the QoS preference, `qos_cb` records `max_sdu`, `enable_cb` validates the codec, `metadata_cb` parses metadata, and `disable`/`stop`/`release` log their requests.
7. `stream_enabled_cb` auto-starts sink streams server-side; `stream_started_cb` resets TX or RX counters depending on direction; `stream_recv_cb` updates per-stream RX metrics on every ISO SDU.
8. On disconnect `acl_disconnect` logs the reason and restarts `ext_adv_start`.
## Example Output
## Expected Log
TAG: `BAP_USR`.
Startup and advertising:
```
I (xxx) BAP_USR: Extended adv instance 0 started
I (xxx) BAP_USR: connection established, status 0
I (xxx) BAP_USR: ASE Codec Config: conn 0x... ep 0x... dir ...
I (xxx) BAP_USR: Stream 0x... enabled
I (xxx) BAP_USR: Stream 0x... started
I (xxx) BAP_USR: Received 1000(1000/0/0) ISO data packets (stream 0x...)
...
I (xxx) BAP_USR: PACS: locations and contexts set
I (xxx) BAP_USR: Advertising started (handle 0)
```
If the client disconnects:
Client connects:
```
I (xxx) BAP_USR: connection disconnected, reason 0x...
I (xxx) BAP_USR: Connected: handle ... role ... peer ...
I (xxx) BAP_USR: MTU updated: handle ... mtu ...
I (xxx) BAP_USR: Service discovery started: handle ...
I (xxx) BAP_USR: Service discovery complete: handle ... status ...
```
ASCS operations:
```
I (xxx) BAP_USR: [SNK #...] Config request
I (xxx) BAP_USR: [SNK #...] QoS request:
I (xxx) BAP_USR: [SNK #...] Enable request: ... Hz, ... us, ... block(s)/SDU
I (xxx) BAP_USR: [SNK #...] Stream enabled
I (xxx) BAP_USR: [SNK #...] Stream started
I (xxx) BAP_USR: [SRC #...] Config request
I (xxx) BAP_USR: [SRC #...] QoS request:
I (xxx) BAP_USR: [SRC #...] Enable request: ... Hz, ... us, ... block(s)/SDU
I (xxx) BAP_USR: [SRC #...] Stream enabled
I (xxx) BAP_USR: [SRC #...] Stream started
```
Teardown:
```
I (xxx) BAP_USR: [SNK #...] Disable request
I (xxx) BAP_USR: [SNK #...] Stop request
I (xxx) BAP_USR: [SNK #...] Stream stopped, reason 0x...
I (xxx) BAP_USR: [SNK #...] Release request
I (xxx) BAP_USR: Disconnected: handle ... reason 0x...
```
## Peer Pairing
Run [unicast_client](../unicast_client/) on a second board.
1. Server boots and starts connectable extended advertising with the ASCS UUID and `BAP Unicast Server` name.
2. Client boots and scans; on matching the name and ASCS service data it cancels scanning and issues `ble_gap_connect`.
3. After ACL is up the client initiates pairing/security; both sides log `Connected:` and the client logs `Security:`.
4. Client exchanges MTU and runs GATT discovery, then discovers sink ASEs followed by source ASEs on the server.
5. Client drives ASCS Config -> QoS -> Enable -> ISO Connect on every ASE; the server returns its QoS preference and accepts each operation.
6. Client starts source streams; server auto-starts each sink stream from `stream_enabled_cb`, after which the client's TX scheduler feeds SDUs and the server's `stream_recv_cb` accumulates RX metrics.
@@ -128,6 +128,31 @@ static esp_ble_audio_dir_t stream_dir(const esp_ble_audio_bap_stream_t *stream)
assert(0 && "Invalid stream");
}
static const char *dir_str(esp_ble_audio_dir_t dir)
{
return dir == ESP_BLE_AUDIO_DIR_SINK ? "SNK" : "SRC";
}
static const char *stream_dir_str(const esp_ble_audio_bap_stream_t *stream)
{
return dir_str(stream_dir(stream));
}
static int stream_index(const esp_ble_audio_bap_stream_t *stream)
{
for (size_t i = 0; i < ARRAY_SIZE(sink_streams); i++) {
if (stream == &sink_streams[i].stream) {
return (int)i;
}
}
for (size_t i = 0; i < ARRAY_SIZE(source_streams); i++) {
if (stream == &source_streams[i].stream) {
return (int)i;
}
}
return -1;
}
static esp_ble_audio_bap_stream_t *stream_alloc(esp_ble_audio_dir_t dir)
{
if (dir == ESP_BLE_AUDIO_DIR_SOURCE) {
@@ -159,20 +184,18 @@ static int config_cb(esp_ble_conn_t *conn,
esp_ble_audio_bap_qos_cfg_pref_t *const pref,
esp_ble_audio_bap_ascs_rsp_t *rsp)
{
ESP_LOGI(TAG, "Config: conn %p ep %p dir %u", conn, ep, dir);
example_print_codec_cfg(codec_cfg);
example_print_codec_cfg(TAG, codec_cfg);
*stream = stream_alloc(dir);
if (*stream == NULL) {
ESP_LOGI(TAG, "No streams available");
ESP_LOGW(TAG, "[%s] No streams available", dir_str(dir));
*rsp = ESP_BLE_AUDIO_BAP_ASCS_RSP(ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_NO_MEM,
ESP_BLE_AUDIO_BAP_ASCS_REASON_NONE);
return -ENOMEM;
}
ESP_LOGI(TAG, "Config stream %p", *stream);
ESP_LOGI(TAG, "[%s #%d] Config request", dir_str(dir), stream_index(*stream));
if (dir == ESP_BLE_AUDIO_DIR_SOURCE) {
configured_source_stream_count++;
@@ -189,9 +212,8 @@ static int reconfig_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_qos_cfg_pref_t *const pref,
esp_ble_audio_bap_ascs_rsp_t *rsp)
{
ESP_LOGI(TAG, "Reconfig: stream %p dir %u", stream, dir);
example_print_codec_cfg(codec_cfg);
ESP_LOGW(TAG, "[%s #%d] Reconfig rejected (not supported)",
dir_str(dir), stream_index(stream));
*rsp = ESP_BLE_AUDIO_BAP_ASCS_RSP(ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED,
ESP_BLE_AUDIO_BAP_ASCS_REASON_NONE);
@@ -204,9 +226,9 @@ static int qos_cb(esp_ble_audio_bap_stream_t *stream,
const esp_ble_audio_bap_qos_cfg_t *qos,
esp_ble_audio_bap_ascs_rsp_t *rsp)
{
ESP_LOGI(TAG, "QoS: stream %p qos %p", stream, qos);
example_print_qos(qos);
ESP_LOGI(TAG, "[%s #%d] QoS request:",
stream_dir_str(stream), stream_index(stream));
example_print_qos(TAG, qos);
for (size_t i = 0; i < ARRAY_SIZE(source_streams); i++) {
if (stream == &source_streams[i].stream) {
@@ -274,8 +296,9 @@ static int enable_cb(esp_ble_audio_bap_stream_t *stream,
return err;
}
ESP_LOGI(TAG, "Enable: stream %p meta_len %u codec %u %u %u",
stream, meta_len, freq_hz, frame_dur_us, frame_blocks);
ESP_LOGI(TAG, "[%s #%d] Enable request: %lu Hz, %lu us, %u block(s)/SDU",
stream_dir_str(stream), stream_index(stream),
freq_hz, frame_dur_us, frame_blocks);
*rsp = ESP_BLE_AUDIO_BAP_ASCS_RSP(ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_SUCCESS,
ESP_BLE_AUDIO_BAP_ASCS_REASON_CODEC_DATA);
@@ -285,8 +308,6 @@ static int enable_cb(esp_ble_audio_bap_stream_t *stream,
static int start_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_t *rsp)
{
ESP_LOGI(TAG, "Start: stream %p", stream);
for (size_t i = 0; i < ARRAY_SIZE(source_streams); i++) {
if (stream == &source_streams[i].stream) {
source_streams[i].seq_num = 0;
@@ -304,7 +325,7 @@ static bool data_cb(uint8_t type, const uint8_t *data,
esp_ble_audio_bap_ascs_rsp_t *rsp = user_data;
if (ESP_BLE_AUDIO_METADATA_TYPE_IS_KNOWN(type) == false) {
ESP_LOGW(TAG, "Invalid metadata type %u or length %u", type, data_len);
ESP_LOGW(TAG, "Invalid metadata type 0x%02x len %u", type, data_len);
*rsp = ESP_BLE_AUDIO_BAP_ASCS_RSP(ESP_BLE_AUDIO_BAP_ASCS_RSP_CODE_METADATA_REJECTED,
type);
@@ -320,7 +341,9 @@ static int metadata_cb(esp_ble_audio_bap_stream_t *stream,
{
esp_err_t err;
ESP_LOGI(TAG, "Metadata: stream %p meta_len %u", stream, meta_len);
ESP_LOGI(TAG, "[%s #%d] Metadata update (%u bytes)",
stream_dir_str(stream), stream_index(stream), meta_len);
example_print_meta(TAG, meta, meta_len);
err = esp_ble_audio_data_parse(meta, meta_len, data_cb, rsp);
if (err) {
@@ -333,21 +356,24 @@ static int metadata_cb(esp_ble_audio_bap_stream_t *stream,
static int disable_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_t *rsp)
{
ESP_LOGI(TAG, "Disable: stream %p", stream);
ESP_LOGI(TAG, "[%s #%d] Disable request",
stream_dir_str(stream), stream_index(stream));
return 0;
}
static int stop_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_t *rsp)
{
ESP_LOGI(TAG, "Stop: stream %p", stream);
ESP_LOGI(TAG, "[%s #%d] Stop request",
stream_dir_str(stream), stream_index(stream));
return 0;
}
static int release_cb(esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_bap_ascs_rsp_t *rsp)
{
ESP_LOGI(TAG, "Release: stream %p", stream);
ESP_LOGI(TAG, "[%s #%d] Release request",
stream_dir_str(stream), stream_index(stream));
if (stream_dir(stream) == ESP_BLE_AUDIO_DIR_SOURCE &&
configured_source_stream_count > 0) {
@@ -373,7 +399,8 @@ static void stream_enabled_cb(esp_ble_audio_bap_stream_t *stream)
{
esp_err_t err;
ESP_LOGI(TAG, "Stream %p enabled", stream);
ESP_LOGI(TAG, "[%s #%d] Stream enabled",
stream_dir_str(stream), stream_index(stream));
/* The unicast server is responsible for starting sink ASEs
* after the client has enabled them.
@@ -381,14 +408,16 @@ static void stream_enabled_cb(esp_ble_audio_bap_stream_t *stream)
if (stream_dir(stream) == ESP_BLE_AUDIO_DIR_SINK) {
err = esp_ble_audio_bap_stream_start(stream);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start stream %p, err %d", stream, err);
ESP_LOGE(TAG, "[SNK #%d] Failed to start stream, err %d",
stream_index(stream), err);
}
}
}
static void stream_started_cb(esp_ble_audio_bap_stream_t *stream)
{
ESP_LOGI(TAG, "Stream %p started", stream);
ESP_LOGI(TAG, "[%s #%d] Stream started",
stream_dir_str(stream), stream_index(stream));
if (stream_dir(stream) == ESP_BLE_AUDIO_DIR_SOURCE) {
struct unicast_server_stream *source_stream =
@@ -408,7 +437,8 @@ static void stream_started_cb(esp_ble_audio_bap_stream_t *stream)
static void stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint8_t reason)
{
ESP_LOGI(TAG, "Stream %p stopped, reason 0x%02x", stream, reason);
ESP_LOGI(TAG, "[%s #%d] Stream stopped, reason 0x%02x",
stream_dir_str(stream), stream_index(stream), reason);
}
static void stream_recv_cb(esp_ble_audio_bap_stream_t *stream,
@@ -418,10 +448,13 @@ static void stream_recv_cb(esp_ble_audio_bap_stream_t *stream,
if (stream_dir(stream) == ESP_BLE_AUDIO_DIR_SINK) {
struct unicast_server_sink_stream *sink_stream =
CONTAINER_OF(stream, struct unicast_server_sink_stream, stream);
char name[24];
size_t idx = sink_stream - sink_streams;
snprintf(name, sizeof(name), "SNK #%zu", idx);
sink_stream->rx_metrics.last_sdu_len = len;
example_audio_rx_metrics_on_recv(info, &sink_stream->rx_metrics,
TAG, "stream", stream);
TAG, name);
}
}
@@ -452,8 +485,6 @@ static int set_pacs_location(void)
}
}
ESP_LOGI(TAG, "Location successfully set");
return 0;
}
@@ -477,8 +508,6 @@ static int set_pacs_supported_contexts(void)
}
}
ESP_LOGI(TAG, "Supported contexts successfully set");
return 0;
}
@@ -502,7 +531,7 @@ static int set_pacs_available_contexts(void)
}
}
ESP_LOGI(TAG, "Available contexts successfully set");
ESP_LOGI(TAG, "PACS: locations and contexts set");
return 0;
}
@@ -591,28 +620,26 @@ static void ext_adv_start(void)
return;
}
ESP_LOGI(TAG, "Extended adv instance %u started", ADV_HANDLE);
ESP_LOGI(TAG, "Advertising started (handle %u)", ADV_HANDLE);
}
static void acl_connect(esp_ble_audio_gap_app_event_t *event)
{
if (event->acl_connect.status) {
ESP_LOGE(TAG, "connection failed, status %d", event->acl_connect.status);
ESP_LOGE(TAG, "Connection failed, status %d", event->acl_connect.status);
return;
}
ESP_LOGI(TAG, "Conn established:");
ESP_LOGI(TAG, "conn_handle 0x%04x status 0x%02x role %u peer %02x:%02x:%02x:%02x:%02x:%02x",
event->acl_connect.conn_handle, event->acl_connect.status,
event->acl_connect.role, event->acl_connect.dst.val[5],
event->acl_connect.dst.val[4], event->acl_connect.dst.val[3],
event->acl_connect.dst.val[2], event->acl_connect.dst.val[1],
event->acl_connect.dst.val[0]);
ESP_LOGI(TAG, "Connected: handle %u role %u peer %02x:%02x:%02x:%02x:%02x:%02x",
event->acl_connect.conn_handle, event->acl_connect.role,
event->acl_connect.dst.val[5], event->acl_connect.dst.val[4],
event->acl_connect.dst.val[3], event->acl_connect.dst.val[2],
event->acl_connect.dst.val[1], event->acl_connect.dst.val[0]);
}
static void acl_disconnect(esp_ble_audio_gap_app_event_t *event)
{
ESP_LOGI(TAG, "Conn terminated: conn_handle 0x%04x reason 0x%02x",
ESP_LOGI(TAG, "Disconnected: handle %u reason 0x%02x",
event->acl_disconnect.conn_handle, event->acl_disconnect.reason);
ext_adv_start();
@@ -636,11 +663,11 @@ static void gatt_mtu_change(esp_ble_audio_gatt_app_event_t *event)
{
int err;
ESP_LOGI(TAG, "gatt mtu change, conn_handle %u, mtu %u",
ESP_LOGI(TAG, "MTU updated: handle %u mtu %u",
event->gatt_mtu_change.conn_handle, event->gatt_mtu_change.mtu);
if (event->gatt_mtu_change.mtu < ESP_BLE_AUDIO_ATT_MTU_MIN) {
ESP_LOGW(TAG, "Invalid new mtu %u, shall be at least %u",
ESP_LOGW(TAG, "MTU %u below minimum %u",
event->gatt_mtu_change.mtu, ESP_BLE_AUDIO_ATT_MTU_MIN);
return;
}
@@ -651,17 +678,19 @@ static void gatt_mtu_change(esp_ble_audio_gatt_app_event_t *event)
*/
err = esp_ble_audio_gattc_disc_start(event->gatt_mtu_change.conn_handle);
if (err) {
ESP_LOGE(TAG, "Failed to start svc disc, err %d", err);
ESP_LOGE(TAG, "Failed to start service discovery, err %d", err);
return;
}
ESP_LOGI(TAG, "Start discovering gatt services");
ESP_LOGI(TAG, "Service discovery started: handle %u",
event->gatt_mtu_change.conn_handle);
}
static void gattc_disc_cmpl(esp_ble_audio_gatt_app_event_t *event)
{
ESP_LOGI(TAG, "gattc disc cmpl, status %u, conn_handle %u",
event->gattc_disc_cmpl.status, event->gattc_disc_cmpl.conn_handle);
ESP_LOGI(TAG, "Service discovery complete: handle %u status %u",
event->gattc_disc_cmpl.conn_handle,
event->gattc_disc_cmpl.status);
}
static void iso_gatt_app_cb(esp_ble_audio_gatt_app_event_t *event)
@@ -6,6 +6,7 @@ CONFIG_BT_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_NVS_PERSIST=y
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
CONFIG_BT_NIMBLE_MAX_CCCDS=20
CONFIG_BT_NIMBLE_ISO=y