| Supported Targets | ESP32-H4 | ESP32-S31 |
|---|
BAP Broadcast Source 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 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 selected BLE host stack (Bluedroid by default; NimBLE via the sdkconfig.defaults.nimble overlay) 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, ESP32-S31)
- A peer running the broadcast_sink example to receive and decode the BIS streams
Configuration
Open menuconfig:
idf.py menuconfig
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.
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(NULL)(no GAP callback for this role).broadcast_source_setup()registerssource_started/source_stoppedcallbacks, populates per-stream channel-allocation BIS metadata (left/right), registersstream_opson each stream, and callsesp_ble_audio_bap_broadcast_source_create()with the encrypted preset.esp_ble_audio_common_start(NULL)starts the audio stack; each stream'sexample_audio_tx_scheduler_init()is wired withtx_scheduler_cb.adv_init()performs host-specific GAP setup (Bluedroid: registers the GAP callback for*_COMPLETE_EVTsemaphore signalling; NimBLE: no-op — adv-instance callback is passed at configure time).ext_adv_start()builds the extended (Broadcast Audio Service Data + Broadcast ID + complete name) and periodic (BASE fromesp_ble_audio_bap_broadcast_source_get_base()) payloads in host-agnostic bytes, then callsadv_start(ext_data, ext_len, per_data, per_len). The host-specific implementation inmain/bluedroid/adv.cormain/nimble/adv.cconfigures non-connectable extended adv (1M primary / 2M secondary, 200 ms interval), configures periodic adv (100 ms), writes both payloads, and starts periodic and extended advertising.broadcast_start()callsesp_ble_audio_bap_broadcast_adv_add()andesp_ble_audio_bap_broadcast_source_start()on the sameADV_HANDLE, which kicks off BIGInfo + BIS creation.- When each BIS stream goes streaming,
stream_started_cballocates an SDU-sized buffer and callsexample_audio_tx_scheduler_start()atpreset_active.qos.interval; the scheduler invokesbroadcast_source_tx(), which fills the buffer with the low byte ofseq_numand callsesp_ble_audio_bap_stream_send(). stream_sent_cbforwards completions toexample_audio_tx_scheduler_on_sent()for drift accounting;stream_stopped_cbandstream_disconnected_cbstop the scheduler;source_stopped_cbfrees all per-stream buffers.
Expected Log
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
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: [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 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.