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

This commit is contained in:
Liu Linyan
2026-08-04 14:07:10 +08:00
committed by Linyan Liu
parent 6d980d673f
commit af9ae1c0aa
113 changed files with 4547 additions and 2104 deletions
@@ -128,6 +128,7 @@ Source (SRC) Assistant (ASS) Acceptor (ACC)
| `FLAG_BROADCAST_CODE_RECEIVED` | `broadcast_code_cb` (BASS Set Broadcast Code); self-scan local code | `broadcast_sink_reset` |
| `FLAG_BROADCAST_SYNC_REQUESTED` | `bis_sync_req_cb` (bitmap ≠ 0); self-scan PA match | `bis_sync_req_cb` (bitmap = 0); `broadcast_sink_reset` |
| `FLAG_BROADCAST_RESYNC_PENDING` | `bis_sync_req_cb` before `_stop` (bitmap change while streaming) | `stream_stopped_cb` after driving the re-sync; `_stop` failure; `broadcast_sink_reset` |
| `FLAG_BROADCAST_STOP_PENDING` | `broadcast_stream_stopped_cb` (last BIS stops) | `stopped_cb`; `broadcast_sink_reset` |
| `FLAG_BROADCAST_SYNCING` | `check_sync_broadcast` after `_sync` returns OK | `stream_started_cb`; `stream_stopped_cb` |
| `FLAG_BROADCAST_SYNCED` | `stream_started_cb` | `stream_stopped_cb` |
@@ -161,9 +162,9 @@ BASE_RECEIVED && BROADCAST_SYNCABLE
+-----+-----+ |
| | BIG drops while PA gone
| v
+---------> stream_stopped_cb + !PA_SYNCED
+--> stream_stopped_cb (set STOP_PENDING, !PA_SYNCED)
|
| _delete + broadcast_sink_reset
| stopped_cb: _delete + broadcast_sink_reset
v
[end]
```
@@ -171,8 +172,8 @@ BASE_RECEIVED && BROADCAST_SYNCABLE
Key invariants:
- **PA loss does NOT tear down a running BIS.** Per BASS § 3.2.1.6 / § 3.2.1.9, `PA_Sync_State` and `BIS_Sync_State` are independent. While BIS is streaming/syncing, `broadcast_pa_lost` only notifies the assistant (`PA_Sync_State = 0x00`) and clears PA-only local state (`sync_handle`, `FLAG_PA_SYNCED`); the BIG keeps running and audio continues to flow.
- **PA loss with BIS idle tears down the sink.** The sink is bound to the now-dead sync handle and its cached BASE / BIGInfo are stale. `broadcast_pa_lost` calls `_delete` and clears `FLAG_BASE_RECEIVED` / `FLAG_BROADCAST_SYNCABLE` / `FLAG_BROADCAST_CODE_REQUIRED`. The assistant's subscription (`requested_bis_sync`, `FLAG_BROADCAST_SYNC_REQUESTED`, `FLAG_BROADCAST_CODE_RECEIVED`) is preserved so the next PA sync re-creates a fresh sink and resumes streaming.
- **Sink deletion happens in `stream_stopped_cb` when both PA and BIS are gone.** Triggers: assistant unsubscribes via `Modify Source bis_sync = 0`, or the broadcaster stops the BIG while PA is already gone.
- **PA loss with BIS idle tears down the sink.** The sink is bound to the now-dead sync handle and its cached BASE / BIGInfo are stale. `broadcast_pa_lost` calls `_delete` (unless `FLAG_BROADCAST_STOP_PENDING` is set, in which case `stopped_cb` deletes after `bis_sync` clear) and clears `FLAG_BASE_RECEIVED` / `FLAG_BROADCAST_SYNCABLE` / `FLAG_BROADCAST_CODE_REQUIRED`. The assistant's subscription (`requested_bis_sync`, `FLAG_BROADCAST_SYNC_REQUESTED`, `FLAG_BROADCAST_CODE_RECEIVED`) is preserved so the next PA sync re-creates a fresh sink and resumes streaming.
- **Sink deletion happens in `stopped_cb` (the `stopped` sink callback) once BASS has cleared `bis_sync`, when PA is gone.** `broadcast_stream_stopped_cb` of the last BIS only sets `FLAG_BROADCAST_STOP_PENDING` (and re-`_sync`s on a pending bitmap change) — it does not delete, because deleting there races `rem_src` while `bis_sync` is still non-zero. Triggers: assistant unsubscribes via `Modify Source bis_sync = 0`, or the broadcaster stops the BIG while PA is already gone.
- `bis_sync_req_cb` going `X → 0` (Assistant pause) only issues `_stop`, never `_delete`. Going `X → Y` (BIS bitmap switch) likewise only `_stop`s; the next `check_sync_broadcast` (called from `stream_stopped_cb` when PA still synced) re-`_sync`s the same object.
- `pa_sync_term_req_cb` issues the HCI Periodic Advertising Terminate Sync but does **not** clear `broadcast_sink.sync_handle`. Cleanup runs from `BLE_GAP_EVENT_PERIODIC_SYNC_LOST` → `broadcast_pa_lost`. Resetting the handle early would make that gate miss.
@@ -181,17 +182,17 @@ Key invariants:
| Event | Action |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| **PA lost while BIS active** (broadcaster moved mid-stream) | Set `PA_Sync_State = 0x00`; clear PA-only state (`sync_handle`, `FLAG_PA_SYNCED`). Sink + BIS untouched; BIG keeps running. |
| **PA lost while BIS idle** (Assistant `PA_Sync = 0`, or PA dropped after Assistant paused BIS) | Set `PA_Sync_State = 0x00` (skipped if BASS already updated it in-place); clear PA-only state; `_delete` the sink and clear `FLAG_BASE_RECEIVED` / `FLAG_BROADCAST_SYNCABLE` / `FLAG_BROADCAST_CODE_REQUIRED`. The next PA sync starts from a clean sink and lets the lib redeliver BASE / BIGInfo. |
| **PA lost while BIS idle** (Assistant `PA_Sync = 0`, or PA dropped after Assistant paused BIS) | Set `PA_Sync_State = 0x00` (skipped if BASS already updated it in-place); clear PA-only state; unless `FLAG_BROADCAST_STOP_PENDING` is set (BIG teardown in flight — `stopped_cb` deletes after `bis_sync` clear), `_delete` the sink and clear `FLAG_BASE_RECEIVED` / `FLAG_BROADCAST_SYNCABLE` / `FLAG_BROADCAST_CODE_REQUIRED`. The next PA sync starts from a clean sink and lets the lib redeliver BASE / BIGInfo. |
| **Modify Source `bis_sync = 0`** (PA still synced) | `bis_sync_req_cb` clears `FLAG_BROADCAST_SYNC_REQUESTED` then `_stop`s the BIG. Sink retained. |
| **Modify Source bitmap change** (PA still synced, streaming) | Update `requested_bis_sync` + `FLAG_BROADCAST_SYNC_REQUESTED` + set `FLAG_BROADCAST_RESYNC_PENDING`, then `_stop`. `stream_stopped_cb` clears the flag and re-`_sync`s with the new bitmap. |
| **BIG drops while PA still synced** (e.g. broadcaster pause) | `stream_stopped_cb` clears SYNCED/SYNCING and exposes the loss via `BIS_Sync_State`. `FLAG_BROADCAST_RESYNC_PENDING` is not set, so no auto-retry — per BASS § 3.2.1.9 the assistant drives recovery via Modify Source. |
| **BIG drops after PA lost** (broadcaster turned off) | `stream_stopped_cb` of the last active stream sees `!PA_SYNCED` → `_delete` + `broadcast_sink_reset`. Multi-BIS: earlier callbacks just decrement `active_streams` so `_delete` is not called while the sink is still in use. |
| **BIG drops after PA lost** (broadcaster turned off) | `broadcast_stream_stopped_cb` of the last active stream sees `!PA_SYNCED` and sets `FLAG_BROADCAST_STOP_PENDING`; `stopped_cb` then runs `_delete` + `broadcast_sink_reset` once BASS has cleared `bis_sync`. Multi-BIS: earlier callbacks just decrement `active_streams` so the flag is not set while the sink is still in use. |
| **Assistant Remove Source** | Spec allows only when BIS not synced; lib handles, app sees no special event. |
Two recurring patterns that drive the above behavior:
- **Update local state before calling `_stop`/`_delete`.** The lib may fire `stream_stopped_cb` synchronously from within `_stop`, so the callback must see the post-stop state. Applies in `bis_sync_req_cb` (updates `requested_bis_sync` + flag before `_stop`) and `broadcast_pa_lost` (no longer calls `_stop`).
- **Sink lifetime is BIS-driven, not PA-driven.** Sink is created on first PA sync and deleted only when the BIG itself stops and PA is also gone. This matches BASS spec's independent PA/BIS state model.
- **Sink lifetime is BIS-driven, not PA-driven.** Sink is created on first PA sync and deleted (from `stopped_cb`, after BASS clears `bis_sync`) only when the BIG itself stops and PA is also gone. This matches BASS spec's independent PA/BIS state model.
### Multi-BIS (stereo) configuration
@@ -290,6 +291,7 @@ On PA sync loss:
```
I (xxx) CAP_ACC: [SNK #0] Stream stopped, reason 0x...
I (xxx) CAP_ACC: Broadcast sink stopped, reason 0x...
I (xxx) CAP_ACC: PA sync lost: sync_handle ... reason 0x...
I (xxx) CAP_ACC: Scanning for broadcast source...
```
@@ -29,6 +29,11 @@ enum broadcast_flag {
FLAG_BROADCAST_SYNCING,
FLAG_BROADCAST_SYNCED,
FLAG_BROADCAST_RESYNC_PENDING,
/* Set when the last BIS stream stops; cleared in stopped_cb.
* Bridges the window where EPs are IDLE but big_stopped has not yet cleared
* BASS bis_sync — pa_lost must not rem_src/delete during that window.
*/
FLAG_BROADCAST_STOP_PENDING,
FLAG_BASE_RECEIVED,
FLAG_PA_SYNCING,
FLAG_PA_SYNCED,
@@ -201,6 +206,7 @@ static void broadcast_sink_reset(void)
flag_clear(FLAG_BROADCAST_CODE_RECEIVED);
flag_clear(FLAG_BROADCAST_SYNC_REQUESTED);
flag_clear(FLAG_BROADCAST_RESYNC_PENDING);
flag_clear(FLAG_BROADCAST_STOP_PENDING);
flag_clear(FLAG_SCANNING);
#if CONFIG_EXAMPLE_SCAN_SELF
@@ -330,8 +336,6 @@ static void broadcast_stream_started_cb(esp_ble_audio_bap_stream_t *stream)
static void broadcast_stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint8_t reason)
{
esp_err_t err;
ESP_LOGI(TAG, "[SNK #%u] Stream stopped, reason 0x%02x",
broadcast_stream_idx(stream), reason);
@@ -349,21 +353,14 @@ static void broadcast_stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint
flag_clear(FLAG_BROADCAST_SYNCING);
flag_clear(FLAG_BROADCAST_SYNCED);
/* Mark BIG teardown in progress until stopped_cb runs
* (after update_recv_state_big_cleared). Do not delete here — that
* races rem_src while bis_sync is still non-zero.
*/
flag_set(FLAG_BROADCAST_STOP_PENDING);
if (flag_test(FLAG_PA_SYNCED) == false) {
/* Both PA and BIS are gone — no path to recover BIGInfo, so the
* sink can no longer drive a new BIG sync. Delete it and clear all
* state.
*/
if (broadcast_sink.sink) {
err = esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink.sink);
if (err) {
ESP_LOGE(TAG, "Failed to delete broadcast sink, err %d", err);
return;
}
}
broadcast_sink_reset();
/* Both PA and BIS are gone — stopped_cb deletes. */
return;
}
@@ -378,6 +375,35 @@ static void broadcast_stream_stopped_cb(esp_ble_audio_bap_stream_t *stream, uint
}
}
static void stopped_cb(esp_ble_audio_bap_broadcast_sink_t *sink,
uint8_t reason)
{
esp_err_t err;
ESP_LOGI(TAG, "Broadcast sink stopped, reason 0x%02x", reason);
flag_clear(FLAG_BROADCAST_STOP_PENDING);
/* Keep the sink when PA is still synced (assistant pause, BIS bitmap
* resync, or spontaneous BIG drop with PA alive).
*/
if (flag_test(FLAG_PA_SYNCED)) {
return;
}
if (broadcast_sink.sink == NULL) {
return;
}
err = esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink.sink);
if (err) {
ESP_LOGE(TAG, "Failed to delete broadcast sink, err %d", err);
return;
}
broadcast_sink_reset();
}
static void broadcast_stream_recv_cb(esp_ble_audio_bap_stream_t *stream,
const esp_ble_iso_recv_info_t *info,
const uint8_t *data, uint16_t len)
@@ -740,14 +766,15 @@ void broadcast_pa_lost(uint16_t sync_handle)
flag_clear(FLAG_PA_SYNCED);
flag_clear(FLAG_PA_SYNCING);
/* BIS still active → BIG keeps running per § 3.2.1.9, leave the sink;
* stream_stopped_cb will tear it down when BIS eventually stops.
/* BIS still active or BIG teardown pending → leave the sink;
* stopped_cb will tear it down when bis_sync is clear.
* Otherwise the sink is bound to a dead PA handle and its cached
* BASE / BIGInfo are stale: delete the sink and clear the PA-derived
* flags so the next PA sync creates a fresh sink and lets the lib
* redeliver BASE / BIGInfo.
*/
if (flag_test(FLAG_BROADCAST_SYNCING) || flag_test(FLAG_BROADCAST_SYNCED)) {
if (flag_test(FLAG_BROADCAST_SYNCING) || flag_test(FLAG_BROADCAST_SYNCED) ||
flag_test(FLAG_BROADCAST_STOP_PENDING)) {
return;
}
@@ -789,6 +816,7 @@ int cap_acceptor_broadcast_init(void)
static esp_ble_audio_bap_broadcast_sink_cb_t broadcast_sink_cbs = {
.base_recv = base_recv_cb,
.syncable = syncable_cb,
.stopped = stopped_cb,
};
static esp_ble_audio_bap_stream_ops_t broadcast_stream_ops = {
.started = broadcast_stream_started_cb,