feat(ble_audio): Support running ISO & LE Audio with Bluedroid Host

This commit is contained in:
Liu Linyan
2026-06-04 15:41:36 +08:00
parent 77f5214ab0
commit 91fc8d3905
223 changed files with 14714 additions and 2347 deletions
@@ -4,16 +4,21 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "sdkconfig.h"
#if CONFIG_BLE_LOG_ENABLED
#include "ble_log.h"
#endif
#ifdef CONFIG_BT_BLUEDROID_ENABLED
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#ifdef CONFIG_BT_NIMBLE_ENABLED
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
@@ -24,9 +29,109 @@
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#endif /* CONFIG_BT_NIMBLE_ENABLED */
#include "ble_audio_example_init.h"
#define TAG "EXAMPLE_INIT"
#ifdef CONFIG_BT_BLUEDROID_ENABLED
esp_err_t bluetooth_init(void)
{
esp_err_t ret;
/* When controller log is disabled (or running in v1 mode), the
* automatic ble_log_init() inside esp_bt_controller_init() is skipped.
* Init manually here so HOST/ISO/AUDIO compressed logs aren't dropped
* during host bring-up. The function is idempotent.
*
* TODO: Remove this block (and the ble_log_flush() below) once
* ble_log_init() is decoupled from the controller log path and
* owns its own initialization independently. */
#if CONFIG_BLE_LOG_ENABLED && \
!(CONFIG_BT_LE_CONTROLLER_LOG_ENABLED && CONFIG_BT_LE_CONTROLLER_LOG_MODE_BLE_LOG_V2)
if (!ble_log_init()) {
ESP_LOGE(TAG, "Failed to init ble_log");
return ESP_FAIL;
}
#endif
ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
if (ret && ret != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG, "Failed to release classic BT memory, err %d", ret);
goto err_log;
}
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "Failed to init controller, err %d", ret);
goto err_log;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "Failed to enable controller, err %d", ret);
goto err_controller_init;
}
esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ret = esp_bluedroid_init_with_cfg(&cfg);
if (ret) {
ESP_LOGE(TAG, "Failed to init bluedroid, err %d", ret);
goto err_controller_enable;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "Failed to enable bluedroid, err %d", ret);
goto err_bluedroid_init;
}
/* Just Works pairing (no MITM, SC + bond, IO_CAP=NONE).
* Matches the NimBLE ble_hs_cfg defaults below. */
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_BOND;
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
uint8_t key_size = 16;
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(auth_req));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(iocap));
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(key_size));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(init_key));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(rsp_key));
/* Drain startup-phase log buffers and emit a FLUSH boundary so the
* parser resets stats here — the example's runtime logs start clean.
*
* TODO: Remove together with the manual ble_log_init() above once
* ble_log_init() is decoupled from the controller log path. */
#if CONFIG_BLE_LOG_ENABLED
ble_log_flush();
#endif
return ESP_OK;
err_bluedroid_init:
esp_bluedroid_deinit();
err_controller_enable:
esp_bt_controller_disable();
err_controller_init:
esp_bt_controller_deinit();
err_log:
/* Mirror the manual ble_log_init() block above — same Kconfig gate so
* we only deinit what we initialized; the controller-owned path is
* left alone. */
#if CONFIG_BLE_LOG_ENABLED && \
!(CONFIG_BT_LE_CONTROLLER_LOG_ENABLED && CONFIG_BT_LE_CONTROLLER_LOG_MODE_BLE_LOG_V2)
ble_log_deinit();
#endif
return ret;
}
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#ifdef CONFIG_BT_NIMBLE_ENABLED
static SemaphoreHandle_t example_audio_sem;
void ble_store_config_init(void);
@@ -228,7 +333,7 @@ esp_err_t bluetooth_init(void)
/* When controller log is disabled (or running in v1 mode), the
* automatic ble_log_init() inside esp_bt_controller_init() is skipped.
* Init manually here so HOST/ISO/AUDIO compressed logs aren't dropped
* during NimBLE bring-up. The function is idempotent.
* during host bring-up. The function is idempotent.
*
* TODO: Remove this block (and the ble_log_flush() below) once
* ble_log_init() is decoupled from the controller log path and
@@ -297,3 +402,4 @@ err_log:
#endif
return ret;
}
#endif /* CONFIG_BT_NIMBLE_ENABLED */
@@ -26,66 +26,6 @@
*/
#define LOG_INTERVAL_PACKETS 6000
int example_audio_gap_event_cb(struct ble_gap_event *event, void *arg)
{
if (event->type == BLE_GAP_EVENT_EXT_DISC ||
event->type == BLE_GAP_EVENT_PERIODIC_SYNC ||
event->type == BLE_GAP_EVENT_PERIODIC_REPORT ||
event->type == BLE_GAP_EVENT_PERIODIC_SYNC_LOST ||
event->type == BLE_GAP_EVENT_PERIODIC_TRANSFER ||
event->type == BLE_GAP_EVENT_PERIODIC_TRANSFER_V2 ||
event->type == BLE_GAP_EVENT_CONNECT ||
event->type == BLE_GAP_EVENT_DISCONNECT ||
event->type == BLE_GAP_EVENT_ENC_CHANGE) {
esp_ble_audio_gap_app_post_event(event->type, event);
} else if (event->type == BLE_GAP_EVENT_MTU ||
event->type == BLE_GAP_EVENT_NOTIFY_RX ||
event->type == BLE_GAP_EVENT_NOTIFY_TX ||
event->type == BLE_GAP_EVENT_SUBSCRIBE) {
esp_ble_audio_gatt_app_post_event(event->type, event);
} else if (event->type == BLE_GAP_EVENT_REPEAT_PAIRING) {
/* Peer wants to re-pair on top of an existing bond (e.g. phone
* cleared its LTK via "Forget device" but our NVS still has it).
* Delete our stale bond and tell NimBLE to retry pairing.
*/
struct ble_gap_conn_desc desc = {0};
int rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
if (rc == 0) {
ble_store_util_delete_peer(&desc.peer_id_addr);
}
return BLE_GAP_REPEAT_PAIRING_RETRY;
}
return 0;
}
void example_audio_security_failed_recover(const char *tag, uint16_t conn_handle, uint8_t status)
{
struct ble_gap_conn_desc desc = {0};
int rc;
/* status 13 = BLE_HS_ETIMEOUT: SMP exchange did not complete. Typical
* cause is asymmetric bond state — the two sides bonded previously,
* then one side erased its NVS while the other kept the LTK. The side
* that still has the bond tries encryption with the cached key; the
* other side has nothing to match it against, so SMP times out. Drop
* our entry for this peer and disconnect; the next connection runs
* fresh pairing (peer side recovers via REPEAT_PAIRING).
*/
ESP_LOGE(tag, "Security change failed, status %u, clearing local bond and reconnecting", status);
rc = ble_gap_conn_find(conn_handle, &desc);
if (rc == 0) {
ble_store_util_delete_peer(&desc.peer_id_addr);
}
/* Tear down the link: SMP cannot be retried on the same connection
* after a failed/timed-out exchange. The next reconnect starts a
* fresh SMP procedure with the now-cleared bond state.
*/
ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
static void print_hex(const uint8_t *ptr, size_t len)
{
while (len--) {
@@ -16,12 +16,10 @@
#include "sdkconfig.h"
#include "esp_err.h"
#include "esp_log.h"
#include "zephyr/kernel.h"
#include "host/ble_gap.h"
#include "host/ble_hs_adv.h"
#include "host/ble_store.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_ble_audio_bap_api.h"
@@ -52,9 +50,60 @@
#define EXAMPLE_BYTES_LIST_LE48 BT_BYTES_LIST_LE48
#define EXAMPLE_BYTES_LIST_LE64 BT_BYTES_LIST_LE64
int example_audio_gap_event_cb(struct ble_gap_event *event, void *arg);
/* bt_le_addr.val carries the host stack's native byte order: NimBLE is
* LSB-first (BT spec wire order), Bluedroid is MSB-first (BD_ADDR). Use
* this with "%02x:%02x:%02x:%02x:%02x:%02x" to print MSB-first regardless
* of host. Examples can otherwise pass val[] straight to their host APIs
* without conversion. */
#if CONFIG_BT_BLUEDROID_ENABLED
#define EXAMPLE_BT_ADDR_PRINT_ARGS(_v) \
(_v)[0], (_v)[1], (_v)[2], (_v)[3], (_v)[4], (_v)[5]
void example_audio_security_failed_recover(const char *tag, uint16_t conn_handle, uint8_t status);
/* Fire-and-wait helper for Bluedroid GAP APIs that pair an async call with
* a matching ESP_GAP_BLE_*_COMPLETE_EVT. The example's GAP event handler
* is expected to xSemaphoreGive(_sem) on that COMPLETE_EVT. `TAG` and the
* enclosing function's `esp_err_t` return are taken from the call site.
*
* !!! Demo-only — DO NOT copy into production code. !!!
* Risks (acceptable in linear init sequences, not in customer apps):
* - Same-task deadlock: must NOT be called from the task that dispatches
* the COMPLETE_EVT (BTC task by default; lib forwarding can route some
* events through other tasks — verify the path for your event).
* - portMAX_DELAY = no timeout: hangs forever on controller/firmware
* misbehavior. Production must use a bounded timeout + recovery.
* - Shared binary sem: concurrent callers race; first take consumes
* whichever give arrived. Examples avoid this by serializing init.
* - No abort hook: nothing can break a portMAX_DELAY wait at shutdown.
* - Status not checked: see EXAMPLE_WAIT_API_CHECK below.
*
* Production should drive BLE init/teardown via an event-driven state
* machine (one task owns the BLE flow, callbacks advance state, no
* blocking from inside callbacks). NimBLE host returns ctrl errors
* synchronously and sidesteps most of this complexity. */
#define EXAMPLE_WAIT_API(_call, _sem, _delay) do { \
esp_err_t _err = (_call); \
if (_err != ESP_OK) { \
ESP_LOGE(TAG, #_call " failed, err %d", _err); \
return _err; \
} \
xSemaphoreTake(_sem, _delay); \
} while (0)
/* As EXAMPLE_WAIT_API but also checks the controller status latched by the
* gap_event_handler into `_op_status` after the sem is taken. Returns
* ESP_FAIL on async failure. Caller must provide a static esp_bt_status_t
* and write it from each COMPLETE_EVT param before xSemaphoreGive. */
#define EXAMPLE_WAIT_API_CHECK(_call, _sem, _delay, _op_status) do { \
EXAMPLE_WAIT_API(_call, _sem, _delay); \
if ((_op_status) != ESP_BT_STATUS_SUCCESS) { \
ESP_LOGE(TAG, #_call " ctrl status %d", (_op_status)); \
return ESP_FAIL; \
} \
} while (0)
#else
#define EXAMPLE_BT_ADDR_PRINT_ARGS(_v) \
(_v)[5], (_v)[4], (_v)[3], (_v)[2], (_v)[1], (_v)[0]
#endif
void example_print_codec_cfg(const char *tag, const esp_ble_audio_codec_cfg_t *codec_cfg);