| Supported Targets | ESP32-H4 | ESP32-S31 |
|---|
BAP Unicast Client Example
(See the README.md file in the upper level examples directory for more information about examples.)
Overview
This example implements a BAP Unicast Client on top of the selected BLE host stack (Bluedroid by default; NimBLE via the sdkconfig.defaults.nimble overlay). 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 BLE 5.2, ISO, and LE Audio support (e.g. ESP32-H4, ESP32-S31)
- Peer device running the paired example
Configuration
Open menuconfig:
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
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, the BLE host, runsapp_host_initfor host-specific setup (GAP callback, plus a Bluedroid-only GATTC app registration so we have agattc_iffor outgoing aux-open), brings up the LE Audio common layer, registers stream ops on every sink/source slot, registers the BAP unicast client callbacks, callsset_device_name()(usesLOCAL_DEVICE_NAMEfromcentral.h), then callsext_scan_start.- Passive extended scanning runs forever;
scan_data_cbmatches the complete local nameBAP Unicast Serverand parses the ASCS service-data AD for announcement type plus a 32-bit available-contexts field. - On a connectable match
ext_scan_recvcallsext_scan_stopand thenconn_create(NimBLE:ble_gap_connect; Bluedroid:esp_ble_gattc_aux_open); the resultingESP_BLE_AUDIO_GAP_EVENT_ACL_CONNECTsaves the handle and callspairing_start(NimBLE:ble_gap_security_initiate; Bluedroid:esp_ble_set_encryption). - After the security-change event the client triggers
exchange_mtu. On NimBLE this issuesble_gattc_exchange_mtu; on Bluedroid the GATTC adapter auto-configures MTU insideBTA_GATTC_Enh_Open, soexchange_mtudrivesesp_ble_audio_gattc_disc_startdirectly.gatt_mtu_changethen callsesp_ble_audio_gattc_disc_start(the retry is idempotent —-EALREADYis treated as success), and once both MTU and discovery are reporteddiscover_sinksruns. discover_cbchains sink discovery intodiscover_sources;endpoint_cbrecords each EP into the next freesinks[]/sources[]slot, and source completion kicksconfigure_stream, which configures every EP withunicast_preset.codec_cfg.- After the last
config_cb,create_groupbuildspair_params(sinks first, then sources) withESP_BLE_ISO_PACKING_SEQUENTIALand callsesp_ble_audio_bap_unicast_group_create, thenset_stream_qos. - When
is_all_stream_qos_setreturns trueenable_streamwalks every stream; on the finalenable_cb,connect_streamISO-connects each pair (sink first if available); when all are upstart_streamstarts only source streams. Sink streams are started by the server, andstream_started_cbcallsstream_tx_registerto begin TX.
Expected Log
TAG: BAP_UCL.
Startup and scanning:
I (xxx) BAP_UCL: Scanning for unicast server...
I (xxx) BAP_UCL: Unicast server found: type ... contexts 0x...
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 on a second board.
- Server boots and starts connectable extended advertising with the ASCS UUID and
BAP Unicast Servername. - Client boots and scans; on matching the name and ASCS service data it cancels scanning and issues
conn_create(NimBLE:ble_gap_connect; Bluedroid:esp_ble_gattc_aux_open). - After ACL is up the client initiates pairing/security; both sides log
Connected:andSecurity:. - Client exchanges MTU and runs GATT discovery, then discovers sink ASEs followed by source ASEs on the server.
- Client drives ASCS Config -> QoS -> Enable -> ISO Connect on every ASE; the server returns its QoS preference and accepts each operation.
- 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'sstream_recv_cbaccumulates RX metrics.