feat(ble_audio): Support ISO & LE Audio functionalities (Preview)

This commit is contained in:
Linyan Liu
2026-04-17 09:46:23 +08:00
committed by BOT
parent 15a0d3fdea
commit 3ef5da096a
383 changed files with 155035 additions and 5 deletions
@@ -0,0 +1,3 @@
idf_component_register(SRCS "ble_audio_example_init.c"
INCLUDE_DIRS "."
REQUIRES bt)
@@ -0,0 +1,123 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "sdkconfig.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/ble_gap.h"
#include "host/ble_gatt.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#define TAG "INIT"
static SemaphoreHandle_t example_audio_sem;
void ble_store_config_init(void);
static void example_audio_gatt_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
{
char buf[BLE_UUID_STR_LEN];
switch (ctxt->op) {
case BLE_GATT_REGISTER_OP_SVC:
ESP_LOGI(TAG, "Register svc %s, handle %u",
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
ctxt->svc.handle);
break;
case BLE_GATT_REGISTER_OP_CHR:
ESP_LOGI(TAG, "Register chr %s, handles %u/%u",
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
ctxt->chr.def_handle, ctxt->chr.val_handle);
break;
case BLE_GATT_REGISTER_OP_DSC:
ESP_LOGI(TAG, "Register dsc %s, handle %u",
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), ctxt->dsc.handle);
break;
default:
assert(0);
break;
}
}
static void example_audio_on_reset(int reason)
{
ESP_LOGI(TAG, "Resetting state; reason=%d", reason);
}
static void example_audio_on_sync(void)
{
int rc;
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
xSemaphoreGive(example_audio_sem);
}
static void example_audio_host_task(void *param)
{
ESP_LOGI(TAG, "BLE Host Task Started");
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();
nimble_port_freertos_deinit();
}
esp_err_t bluetooth_init(void)
{
esp_err_t ret;
example_audio_sem = xSemaphoreCreateBinary();
if (example_audio_sem == NULL) {
ESP_LOGE(TAG, "Failed to create audio semaphore");
return ESP_FAIL;
}
ret = nimble_port_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to init nimble %d ", ret);
vSemaphoreDelete(example_audio_sem);
example_audio_sem = NULL;
return ret;
}
/* Initialize the NimBLE host configuration */
ble_hs_cfg.gatts_register_cb = example_audio_gatt_register_cb;
ble_hs_cfg.reset_cb = example_audio_on_reset;
ble_hs_cfg.sync_cb = example_audio_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
#if 0
ble_hs_cfg.sm_our_key_dist = 1;
ble_hs_cfg.sm_their_key_dist = 1;
ble_hs_cfg.sm_sc = 1;
ble_hs_cfg.sm_mitm = 0;
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_NO_IO;
#endif
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(example_audio_host_task);
xSemaphoreTake(example_audio_sem, portMAX_DELAY);
return ESP_OK;
}
@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef BLE_AUDIO_EXAMPLE_INIT_H_
#define BLE_AUDIO_EXAMPLE_INIT_H_
#include "esp_err.h"
esp_err_t bluetooth_init(void);
#endif /* BLE_AUDIO_EXAMPLE_INIT_H_ */
@@ -0,0 +1,3 @@
idf_component_register(SRCS "ble_audio_example_utils.c"
INCLUDE_DIRS "."
REQUIRES bt esp_timer freertos)
@@ -0,0 +1,344 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Nordic Semiconductor ASA
* SPDX-FileCopyrightText: 2024 Demant A/S
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "esp_log.h"
#include "ble_audio_example_utils.h"
#include "esp_ble_audio_common_api.h"
#define TAG "UTILS"
#define LOG_GREEN "\033[0;" "32" "m"
#define LOG_RESET "\033[0m"
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_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);
}
return 0;
}
static void print_hex(const uint8_t *ptr, size_t len)
{
while (len--) {
printf(LOG_GREEN "%02x" LOG_RESET, *ptr++);
}
}
static bool print_cb(uint8_t type, const uint8_t *data,
uint8_t data_len, void *user_data)
{
const char *str = (const char *)user_data;
printf(LOG_GREEN "I (%lu) %s: %s, type 0x%02x len %u data " LOG_RESET,
esp_log_timestamp(), TAG, str, type, data_len);
print_hex(data, data_len);
printf("\n");
return true;
}
void example_print_codec_cfg(const esp_ble_audio_codec_cfg_t *codec_cfg)
{
esp_err_t err;
ESP_LOGI(TAG, "codec_cfg, id 0x%02x cid 0x%04x vid 0x%04x count %u",
codec_cfg->id, codec_cfg->cid,
codec_cfg->vid, codec_cfg->data_len);
if (codec_cfg->id == ESP_BLE_ISO_CODING_FORMAT_LC3) {
esp_ble_audio_codec_cfg_frame_dur_t frame_dur;
esp_ble_audio_location_t chan_allocation;
esp_ble_audio_codec_cfg_freq_t freq;
uint16_t octets_per_frame;
uint32_t frame_dur_us;
uint8_t frame_blocks;
uint32_t freq_hz;
/* LC3 uses the generic LTV format - other codecs might do as well */
err = esp_ble_audio_data_parse(codec_cfg->data, codec_cfg->data_len, print_cb, "data");
if (err) {
ESP_LOGE(TAG, "Failed to parse codec_cfg data");
return;
}
err = esp_ble_audio_codec_cfg_get_freq(codec_cfg, &freq);
if (err) {
ESP_LOGE(TAG, "Failed to get frequency");
return;
}
err = esp_ble_audio_codec_cfg_freq_to_freq_hz(freq, &freq_hz);
if (err) {
ESP_LOGE(TAG, "Failed to get frequency hz");
return;
}
ESP_LOGI(TAG, "Frequency: %lu Hz", freq_hz);
err = esp_ble_audio_codec_cfg_get_frame_dur(codec_cfg, &frame_dur);
if (err) {
ESP_LOGE(TAG, "Failed to get frame duration");
return;
}
err = esp_ble_audio_codec_cfg_frame_dur_to_frame_dur_us(frame_dur, &frame_dur_us);
if (err) {
ESP_LOGE(TAG, "Failed to get frame duration us");
return;
}
ESP_LOGI(TAG, "Frame Duration: %lu us", frame_dur_us);
err = esp_ble_audio_codec_cfg_get_chan_allocation(codec_cfg, &chan_allocation, false);
if (err) {
ESP_LOGE(TAG, "Failed to get channel allocation");
return;
}
ESP_LOGI(TAG, "Channel allocation: 0x%08lx", chan_allocation);
err = esp_ble_audio_codec_cfg_get_octets_per_frame(codec_cfg, &octets_per_frame);
if (err) {
ESP_LOGE(TAG, "Failed to get octets per frame");
return;
}
ESP_LOGI(TAG, "Octets per frame: %u", octets_per_frame);
err = esp_ble_audio_codec_cfg_get_frame_blocks_per_sdu(codec_cfg, &frame_blocks, true);
if (err) {
ESP_LOGE(TAG, "Failed to get frame blocks per sdu");
return;
}
ESP_LOGI(TAG, "Frames per SDU: %u", frame_blocks);
} else {
print_hex(codec_cfg->data, codec_cfg->data_len);
}
err = esp_ble_audio_data_parse(codec_cfg->meta, codec_cfg->meta_len, print_cb, "meta");
if (err) {
ESP_LOGE(TAG, "Failed to parse codec_cfg meta");
return;
}
}
void example_print_codec_cap(const esp_ble_audio_codec_cap_t *codec_cap)
{
esp_err_t err;
ESP_LOGI(TAG, "codec_cap, id 0x%02x cid 0x%04x vid 0x%04x count %u",
codec_cap->id, codec_cap->cid,
codec_cap->vid, codec_cap->data_len);
if (codec_cap->id == ESP_BLE_ISO_CODING_FORMAT_LC3) {
err = esp_ble_audio_data_parse(codec_cap->data, codec_cap->data_len, print_cb, "data");
if (err) {
ESP_LOGE(TAG, "Failed to parse codec_cap data");
return;
}
} else {
printf(LOG_GREEN "I (%lu) %s: data: " LOG_RESET, esp_log_timestamp(), TAG);
print_hex(codec_cap->data, codec_cap->data_len);
printf("\n");
}
err = esp_ble_audio_data_parse(codec_cap->meta, codec_cap->meta_len, print_cb, "meta");
if (err) {
ESP_LOGE(TAG, "Failed to parse codec_cap meta");
return;
}
}
void example_print_qos(const esp_ble_audio_bap_qos_cfg_t *qos)
{
ESP_LOGI(TAG, "QoS: interval %u framing 0x%02x phy 0x%02x", qos->interval, qos->framing, qos->phy);
ESP_LOGI(TAG, " sdu %u rtn %u latency %u pd %u", qos->sdu, qos->rtn, qos->latency, qos->pd);
}
void example_print_qos_pref(const esp_ble_audio_bap_qos_cfg_pref_t *pref)
{
ESP_LOGI(TAG, "QoS pref: unframed %s, phy %u, rtn %u, latency %u",
pref->unframed_supported ? "supported" : "not supported",
pref->phy, pref->rtn, pref->latency);
ESP_LOGI(TAG, " pd_min %u, pd_max %u",
pref->pd_min, pref->pd_max);
ESP_LOGI(TAG, " pref_pd_min %u, pref_pd_max %u",
pref->pref_pd_min, pref->pref_pd_max);
}
bool example_is_substring(const char *substr, const char *str)
{
const size_t sub_str_len = strlen(substr);
const size_t str_len = strlen(str);
if (sub_str_len > str_len) {
return false;
}
for (size_t pos = 0; pos < str_len; pos++) {
if (pos + sub_str_len > str_len) {
return false;
}
if (strncasecmp(substr, &str[pos], sub_str_len) == 0) {
return true;
}
}
return false;
}
static void example_audio_tx_work_handler(struct k_work *work)
{
example_audio_tx_scheduler_t *scheduler = work->user_data;
size_t count;
assert(scheduler);
assert(scheduler->cb);
count = 1 + scheduler->drift;
scheduler->drift = 0;
for (size_t i = 0; i < count; i++) {
scheduler->cb(scheduler->arg);
}
}
int example_audio_tx_scheduler_init(example_audio_tx_scheduler_t *scheduler,
example_audio_tx_send_cb_t cb,
void *arg)
{
assert(scheduler);
assert(cb);
memset(scheduler, 0, sizeof(*scheduler));
scheduler->cb = cb;
scheduler->arg = arg;
k_work_init_delayable(&scheduler->timer, example_audio_tx_work_handler);
scheduler->timer.work.user_data = scheduler;
return 0;
}
void example_audio_tx_scheduler_reset(example_audio_tx_scheduler_t *scheduler)
{
assert(scheduler);
scheduler->drift = 0;
scheduler->count = 0;
}
int example_audio_tx_scheduler_start(example_audio_tx_scheduler_t *scheduler,
uint64_t period_us)
{
assert(scheduler);
assert(period_us > 0);
return k_work_schedule_periodic(&scheduler->timer, (uint32_t)(period_us / 1000));
}
int example_audio_tx_scheduler_stop(example_audio_tx_scheduler_t *scheduler)
{
assert(scheduler);
return k_work_cancel_delayable(&scheduler->timer);
}
void example_audio_tx_scheduler_on_sent(example_audio_tx_scheduler_t *scheduler,
const esp_ble_iso_tx_cb_info_t *info,
const char *tag,
const char *obj_name,
const void *obj)
{
size_t drift_count = 0;
assert(scheduler);
assert(info);
for (size_t i = 0; i < info->pkt_cnt; i++) {
if (info->pkt[i].drift) {
drift_count++;
}
}
scheduler->drift += drift_count;
scheduler->count++;
if (scheduler->count % 1000 == 0) {
ESP_LOGI(tag, "Transmitted %u ISO data packets (%s %p)",
scheduler->count, (obj_name ? obj_name : ""), obj);
}
}
void example_audio_rx_metrics_reset(example_audio_rx_metrics_t *metrics)
{
assert(metrics);
metrics->recv_count = 0;
metrics->valid_count = 0;
metrics->error_count = 0;
metrics->lost_count = 0;
metrics->null_sdu_count = 0;
metrics->last_sdu_len = 0;
}
void example_audio_rx_metrics_on_recv(const esp_ble_iso_recv_info_t *info,
example_audio_rx_metrics_t *metrics,
const char *tag,
const char *obj_name,
const void *obj)
{
assert(info);
assert(metrics);
if (info->flags & ESP_BLE_ISO_FLAGS_ERROR) {
metrics->error_count++;
}
if (info->flags & ESP_BLE_ISO_FLAGS_LOST) {
metrics->lost_count++;
}
if (info->flags & ESP_BLE_ISO_FLAGS_VALID) {
metrics->valid_count++;
if (metrics->last_sdu_len == 0) {
metrics->null_sdu_count++;
}
}
metrics->recv_count++;
if (metrics->recv_count % 1000 == 0) {
ESP_LOGI(tag, "Received %u ISO data packets (%s %p)",
metrics->recv_count, (obj_name ? obj_name : ""), obj);
}
}
@@ -0,0 +1,118 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Nordic Semiconductor ASA
* SPDX-FileCopyrightText: 2024 Demant A/S
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef BLE_AUDIO_EXAMPLE_UTILS_H_
#define BLE_AUDIO_EXAMPLE_UTILS_H_
#include <stddef.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "zephyr/kernel.h"
#include "host/ble_gap.h"
#include "host/ble_hs_adv.h"
#include "esp_ble_audio_bap_api.h"
#define EXAMPLE_AD_TYPE_FLAGS BT_DATA_FLAGS
#define EXAMPLE_AD_TYPE_UUID16_SOME BT_DATA_UUID16_SOME
#define EXAMPLE_AD_TYPE_UUID16_ALL BT_DATA_UUID16_ALL
#define EXAMPLE_AD_TYPE_NAME_SHORTENED BT_DATA_NAME_SHORTENED
#define EXAMPLE_AD_TYPE_NAME_COMPLETE BT_DATA_NAME_COMPLETE
#define EXAMPLE_AD_TYPE_SERVICE_DATA16 BT_DATA_SVC_DATA16
#define EXAMPLE_AD_TYPE_GAP_APPEARANCE BT_DATA_GAP_APPEARANCE
#define EXAMPLE_AD_TYPE_CSIS_RSI BT_DATA_CSIS_RSI
#define EXAMPLE_AD_TYPE_BROADCAST_NAME BT_DATA_BROADCAST_NAME
#define EXAMPLE_AD_FLAGS_LIMITED BT_LE_AD_LIMITED
#define EXAMPLE_AD_FLAGS_GENERAL BT_LE_AD_GENERAL
#define EXAMPLE_AD_FLAGS_NO_BREDR BT_LE_AD_NO_BREDR
#define EXAMPLE_ADV_PROP_CONNECTABLE BT_GAP_ADV_PROP_CONNECTABLE
#define EXAMPLE_ADV_PROP_SCANNABLE BT_GAP_ADV_PROP_SCANNABLE
#define EXAMPLE_ADV_PROP_DIRECTED BT_GAP_ADV_PROP_DIRECTED
#define EXAMPLE_ADV_PROP_SCAN_RESPONSE BT_GAP_ADV_PROP_SCAN_RESPONSE
#define EXAMPLE_ADV_PROP_EXT_ADV BT_GAP_ADV_PROP_EXT_ADV
#define EXAMPLE_BYTES_LIST_LE16 BT_BYTES_LIST_LE16
#define EXAMPLE_BYTES_LIST_LE24 BT_BYTES_LIST_LE24
#define EXAMPLE_BYTES_LIST_LE32 BT_BYTES_LIST_LE32
#define EXAMPLE_BYTES_LIST_LE40 BT_BYTES_LIST_LE40
#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);
void example_print_codec_cfg(const esp_ble_audio_codec_cfg_t *codec_cfg);
void example_print_codec_cap(const esp_ble_audio_codec_cap_t *codec_cap);
void example_print_qos(const esp_ble_audio_bap_qos_cfg_t *qos);
void example_print_qos_pref(const esp_ble_audio_bap_qos_cfg_pref_t *pref);
bool example_is_substring(const char *substr, const char *str);
/**
* @brief TX scheduler for periodic audio data transmission.
*
* Uses k_work_delayable to schedule send callbacks in the ISO task context,
* ensuring thread safety without mutexes since both the timer handler and
* BLE audio callbacks execute in the same task.
*/
typedef void (*example_audio_tx_send_cb_t)(void *ctx);
typedef struct {
struct k_work_delayable timer;
size_t drift;
uint32_t count;
/* Called periodically in ISO task context to transmit data */
example_audio_tx_send_cb_t cb;
void *arg;
} example_audio_tx_scheduler_t;
typedef struct {
uint32_t recv_count;
uint32_t valid_count;
uint32_t error_count;
uint32_t lost_count;
uint32_t null_sdu_count;
uint16_t last_sdu_len;
} example_audio_rx_metrics_t;
int example_audio_tx_scheduler_init(example_audio_tx_scheduler_t *scheduler,
example_audio_tx_send_cb_t cb,
void *arg);
void example_audio_tx_scheduler_reset(example_audio_tx_scheduler_t *scheduler);
int example_audio_tx_scheduler_start(example_audio_tx_scheduler_t *scheduler,
uint64_t period_us);
int example_audio_tx_scheduler_stop(example_audio_tx_scheduler_t *scheduler);
void example_audio_tx_scheduler_on_sent(example_audio_tx_scheduler_t *scheduler,
const esp_ble_iso_tx_cb_info_t *info,
const char *tag,
const char *obj_name,
const void *obj);
void example_audio_rx_metrics_reset(example_audio_rx_metrics_t *metrics);
void example_audio_rx_metrics_on_recv(const esp_ble_iso_recv_info_t *info,
example_audio_rx_metrics_t *metrics,
const char *tag,
const char *obj_name,
const void *obj);
#endif /* BLE_AUDIO_EXAMPLE_UTILS_H_ */