| Supported Targets | ESP32-H4 | ESP32-S31 |
|---|
BAP Unicast Server Example
(See the README.md file in the upper level examples directory for more information about examples.)
Overview
This example implements a BAP Unicast Server on top of the selected BLE host stack (Bluedroid by default; NimBLE via the sdkconfig.defaults.nimble overlay). 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 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, NimBLE, and the LE Audio common layer, then callsesp_ble_audio_pacs_registerwithsnk_pac/snk_loc/src_pac/src_locall true.- It registers the unicast server (
esp_ble_audio_bap_unicast_server_registerwithCONFIG_BT_ASCS_MAX_ASE_SNK_COUNT/CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT) and its callback table, registers sink and source PACS capabilities, and attachesstream_opsto every preallocated stream slot. - PACS location is set per direction (
SINK_LOCATION/SOURCE_LOCATION) and supported plus available contexts are pushed for sink and source. esp_ble_audio_common_startruns, thenadv_init("BAP Unicast Server")performs host-specific setup (Bluedroid: create completion sem + register GAP cb +esp_ble_gap_set_device_name; NimBLE:ble_svc_gap_device_name_set).ext_adv_startthen buildsext_adv_datawith Flags, ASCS UUID, ASCS service data (targeted announcement + contexts), and the device name in host-agnostic bytes, and callsadv_start(ext_adv_data, sizeof(ext_adv_data)). The host-specific implementation inmain/bluedroid/adv.cormain/nimble/adv.cconfigures and starts the connectable extended advertising set onADV_HANDLE0.- On
ESP_BLE_AUDIO_GAP_EVENT_ACL_CONNECTthe connection is logged; after the peer client triggers MTU exchange the server logs the new MTU and callsesp_ble_audio_gattc_disc_start. - ASCS callbacks fire as the client drives the state machine:
config_cballocates a stream and returns the QoS preference,qos_cbrecordsmax_sdu,enable_cbvalidates the codec,metadata_cbparses metadata, anddisable/stop/releaselog their requests. stream_enabled_cbauto-starts sink streams server-side;stream_started_cbresets TX or RX counters depending on direction;stream_recv_cbupdates per-stream RX metrics on every ISO SDU.- On disconnect
acl_disconnectlogs the reason and restartsext_adv_start.
Expected Log
TAG: BAP_USR.
Startup and advertising:
I (xxx) BAP_USR: PACS: locations and contexts set
I (xxx) BAP_USR: Advertising started (handle 0)
Client connects:
I (xxx) BAP_USR: Connected: handle ... role ... peer ...
I (xxx) BAP_USR: Security: handle ... level ... bonded ...
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...
Note — disconnect race. On peer-initiated disconnect (e.g. supervision timeout, peer drops the link) with active streams, an additional Bluedroid error may interleave:
W (xxx) BT_APPL: gattc_conn_cb: if=4 st=0 id=4 rsn=0x8
W (xxx) BT_HCI: hcif disc complete: hdl 0x0, rsn 0x8 dev_find 1
I (xxx) BAP_USR: [SNK #0] ISO disconnected, reason 0x08
I (xxx) BAP_USR: [SNK #0] Stream disabled
E (xxx) BT_APPL: Unknown connection ID: 3 fail sending notification
I (xxx) BAP_USR: [SNK #0] Stream stopped, reason 0x08
Unknown connection ID is harmless. GATT notifications for the ASE state changes are queued for Bluedroid to send. If Bluedroid clears the BTA connection on the ACL disconnect before those queued sends drain, they fail with this error. The peer has already disconnected, so the missed notifications have no effect on either side.
Peer Pairing
Run unicast_client 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
ble_gap_connect. - 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.