chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:04:25 +08:00
commit 548b49ebc0
20937 changed files with 5455372 additions and 0 deletions
@@ -0,0 +1,3 @@
idf_component_register(SRCS "ble_audio_example_utils.c"
INCLUDE_DIRS "."
REQUIRES bt esp_timer freertos)
@@ -0,0 +1,540 @@
/*
* 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 LOG_GREEN "\033[0;" "32" "m"
#define LOG_RESET "\033[0m"
/* Packet count between throughput summary logs.
* At 10 ms SDU interval this is ~60 s; at 7.5 ms ~45 s.
*/
#define LOG_INTERVAL_PACKETS 6000
static void print_hex(const uint8_t *ptr, size_t len)
{
while (len--) {
printf(LOG_GREEN "%02x" LOG_RESET, *ptr++);
}
}
static void append_audio_contexts(char *buf, size_t buf_size, uint16_t ctx_bitmap)
{
static const char *const names[] = {
"Unspecified", "Conversational", "Media", "Game",
"Instructional", "Voice Assistants", "Live", "Sound Effects",
"Notifications", "Ringtone", "Alerts", "Emergency Alarm",
};
size_t pos = 0;
buf[0] = '\0';
if (ctx_bitmap == 0) {
snprintf(buf, buf_size, "None");
return;
}
for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
if (ctx_bitmap & (1U << i)) {
int n = snprintf(buf + pos, buf_size - pos, "%s%s",
pos == 0 ? "" : ", ", names[i]);
if (n < 0 || (size_t)n >= buf_size - pos) {
break;
}
pos += n;
}
}
}
static void copy_to_cstr(char *dst, size_t dst_size, const uint8_t *src, uint8_t src_len)
{
size_t n = (src_len < dst_size - 1) ? src_len : dst_size - 1;
memcpy(dst, src, n);
dst[n] = '\0';
}
static void phy_to_str(char *buf, size_t buf_size, uint8_t phy)
{
size_t pos = 0;
buf[0] = '\0';
if (phy & ESP_BLE_ISO_PHY_1M) {
pos += snprintf(buf + pos, buf_size - pos, "1M");
}
if (phy & ESP_BLE_ISO_PHY_2M) {
pos += snprintf(buf + pos, buf_size - pos, "%s2M", pos ? " " : "");
}
if (phy & ESP_BLE_ISO_PHY_CODED) {
pos += snprintf(buf + pos, buf_size - pos, "%sCoded", pos ? " " : "");
}
if (pos == 0) {
snprintf(buf, buf_size, "0x%02x", phy);
}
}
static bool print_meta_cb(uint8_t type, const uint8_t *data,
uint8_t data_len, void *user_data)
{
const char *tag = (const char *)user_data;
char buf[128];
switch (type) {
case ESP_BLE_AUDIO_METADATA_TYPE_PREF_CONTEXT:
case ESP_BLE_AUDIO_METADATA_TYPE_STREAM_CONTEXT: {
uint16_t ctx = (data_len >= 2)
? (uint16_t)data[0] | ((uint16_t)data[1] << 8)
: 0;
append_audio_contexts(buf, sizeof(buf), ctx);
ESP_LOGI(tag, "| %-19s: %s",
type == ESP_BLE_AUDIO_METADATA_TYPE_PREF_CONTEXT
? "Preferred Context" : "Streaming Context",
buf);
break;
}
case ESP_BLE_AUDIO_METADATA_TYPE_PROGRAM_INFO:
copy_to_cstr(buf, sizeof(buf), data, data_len);
ESP_LOGI(tag, "| %-19s: %s", "Program Info", buf);
break;
case ESP_BLE_AUDIO_METADATA_TYPE_LANG:
copy_to_cstr(buf, sizeof(buf), data, data_len);
ESP_LOGI(tag, "| %-19s: %s", "Language", buf);
break;
case ESP_BLE_AUDIO_METADATA_TYPE_CCID_LIST: {
size_t pos = 0;
buf[0] = '\0';
for (uint8_t i = 0; i < data_len; i++) {
int n = snprintf(buf + pos, sizeof(buf) - pos, "%s%u",
pos == 0 ? "" : " ", data[i]);
if (n < 0 || (size_t)n >= sizeof(buf) - pos) {
break;
}
pos += n;
}
ESP_LOGI(tag, "| %-19s: %s", "CCID List", buf);
break;
}
case ESP_BLE_AUDIO_METADATA_TYPE_PARENTAL_RATING:
ESP_LOGI(tag, "| %-19s: %u", "Parental Rating",
data_len >= 1 ? data[0] : 0);
break;
case ESP_BLE_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI:
copy_to_cstr(buf, sizeof(buf), data, data_len);
ESP_LOGI(tag, "| %-19s: %s", "Program Info URI", buf);
break;
case ESP_BLE_AUDIO_METADATA_TYPE_AUDIO_STATE:
ESP_LOGI(tag, "| %-19s: 0x%02x", "Audio Active State",
data_len >= 1 ? data[0] : 0);
break;
case ESP_BLE_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE:
ESP_LOGI(tag, "| %-19s: (set)", "Broadcast Immediate");
break;
case ESP_BLE_AUDIO_METADATA_TYPE_ASSISTED_LISTENING_STREAM:
ESP_LOGI(tag, "| %-19s: 0x%02x", "Assisted Listening",
data_len >= 1 ? data[0] : 0);
break;
case ESP_BLE_AUDIO_METADATA_TYPE_BROADCAST_NAME:
copy_to_cstr(buf, sizeof(buf), data, data_len);
ESP_LOGI(tag, "| %-19s: %s", "Broadcast Name", buf);
break;
case ESP_BLE_AUDIO_METADATA_TYPE_EXTENDED:
case ESP_BLE_AUDIO_METADATA_TYPE_VENDOR:
default:
printf(LOG_GREEN "I (%lu) %s: | meta[type 0x%02x len %u]: " LOG_RESET,
esp_log_timestamp(), tag, type, data_len);
print_hex(data, data_len);
printf("\n");
break;
}
return true;
}
void example_print_meta(const char *tag, const uint8_t *meta, size_t meta_len)
{
esp_err_t err;
if (meta_len == 0) {
return;
}
ESP_LOGI(tag, "| Metadata:");
err = esp_ble_audio_data_parse(meta, meta_len, print_meta_cb, (void *)tag);
if (err) {
ESP_LOGE(tag, "Failed to parse meta");
}
}
static void print_raw_data(const char *tag, const uint8_t *data, size_t data_len)
{
printf(LOG_GREEN "I (%lu) %s: | data: " LOG_RESET, esp_log_timestamp(), tag);
print_hex(data, data_len);
printf("\n");
}
static void print_codec_cfg_lc3(const char *tag, const esp_ble_audio_codec_cfg_t *codec_cfg)
{
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;
esp_err_t err;
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, "| %-19s: %lu Hz", "Frequency", 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, "| %-19s: %lu us", "Frame Duration", 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, "| %-19s: 0x%08lx", "Channel allocation", 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, "| %-19s: %u bytes", "Octets per frame", 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, "| %-19s: %u", "Frames per SDU", frame_blocks);
}
static void print_codec_cap_lc3(const char *tag, const esp_ble_audio_codec_cap_t *codec_cap)
{
static const uint16_t freq_khz[] = {
8, 11, 16, 22, 24, 32, 44, 48, 88, 96, 176, 192, 384,
};
esp_ble_audio_codec_cap_freq_t freq;
esp_ble_audio_codec_cap_frame_dur_t frame_dur;
esp_ble_audio_codec_cap_chan_count_t chan_count;
esp_ble_audio_codec_octets_per_codec_frame_t octets;
uint8_t max_frames;
char buf[64];
int pos;
esp_err_t err;
err = esp_ble_audio_codec_cap_get_freq(codec_cap, &freq);
if (err) {
ESP_LOGE(tag, "Failed to get supported frequencies");
return;
}
pos = 0;
buf[0] = '\0';
for (size_t i = 0; i < sizeof(freq_khz) / sizeof(freq_khz[0]); i++) {
if (freq & (1U << i)) {
int n = snprintf(buf + pos, sizeof(buf) - pos, "%s%u",
pos == 0 ? "" : " ", freq_khz[i]);
if (n < 0 || (size_t)n >= sizeof(buf) - pos) {
break;
}
pos += n;
}
}
ESP_LOGI(tag, "| %-19s: %s kHz", "Frequencies", buf);
err = esp_ble_audio_codec_cap_get_frame_dur(codec_cap, &frame_dur);
if (err) {
ESP_LOGE(tag, "Failed to get supported frame durations");
return;
}
pos = 0;
buf[0] = '\0';
if (frame_dur & ESP_BLE_AUDIO_CODEC_CAP_DURATION_7_5) {
pos += snprintf(buf + pos, sizeof(buf) - pos, "7.5");
}
if (frame_dur & ESP_BLE_AUDIO_CODEC_CAP_DURATION_10) {
pos += snprintf(buf + pos, sizeof(buf) - pos, "%s10",
pos == 0 ? "" : " ");
}
ESP_LOGI(tag, "| %-19s: %s ms", "Frame Durations", buf);
err = esp_ble_audio_codec_cap_get_supported_audio_chan_counts(codec_cap, &chan_count, true);
if (err) {
ESP_LOGE(tag, "Failed to get supported channel counts");
return;
}
pos = 0;
buf[0] = '\0';
for (size_t i = 0; i < 8; i++) {
if (chan_count & (1U << i)) {
int n = snprintf(buf + pos, sizeof(buf) - pos, "%s%u",
pos == 0 ? "" : " ", (unsigned)(i + 1));
if (n < 0 || (size_t)n >= sizeof(buf) - pos) {
break;
}
pos += n;
}
}
ESP_LOGI(tag, "| %-19s: %s", "Channel Counts", buf);
err = esp_ble_audio_codec_cap_get_octets_per_frame(codec_cap, &octets);
if (err) {
ESP_LOGE(tag, "Failed to get supported octets per frame");
return;
}
ESP_LOGI(tag, "| %-19s: %u-%u bytes", "Octets per Frame", octets.min, octets.max);
err = esp_ble_audio_codec_cap_get_max_codec_frames_per_sdu(codec_cap, &max_frames, true);
if (err) {
ESP_LOGE(tag, "Failed to get max frames per SDU");
return;
}
ESP_LOGI(tag, "| %-19s: %u", "Max Frames per SDU", max_frames);
}
void example_print_codec_cfg(const char *tag, const esp_ble_audio_codec_cfg_t *codec_cfg)
{
ESP_LOGI(tag, "Codec Configuration:");
ESP_LOGI(tag, "| id 0x%02x cid 0x%04x vid 0x%04x len %u",
codec_cfg->id, codec_cfg->cid,
codec_cfg->vid, codec_cfg->data_len);
if (codec_cfg->id == ESP_BLE_ISO_CODING_FORMAT_LC3) {
print_codec_cfg_lc3(tag, codec_cfg);
} else {
print_raw_data(tag, codec_cfg->data, codec_cfg->data_len);
}
example_print_meta(tag, codec_cfg->meta, codec_cfg->meta_len);
}
void example_print_codec_cap(const char *tag, const esp_ble_audio_codec_cap_t *codec_cap)
{
ESP_LOGI(tag, "Codec Capability:");
ESP_LOGI(tag, "| id 0x%02x cid 0x%04x vid 0x%04x len %u",
codec_cap->id, codec_cap->cid,
codec_cap->vid, codec_cap->data_len);
if (codec_cap->id == ESP_BLE_ISO_CODING_FORMAT_LC3) {
print_codec_cap_lc3(tag, codec_cap);
} else {
print_raw_data(tag, codec_cap->data, codec_cap->data_len);
}
example_print_meta(tag, codec_cap->meta, codec_cap->meta_len);
}
/* Prints QoS fields only. Caller is expected to print a context header
* ending with ':' so the '| ' lines below naturally attach to that header.
*/
void example_print_qos(const char *tag, const esp_ble_audio_bap_qos_cfg_t *qos)
{
char phy_str[16];
phy_to_str(phy_str, sizeof(phy_str), qos->phy);
ESP_LOGI(tag, "| %-8s: %u us", "interval", qos->interval);
ESP_LOGI(tag, "| %-8s: %s", "framing", qos->framing ? "framed" : "unframed");
ESP_LOGI(tag, "| %-8s: %s", "phy", phy_str);
ESP_LOGI(tag, "| %-8s: %u bytes", "sdu", qos->sdu);
ESP_LOGI(tag, "| %-8s: %u retransmission(s)", "rtn", qos->rtn);
ESP_LOGI(tag, "| %-8s: %u ms", "latency", qos->latency);
ESP_LOGI(tag, "| %-8s: %u us", "pd", qos->pd);
}
/* Prints QoS Preference fields only. Same expectation as example_print_qos. */
void example_print_qos_pref(const char *tag, const esp_ble_audio_bap_qos_cfg_pref_t *pref)
{
char phy_str[16];
phy_to_str(phy_str, sizeof(phy_str), pref->phy);
ESP_LOGI(tag, "| %-11s: %s", "unframed",
pref->unframed_supported ? "supported" : "not supported");
ESP_LOGI(tag, "| %-11s: %s", "phy", phy_str);
ESP_LOGI(tag, "| %-11s: %u retransmission(s)", "rtn", pref->rtn);
ESP_LOGI(tag, "| %-11s: %u ms", "latency", pref->latency);
ESP_LOGI(tag, "| %-11s: %u us", "pd_min", pref->pd_min);
ESP_LOGI(tag, "| %-11s: %u us", "pd_max", pref->pd_max);
ESP_LOGI(tag, "| %-11s: %u us", "pref_pd_min", pref->pref_pd_min);
ESP_LOGI(tag, "| %-11s: %u us", "pref_pd_max", 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)
{
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 % LOG_INTERVAL_PACKETS == 0) {
ESP_LOGI(tag, "[%s] TX: %u packets",
obj_name ? obj_name : "ISO", scheduler->count);
}
}
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)
{
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 % LOG_INTERVAL_PACKETS == 0) {
ESP_LOGI(tag, "[%s] RX: %u packets",
obj_name ? obj_name : "ISO", metrics->recv_count);
}
}
@@ -0,0 +1,170 @@
/*
* 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 "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.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
/* 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]
/* 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);
void example_print_codec_cap(const char *tag, const esp_ble_audio_codec_cap_t *codec_cap);
void example_print_meta(const char *tag, const uint8_t *meta, size_t meta_len);
void example_print_qos(const char *tag, const esp_ble_audio_bap_qos_cfg_t *qos);
void example_print_qos_pref(const char *tag, 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);
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);
#endif /* BLE_AUDIO_EXAMPLE_UTILS_H_ */