| Supported Targets | ESP32-H4 | ESP32-S31 |
|---|
BAP Broadcast Sink Example
(See the README.md file in the upper level examples directory for more information about examples.)
Overview
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 selected BLE host stack (Bluedroid by default; NimBLE via the sdkconfig.defaults.nimble overlay) 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, scan_stop() halts the extended scanner — BASE/BIGInfo arrive over the PA channel — and pa_sync_lost() calls scan_start() to re-arm. The host-specific GAP/PA-sync plumbing lives in main/bluedroid/scan.c and main/nimble/scan.c; main.c only sees the host-agnostic interface in scan.h.
Requirements
- A board with Bluetooth LE 5.2, ISO, and LE Audio support (e.g. ESP32-H4, ESP32-S31)
- A peer running the broadcast_source example (or another BAP Broadcast Source advertising the matching name)
Configuration
Open menuconfig:
idf.py menuconfig
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).
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
The base sdkconfig.defaults defaults to the Bluedroid host; idf.py automatically merges the per-target overlay (sdkconfig.defaults.$IDF_TARGET). To build with NimBLE host instead, layer sdkconfig.defaults.nimble on top via -DSDKCONFIG_DEFAULTS.
Bluedroid host (default)
idf.py set-target esp32h4
idf.py -p PORT flash monitor
NimBLE host
idf.py set-target esp32h4
idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.esp32h4;sdkconfig.defaults.nimble" -p PORT flash monitor
For esp32s31, replace the chip overlay accordingly.
(Exit serial monitor with Ctrl-].)
Example Flow
app_maininitializes NVS, callsbluetooth_init(), and callsesp_ble_audio_common_init(&info)withinfo.gap_cb = iso_gap_app_cb.- PACS is registered (
snk_pac+snk_loc), each stream'sopsfield is wired tostream_ops, and the LC3 sink capability is registered viaesp_ble_audio_pacs_cap_register(ESP_BLE_AUDIO_DIR_SINK, ...). - 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, thenesp_ble_audio_common_start(NULL)runs. scan_init()performs host-specific GAP setup (Bluedroid: registers the GAP callback for*_COMPLETE_EVTsemaphore signalling + postsEXT_ADV_REPORT/PERIODIC_ADV_SYNC_ESTAB/PERIODIC_ADV_REPORT/PERIODIC_ADV_SYNC_LOSTto the audio engine; NimBLE: no-op — the scan-instance callback is passed atble_gap_disc/ble_gap_periodic_adv_sync_createtime).scan_start()then runs passive extended discovery (itvl=window=160). For eachESP_BLE_AUDIO_GAP_EVENT_EXT_SCAN_RECV,data_cbmatches the complete/short/broadcast name AD type against"BAP Broadcast Source"and records the Broadcast ID from the Broadcast Audio Service Data.- On match (and only when not already PA-syncing and no scan-delegator state is pinned),
pa_sync_create(addr_type, addr, sid)invokes the host-specific PA-sync create routine (ble_gap_periodic_adv_sync_create/esp_ble_gap_periodic_adv_create_sync) withskip=0,sync_timeout=10s. ESP_BLE_AUDIO_GAP_EVENT_PA_SYNCclearspa_syncing, cancels discovery, storessync_handle, and callsesp_ble_audio_bap_broadcast_sink_create().base_recv_cbextracts the subgroup count and BIS index bitfield (masked bybis_index_mask); when no Broadcast Assistant is connected,requested_bis_syncdefaults toESP_BLE_AUDIO_BAP_BIS_SYNC_NO_PREF.syncable_cbAND-masks the BASE bitfield with the requested mask, copiesTARGET_BROADCAST_CODEif the BIG is encrypted (unless BASS already supplied one), and callsesp_ble_audio_bap_broadcast_sink_sync()with the chosen mask andstreams_p.stream_started_cbresets per-stream RX metrics and incrementsstream_count_started;stream_recv_cbupdates metrics viaexample_audio_rx_metrics_on_recv(). When all streams have stopped,stream_stopped_cbdeletes the broadcast sink.pa_sync_lost()clears the cachedreq_recv_state, deletes any sink, and restarts the scanner.
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: 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 (.../...)
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: 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 on a second board. Expected interaction sequence:
- 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. - Source's periodic advertising carries the encoded BASE; sink establishes PA sync and parses the BASE to recover subgroup count and BIS index bitfield.
- Source's BIGInfo advertises the BIG as encrypted (broadcast code
"1234"); sink reportsBIG encrypted. - Source starts the BIG and the two BIS streams (
FRONT_LEFT,FRONT_RIGHT); sink callsesp_ble_audio_bap_broadcast_sink_sync()with the chosen BIS bitfield and the matching broadcast code. - Source's TX scheduler keeps pushing SDUs at
preset_active.qos.interval; sink streamrecvcallbacks deliver the data and update RX metrics. - Stopping the source (or losing PA sync) triggers
stream_stopped_cbon the sink, which deletes the broadcast sink and resumes scanning.