zephyr/subsys/bluetooth/audio/shell/bap.c
Emil Gydesen 025032232c Bluetooth: BAP: Shell: Fix issue with stopping broadcast sink
When the broadcast sink is stopped, the sink was also
set to NULL via

default_broadcast_sink.bap_sink = NULL;

However the lifetime of the broadcast sink does not
follow the state of the streams, and it still exists
afterwards. The broadcast sink can only be terminated
(deleted) via the term_broadcast_sink command.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2024-03-12 17:57:49 +00:00

3754 lines
99 KiB
C

/** @file
* @brief Bluetooth Basic Audio Profile shell
*
*/
/*
* Copyright (c) 2020 Intel Corporation
* Copyright (c) 2022-2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <zephyr/kernel.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/gmap.h>
#include <zephyr/bluetooth/audio/pacs.h>
#if defined(CONFIG_LIBLC3)
#include "lc3.h"
#define LC3_MAX_SAMPLE_RATE 48000
#define LC3_MAX_FRAME_DURATION_US 10000
#define LC3_MAX_NUM_SAMPLES ((LC3_MAX_FRAME_DURATION_US * LC3_MAX_SAMPLE_RATE) / USEC_PER_SEC)
#endif /* CONFIG_LIBLC3 */
#include "shell/bt.h"
#include "audio.h"
/* Determines if we can initiate streaming */
#define IS_BAP_INITIATOR \
(IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SOURCE) || IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT))
#if defined(CONFIG_BT_BAP_UNICAST)
struct shell_stream unicast_streams[CONFIG_BT_MAX_CONN *
(UNICAST_SERVER_STREAM_COUNT + UNICAST_CLIENT_STREAM_COUNT)];
#if defined(CONFIG_BT_BAP_UNICAST_CLIENT)
struct bt_bap_unicast_group *default_unicast_group;
static struct bt_bap_unicast_client_cb unicast_client_cbs;
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0
struct bt_bap_ep *snks[CONFIG_BT_MAX_CONN][CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT];
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0
struct bt_bap_ep *srcs[CONFIG_BT_MAX_CONN][CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT];
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
#endif /* CONFIG_BT_BAP_UNICAST */
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
struct shell_stream broadcast_source_streams[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT];
struct broadcast_source default_source;
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
#if defined(CONFIG_BT_BAP_BROADCAST_SINK)
static struct shell_stream broadcast_sink_streams[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT];
static struct broadcast_sink default_broadcast_sink;
#endif /* CONFIG_BT_BAP_BROADCAST_SINK */
#if defined(CONFIG_BT_BAP_UNICAST) || defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
static struct bt_bap_stream *default_stream;
#endif /* CONFIG_BT_BAP_UNICAST || CONFIG_BT_BAP_BROADCAST_SOURCE */
#if IS_BAP_INITIATOR
/* Default to 16_2_1 */
struct named_lc3_preset default_sink_preset = {"16_2_1",
BT_BAP_LC3_UNICAST_PRESET_16_2_1(LOCATION, CONTEXT)};
struct named_lc3_preset default_source_preset = {
"16_2_1", BT_BAP_LC3_UNICAST_PRESET_16_2_1(LOCATION, CONTEXT)};
static struct named_lc3_preset default_broadcast_source_preset = {
"16_2_1", BT_BAP_LC3_BROADCAST_PRESET_16_2_1(LOCATION, CONTEXT)};
#endif /* IS_BAP_INITIATOR */
static const struct named_lc3_preset lc3_unicast_presets[] = {
{"8_1_1", BT_BAP_LC3_UNICAST_PRESET_8_1_1(LOCATION, CONTEXT)},
{"8_2_1", BT_BAP_LC3_UNICAST_PRESET_8_2_1(LOCATION, CONTEXT)},
{"16_1_1", BT_BAP_LC3_UNICAST_PRESET_16_1_1(LOCATION, CONTEXT)},
{"16_2_1", BT_BAP_LC3_UNICAST_PRESET_16_2_1(LOCATION, CONTEXT)},
{"24_1_1", BT_BAP_LC3_UNICAST_PRESET_24_1_1(LOCATION, CONTEXT)},
{"24_2_1", BT_BAP_LC3_UNICAST_PRESET_24_2_1(LOCATION, CONTEXT)},
{"32_1_1", BT_BAP_LC3_UNICAST_PRESET_32_1_1(LOCATION, CONTEXT)},
{"32_2_1", BT_BAP_LC3_UNICAST_PRESET_32_2_1(LOCATION, CONTEXT)},
{"441_1_1", BT_BAP_LC3_UNICAST_PRESET_441_1_1(LOCATION, CONTEXT)},
{"441_2_1", BT_BAP_LC3_UNICAST_PRESET_441_2_1(LOCATION, CONTEXT)},
{"48_1_1", BT_BAP_LC3_UNICAST_PRESET_48_1_1(LOCATION, CONTEXT)},
{"48_2_1", BT_BAP_LC3_UNICAST_PRESET_48_2_1(LOCATION, CONTEXT)},
{"48_3_1", BT_BAP_LC3_UNICAST_PRESET_48_3_1(LOCATION, CONTEXT)},
{"48_4_1", BT_BAP_LC3_UNICAST_PRESET_48_4_1(LOCATION, CONTEXT)},
{"48_5_1", BT_BAP_LC3_UNICAST_PRESET_48_5_1(LOCATION, CONTEXT)},
{"48_6_1", BT_BAP_LC3_UNICAST_PRESET_48_6_1(LOCATION, CONTEXT)},
/* High-reliability presets */
{"8_1_2", BT_BAP_LC3_UNICAST_PRESET_8_1_2(LOCATION, CONTEXT)},
{"8_2_2", BT_BAP_LC3_UNICAST_PRESET_8_2_2(LOCATION, CONTEXT)},
{"16_1_2", BT_BAP_LC3_UNICAST_PRESET_16_1_2(LOCATION, CONTEXT)},
{"16_2_2", BT_BAP_LC3_UNICAST_PRESET_16_2_2(LOCATION, CONTEXT)},
{"24_1_2", BT_BAP_LC3_UNICAST_PRESET_24_1_2(LOCATION, CONTEXT)},
{"24_2_2", BT_BAP_LC3_UNICAST_PRESET_24_2_2(LOCATION, CONTEXT)},
{"32_1_2", BT_BAP_LC3_UNICAST_PRESET_32_1_2(LOCATION, CONTEXT)},
{"32_2_2", BT_BAP_LC3_UNICAST_PRESET_32_2_2(LOCATION, CONTEXT)},
{"441_1_2", BT_BAP_LC3_UNICAST_PRESET_441_1_2(LOCATION, CONTEXT)},
{"441_2_2", BT_BAP_LC3_UNICAST_PRESET_441_2_2(LOCATION, CONTEXT)},
{"48_1_2", BT_BAP_LC3_UNICAST_PRESET_48_1_2(LOCATION, CONTEXT)},
{"48_2_2", BT_BAP_LC3_UNICAST_PRESET_48_2_2(LOCATION, CONTEXT)},
{"48_3_2", BT_BAP_LC3_UNICAST_PRESET_48_3_2(LOCATION, CONTEXT)},
{"48_4_2", BT_BAP_LC3_UNICAST_PRESET_48_4_2(LOCATION, CONTEXT)},
{"48_5_2", BT_BAP_LC3_UNICAST_PRESET_48_5_2(LOCATION, CONTEXT)},
{"48_6_2", BT_BAP_LC3_UNICAST_PRESET_48_6_2(LOCATION, CONTEXT)},
};
static const struct named_lc3_preset lc3_broadcast_presets[] = {
{"8_1_1", BT_BAP_LC3_BROADCAST_PRESET_8_1_1(LOCATION, CONTEXT)},
{"8_2_1", BT_BAP_LC3_BROADCAST_PRESET_8_2_1(LOCATION, CONTEXT)},
{"16_1_1", BT_BAP_LC3_BROADCAST_PRESET_16_1_1(LOCATION, CONTEXT)},
{"16_2_1", BT_BAP_LC3_BROADCAST_PRESET_16_2_1(LOCATION, CONTEXT)},
{"24_1_1", BT_BAP_LC3_BROADCAST_PRESET_24_1_1(LOCATION, CONTEXT)},
{"24_2_1", BT_BAP_LC3_BROADCAST_PRESET_24_2_1(LOCATION, CONTEXT)},
{"32_1_1", BT_BAP_LC3_BROADCAST_PRESET_32_1_1(LOCATION, CONTEXT)},
{"32_2_1", BT_BAP_LC3_BROADCAST_PRESET_32_2_1(LOCATION, CONTEXT)},
{"441_1_1", BT_BAP_LC3_BROADCAST_PRESET_441_1_1(LOCATION, CONTEXT)},
{"441_2_1", BT_BAP_LC3_BROADCAST_PRESET_441_2_1(LOCATION, CONTEXT)},
{"48_1_1", BT_BAP_LC3_BROADCAST_PRESET_48_1_1(LOCATION, CONTEXT)},
{"48_2_1", BT_BAP_LC3_BROADCAST_PRESET_48_2_1(LOCATION, CONTEXT)},
{"48_3_1", BT_BAP_LC3_BROADCAST_PRESET_48_3_1(LOCATION, CONTEXT)},
{"48_4_1", BT_BAP_LC3_BROADCAST_PRESET_48_4_1(LOCATION, CONTEXT)},
{"48_5_1", BT_BAP_LC3_BROADCAST_PRESET_48_5_1(LOCATION, CONTEXT)},
{"48_6_1", BT_BAP_LC3_BROADCAST_PRESET_48_6_1(LOCATION, CONTEXT)},
/* High-reliability presets */
{"8_1_2", BT_BAP_LC3_BROADCAST_PRESET_8_1_2(LOCATION, CONTEXT)},
{"8_2_2", BT_BAP_LC3_BROADCAST_PRESET_8_2_2(LOCATION, CONTEXT)},
{"16_1_2", BT_BAP_LC3_BROADCAST_PRESET_16_1_2(LOCATION, CONTEXT)},
{"16_2_2", BT_BAP_LC3_BROADCAST_PRESET_16_2_2(LOCATION, CONTEXT)},
{"24_1_2", BT_BAP_LC3_BROADCAST_PRESET_24_1_2(LOCATION, CONTEXT)},
{"24_2_2", BT_BAP_LC3_BROADCAST_PRESET_24_2_2(LOCATION, CONTEXT)},
{"32_1_2", BT_BAP_LC3_BROADCAST_PRESET_32_1_2(LOCATION, CONTEXT)},
{"32_2_2", BT_BAP_LC3_BROADCAST_PRESET_32_2_2(LOCATION, CONTEXT)},
{"441_1_2", BT_BAP_LC3_BROADCAST_PRESET_441_1_2(LOCATION, CONTEXT)},
{"441_2_2", BT_BAP_LC3_BROADCAST_PRESET_441_2_2(LOCATION, CONTEXT)},
{"48_1_2", BT_BAP_LC3_BROADCAST_PRESET_48_1_2(LOCATION, CONTEXT)},
{"48_2_2", BT_BAP_LC3_BROADCAST_PRESET_48_2_2(LOCATION, CONTEXT)},
{"48_3_2", BT_BAP_LC3_BROADCAST_PRESET_48_3_2(LOCATION, CONTEXT)},
{"48_4_2", BT_BAP_LC3_BROADCAST_PRESET_48_4_2(LOCATION, CONTEXT)},
{"48_5_2", BT_BAP_LC3_BROADCAST_PRESET_48_5_2(LOCATION, CONTEXT)},
{"48_6_2", BT_BAP_LC3_BROADCAST_PRESET_48_6_2(LOCATION, CONTEXT)},
};
static bool initialized;
static struct shell_stream *shell_stream_from_bap_stream(struct bt_bap_stream *bap_stream)
{
struct bt_cap_stream *cap_stream =
CONTAINER_OF(bap_stream, struct bt_cap_stream, bap_stream);
struct shell_stream *sh_stream = CONTAINER_OF(cap_stream, struct shell_stream, stream);
return sh_stream;
}
static struct bt_bap_stream *bap_stream_from_shell_stream(struct shell_stream *sh_stream)
{
return &sh_stream->stream.bap_stream;
}
#if defined(CONFIG_BT_AUDIO_TX)
static uint16_t get_next_seq_num(struct bt_bap_stream *bap_stream)
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(bap_stream);
const uint32_t interval_us = bap_stream->qos->interval;
int64_t uptime_ticks;
int64_t delta_ticks;
uint64_t delta_us;
uint16_t seq_num;
/* Note: This does not handle wrapping of ticks when they go above 2^(62-1) */
uptime_ticks = k_uptime_ticks();
delta_ticks = uptime_ticks - sh_stream->connected_at_ticks;
delta_us = k_ticks_to_us_near64((uint64_t)delta_ticks);
/* Calculate the sequence number by dividing the stream uptime by the SDU interval */
seq_num = (uint16_t)(delta_us / interval_us);
return seq_num;
}
#endif /* CONFIG_BT_AUDIO_TX */
#if defined(CONFIG_LIBLC3) && defined(CONFIG_BT_AUDIO_TX)
/* For the first call-back we push multiple audio frames to the buffer to use the
* controller ISO buffer to handle jitter.
*/
#define PRIME_COUNT 2U
NET_BUF_POOL_FIXED_DEFINE(sine_tx_pool, CONFIG_BT_ISO_TX_BUF_COUNT,
BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
#include "math.h"
#define AUDIO_VOLUME (INT16_MAX - 3000) /* codec does clipping above INT16_MAX - 3000 */
#define AUDIO_TONE_FREQUENCY_HZ 400
static int16_t lc3_tx_buf[LC3_MAX_NUM_SAMPLES];
static lc3_encoder_t lc3_encoder;
static lc3_encoder_mem_48k_t lc3_encoder_mem;
static int lc3_encoder_freq_hz;
static int lc3_encoder_frame_duration_us;
static void clear_lc3_sine_data(struct bt_bap_stream *bap_stream)
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(bap_stream);
sh_stream->tx_active = false;
(void)k_work_cancel_delayable(&sh_stream->audio_send_work);
}
/**
* Use the math lib to generate a sine-wave using 16 bit samples into a buffer.
*
* @param buf Destination buffer
* @param length_us Length of the buffer in microseconds
* @param frequency_hz frequency in Hz
* @param sample_rate_hz sample-rate in Hz.
*/
static void fill_lc3_tx_buf_sin(int16_t *buf, int length_us, int frequency_hz, int sample_rate_hz)
{
const uint32_t sine_period_samples = sample_rate_hz / frequency_hz;
const size_t num_samples = (length_us * sample_rate_hz) / USEC_PER_SEC;
const float step = 2 * 3.1415 / sine_period_samples;
for (size_t i = 0; i < num_samples; i++) {
const float sample = sinf(i * step);
buf[i] = (int16_t)(AUDIO_VOLUME * sample);
}
}
static int init_lc3_encoder(const struct shell_stream *sh_stream)
{
size_t num_samples;
if (sh_stream == NULL) {
shell_error(ctx_shell, "invalid stream to init LC3");
return -EINVAL;
}
if (sh_stream->lc3_freq_hz == 0 || sh_stream->lc3_frame_duration_us == 0) {
shell_error(ctx_shell, "Invalid freq (%u) or frame duration (%u)",
sh_stream->lc3_freq_hz, sh_stream->lc3_frame_duration_us);
return -EINVAL;
}
/* Create the encoder instance. This shall complete before stream_started() is called. */
lc3_encoder = lc3_setup_encoder(sh_stream->lc3_frame_duration_us, sh_stream->lc3_freq_hz,
0, /* No resampling */
&lc3_encoder_mem);
if (lc3_encoder == NULL) {
shell_error(ctx_shell, "Failed to setup LC3 encoder - wrong parameters?\n");
return -EINVAL;
}
lc3_encoder_freq_hz = sh_stream->lc3_freq_hz;
lc3_encoder_frame_duration_us = sh_stream->lc3_frame_duration_us;
/* Fill audio buffer with Sine wave only once and repeat encoding the same tone frame */
fill_lc3_tx_buf_sin(lc3_tx_buf, lc3_encoder_frame_duration_us, AUDIO_TONE_FREQUENCY_HZ,
lc3_encoder_freq_hz);
num_samples = ((lc3_encoder_frame_duration_us * lc3_encoder_freq_hz) / USEC_PER_SEC);
for (size_t i = 0; i < num_samples; i++) {
printk("%zu: %6i\n", i, lc3_tx_buf[i]);
}
return 0;
}
static void lc3_audio_send_data(struct k_work *work)
{
struct shell_stream *sh_stream = CONTAINER_OF(k_work_delayable_from_work(work),
struct shell_stream, audio_send_work);
struct bt_bap_stream *bap_stream = bap_stream_from_shell_stream(sh_stream);
const uint16_t tx_sdu_len = sh_stream->lc3_frames_per_sdu * sh_stream->lc3_octets_per_frame;
struct net_buf *buf;
uint8_t *net_buffer;
off_t offset = 0;
int err;
if (!sh_stream->tx_active) {
/* TX has been aborted */
return;
}
if (lc3_encoder == NULL) {
shell_error(ctx_shell, "LC3 encoder not setup, cannot encode data");
return;
}
if (bap_stream == NULL || bap_stream->qos == NULL) {
shell_error(ctx_shell, "invalid stream, aborting");
return;
}
if (tx_sdu_len == 0U) {
shell_error(
ctx_shell,
"Cannot send 0 length SDU (from frames per sdu %u and %u octets per frame)",
sh_stream->lc3_frames_per_sdu, sh_stream->lc3_octets_per_frame);
return;
}
if (atomic_get(&sh_stream->lc3_enqueue_cnt) == 0U) {
shell_error(ctx_shell, "Stream %p enqueue count was 0", bap_stream);
/* Reschedule for next interval */
k_work_reschedule(k_work_delayable_from_work(work),
K_USEC(bap_stream->qos->interval));
return;
}
buf = net_buf_alloc(&sine_tx_pool, K_FOREVER);
net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);
net_buffer = net_buf_tail(buf);
buf->len += tx_sdu_len;
for (uint8_t i = 0U; i < sh_stream->lc3_frames_per_sdu; i++) {
int lc3_ret;
lc3_ret = lc3_encode(lc3_encoder, LC3_PCM_FORMAT_S16, lc3_tx_buf, 1,
sh_stream->lc3_octets_per_frame, net_buffer + offset);
offset += sh_stream->lc3_octets_per_frame;
if (lc3_ret == -1) {
shell_error(ctx_shell, "LC3 encoder failed - wrong parameters?: %d",
lc3_ret);
net_buf_unref(buf);
/* Reschedule for next interval */
k_work_reschedule(k_work_delayable_from_work(work),
K_USEC(bap_stream->qos->interval));
return;
}
}
err = bt_bap_stream_send(bap_stream, buf, sh_stream->seq_num);
if (err < 0) {
shell_error(ctx_shell, "Failed to send LC3 audio data (%d)", err);
net_buf_unref(buf);
/* Reschedule for next interval */
k_work_reschedule(k_work_delayable_from_work(work),
K_USEC(bap_stream->qos->interval));
return;
}
if ((sh_stream->lc3_sdu_cnt % 100) == 0) {
shell_info(ctx_shell, "[%zu]: stream %p : TX LC3: %zu (seq_num %u)",
sh_stream->lc3_sdu_cnt, bap_stream, tx_sdu_len, sh_stream->seq_num);
}
sh_stream->lc3_sdu_cnt++;
sh_stream->seq_num++;
atomic_dec(&sh_stream->lc3_enqueue_cnt);
if (atomic_get(&sh_stream->lc3_enqueue_cnt) > 0) {
/* If we have more buffers available, we reschedule the workqueue item immediately
* to trigger another encode + TX, but without blocking this call for too long
*/
k_work_reschedule(k_work_delayable_from_work(work), K_NO_WAIT);
}
}
static void lc3_sent_cb(struct bt_bap_stream *bap_stream)
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(bap_stream);
int err;
atomic_inc(&sh_stream->lc3_enqueue_cnt);
if (!sh_stream->tx_active) {
/* TX has been aborted */
return;
}
err = k_work_schedule(&sh_stream->audio_send_work, K_NO_WAIT);
if (err < 0) {
shell_error(ctx_shell, "Failed to schedule TX for stream %p: %d", bap_stream, err);
}
}
#endif /* CONFIG_LIBLC3 && CONFIG_BT_AUDIO_TX */
const struct named_lc3_preset *bap_get_named_preset(bool is_unicast, enum bt_audio_dir dir,
const char *preset_arg)
{
if (is_unicast) {
for (size_t i = 0U; i < ARRAY_SIZE(lc3_unicast_presets); i++) {
if (!strcmp(preset_arg, lc3_unicast_presets[i].name)) {
return &lc3_unicast_presets[i];
}
}
} else {
for (size_t i = 0U; i < ARRAY_SIZE(lc3_broadcast_presets); i++) {
if (!strcmp(preset_arg, lc3_broadcast_presets[i].name)) {
return &lc3_broadcast_presets[i];
}
}
}
if (IS_ENABLED(CONFIG_BT_GMAP)) {
return gmap_get_named_preset(is_unicast, dir, preset_arg);
}
return NULL;
}
#if defined(CONFIG_BT_PACS)
static const struct bt_audio_codec_cap lc3_codec_cap =
BT_AUDIO_CODEC_CAP_LC3(BT_AUDIO_CODEC_CAP_FREQ_ANY, BT_AUDIO_CODEC_CAP_DURATION_ANY,
BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1, 2), 30, 240, 2, CONTEXT);
#if defined(CONFIG_BT_PAC_SNK)
static struct bt_pacs_cap cap_sink = {
.codec_cap = &lc3_codec_cap,
};
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
static struct bt_pacs_cap cap_source = {
.codec_cap = &lc3_codec_cap,
};
#endif /* CONFIG_BT_PAC_SRC */
#endif /* CONFIG_BT_PACS */
#if defined(CONFIG_BT_BAP_UNICAST)
static void set_unicast_stream(struct bt_bap_stream *stream)
{
default_stream = stream;
for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) {
if (stream == bap_stream_from_shell_stream(&unicast_streams[i])) {
shell_print(ctx_shell, "Default stream: %u", i + 1);
}
}
}
static int cmd_select_unicast(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_bap_stream *stream;
unsigned long index;
int err = 0;
index = shell_strtoul(argv[1], 0, &err);
if (err != 0) {
shell_error(sh, "Could not parse index: %d", err);
return -ENOEXEC;
}
if (index > ARRAY_SIZE(unicast_streams)) {
shell_error(sh, "Invalid index: %lu", index);
return -ENOEXEC;
}
stream = bap_stream_from_shell_stream(&unicast_streams[index]);
set_unicast_stream(stream);
return 0;
}
#if defined(CONFIG_BT_BAP_UNICAST_SERVER)
static const struct bt_audio_codec_qos_pref qos_pref =
BT_AUDIO_CODEC_QOS_PREF(true, BT_GAP_LE_PHY_2M, 0u, 60u, 10000u, 60000u, 10000u, 60000u);
static struct bt_bap_stream *stream_alloc(void)
{
for (size_t i = 0; i < ARRAY_SIZE(unicast_streams); i++) {
struct bt_bap_stream *stream = bap_stream_from_shell_stream(&unicast_streams[i]);
if (!stream->conn) {
return stream;
}
}
return NULL;
}
static int lc3_config(struct bt_conn *conn, const struct bt_bap_ep *ep, enum bt_audio_dir dir,
const struct bt_audio_codec_cfg *codec_cfg, struct bt_bap_stream **stream,
struct bt_audio_codec_qos_pref *const pref, struct bt_bap_ascs_rsp *rsp)
{
shell_print(ctx_shell, "ASE Codec Config: conn %p ep %p dir %u", conn, ep, dir);
print_codec_cfg(ctx_shell, 0, codec_cfg);
*stream = stream_alloc();
if (*stream == NULL) {
shell_print(ctx_shell, "No unicast_streams available");
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_NO_MEM, BT_BAP_ASCS_REASON_NONE);
return -ENOMEM;
}
shell_print(ctx_shell, "ASE Codec Config stream %p", *stream);
set_unicast_stream(*stream);
*pref = qos_pref;
return 0;
}
static int lc3_reconfig(struct bt_bap_stream *stream, enum bt_audio_dir dir,
const struct bt_audio_codec_cfg *codec_cfg,
struct bt_audio_codec_qos_pref *const pref, struct bt_bap_ascs_rsp *rsp)
{
shell_print(ctx_shell, "ASE Codec Reconfig: stream %p", stream);
print_codec_cfg(ctx_shell, 0, codec_cfg);
if (default_stream == NULL) {
set_unicast_stream(stream);
}
*pref = qos_pref;
return 0;
}
static int lc3_qos(struct bt_bap_stream *stream, const struct bt_audio_codec_qos *qos,
struct bt_bap_ascs_rsp *rsp)
{
shell_print(ctx_shell, "QoS: stream %p %p", stream, qos);
print_qos(ctx_shell, qos);
return 0;
}
static int lc3_enable(struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len,
struct bt_bap_ascs_rsp *rsp)
{
shell_print(ctx_shell, "Enable: stream %p meta_len %zu", stream,
meta_len);
return 0;
}
static int lc3_start(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
shell_print(ctx_shell, "Start: stream %p", stream);
return 0;
}
static bool meta_data_func_cb(struct bt_data *data, void *user_data)
{
struct bt_bap_ascs_rsp *rsp = (struct bt_bap_ascs_rsp *)user_data;
if (!BT_AUDIO_METADATA_TYPE_IS_KNOWN(data->type)) {
printk("Invalid metadata type %u or length %u\n", data->type, data->data_len);
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED, data->type);
return false;
}
return true;
}
static int lc3_metadata(struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len,
struct bt_bap_ascs_rsp *rsp)
{
shell_print(ctx_shell, "Metadata: stream %p meta_len %zu", stream,
meta_len);
return bt_audio_data_parse(meta, meta_len, meta_data_func_cb, rsp);
}
static int lc3_disable(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
shell_print(ctx_shell, "Disable: stream %p", stream);
return 0;
}
static int lc3_stop(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
shell_print(ctx_shell, "Stop: stream %p", stream);
return 0;
}
static int lc3_release(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
shell_print(ctx_shell, "Release: stream %p", stream);
if (stream == default_stream) {
default_stream = NULL;
}
return 0;
}
static const struct bt_bap_unicast_server_cb unicast_server_cb = {
.config = lc3_config,
.reconfig = lc3_reconfig,
.qos = lc3_qos,
.enable = lc3_enable,
.start = lc3_start,
.metadata = lc3_metadata,
.disable = lc3_disable,
.stop = lc3_stop,
.release = lc3_release,
};
#endif /* CONFIG_BT_BAP_UNICAST_SERVER */
static uint16_t strmeta(const char *name)
{
if (strcmp(name, "Unspecified") == 0) {
return BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED;
} else if (strcmp(name, "Conversational") == 0) {
return BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL;
} else if (strcmp(name, "Media") == 0) {
return BT_AUDIO_CONTEXT_TYPE_MEDIA;
} else if (strcmp(name, "Game") == 0) {
return BT_AUDIO_CONTEXT_TYPE_GAME;
} else if (strcmp(name, "Instructional") == 0) {
return BT_AUDIO_CONTEXT_TYPE_INSTRUCTIONAL;
} else if (strcmp(name, "VoiceAssistants") == 0) {
return BT_AUDIO_CONTEXT_TYPE_VOICE_ASSISTANTS;
} else if (strcmp(name, "Live") == 0) {
return BT_AUDIO_CONTEXT_TYPE_LIVE;
} else if (strcmp(name, "SoundEffects") == 0) {
return BT_AUDIO_CONTEXT_TYPE_SOUND_EFFECTS;
} else if (strcmp(name, "Notifications") == 0) {
return BT_AUDIO_CONTEXT_TYPE_NOTIFICATIONS;
} else if (strcmp(name, "Ringtone") == 0) {
return BT_AUDIO_CONTEXT_TYPE_RINGTONE;
} else if (strcmp(name, "Alerts") == 0) {
return BT_AUDIO_CONTEXT_TYPE_ALERTS;
} else if (strcmp(name, "EmergencyAlarm") == 0) {
return BT_AUDIO_CONTEXT_TYPE_EMERGENCY_ALARM;
}
return 0u;
}
static int set_metadata(struct bt_audio_codec_cfg *codec_cfg, const char *meta_str)
{
uint16_t context;
context = strmeta(meta_str);
if (context == 0) {
return -ENOEXEC;
}
/* TODO: Check the type and only overwrite the streaming context */
sys_put_le16(context, codec_cfg->meta);
return 0;
}
#if defined(CONFIG_BT_BAP_UNICAST_CLIENT)
int bap_ac_create_unicast_group(const struct bap_unicast_ac_param *param,
struct shell_stream *snk_uni_streams[], size_t snk_cnt,
struct shell_stream *src_uni_streams[], size_t src_cnt)
{
struct bt_bap_unicast_group_stream_param snk_group_stream_params[BAP_UNICAST_AC_MAX_SNK] = {
0};
struct bt_bap_unicast_group_stream_param src_group_stream_params[BAP_UNICAST_AC_MAX_SRC] = {
0};
struct bt_bap_unicast_group_stream_pair_param pair_params[BAP_UNICAST_AC_MAX_PAIR] = {0};
struct bt_bap_unicast_group_param group_param = {0};
struct bt_audio_codec_qos *snk_qos[BAP_UNICAST_AC_MAX_SNK];
struct bt_audio_codec_qos *src_qos[BAP_UNICAST_AC_MAX_SRC];
size_t snk_stream_cnt = 0U;
size_t src_stream_cnt = 0U;
size_t pair_cnt = 0U;
for (size_t i = 0U; i < snk_cnt; i++) {
snk_qos[i] = &snk_uni_streams[i]->qos;
}
for (size_t i = 0U; i < src_cnt; i++) {
src_qos[i] = &src_uni_streams[i]->qos;
}
/* Create Group
*
* First setup the individual stream parameters and then match them in pairs by connection
* and direction
*/
for (size_t i = 0U; i < snk_cnt; i++) {
snk_group_stream_params[i].qos = snk_qos[i];
snk_group_stream_params[i].stream =
bap_stream_from_shell_stream(snk_uni_streams[i]);
}
for (size_t i = 0U; i < src_cnt; i++) {
src_group_stream_params[i].qos = src_qos[i];
src_group_stream_params[i].stream =
bap_stream_from_shell_stream(src_uni_streams[i]);
}
for (size_t i = 0U; i < param->conn_cnt; i++) {
for (size_t j = 0; j < MAX(param->snk_cnt[i], param->src_cnt[i]); j++) {
if (param->snk_cnt[i] > j) {
pair_params[pair_cnt].tx_param =
&snk_group_stream_params[snk_stream_cnt++];
} else {
pair_params[pair_cnt].tx_param = NULL;
}
if (param->src_cnt[i] > j) {
pair_params[pair_cnt].rx_param =
&src_group_stream_params[src_stream_cnt++];
} else {
pair_params[pair_cnt].rx_param = NULL;
}
pair_cnt++;
}
}
group_param.packing = BT_ISO_PACKING_SEQUENTIAL;
group_param.params = pair_params;
group_param.params_count = pair_cnt;
return bt_bap_unicast_group_create(&group_param, &default_unicast_group);
}
static uint8_t stream_dir(const struct bt_bap_stream *stream)
{
if (stream->conn) {
uint8_t conn_index = bt_conn_index(stream->conn);
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0
for (size_t i = 0; i < ARRAY_SIZE(snks[conn_index]); i++) {
const struct bt_bap_ep *snk_ep = snks[conn_index][i];
if (snk_ep != NULL && stream->ep == snk_ep) {
return BT_AUDIO_DIR_SINK;
}
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0
for (size_t i = 0; i < ARRAY_SIZE(srcs[conn_index]); i++) {
const struct bt_bap_ep *src_ep = srcs[conn_index][i];
if (src_ep != NULL && stream->ep == src_ep) {
return BT_AUDIO_DIR_SOURCE;
}
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */
}
__ASSERT(false, "Invalid stream");
return 0;
}
static void print_remote_codec_cap(const struct bt_conn *conn,
const struct bt_audio_codec_cap *codec_cap,
enum bt_audio_dir dir)
{
shell_print(ctx_shell, "conn %p: codec_cap %p dir 0x%02x", conn, codec_cap,
dir);
print_codec_cap(ctx_shell, 0, codec_cap);
}
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0
static void add_sink(const struct bt_conn *conn, struct bt_bap_ep *ep)
{
const uint8_t conn_index = bt_conn_index(conn);
for (size_t i = 0U; i < ARRAY_SIZE(snks[conn_index]); i++) {
if (snks[conn_index][i] == NULL) {
shell_print(ctx_shell, "Conn: %p, Sink #%zu: ep %p", conn, i, ep);
snks[conn_index][i] = ep;
return;
}
}
shell_error(ctx_shell, "Could not add more sink endpoints");
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0
static void add_source(const struct bt_conn *conn, struct bt_bap_ep *ep)
{
const uint8_t conn_index = bt_conn_index(conn);
for (size_t i = 0U; i < ARRAY_SIZE(srcs[conn_index]); i++) {
if (srcs[conn_index][i] == NULL) {
shell_print(ctx_shell, "Conn: %p, Source #%zu: ep %p", conn, i, ep);
srcs[conn_index][i] = ep;
return;
}
}
shell_error(ctx_shell, "Could not add more sink endpoints");
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */
static void pac_record_cb(struct bt_conn *conn, enum bt_audio_dir dir,
const struct bt_audio_codec_cap *codec_cap)
{
print_remote_codec_cap(conn, codec_cap, dir);
}
static void endpoint_cb(struct bt_conn *conn, enum bt_audio_dir dir, struct bt_bap_ep *ep)
{
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0
if (dir == BT_AUDIO_DIR_SINK) {
add_sink(conn, ep);
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0
if (dir == BT_AUDIO_DIR_SOURCE) {
add_source(conn, ep);
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0*/
}
static void discover_cb(struct bt_conn *conn, int err, enum bt_audio_dir dir)
{
shell_print(ctx_shell, "Discover complete: err %d", err);
}
static void discover_all(struct bt_conn *conn, int err, enum bt_audio_dir dir)
{
/* Sinks discovery complete, now discover sources */
if (dir == BT_AUDIO_DIR_SINK) {
dir = BT_AUDIO_DIR_SOURCE;
unicast_client_cbs.discover = discover_cb;
err = bt_bap_unicast_client_discover(default_conn, dir);
if (err) {
shell_error(ctx_shell, "bt_bap_unicast_client_discover err %d", err);
}
}
}
static void unicast_client_location_cb(struct bt_conn *conn,
enum bt_audio_dir dir,
enum bt_audio_location loc)
{
shell_print(ctx_shell, "dir %u loc %X\n", dir, loc);
}
static void available_contexts_cb(struct bt_conn *conn,
enum bt_audio_context snk_ctx,
enum bt_audio_context src_ctx)
{
shell_print(ctx_shell, "snk ctx %u src ctx %u\n", snk_ctx, src_ctx);
}
static void config_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code,
enum bt_bap_ascs_reason reason)
{
shell_print(ctx_shell, "stream %p config operation rsp_code %u reason %u",
stream, rsp_code, reason);
if (default_stream == NULL) {
default_stream = stream;
}
}
static void qos_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code,
enum bt_bap_ascs_reason reason)
{
shell_print(ctx_shell, "stream %p qos operation rsp_code %u reason %u",
stream, rsp_code, reason);
}
static void enable_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code,
enum bt_bap_ascs_reason reason)
{
shell_print(ctx_shell, "stream %p enable operation rsp_code %u reason %u",
stream, rsp_code, reason);
}
static void start_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code,
enum bt_bap_ascs_reason reason)
{
shell_print(ctx_shell, "stream %p start operation rsp_code %u reason %u",
stream, rsp_code, reason);
}
static void stop_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code,
enum bt_bap_ascs_reason reason)
{
shell_print(ctx_shell, "stream %p stop operation rsp_code %u reason %u",
stream, rsp_code, reason);
}
static void disable_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code,
enum bt_bap_ascs_reason reason)
{
shell_print(ctx_shell, "stream %p disable operation rsp_code %u reason %u",
stream, rsp_code, reason);
}
static void metadata_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code,
enum bt_bap_ascs_reason reason)
{
shell_print(ctx_shell, "stream %p metadata operation rsp_code %u reason %u",
stream, rsp_code, reason);
}
static void release_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code,
enum bt_bap_ascs_reason reason)
{
shell_print(ctx_shell, "stream %p release operation rsp_code %u reason %u",
stream, rsp_code, reason);
}
static struct bt_bap_unicast_client_cb unicast_client_cbs = {
.location = unicast_client_location_cb,
.available_contexts = available_contexts_cb,
.config = config_cb,
.qos = qos_cb,
.enable = enable_cb,
.start = start_cb,
.stop = stop_cb,
.disable = disable_cb,
.metadata = metadata_cb,
.release = release_cb,
.pac_record = pac_record_cb,
.endpoint = endpoint_cb,
};
static int cmd_discover(const struct shell *sh, size_t argc, char *argv[])
{
static bool cbs_registered;
enum bt_audio_dir dir;
uint8_t conn_index;
int err;
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
if (!initialized) {
shell_error(sh, "Not initialized");
return -ENOEXEC;
}
if (!cbs_registered) {
err = bt_bap_unicast_client_register_cb(&unicast_client_cbs);
if (err != 0) {
shell_error(sh, "Failed to register unicast client callbacks: %d", err);
return err;
}
cbs_registered = true;
}
unicast_client_cbs.discover = discover_all;
dir = BT_AUDIO_DIR_SINK;
if (argc > 1) {
if (!strcmp(argv[1], "sink")) {
unicast_client_cbs.discover = discover_cb;
} else if (!strcmp(argv[1], "source")) {
unicast_client_cbs.discover = discover_cb;
dir = BT_AUDIO_DIR_SOURCE;
} else {
shell_error(sh, "Unsupported dir: %s", argv[1]);
return -ENOEXEC;
}
}
err = bt_bap_unicast_client_discover(default_conn, dir);
if (err != 0) {
return -ENOEXEC;
}
conn_index = bt_conn_index(default_conn);
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0
memset(srcs[conn_index], 0, sizeof(srcs[conn_index]));
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0
memset(snks[conn_index], 0, sizeof(snks[conn_index]));
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */
return 0;
}
static int cmd_config(const struct shell *sh, size_t argc, char *argv[])
{
enum bt_audio_location location = BT_AUDIO_LOCATION_MONO_AUDIO;
const struct named_lc3_preset *named_preset;
struct shell_stream *uni_stream;
struct bt_bap_stream *bap_stream;
struct bt_bap_ep *ep = NULL;
enum bt_audio_dir dir;
unsigned long index;
uint8_t conn_index;
int err = 0;
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
conn_index = bt_conn_index(default_conn);
if (default_stream == NULL) {
bap_stream = bap_stream_from_shell_stream(&unicast_streams[0]);
} else {
bap_stream = default_stream;
}
index = shell_strtoul(argv[2], 0, &err);
if (err != 0) {
shell_error(sh, "Could not parse index: %d", err);
return -ENOEXEC;
}
if (index > ARRAY_SIZE(unicast_streams)) {
shell_error(sh, "Invalid index: %lu", index);
return -ENOEXEC;
}
if (false) {
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0
} else if (!strcmp(argv[1], "sink")) {
dir = BT_AUDIO_DIR_SINK;
ep = snks[conn_index][index];
named_preset = &default_sink_preset;
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0
} else if (!strcmp(argv[1], "source")) {
dir = BT_AUDIO_DIR_SOURCE;
ep = srcs[conn_index][index];
named_preset = &default_source_preset;
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */
} else {
shell_error(sh, "Unsupported dir: %s", argv[1]);
return -ENOEXEC;
}
if (!ep) {
shell_error(sh, "Unable to find endpoint");
return -ENOEXEC;
}
for (size_t i = 3U; i < argc; i++) {
const char *arg = argv[i];
/* argc needs to be larger than `i` to parse the argument value */
if (argc <= i) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
if (strcmp(arg, "loc") == 0) {
unsigned long loc_bits;
arg = argv[++i];
loc_bits = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Could not parse loc_bits: %d", err);
return -ENOEXEC;
}
if (loc_bits > BT_AUDIO_LOCATION_ANY) {
shell_error(sh, "Invalid loc_bits: %lu", loc_bits);
return -ENOEXEC;
}
location = (enum bt_audio_location)loc_bits;
} else if (strcmp(arg, "preset") == 0) {
if (argc > i) {
arg = argv[++i];
named_preset = bap_get_named_preset(true, dir, arg);
if (named_preset == NULL) {
shell_error(sh, "Unable to parse named_preset %s", arg);
return -ENOEXEC;
}
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
uni_stream = shell_stream_from_bap_stream(bap_stream);
copy_unicast_stream_preset(uni_stream, named_preset);
/* If location has been modifed, we update the location in the codec configuration */
struct bt_audio_codec_cfg *codec_cfg = &uni_stream->codec_cfg;
for (size_t i = 0U; i < codec_cfg->data_len;) {
const uint8_t len = codec_cfg->data[i++];
uint8_t *value;
uint8_t data_len;
uint8_t type;
if (len == 0 || len > codec_cfg->data_len - i) {
/* Invalid len field */
return false;
}
type = codec_cfg->data[i++];
value = &codec_cfg->data[i];
if (type == BT_AUDIO_CODEC_CFG_CHAN_ALLOC) {
const uint32_t loc_32 = location;
sys_put_le32(loc_32, value);
shell_print(sh, "Setting location to 0x%08X", location);
break;
}
data_len = len - sizeof(type);
/* Since we are incrementing i by the value_len, we don't need to increment
* it further in the `for` statement
*/
i += data_len;
}
if (bap_stream->ep == ep) {
err = bt_bap_stream_reconfig(bap_stream, &uni_stream->codec_cfg);
if (err != 0) {
shell_error(sh, "Unable reconfig stream: %d", err);
return -ENOEXEC;
}
} else {
err = bt_bap_stream_config(default_conn, bap_stream, ep,
&uni_stream->codec_cfg);
if (err != 0) {
shell_error(sh, "Unable to config stream: %d", err);
return err;
}
}
shell_print(sh, "ASE config: preset %s", named_preset->name);
return 0;
}
static int cmd_stream_qos(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_audio_codec_qos *qos;
unsigned long interval;
int err = 0;
if (default_stream == NULL) {
shell_print(sh, "No stream selected");
return -ENOEXEC;
}
qos = default_stream->qos;
if (qos == NULL) {
shell_print(sh, "Stream not configured");
return -ENOEXEC;
}
interval = shell_strtoul(argv[1], 0, &err);
if (err != 0) {
return -ENOEXEC;
}
if (!IN_RANGE(interval, BT_ISO_SDU_INTERVAL_MIN, BT_ISO_SDU_INTERVAL_MAX)) {
return -ENOEXEC;
}
qos->interval = interval;
if (argc > 2) {
unsigned long framing;
framing = shell_strtoul(argv[2], 0, &err);
if (err != 0) {
return -ENOEXEC;
}
if (framing != BT_ISO_FRAMING_UNFRAMED && framing != BT_ISO_FRAMING_FRAMED) {
return -ENOEXEC;
}
qos->framing = framing;
}
if (argc > 3) {
unsigned long latency;
latency = shell_strtoul(argv[3], 0, &err);
if (err != 0) {
return -ENOEXEC;
}
if (!IN_RANGE(latency, BT_ISO_LATENCY_MIN, BT_ISO_LATENCY_MAX)) {
return -ENOEXEC;
}
qos->latency = latency;
}
if (argc > 4) {
unsigned long pd;
pd = shell_strtoul(argv[4], 0, &err);
if (err != 0) {
return -ENOEXEC;
}
if (pd > BT_AUDIO_PD_MAX) {
return -ENOEXEC;
}
qos->pd = pd;
}
if (argc > 5) {
unsigned long sdu;
sdu = shell_strtoul(argv[5], 0, &err);
if (err != 0) {
return -ENOEXEC;
}
if (sdu > BT_ISO_MAX_SDU) {
return -ENOEXEC;
}
qos->sdu = sdu;
}
if (argc > 6) {
unsigned long phy;
phy = shell_strtoul(argv[6], 0, &err);
if (err != 0) {
return -ENOEXEC;
}
if (phy != BT_GAP_LE_PHY_1M && phy != BT_GAP_LE_PHY_2M &&
phy != BT_GAP_LE_PHY_CODED) {
return -ENOEXEC;
}
qos->phy = phy;
}
if (argc > 7) {
unsigned long rtn;
rtn = shell_strtoul(argv[7], 0, &err);
if (err != 0) {
return -ENOEXEC;
}
if (rtn > BT_ISO_CONNECTED_RTN_MAX) {
return -ENOEXEC;
}
qos->rtn = rtn;
}
return 0;
}
static int create_unicast_group(const struct shell *sh)
{
struct bt_bap_unicast_group_stream_pair_param pair_param[ARRAY_SIZE(unicast_streams)];
struct bt_bap_unicast_group_stream_param stream_params[ARRAY_SIZE(unicast_streams)];
struct bt_bap_unicast_group_param group_param;
size_t source_cnt = 0;
size_t sink_cnt = 0;
size_t cnt = 0;
int err;
memset(pair_param, 0, sizeof(pair_param));
memset(stream_params, 0, sizeof(stream_params));
memset(&group_param, 0, sizeof(group_param));
for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) {
struct bt_bap_stream *stream = bap_stream_from_shell_stream(&unicast_streams[i]);
struct shell_stream *uni_stream = &unicast_streams[i];
if (stream->ep != NULL) {
struct bt_bap_unicast_group_stream_param *stream_param;
stream_param = &stream_params[cnt];
stream_param->stream = stream;
if (stream_dir(stream) == BT_AUDIO_DIR_SINK) {
stream_param->qos = &uni_stream->qos;
pair_param[sink_cnt++].tx_param = stream_param;
} else {
stream_param->qos = &uni_stream->qos;
pair_param[source_cnt++].rx_param = stream_param;
}
cnt++;
}
}
if (cnt == 0U) {
shell_error(sh, "Stream cnt is 0");
return -ENOEXEC;
}
group_param.packing = BT_ISO_PACKING_SEQUENTIAL;
group_param.params = pair_param;
group_param.params_count = MAX(source_cnt, sink_cnt);
err = bt_bap_unicast_group_create(&group_param,
&default_unicast_group);
if (err != 0) {
shell_error(sh,
"Unable to create default unicast group: %d",
err);
return -ENOEXEC;
}
return 0;
}
static int cmd_qos(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (default_stream == NULL) {
shell_print(sh, "No stream selected");
return -ENOEXEC;
}
if (default_conn == NULL) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
if (default_unicast_group == NULL) {
err = create_unicast_group(sh);
if (err != 0) {
return err;
}
}
err = bt_bap_stream_qos(default_conn, default_unicast_group);
if (err) {
shell_error(sh, "Unable to setup QoS: %d", err);
return -ENOEXEC;
}
return 0;
}
static int cmd_enable(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_audio_codec_cfg *codec_cfg;
int err;
if (default_stream == NULL) {
shell_error(sh, "No stream selected");
return -ENOEXEC;
}
codec_cfg = default_stream->codec_cfg;
if (argc > 1) {
err = set_metadata(codec_cfg, argv[1]);
if (err != 0) {
shell_error(sh, "Unable to handle metadata update: %d", err);
return err;
}
}
err = bt_bap_stream_enable(default_stream, codec_cfg->meta, codec_cfg->meta_len);
if (err) {
shell_error(sh, "Unable to enable Channel");
return -ENOEXEC;
}
return 0;
}
static int cmd_stop(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (default_stream == NULL) {
shell_error(sh, "No stream selected");
return -ENOEXEC;
}
err = bt_bap_stream_stop(default_stream);
if (err) {
shell_error(sh, "Unable to stop Channel");
return -ENOEXEC;
}
return 0;
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
static int cmd_metadata(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_audio_codec_cfg *codec_cfg;
int err;
if (default_stream == NULL) {
shell_error(sh, "No stream selected");
return -ENOEXEC;
}
codec_cfg = default_stream->codec_cfg;
if (argc > 1) {
err = set_metadata(codec_cfg, argv[1]);
if (err != 0) {
shell_error(sh, "Unable to handle metadata update: %d", err);
return err;
}
}
err = bt_bap_stream_metadata(default_stream, codec_cfg->meta, codec_cfg->meta_len);
if (err) {
shell_error(sh, "Unable to set Channel metadata");
return -ENOEXEC;
}
return 0;
}
static int cmd_start(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (default_stream == NULL) {
shell_error(sh, "No stream selected");
return -ENOEXEC;
}
err = bt_bap_stream_start(default_stream);
if (err) {
shell_error(sh, "Unable to start Channel");
return -ENOEXEC;
}
return 0;
}
static int cmd_disable(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (default_stream == NULL) {
shell_error(sh, "No stream selected");
return -ENOEXEC;
}
err = bt_bap_stream_disable(default_stream);
if (err) {
shell_error(sh, "Unable to disable Channel");
return -ENOEXEC;
}
return 0;
}
#if defined(CONFIG_BT_BAP_UNICAST_CLIENT)
static void conn_list_eps(struct bt_conn *conn, void *data)
{
const struct shell *sh = (const struct shell *)data;
uint8_t conn_index = bt_conn_index(conn);
shell_print(sh, "Conn: %p", conn);
shell_print(sh, " Sinks:");
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0
for (size_t i = 0U; i < ARRAY_SIZE(snks[conn_index]); i++) {
const struct bt_bap_ep *ep = snks[conn_index][i];
if (ep != NULL) {
shell_print(sh, " #%u: ep %p", i, ep);
}
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */
#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0
shell_print(sh, " Sources:");
for (size_t i = 0U; i < ARRAY_SIZE(srcs[conn_index]); i++) {
const struct bt_bap_ep *ep = srcs[conn_index][i];
if (ep != NULL) {
shell_print(sh, " #%u: ep %p", i, ep);
}
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
#if defined(CONFIG_BT_BAP_UNICAST_CLIENT)
static int cmd_list(const struct shell *sh, size_t argc, char *argv[])
{
shell_print(sh, "Configured Channels:");
for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) {
struct bt_bap_stream *stream = &unicast_streams[i].stream.bap_stream;
if (stream != NULL && stream->conn != NULL) {
shell_print(sh, " %s#%u: stream %p dir 0x%02x group %p",
stream == default_stream ? "*" : " ", i, stream,
stream_dir(stream), stream->group);
}
}
bt_conn_foreach(BT_CONN_TYPE_LE, conn_list_eps, (void *)sh);
return 0;
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
static int cmd_release(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (default_stream == NULL) {
shell_print(sh, "No stream selected");
return -ENOEXEC;
}
err = bt_bap_stream_release(default_stream);
if (err) {
shell_error(sh, "Unable to release Channel");
return -ENOEXEC;
}
return 0;
}
#endif /* CONFIG_BT_BAP_UNICAST */
#if IS_BAP_INITIATOR
static ssize_t parse_config_data_args(const struct shell *sh, size_t argn, size_t argc,
char *argv[], struct bt_audio_codec_cfg *codec_cfg)
{
for (; argn < argc; argn++) {
const char *arg = argv[argn];
unsigned long val;
int err = 0;
if (strcmp(arg, "freq") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
val = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Failed to parse freq from %s: %d", arg, err);
return -1;
}
if (val > UINT8_MAX) {
shell_error(sh, "Invalid freq value: %lu", val);
return -1;
}
err = bt_audio_codec_cfg_set_freq(codec_cfg,
(enum bt_audio_codec_cfg_freq)val);
if (err < 0) {
shell_error(sh, "Failed to set freq with value %lu: %d", val, err);
return -1;
}
} else if (strcmp(arg, "dur") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
val = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Failed to parse dur from %s: %d", arg, err);
return -1;
}
if (val > UINT8_MAX) {
shell_error(sh, "Invalid dur value: %lu", val);
return -1;
}
err = bt_audio_codec_cfg_set_frame_dur(
codec_cfg, (enum bt_audio_codec_cfg_frame_dur)val);
if (err < 0) {
shell_error(sh, "Failed to set dur with value %lu: %d", val, err);
return -1;
}
} else if (strcmp(arg, "chan_alloc") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
val = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Failed to parse chan alloc from %s: %d", arg, err);
return -1;
}
if (val > UINT32_MAX) {
shell_error(sh, "Invalid chan alloc value: %lu", val);
return -1;
}
err = bt_audio_codec_cfg_set_chan_allocation(codec_cfg,
(enum bt_audio_location)val);
if (err < 0) {
shell_error(sh, "Failed to set chan alloc with value %lu: %d", val,
err);
return -1;
}
} else if (strcmp(arg, "frame_len") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
val = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Failed to frame len from %s: %d", arg, err);
return -1;
}
if (val > UINT16_MAX) {
shell_error(sh, "Invalid frame len value: %lu", val);
return -1;
}
err = bt_audio_codec_cfg_set_octets_per_frame(codec_cfg, (uint16_t)val);
if (err < 0) {
shell_error(sh, "Failed to set frame len with value %lu: %d", val,
err);
return -1;
}
} else if (strcmp(arg, "frame_blks") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
val = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Failed to parse frame blks from %s: %d", arg, err);
return -1;
}
if (val > UINT8_MAX) {
shell_error(sh, "Invalid frame blks value: %lu", val);
return -1;
}
err = bt_audio_codec_cfg_set_frame_blocks_per_sdu(codec_cfg, (uint8_t)val);
if (err < 0) {
shell_error(sh, "Failed to set frame blks with value %lu: %d", val,
err);
return -1;
}
} else { /* we are no longer parsing codec config values */
/* Decrement to return taken argument */
argn--;
break;
}
}
return argn;
}
static ssize_t parse_config_meta_args(const struct shell *sh, size_t argn, size_t argc,
char *argv[], struct bt_audio_codec_cfg *codec_cfg)
{
for (; argn < argc; argn++) {
const char *arg = argv[argn];
unsigned long val;
int err = 0;
if (strcmp(arg, "pref_ctx") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
val = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Failed to parse pref ctx from %s: %d", arg, err);
return -1;
}
if (val > UINT16_MAX) {
shell_error(sh, "Invalid pref ctx value: %lu", val);
return -1;
}
err = bt_audio_codec_cfg_meta_set_pref_context(codec_cfg,
(enum bt_audio_context)val);
if (err < 0) {
shell_error(sh, "Failed to set pref ctx with value %lu: %d", val,
err);
return -1;
}
} else if (strcmp(arg, "stream_ctx") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
val = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Failed to parse stream ctx from %s: %d", arg, err);
return -1;
}
if (val > UINT16_MAX) {
shell_error(sh, "Invalid stream ctx value: %lu", val);
return -1;
}
err = bt_audio_codec_cfg_meta_set_stream_context(
codec_cfg, (enum bt_audio_context)val);
if (err < 0) {
shell_error(sh, "Failed to set stream ctx with value %lu: %d", val,
err);
return -1;
}
} else if (strcmp(arg, "program_info") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
err = bt_audio_codec_cfg_meta_set_program_info(codec_cfg, arg, strlen(arg));
if (err != 0) {
shell_error(sh, "Failed to set program info with value %s: %d", arg,
err);
return -1;
}
} else if (strcmp(arg, "stream_lang") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
if (strlen(arg) != 3) {
shell_error(sh, "Failed to parse stream lang from %s", arg);
return -1;
}
val = sys_get_le24(arg);
err = bt_audio_codec_cfg_meta_set_stream_lang(codec_cfg, (uint32_t)val);
if (err < 0) {
shell_error(sh, "Failed to set stream lang with value %lu: %d", val,
err);
return -1;
}
} else if (strcmp(arg, "ccid_list") == 0) {
uint8_t ccid_list[CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE];
size_t ccid_list_len;
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
ccid_list_len = hex2bin(arg, strlen(arg), ccid_list, sizeof(ccid_list));
if (ccid_list_len == 0) {
shell_error(sh, "Failed to parse ccid list from %s", arg);
return -1;
}
err = bt_audio_codec_cfg_meta_set_ccid_list(codec_cfg, ccid_list,
ccid_list_len);
if (err < 0) {
shell_error(sh, "Failed to set ccid list with value %s: %d", arg,
err);
return -1;
}
} else if (strcmp(arg, "parental_rating") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
val = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Failed to parse parental rating from %s: %d", arg,
err);
return -1;
}
if (val > UINT8_MAX) {
shell_error(sh, "Invalid parental rating value: %lu", val);
return -1;
}
err = bt_audio_codec_cfg_meta_set_parental_rating(
codec_cfg, (enum bt_audio_parental_rating)val);
if (err < 0) {
shell_error(sh, "Failed to set parental rating with value %lu: %d",
val, err);
return -1;
}
} else if (strcmp(arg, "program_info_uri") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
err = bt_audio_codec_cfg_meta_set_program_info_uri(codec_cfg, arg,
strlen(arg));
if (err < 0) {
shell_error(sh, "Failed to set program info URI with value %s: %d",
arg, err);
return -1;
}
} else if (strcmp(arg, "audio_active_state") == 0) {
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
val = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Failed to parse audio active state from %s: %d",
arg, err);
return -1;
}
if (val > UINT8_MAX) {
shell_error(sh, "Invalid audio active state value: %lu", val);
return -1;
}
err = bt_audio_codec_cfg_meta_set_audio_active_state(
codec_cfg, (enum bt_audio_active_state)val);
if (err < 0) {
shell_error(sh,
"Failed to set audio active state with value %lu: %d",
val, err);
return -1;
}
} else if (strcmp(arg, "bcast_flag") == 0) {
err = bt_audio_codec_cfg_meta_set_bcast_audio_immediate_rend_flag(
codec_cfg);
if (err < 0) {
shell_error(sh, "Failed to set audio active state: %d", err);
return -1;
}
} else if (strcmp(arg, "extended") == 0) {
uint8_t extended[CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE];
size_t extended_len;
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
extended_len = hex2bin(arg, strlen(arg), extended, sizeof(extended));
if (extended_len == 0) {
shell_error(sh, "Failed to parse extended meta from %s", arg);
return -1;
}
err = bt_audio_codec_cfg_meta_set_extended(codec_cfg, extended,
extended_len);
if (err < 0) {
shell_error(sh, "Failed to set extended meta with value %s: %d",
arg, err);
return -1;
}
} else if (strcmp(arg, "vendor") == 0) {
uint8_t vendor[CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE];
size_t vendor_len;
if (++argn == argc) {
shell_help(sh);
return -1;
}
arg = argv[argn];
vendor_len = hex2bin(arg, strlen(arg), vendor, sizeof(vendor));
if (vendor_len == 0) {
shell_error(sh, "Failed to parse vendor meta from %s", arg);
return -1;
}
err = bt_audio_codec_cfg_meta_set_vendor(codec_cfg, vendor, vendor_len);
if (err < 0) {
shell_error(sh, "Failed to set vendor meta with value %s: %d", arg,
err);
return -1;
}
} else { /* we are no longer parsing codec config meta values */
/* Decrement to return taken argument */
argn--;
break;
}
}
return argn;
}
static int cmd_preset(const struct shell *sh, size_t argc, char *argv[])
{
const struct named_lc3_preset *named_preset;
enum bt_audio_dir dir;
bool unicast = true;
if (!strcmp(argv[1], "sink")) {
dir = BT_AUDIO_DIR_SINK;
named_preset = &default_sink_preset;
} else if (!strcmp(argv[1], "source")) {
dir = BT_AUDIO_DIR_SOURCE;
named_preset = &default_source_preset;
} else if (!strcmp(argv[1], "broadcast")) {
unicast = false;
dir = BT_AUDIO_DIR_SOURCE;
named_preset = &default_broadcast_source_preset;
} else {
shell_error(sh, "Unsupported dir: %s", argv[1]);
return -ENOEXEC;
}
if (argc > 2) {
struct bt_audio_codec_cfg *codec_cfg;
named_preset = bap_get_named_preset(unicast, dir, argv[2]);
if (named_preset == NULL) {
shell_error(sh, "Unable to parse named_preset %s", argv[2]);
return -ENOEXEC;
}
if (!strcmp(argv[1], "sink")) {
named_preset = memcpy(&default_sink_preset, named_preset,
sizeof(default_sink_preset));
codec_cfg = &default_sink_preset.preset.codec_cfg;
} else if (!strcmp(argv[1], "source")) {
named_preset = memcpy(&default_source_preset, named_preset,
sizeof(default_sink_preset));
codec_cfg = &default_source_preset.preset.codec_cfg;
} else if (!strcmp(argv[1], "broadcast")) {
named_preset = memcpy(&default_broadcast_source_preset, named_preset,
sizeof(default_sink_preset));
codec_cfg = &default_broadcast_source_preset.preset.codec_cfg;
} else {
shell_error(sh, "Invalid dir: %s", argv[1]);
return -ENOEXEC;
}
if (argc > 3) {
struct bt_audio_codec_cfg codec_cfg_backup;
memcpy(&codec_cfg_backup, codec_cfg, sizeof(codec_cfg_backup));
for (size_t argn = 3; argn < argc; argn++) {
const char *arg = argv[argn];
if (strcmp(arg, "config") == 0) {
ssize_t ret;
if (++argn == argc) {
shell_help(sh);
memcpy(codec_cfg, &codec_cfg_backup,
sizeof(codec_cfg_backup));
return SHELL_CMD_HELP_PRINTED;
}
ret = parse_config_data_args(sh, argn, argc, argv,
codec_cfg);
if (ret < 0) {
memcpy(codec_cfg, &codec_cfg_backup,
sizeof(codec_cfg_backup));
return -ENOEXEC;
}
argn = ret;
} else if (strcmp(arg, "meta") == 0) {
ssize_t ret;
if (++argn == argc) {
shell_help(sh);
memcpy(codec_cfg, &codec_cfg_backup,
sizeof(codec_cfg_backup));
return SHELL_CMD_HELP_PRINTED;
}
ret = parse_config_meta_args(sh, argn, argc, argv,
codec_cfg);
if (ret < 0) {
memcpy(codec_cfg, &codec_cfg_backup,
sizeof(codec_cfg_backup));
return -ENOEXEC;
}
argn = ret;
} else {
shell_error(sh, "Invalid argument: %s", arg);
shell_help(sh);
return -ENOEXEC;
}
}
}
}
shell_print(sh, "%s", named_preset->name);
print_codec_cfg(ctx_shell, 0, &named_preset->preset.codec_cfg);
print_qos(ctx_shell, &named_preset->preset.qos);
return 0;
}
#endif /* IS_BAP_INITIATOR */
#if defined(CONFIG_BT_BAP_BROADCAST_SINK)
#define INVALID_BROADCAST_ID (BT_AUDIO_BROADCAST_ID_MAX + 1)
#define SYNC_RETRY_COUNT 6 /* similar to retries for connections */
#define PA_SYNC_SKIP 5
static struct broadcast_sink_auto_scan {
struct broadcast_sink *broadcast_sink;
uint32_t broadcast_id;
} auto_scan = {
.broadcast_id = INVALID_BROADCAST_ID,
};
static void clear_auto_scan(void)
{
if (auto_scan.broadcast_id != INVALID_BROADCAST_ID) {
memset(&auto_scan, 0, sizeof(auto_scan));
auto_scan.broadcast_id = INVALID_BROADCAST_ID;
}
}
static uint16_t interval_to_sync_timeout(uint16_t interval)
{
uint32_t interval_ms;
uint16_t timeout;
/* Ensure that the following calculation does not overflow silently */
__ASSERT(SYNC_RETRY_COUNT < 10, "SYNC_RETRY_COUNT shall be less than 10");
/* Add retries and convert to unit in 10's of ms */
interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(interval);
timeout = (interval_ms * SYNC_RETRY_COUNT) / 10;
/* Enforce restraints */
timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, BT_GAP_PER_ADV_MAX_TIMEOUT);
return timeout;
}
static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data)
{
const struct bt_le_scan_recv_info *info = user_data;
char le_addr[BT_ADDR_LE_STR_LEN];
struct bt_uuid_16 adv_uuid;
uint32_t broadcast_id;
if (data->type != BT_DATA_SVC_DATA16) {
return true;
}
if (data->data_len < BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE) {
return true;
}
if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) {
return true;
}
if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO)) {
return true;
}
broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16);
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
shell_print(ctx_shell, "Found broadcaster with ID 0x%06X and addr %s and sid 0x%02X",
broadcast_id, le_addr, info->sid);
if (auto_scan.broadcast_id == broadcast_id && auto_scan.broadcast_sink != NULL &&
auto_scan.broadcast_sink->pa_sync == NULL) {
struct bt_le_per_adv_sync_param create_params = {0};
int err;
err = bt_le_scan_stop();
if (err != 0) {
shell_error(ctx_shell, "Could not stop scan: %d", err);
}
bt_addr_le_copy(&create_params.addr, info->addr);
create_params.options = BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE;
create_params.sid = info->sid;
create_params.skip = PA_SYNC_SKIP;
create_params.timeout = interval_to_sync_timeout(info->interval);
shell_print(ctx_shell, "Attempting to PA sync to the broadcaster");
err = bt_le_per_adv_sync_create(&create_params, &auto_scan.broadcast_sink->pa_sync);
if (err != 0) {
shell_error(ctx_shell, "Could not create Broadcast PA sync: %d", err);
}
}
/* Stop parsing */
return false;
}
static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad)
{
if (passes_scan_filter(info, ad)) {
bt_data_parse(ad, scan_check_and_sync_broadcast, (void *)info);
}
}
static void base_recv(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base,
size_t base_size)
{
/* Don't print duplicates */
if (base_size != default_broadcast_sink.base_size ||
memcmp(base, &default_broadcast_sink.received_base, base_size) != 0) {
shell_print(ctx_shell, "Received BASE from sink %p:", sink);
(void)memcpy(&default_broadcast_sink.received_base, base, base_size);
default_broadcast_sink.base_size = base_size;
print_base(base);
}
}
static void syncable(struct bt_bap_broadcast_sink *sink, const struct bt_iso_biginfo *biginfo)
{
if (default_broadcast_sink.bap_sink == sink) {
if (default_broadcast_sink.syncable) {
return;
}
shell_print(ctx_shell, "Sink %p is ready to sync %s encryption", sink,
biginfo->encryption ? "with" : "without");
default_broadcast_sink.syncable = true;
}
}
static void bap_pa_sync_synced_cb(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info)
{
if (auto_scan.broadcast_sink != NULL && sync == auto_scan.broadcast_sink->pa_sync) {
shell_print(ctx_shell, "PA synced to broadcast with broadcast ID 0x%06x",
auto_scan.broadcast_id);
if (auto_scan.broadcast_sink->bap_sink == NULL) {
shell_print(ctx_shell, "Attempting to sync to the BIG");
int err;
err = bt_bap_broadcast_sink_create(sync, auto_scan.broadcast_id,
&auto_scan.broadcast_sink->bap_sink);
if (err != 0) {
shell_error(ctx_shell, "Could not create broadcast sink: %d", err);
}
} else {
shell_print(ctx_shell, "BIG is already synced");
}
}
clear_auto_scan();
}
static void bap_pa_sync_terminated_cb(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_term_info *info)
{
if (default_broadcast_sink.pa_sync == sync) {
default_broadcast_sink.syncable = false;
(void)memset(&default_broadcast_sink.received_base, 0,
sizeof(default_broadcast_sink.received_base));
}
clear_auto_scan();
}
static void broadcast_scan_timeout_cb(void)
{
shell_print(ctx_shell, "Scan timeout");
clear_auto_scan();
}
static struct bt_bap_broadcast_sink_cb sink_cbs = {
.base_recv = base_recv,
.syncable = syncable,
};
static struct bt_le_per_adv_sync_cb bap_pa_sync_cb = {
.synced = bap_pa_sync_synced_cb,
.term = bap_pa_sync_terminated_cb,
};
static struct bt_le_scan_cb bap_scan_cb = {
.timeout = broadcast_scan_timeout_cb,
.recv = broadcast_scan_recv,
};
#endif /* CONFIG_BT_BAP_BROADCAST_SINK */
#if defined(CONFIG_BT_AUDIO_RX)
static unsigned long recv_stats_interval = 100U;
static void audio_recv(struct bt_bap_stream *stream,
const struct bt_iso_recv_info *info,
struct net_buf *buf)
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(stream);
sh_stream->rx_cnt++;
if (info->ts == sh_stream->last_info.ts) {
sh_stream->dup_ts++;
}
if (info->seq_num == sh_stream->last_info.seq_num) {
sh_stream->dup_psn++;
}
if (info->flags & BT_ISO_FLAGS_ERROR) {
sh_stream->err_pkts++;
}
if (info->flags & BT_ISO_FLAGS_LOST) {
sh_stream->lost_pkts++;
}
if ((sh_stream->rx_cnt % recv_stats_interval) == 0) {
shell_print(ctx_shell,
"[%zu]: Incoming audio on stream %p len %u ts %u seq_num %u flags %u "
"(dup ts %zu; dup psn %zu, err_pkts %zu, lost_pkts %zu)",
sh_stream->rx_cnt, stream, buf->len, info->ts, info->seq_num,
info->flags, sh_stream->dup_ts, sh_stream->dup_psn, sh_stream->err_pkts,
sh_stream->lost_pkts);
}
(void)memcpy(&sh_stream->last_info, info, sizeof(sh_stream->last_info));
}
#endif /* CONFIG_BT_AUDIO_RX */
#if defined(CONFIG_BT_BAP_UNICAST)
static void stream_enabled_cb(struct bt_bap_stream *stream)
{
shell_print(ctx_shell, "Stream %p enabled", stream);
if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER)) {
struct bt_bap_ep_info ep_info;
struct bt_conn_info conn_info;
int err;
err = bt_conn_get_info(stream->conn, &conn_info);
if (err != 0) {
shell_error(ctx_shell, "Failed to get conn info: %d", err);
return;
}
if (conn_info.role == BT_CONN_ROLE_CENTRAL) {
return; /* We also want to autonomously start the stream as the server */
}
err = bt_bap_ep_get_info(stream->ep, &ep_info);
if (err != 0) {
shell_error(ctx_shell, "Failed to get ep info: %d", err);
return;
}
if (ep_info.dir == BT_AUDIO_DIR_SINK) {
/* Automatically do the receiver start ready operation */
err = bt_bap_stream_start(stream);
if (err != 0) {
shell_error(ctx_shell, "Failed to start stream: %d", err);
return;
}
}
}
}
#endif /* CONFIG_BT_BAP_UNICAST */
static void stream_started_cb(struct bt_bap_stream *bap_stream)
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(bap_stream);
#if defined(CONFIG_BT_AUDIO_TX)
sh_stream->connected_at_ticks = k_uptime_ticks();
#if defined(CONFIG_LIBLC3)
atomic_set(&sh_stream->lc3_enqueue_cnt, PRIME_COUNT);
sh_stream->lc3_sdu_cnt = 0U;
#endif /* CONFIG_LIBLC3 */
#endif /* CONFIG_BT_AUDIO_TX */
printk("Stream %p started\n", bap_stream);
#if defined(CONFIG_BT_AUDIO_RX)
sh_stream->lost_pkts = 0U;
sh_stream->err_pkts = 0U;
sh_stream->dup_psn = 0U;
sh_stream->rx_cnt = 0U;
sh_stream->dup_ts = 0U;
#endif
}
static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason)
{
printk("Stream %p stopped with reason 0x%02X\n", stream, reason);
#if defined(CONFIG_LIBLC3) && defined(CONFIG_BT_AUDIO_TX)
clear_lc3_sine_data(stream);
#endif /* CONFIG_LIBLC3 && CONFIG_BT_AUDIO_TX*/
#if defined(CONFIG_BT_BAP_BROADCAST_SINK)
struct shell_stream *sh_stream = shell_stream_from_bap_stream(stream);
if (IS_ARRAY_ELEMENT(broadcast_sink_streams, sh_stream)) {
if (default_broadcast_sink.stream_cnt != 0) {
default_broadcast_sink.stream_cnt--;
}
if (default_broadcast_sink.stream_cnt == 0) {
/* All streams in the broadcast sink has been terminated */
default_broadcast_sink.syncable = true;
memset(&default_broadcast_sink.received_base, 0,
sizeof(default_broadcast_sink.received_base));
default_broadcast_sink.broadcast_id = 0;
default_broadcast_sink.syncable = false;
}
}
#endif /* CONFIG_BT_BAP_BROADCAST_SINK */
}
#if defined(CONFIG_BT_BAP_UNICAST)
static void stream_configured_cb(struct bt_bap_stream *stream,
const struct bt_audio_codec_qos_pref *pref)
{
#if defined(CONFIG_LIBLC3)
if (stream->codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) {
struct shell_stream *sh_stream = shell_stream_from_bap_stream(stream);
int ret;
ret = bt_audio_codec_cfg_get_freq(stream->codec_cfg);
if (ret > 0) {
ret = bt_audio_codec_cfg_freq_to_freq_hz(ret);
if (ret > 0) {
if (ret == 8000 || ret == 16000 || ret == 24000 || ret == 32000 ||
ret == 48000) {
sh_stream->lc3_freq_hz = (uint32_t)ret;
} else {
shell_error(ctx_shell, "Unsupported frequency for LC3: %d",
ret);
sh_stream->lc3_freq_hz = 0U;
}
} else {
shell_error(ctx_shell, "Invalid frequency: %d", ret);
sh_stream->lc3_freq_hz = 0U;
}
} else {
shell_error(ctx_shell, "Could not get frequency: %d", ret);
sh_stream->lc3_freq_hz = 0U;
}
ret = bt_audio_codec_cfg_get_frame_dur(stream->codec_cfg);
if (ret > 0) {
ret = bt_audio_codec_cfg_frame_dur_to_frame_dur_us(ret);
if (ret > 0) {
sh_stream->lc3_frame_duration_us = (uint32_t)ret;
} else {
shell_error(ctx_shell, "Invalid frame duration: %d", ret);
sh_stream->lc3_frame_duration_us = 0U;
}
} else {
shell_error(ctx_shell, "Could not get frame duration: %d", ret);
sh_stream->lc3_frame_duration_us = 0U;
}
ret = bt_audio_codec_cfg_get_frame_blocks_per_sdu(stream->codec_cfg, true);
if (ret > 0) {
sh_stream->lc3_frames_per_sdu = (uint8_t)ret;
} else {
shell_error(ctx_shell, "Could not get frame blocks per SDU: %d", ret);
sh_stream->lc3_frames_per_sdu = 0U;
}
ret = bt_audio_codec_cfg_get_octets_per_frame(stream->codec_cfg);
if (ret > 0) {
sh_stream->lc3_octets_per_frame = (uint16_t)ret;
} else {
shell_error(ctx_shell, "Could not get octets per frame: %d", ret);
sh_stream->lc3_octets_per_frame = 0U;
}
}
#endif /* CONFIG_LIBLC3 */
shell_print(ctx_shell, "Stream %p configured\n", stream);
}
static void stream_released_cb(struct bt_bap_stream *stream)
{
shell_print(ctx_shell, "Stream %p released\n", stream);
#if defined(CONFIG_BT_BAP_UNICAST_CLIENT)
if (default_unicast_group != NULL) {
bool group_can_be_deleted = true;
for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) {
const struct bt_bap_stream *bap_stream =
bap_stream_from_shell_stream(&unicast_streams[i]);
if (bap_stream->ep != NULL) {
struct bt_bap_ep_info ep_info;
int err;
err = bt_bap_ep_get_info(bap_stream->ep, &ep_info);
if (err == 0 && ep_info.state != BT_BAP_EP_STATE_CODEC_CONFIGURED &&
ep_info.state != BT_BAP_EP_STATE_IDLE) {
group_can_be_deleted = false;
break;
}
}
}
if (group_can_be_deleted) {
int err;
shell_print(ctx_shell, "All streams released, deleting group\n");
err = bt_bap_unicast_group_delete(default_unicast_group);
if (err != 0) {
shell_error(ctx_shell, "Failed to delete unicast group: %d", err);
} else {
default_unicast_group = NULL;
}
}
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
#if defined(CONFIG_LIBLC3) && defined(CONFIG_BT_AUDIO_TX)
/* stop sending */
clear_lc3_sine_data(stream);
#endif /* CONFIG_LIBLC3 && defined(CONFIG_BT_AUDIO_TX) */
}
#endif /* CONFIG_BT_BAP_UNICAST */
static struct bt_bap_stream_ops stream_ops = {
#if defined(CONFIG_BT_AUDIO_RX)
.recv = audio_recv,
#endif /* CONFIG_BT_AUDIO_RX */
#if defined(CONFIG_BT_BAP_UNICAST)
.configured = stream_configured_cb,
.released = stream_released_cb,
.enabled = stream_enabled_cb,
#endif /* CONFIG_BT_BAP_UNICAST */
.started = stream_started_cb,
.stopped = stream_stopped_cb,
#if defined(CONFIG_LIBLC3) && defined(CONFIG_BT_AUDIO_TX)
.sent = lc3_sent_cb,
#endif
};
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
static int cmd_select_broadcast_source(const struct shell *sh, size_t argc,
char *argv[])
{
unsigned long index;
int err = 0;
index = shell_strtoul(argv[1], 0, &err);
if (err != 0) {
shell_error(sh, "Could not parse index: %d", err);
return -ENOEXEC;
}
if (index > ARRAY_SIZE(broadcast_source_streams)) {
shell_error(sh, "Invalid index: %lu", index);
return -ENOEXEC;
}
default_stream = bap_stream_from_shell_stream(&broadcast_source_streams[index]);
return 0;
}
static int cmd_create_broadcast(const struct shell *sh, size_t argc,
char *argv[])
{
struct bt_bap_broadcast_source_stream_param
stream_params[ARRAY_SIZE(broadcast_source_streams)];
struct bt_bap_broadcast_source_subgroup_param subgroup_param;
struct bt_bap_broadcast_source_param create_param = {0};
const struct named_lc3_preset *named_preset;
int err;
if (default_source.bap_source != NULL) {
shell_info(sh, "Broadcast source already created");
return -ENOEXEC;
}
named_preset = &default_broadcast_source_preset;
for (size_t i = 1U; i < argc; i++) {
char *arg = argv[i];
if (strcmp(arg, "enc") == 0) {
if (argc > i) {
size_t bcode_len;
i++;
arg = argv[i];
bcode_len = hex2bin(arg, strlen(arg),
create_param.broadcast_code,
sizeof(create_param.broadcast_code));
if (bcode_len != sizeof(create_param.broadcast_code)) {
shell_error(sh, "Invalid Broadcast Code Length: %zu",
bcode_len);
return -ENOEXEC;
}
create_param.encryption = true;
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
} else if (strcmp(arg, "preset") == 0) {
if (argc > i) {
i++;
arg = argv[i];
named_preset = bap_get_named_preset(false, BT_AUDIO_DIR_SOURCE,
arg);
if (named_preset == NULL) {
shell_error(sh, "Unable to parse named_preset %s",
arg);
return -ENOEXEC;
}
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
}
copy_broadcast_source_preset(&default_source, named_preset);
(void)memset(stream_params, 0, sizeof(stream_params));
for (size_t i = 0; i < ARRAY_SIZE(stream_params); i++) {
stream_params[i].stream =
bap_stream_from_shell_stream(&broadcast_source_streams[i]);
}
subgroup_param.params_count = ARRAY_SIZE(stream_params);
subgroup_param.params = stream_params;
subgroup_param.codec_cfg = &default_source.codec_cfg;
create_param.params_count = 1U;
create_param.params = &subgroup_param;
create_param.qos = &default_source.qos;
err = bt_bap_broadcast_source_create(&create_param, &default_source.bap_source);
if (err != 0) {
shell_error(sh, "Unable to create broadcast source: %d", err);
return err;
}
shell_print(sh, "Broadcast source created: preset %s",
named_preset->name);
if (default_stream == NULL) {
default_stream = bap_stream_from_shell_stream(&broadcast_source_streams[0]);
}
return 0;
}
static int cmd_start_broadcast(const struct shell *sh, size_t argc,
char *argv[])
{
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
int err;
if (adv == NULL) {
shell_info(sh, "Extended advertising set is NULL");
return -ENOEXEC;
}
if (default_source.bap_source == NULL || default_source.is_cap) {
shell_info(sh, "Broadcast source not created");
return -ENOEXEC;
}
err = bt_bap_broadcast_source_start(default_source.bap_source, adv_sets[selected_adv]);
if (err != 0) {
shell_error(sh, "Unable to start broadcast source: %d", err);
return err;
}
return 0;
}
static int cmd_stop_broadcast(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (default_source.bap_source == NULL || default_source.is_cap) {
shell_info(sh, "Broadcast source not created");
return -ENOEXEC;
}
err = bt_bap_broadcast_source_stop(default_source.bap_source);
if (err != 0) {
shell_error(sh, "Unable to stop broadcast source: %d", err);
return err;
}
return 0;
}
static int cmd_delete_broadcast(const struct shell *sh, size_t argc,
char *argv[])
{
int err;
if (default_source.bap_source == NULL || default_source.is_cap) {
shell_info(sh, "Broadcast source not created");
return -ENOEXEC;
}
err = bt_bap_broadcast_source_delete(default_source.bap_source);
if (err != 0) {
shell_error(sh, "Unable to delete broadcast source: %d", err);
return err;
}
default_source.bap_source = NULL;
return 0;
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
#if defined(CONFIG_BT_BAP_BROADCAST_SINK)
static int cmd_create_broadcast_sink(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_le_per_adv_sync *per_adv_sync = per_adv_syncs[selected_per_adv_sync];
unsigned long broadcast_id;
int err = 0;
broadcast_id = shell_strtoul(argv[1], 0, &err);
if (err != 0) {
shell_error(sh, "Could not parse broadcast_id: %d", err);
return -ENOEXEC;
}
if (broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
shell_error(sh, "Invalid broadcast_id: %lu", broadcast_id);
return -ENOEXEC;
}
if (per_adv_sync == NULL) {
const struct bt_le_scan_param param = {
.type = BT_LE_SCAN_TYPE_ACTIVE,
.options = BT_LE_SCAN_OPT_NONE,
.interval = BT_GAP_SCAN_FAST_INTERVAL,
.window = BT_GAP_SCAN_FAST_WINDOW,
.timeout = 1000, /* 10ms units -> 10 second timeout */
};
shell_print(sh, "No PA sync available, starting scanning for broadcast_id");
err = bt_le_scan_start(&param, NULL);
if (err) {
shell_print(sh, "Fail to start scanning: %d", err);
return -ENOEXEC;
}
auto_scan.broadcast_sink = &default_broadcast_sink;
auto_scan.broadcast_id = broadcast_id;
} else {
shell_print(sh, "Creating broadcast sink with broadcast ID 0x%06X",
(uint32_t)broadcast_id);
err = bt_bap_broadcast_sink_create(per_adv_sync, (uint32_t)broadcast_id,
&default_broadcast_sink.bap_sink);
if (err != 0) {
shell_error(sh, "Failed to create broadcast sink: %d", err);
return -ENOEXEC;
}
}
return 0;
}
static int cmd_sync_broadcast(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_bap_stream *streams[ARRAY_SIZE(broadcast_sink_streams)];
uint8_t bcode[BT_AUDIO_BROADCAST_CODE_SIZE] = {0};
bool bcode_set = false;
uint32_t bis_bitfield;
size_t stream_cnt;
int err = 0;
bis_bitfield = 0;
stream_cnt = 0U;
for (size_t argn = 1U; argn < argc; argn++) {
const char *arg = argv[argn];
if (strcmp(argv[argn], "bcode") == 0) {
size_t len;
if (++argn == argc) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
arg = argv[argn];
len = hex2bin(arg, strlen(arg), bcode, sizeof(bcode));
if (len == 0) {
shell_print(sh, "Invalid broadcast code: %s", arg);
return -ENOEXEC;
}
bcode_set = true;
} else if (strcmp(argv[argn], "bcode_str") == 0) {
if (++argn == argc) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
arg = argv[argn];
if (strlen(arg) == 0U || strlen(arg) > sizeof(bcode)) {
shell_print(sh, "Invalid broadcast code: %s", arg);
return -ENOEXEC;
}
memcpy(bcode, arg, strlen(arg));
bcode_set = true;
} else {
unsigned long val;
val = shell_strtoul(arg, 0, &err);
if (err != 0) {
shell_error(sh, "Could not parse BIS index val: %d", err);
return -ENOEXEC;
}
if (!IN_RANGE(val, BT_ISO_BIS_INDEX_MIN, BT_ISO_BIS_INDEX_MAX)) {
shell_error(sh, "Invalid index: %lu", val);
return -ENOEXEC;
}
bis_bitfield |= BIT(val);
stream_cnt++;
}
}
if (default_broadcast_sink.bap_sink == NULL) {
shell_error(sh, "No sink available");
return -ENOEXEC;
}
(void)memset(streams, 0, sizeof(streams));
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
streams[i] = bap_stream_from_shell_stream(&broadcast_sink_streams[i]);
}
err = bt_bap_broadcast_sink_sync(default_broadcast_sink.bap_sink, bis_bitfield, streams,
bcode_set ? bcode : NULL);
if (err != 0) {
shell_error(sh, "Failed to sync to broadcast: %d", err);
return err;
}
default_broadcast_sink.stream_cnt = stream_cnt;
return 0;
}
static int cmd_stop_broadcast_sink(const struct shell *sh, size_t argc,
char *argv[])
{
int err;
if (default_broadcast_sink.bap_sink == NULL) {
shell_error(sh, "No sink available");
return -ENOEXEC;
}
err = bt_bap_broadcast_sink_stop(default_broadcast_sink.bap_sink);
if (err != 0) {
shell_error(sh, "Failed to stop sink: %d", err);
return err;
}
return err;
}
static int cmd_term_broadcast_sink(const struct shell *sh, size_t argc,
char *argv[])
{
int err;
if (default_broadcast_sink.bap_sink == NULL) {
shell_error(sh, "No sink available");
return -ENOEXEC;
}
err = bt_bap_broadcast_sink_delete(default_broadcast_sink.bap_sink);
if (err != 0) {
shell_error(sh, "Failed to term sink: %d", err);
return err;
}
default_broadcast_sink.bap_sink = NULL;
default_broadcast_sink.syncable = false;
return err;
}
#endif /* CONFIG_BT_BAP_BROADCAST_SINK */
static int cmd_set_loc(const struct shell *sh, size_t argc, char *argv[])
{
int err = 0;
enum bt_audio_dir dir;
enum bt_audio_location loc;
unsigned long loc_val;
if (!strcmp(argv[1], "sink")) {
dir = BT_AUDIO_DIR_SINK;
} else if (!strcmp(argv[1], "source")) {
dir = BT_AUDIO_DIR_SOURCE;
} else {
shell_error(sh, "Unsupported dir: %s", argv[1]);
return -ENOEXEC;
}
loc_val = shell_strtoul(argv[2], 16, &err);
if (err != 0) {
shell_error(sh, "Could not parse loc_val: %d", err);
return -ENOEXEC;
}
if (loc_val > BT_AUDIO_LOCATION_ANY) {
shell_error(sh, "Invalid location: %lu", loc_val);
return -ENOEXEC;
}
loc = loc_val;
err = bt_pacs_set_location(dir, loc);
if (err) {
shell_error(ctx_shell, "Set available contexts err %d", err);
return -ENOEXEC;
}
return 0;
}
static int cmd_context(const struct shell *sh, size_t argc, char *argv[])
{
int err = 0;
enum bt_audio_dir dir;
enum bt_audio_context ctx;
unsigned long ctx_val;
if (!strcmp(argv[1], "sink")) {
dir = BT_AUDIO_DIR_SINK;
} else if (!strcmp(argv[1], "source")) {
dir = BT_AUDIO_DIR_SOURCE;
} else {
shell_error(sh, "Unsupported dir: %s", argv[1]);
return -ENOEXEC;
}
ctx_val = shell_strtoul(argv[2], 16, &err);
if (err) {
shell_error(sh, "Could not parse context: %d", err);
return err;
}
if (ctx_val == BT_AUDIO_CONTEXT_TYPE_PROHIBITED ||
ctx_val > BT_AUDIO_CONTEXT_TYPE_ANY) {
shell_error(sh, "Invalid context: %lu", ctx_val);
return -ENOEXEC;
}
ctx = ctx_val;
if (!strcmp(argv[3], "supported")) {
err = bt_pacs_set_supported_contexts(dir, ctx);
if (err) {
shell_error(ctx_shell, "Set supported contexts err %d", err);
return err;
}
} else if (!strcmp(argv[3], "available")) {
err = bt_pacs_set_available_contexts(dir, ctx);
if (err) {
shell_error(ctx_shell, "Set available contexts err %d", err);
return err;
}
} else {
shell_error(sh, "Unsupported context type: %s", argv[3]);
return -ENOEXEC;
}
return 0;
}
static int cmd_init(const struct shell *sh, size_t argc, char *argv[])
{
int err, i;
ctx_shell = sh;
if (initialized) {
shell_print(sh, "Already initialized");
return -ENOEXEC;
}
#if defined(CONFIG_BT_BAP_UNICAST_SERVER)
bt_bap_unicast_server_register_cb(&unicast_server_cb);
#endif /* CONFIG_BT_BAP_UNICAST_SERVER */
#if defined(CONFIG_BT_PAC_SNK)
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink);
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &cap_source);
#endif /* CONFIG_BT_PAC_SNK */
if (IS_ENABLED(CONFIG_BT_PAC_SNK_LOC)) {
err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, LOCATION);
__ASSERT(err == 0, "Failed to set sink location: %d", err);
err = bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK,
CONTEXT);
__ASSERT(err == 0, "Failed to set sink supported contexts: %d",
err);
err = bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK,
CONTEXT);
__ASSERT(err == 0, "Failed to set sink available contexts: %d",
err);
}
if (IS_ENABLED(CONFIG_BT_PAC_SRC_LOC)) {
err = bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, LOCATION);
__ASSERT(err == 0, "Failed to set source location: %d", err);
err = bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SOURCE,
CONTEXT);
__ASSERT(err == 0, "Failed to set sink supported contexts: %d",
err);
err = bt_pacs_set_available_contexts(BT_AUDIO_DIR_SOURCE,
CONTEXT);
__ASSERT(err == 0,
"Failed to set source available contexts: %d",
err);
}
#if defined(CONFIG_BT_BAP_UNICAST)
for (i = 0; i < ARRAY_SIZE(unicast_streams); i++) {
bt_bap_stream_cb_register(bap_stream_from_shell_stream(&unicast_streams[i]),
&stream_ops);
if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) &&
IS_ENABLED(CONFIG_BT_CAP_INITIATOR)) {
/* If we use the cap initiator, we need to register the callbacks for CAP
* as well, as CAP will override and use the BAP callbacks if doing a CAP
* procedure
*/
bt_cap_stream_ops_register(&unicast_streams[i].stream, &stream_ops);
}
}
#endif /* CONFIG_BT_BAP_UNICAST */
#if defined(CONFIG_BT_BAP_BROADCAST_SINK)
bt_bap_broadcast_sink_register_cb(&sink_cbs);
bt_le_per_adv_sync_cb_register(&bap_pa_sync_cb);
bt_le_scan_cb_register(&bap_scan_cb);
for (i = 0; i < ARRAY_SIZE(broadcast_sink_streams); i++) {
bt_bap_stream_cb_register(bap_stream_from_shell_stream(&broadcast_sink_streams[i]),
&stream_ops);
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
for (i = 0; i < ARRAY_SIZE(broadcast_source_streams); i++) {
bt_bap_stream_cb_register(
bap_stream_from_shell_stream(&broadcast_source_streams[i]), &stream_ops);
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
initialized = true;
return 0;
}
#if defined(CONFIG_BT_AUDIO_TX)
#define DATA_MTU CONFIG_BT_ISO_TX_MTU
NET_BUF_POOL_FIXED_DEFINE(tx_pool, 1, DATA_MTU, CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
static int cmd_send(const struct shell *sh, size_t argc, char *argv[])
{
static uint8_t data[DATA_MTU - BT_ISO_CHAN_SEND_RESERVE];
int ret, len;
struct net_buf *buf;
if (default_stream == NULL) {
shell_error(sh, "Invalid (NULL) stream");
return -ENOEXEC;
}
if (default_stream->qos == NULL) {
shell_error(sh, "NULL stream QoS");
return -ENOEXEC;
}
if (argc > 1) {
len = hex2bin(argv[1], strlen(argv[1]), data, sizeof(data));
if (len > default_stream->qos->sdu) {
shell_print(sh, "Unable to send: len %d > %u MTU",
len, default_stream->qos->sdu);
return -ENOEXEC;
}
} else {
len = MIN(default_stream->qos->sdu, sizeof(data));
memset(data, 0xff, len);
}
buf = net_buf_alloc(&tx_pool, K_FOREVER);
net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);
net_buf_add_mem(buf, data, len);
ret = bt_bap_stream_send(default_stream, buf, get_next_seq_num(default_stream));
if (ret < 0) {
shell_print(sh, "Unable to send: %d", -ret);
net_buf_unref(buf);
return -ENOEXEC;
}
shell_print(sh, "Sending:");
shell_hexdump(sh, data, len);
return 0;
}
#if defined(CONFIG_LIBLC3)
static bool stream_start_sine_verify(const struct shell_stream *sh_stream)
{
const struct bt_bap_stream *bap_stream;
struct bt_bap_ep_info info;
int err;
if (sh_stream == NULL) {
return false;
}
bap_stream = &sh_stream->stream.bap_stream;
if (bap_stream->qos == NULL) {
return false;
}
err = bt_bap_ep_get_info(bap_stream->ep, &info);
if (err != 0) {
return false;
}
if (info.state != BT_BAP_EP_STATE_STREAMING) {
return false;
}
if (sh_stream->lc3_freq_hz != lc3_encoder_freq_hz ||
sh_stream->lc3_frame_duration_us != lc3_encoder_frame_duration_us) {
return false;
}
return true;
}
static int stream_start_sine(struct shell_stream *sh_stream)
{
int err;
k_work_init_delayable(&sh_stream->audio_send_work, lc3_audio_send_data);
err = k_work_schedule(&sh_stream->audio_send_work, K_NO_WAIT);
if (err < 0) {
return -ENOEXEC;
}
sh_stream->tx_active = true;
sh_stream->seq_num = get_next_seq_num(bap_stream_from_shell_stream(sh_stream));
return 0;
}
static int cmd_start_sine(const struct shell *sh, size_t argc, char *argv[])
{
bool start_all = false;
int err;
if (argc > 1) {
if (strcmp(argv[1], "all") == 0) {
start_all = true;
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
if (start_all) {
bool lc3_initialized = false;
for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) {
struct shell_stream *sh_stream = &unicast_streams[i];
struct bt_bap_stream *bap_stream = bap_stream_from_shell_stream(sh_stream);
if (!lc3_initialized) {
err = init_lc3_encoder(sh_stream);
if (err != 0) {
shell_error(sh, "Failed to init LC3 %d", err);
return -ENOEXEC;
}
lc3_initialized = true;
}
if (!stream_start_sine_verify(sh_stream)) {
continue;
}
err = stream_start_sine(sh_stream);
if (err != 0) {
shell_error(sh, "Failed to start TX for stream %p: %d", bap_stream,
err);
return err;
}
shell_print(sh, "Started transmitting on unicast stream %p", bap_stream);
}
for (size_t i = 0U; i < ARRAY_SIZE(broadcast_source_streams); i++) {
struct shell_stream *sh_stream = &broadcast_source_streams[i];
struct bt_bap_stream *bap_stream = bap_stream_from_shell_stream(sh_stream);
if (!lc3_initialized) {
err = init_lc3_encoder(sh_stream);
if (err != 0) {
shell_error(sh, "Failed to init LC3 %d", err);
return -ENOEXEC;
}
lc3_initialized = true;
}
if (!stream_start_sine_verify(sh_stream)) {
continue;
}
err = stream_start_sine(sh_stream);
if (err != 0) {
shell_error(sh, "Failed to start TX for stream %p: %d", bap_stream,
err);
return err;
}
shell_print(sh, "Started transmitting on broadcast stream %p", bap_stream);
}
} else {
struct shell_stream *sh_stream = shell_stream_from_bap_stream(default_stream);
err = init_lc3_encoder(sh_stream);
if (err != 0) {
shell_error(sh, "Failed to init LC3 %d", err);
return -ENOEXEC;
}
if (!stream_start_sine_verify(sh_stream)) {
shell_error(sh, "Invalid stream %p", default_stream);
return -ENOEXEC;
}
err = stream_start_sine(sh_stream);
if (err != 0) {
shell_error(sh, "Failed to start TX for stream %p: %d", default_stream,
err);
return err;
}
shell_print(sh, "Started transmitting on default_stream %p", default_stream);
}
return 0;
}
static int cmd_stop_sine(const struct shell *sh, size_t argc, char *argv[])
{
bool stop_all = false;
if (argc > 1) {
if (strcmp(argv[1], "all") == 0) {
stop_all = true;
} else {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
if (stop_all) {
for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) {
struct bt_bap_stream *bap_stream =
bap_stream_from_shell_stream(&unicast_streams[i]);
if (unicast_streams[i].tx_active) {
clear_lc3_sine_data(bap_stream);
shell_print(sh, "Stopped transmitting on stream %p", bap_stream);
}
}
for (size_t i = 0U; i < ARRAY_SIZE(broadcast_source_streams); i++) {
struct bt_bap_stream *bap_stream =
bap_stream_from_shell_stream(&broadcast_source_streams[i]);
if (unicast_streams[i].tx_active) {
clear_lc3_sine_data(bap_stream);
shell_print(sh, "Stopped transmitting on stream %p", bap_stream);
}
}
} else {
struct shell_stream *sh_stream = shell_stream_from_bap_stream(default_stream);
if (sh_stream->tx_active) {
clear_lc3_sine_data(default_stream);
shell_print(sh, "Stopped transmitting on stream %p", default_stream);
}
}
return 0;
}
#endif /* CONFIG_LIBLC3 */
#endif /* CONFIG_BT_AUDIO_TX */
#if defined(CONFIG_BT_AUDIO_RX)
static int cmd_recv_stats(const struct shell *sh, size_t argc, char *argv[])
{
if (argc == 1) {
shell_info(sh, "Current receive stats interval: %lu", recv_stats_interval);
} else {
int err = 0;
unsigned long interval;
interval = shell_strtoul(argv[1], 0, &err);
if (err != 0) {
shell_error(sh, "Could not parse interval: %d", err);
return -ENOEXEC;
}
if (interval == 0U) {
shell_error(sh, "Interval cannot be 0");
return -ENOEXEC;
}
recv_stats_interval = interval;
}
return 0;
}
#endif /* CONFIG_BT_AUDIO_RX */
#if defined(CONFIG_BT_BAP_UNICAST_SERVER)
static void print_ase_info(struct bt_bap_ep *ep, void *user_data)
{
struct bt_bap_ep_info info;
int err;
err = bt_bap_ep_get_info(ep, &info);
if (err == 0) {
printk("ASE info: id %u state %u dir %u\n", info.id, info.state, info.dir);
}
}
static int cmd_print_ase_info(const struct shell *sh, size_t argc, char *argv[])
{
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
bt_bap_unicast_server_foreach_ep(default_conn, print_ase_info, NULL);
return 0;
}
#endif /* CONFIG_BT_BAP_UNICAST_SERVER */
/* 31 is a unit separater - without t the tab is seemingly ignored*/
#define HELP_SEP "\n\31\t"
#define HELP_CFG_DATA \
"\n[config" HELP_SEP "[freq <frequency>]" HELP_SEP "[dur <duration>]" HELP_SEP \
"[chan_alloc <location>]" HELP_SEP "[frame_len <frame length>]" HELP_SEP \
"[frame_blks <frame blocks>]]"
#define HELP_CFG_META \
"\n[meta" HELP_SEP "[pref_ctx <context>]" HELP_SEP "[stream_ctx <context>]" HELP_SEP \
"[program_info <program info>]" HELP_SEP "[stream_lang <ISO 639-3 lang>]" HELP_SEP \
"[ccid_list <ccids>]" HELP_SEP "[parental_rating <rating>]" HELP_SEP \
"[program_info_uri <URI>]" HELP_SEP "[audio_active_state <state>]" HELP_SEP \
"[bcast_flag]" HELP_SEP "[extended <meta>]" HELP_SEP "[vendor <meta>]]"
SHELL_STATIC_SUBCMD_SET_CREATE(
bap_cmds, SHELL_CMD_ARG(init, NULL, NULL, cmd_init, 1, 0),
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
SHELL_CMD_ARG(select_broadcast, NULL, "<stream>", cmd_select_broadcast_source, 2, 0),
SHELL_CMD_ARG(create_broadcast, NULL, "[preset <preset_name>] [enc <broadcast_code>]",
cmd_create_broadcast, 1, 2),
SHELL_CMD_ARG(start_broadcast, NULL, "", cmd_start_broadcast, 1, 0),
SHELL_CMD_ARG(stop_broadcast, NULL, "", cmd_stop_broadcast, 1, 0),
SHELL_CMD_ARG(delete_broadcast, NULL, "", cmd_delete_broadcast, 1, 0),
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
#if defined(CONFIG_BT_BAP_BROADCAST_SINK)
SHELL_CMD_ARG(create_broadcast_sink, NULL, "0x<broadcast_id>", cmd_create_broadcast_sink, 2,
0),
SHELL_CMD_ARG(sync_broadcast, NULL,
"0x<bis_index> [[[0x<bis_index>] 0x<bis_index>] ...] "
"[bcode <broadcast code> || bcode_str <broadcast code as string>]",
cmd_sync_broadcast, 2, ARRAY_SIZE(broadcast_sink_streams) + 1),
SHELL_CMD_ARG(stop_broadcast_sink, NULL, "Stops broadcast sink", cmd_stop_broadcast_sink, 1,
0),
SHELL_CMD_ARG(term_broadcast_sink, NULL, "", cmd_term_broadcast_sink, 1, 0),
#endif /* CONFIG_BT_BAP_BROADCAST_SINK */
#if defined(CONFIG_BT_BAP_UNICAST)
#if defined(CONFIG_BT_BAP_UNICAST_CLIENT)
SHELL_CMD_ARG(discover, NULL, "[dir: sink, source]", cmd_discover, 1, 1),
SHELL_CMD_ARG(config, NULL,
"<direction: sink, source> <index> [loc <loc_bits>] [preset <preset_name>]",
cmd_config, 3, 4),
SHELL_CMD_ARG(stream_qos, NULL, "interval [framing] [latency] [pd] [sdu] [phy] [rtn]",
cmd_stream_qos, 2, 6),
SHELL_CMD_ARG(qos, NULL, "Send QoS configure for Unicast Group", cmd_qos, 1, 0),
SHELL_CMD_ARG(enable, NULL, "[context]", cmd_enable, 1, 1),
SHELL_CMD_ARG(stop, NULL, NULL, cmd_stop, 1, 0),
SHELL_CMD_ARG(list, NULL, NULL, cmd_list, 1, 0),
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
#if defined(CONFIG_BT_BAP_UNICAST_SERVER)
SHELL_CMD_ARG(print_ase_info, NULL, "Print ASE info for default connection",
cmd_print_ase_info, 0, 0),
#endif /* CONFIG_BT_BAP_UNICAST_SERVER */
SHELL_CMD_ARG(metadata, NULL, "[context]", cmd_metadata, 1, 1),
SHELL_CMD_ARG(start, NULL, NULL, cmd_start, 1, 0),
SHELL_CMD_ARG(disable, NULL, NULL, cmd_disable, 1, 0),
SHELL_CMD_ARG(release, NULL, NULL, cmd_release, 1, 0),
SHELL_CMD_ARG(select_unicast, NULL, "<stream>", cmd_select_unicast, 2, 0),
#endif /* CONFIG_BT_BAP_UNICAST */
#if IS_BAP_INITIATOR
SHELL_CMD_ARG(preset, NULL,
"<sink, source, broadcast> [preset] " HELP_CFG_DATA " " HELP_CFG_META,
cmd_preset, 2, 34),
#endif /* IS_BAP_INITIATOR */
#if defined(CONFIG_BT_AUDIO_TX)
SHELL_CMD_ARG(send, NULL, "Send to Audio Stream [data]", cmd_send, 1, 1),
#if defined(CONFIG_LIBLC3)
SHELL_CMD_ARG(start_sine, NULL, "Start sending a LC3 encoded sine wave [all]",
cmd_start_sine, 1, 1),
SHELL_CMD_ARG(stop_sine, NULL, "Stop sending a LC3 encoded sine wave [all]", cmd_stop_sine,
1, 1),
#endif /* CONFIG_LIBLC3 */
#endif /* CONFIG_BT_AUDIO_TX */
#if defined(CONFIG_BT_AUDIO_RX)
SHELL_CMD_ARG(recv_stats, NULL,
"Sets or gets the receive statistics reporting interval in # of packets",
cmd_recv_stats, 1, 1),
#endif /* CONFIG_BT_AUDIO_RX */
SHELL_COND_CMD_ARG(CONFIG_BT_PACS, set_location, NULL,
"<direction: sink, source> <location bitmask>", cmd_set_loc, 3, 0),
SHELL_COND_CMD_ARG(CONFIG_BT_PACS, set_context, NULL,
"<direction: sink, source>"
"<context bitmask> <type: supported, available>",
cmd_context, 4, 0),
SHELL_SUBCMD_SET_END);
static int cmd_bap(const struct shell *sh, size_t argc, char **argv)
{
if (argc > 1) {
shell_error(sh, "%s unknown parameter: %s",
argv[0], argv[1]);
} else {
shell_error(sh, "%s Missing subcommand", argv[0]);
}
return -ENOEXEC;
}
SHELL_CMD_ARG_REGISTER(bap, &bap_cmds, "Bluetooth BAP shell commands", cmd_bap, 1, 1);
static ssize_t connectable_ad_data_add(struct bt_data *data_array,
size_t data_array_size)
{
static const uint8_t ad_ext_uuid16[] = {
IF_ENABLED(CONFIG_BT_MICP_MIC_DEV, (BT_UUID_16_ENCODE(BT_UUID_MICS_VAL),))
IF_ENABLED(CONFIG_BT_ASCS, (BT_UUID_16_ENCODE(BT_UUID_ASCS_VAL),))
IF_ENABLED(CONFIG_BT_BAP_SCAN_DELEGATOR, (BT_UUID_16_ENCODE(BT_UUID_BASS_VAL),))
IF_ENABLED(CONFIG_BT_PACS, (BT_UUID_16_ENCODE(BT_UUID_PACS_VAL),))
IF_ENABLED(CONFIG_BT_GTBS, (BT_UUID_16_ENCODE(BT_UUID_GTBS_VAL),))
IF_ENABLED(CONFIG_BT_TBS, (BT_UUID_16_ENCODE(BT_UUID_TBS_VAL),))
IF_ENABLED(CONFIG_BT_VCP_VOL_REND, (BT_UUID_16_ENCODE(BT_UUID_VCS_VAL),))
IF_ENABLED(CONFIG_BT_HAS, (BT_UUID_16_ENCODE(BT_UUID_HAS_VAL),)) /* Shall be last */
};
size_t ad_len = 0;
if (IS_ENABLED(CONFIG_BT_ASCS)) {
static uint8_t ad_bap_announcement[8] = {
BT_UUID_16_ENCODE(BT_UUID_ASCS_VAL),
BT_AUDIO_UNICAST_ANNOUNCEMENT_TARGETED,
};
enum bt_audio_context snk_context, src_context;
snk_context = bt_pacs_get_available_contexts(BT_AUDIO_DIR_SINK);
sys_put_le16(snk_context, &ad_bap_announcement[3]);
src_context = bt_pacs_get_available_contexts(BT_AUDIO_DIR_SOURCE);
sys_put_le16(snk_context, &ad_bap_announcement[5]);
/* Metadata length */
ad_bap_announcement[7] = 0x00;
__ASSERT(data_array_size > ad_len, "No space for AD_BAP_ANNOUNCEMENT");
data_array[ad_len].type = BT_DATA_SVC_DATA16;
data_array[ad_len].data_len = ARRAY_SIZE(ad_bap_announcement);
data_array[ad_len].data = &ad_bap_announcement[0];
ad_len++;
}
if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR)) {
ad_len += cap_acceptor_ad_data_add(&data_array[ad_len], data_array_size - ad_len,
true);
}
if (IS_ENABLED(CONFIG_BT_GMAP)) {
ad_len += gmap_ad_data_add(&data_array[ad_len], data_array_size - ad_len);
}
if (ARRAY_SIZE(ad_ext_uuid16) > 0) {
size_t uuid16_size;
if (data_array_size <= ad_len) {
shell_warn(ctx_shell, "No space for AD_UUID16");
return ad_len;
}
data_array[ad_len].type = BT_DATA_UUID16_SOME;
if (IS_ENABLED(CONFIG_BT_HAS) && IS_ENABLED(CONFIG_BT_PRIVACY)) {
/* If the HA is in one of the GAP connectable modes and is using a
* resolvable private address, the HA shall not include the Hearing Access
* Service UUID in the Service UUID AD type field of the advertising data
* or scan response.
*/
uuid16_size = ARRAY_SIZE(ad_ext_uuid16) - BT_UUID_SIZE_16;
} else {
uuid16_size = ARRAY_SIZE(ad_ext_uuid16);
}
/* We can maximum advertise 127 16-bit UUIDs = 254 octets */
data_array[ad_len].data_len = MIN(uuid16_size, 254);
data_array[ad_len].data = &ad_ext_uuid16[0];
ad_len++;
}
return ad_len;
}
static ssize_t nonconnectable_ad_data_add(struct bt_data *data_array,
const size_t data_array_size)
{
static const uint8_t ad_ext_uuid16[] = {
IF_ENABLED(CONFIG_BT_PACS, (BT_UUID_16_ENCODE(BT_UUID_PACS_VAL),))
IF_ENABLED(CONFIG_BT_CAP_ACCEPTOR, (BT_UUID_16_ENCODE(BT_UUID_CAS_VAL),))
};
size_t ad_len = 0;
if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR)) {
static const uint8_t ad_cap_announcement[3] = {
BT_UUID_16_ENCODE(BT_UUID_CAS_VAL),
BT_AUDIO_UNICAST_ANNOUNCEMENT_TARGETED,
};
__ASSERT(data_array_size > ad_len, "No space for AD_CAP_ANNOUNCEMENT");
data_array[ad_len].type = BT_DATA_SVC_DATA16;
data_array[ad_len].data_len = ARRAY_SIZE(ad_cap_announcement);
data_array[ad_len].data = &ad_cap_announcement[0];
ad_len++;
}
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
if (default_source.bap_source != NULL && !default_source.is_cap) {
static uint8_t ad_bap_broadcast_announcement[5] = {
BT_UUID_16_ENCODE(BT_UUID_BROADCAST_AUDIO_VAL),
};
uint32_t broadcast_id;
int err;
err = bt_bap_broadcast_source_get_id(default_source.bap_source, &broadcast_id);
if (err != 0) {
printk("Unable to get broadcast ID: %d\n", err);
return -1;
}
sys_put_le24(broadcast_id, &ad_bap_broadcast_announcement[2]);
data_array[ad_len].type = BT_DATA_SVC_DATA16;
data_array[ad_len].data_len = ARRAY_SIZE(ad_bap_broadcast_announcement);
data_array[ad_len].data = ad_bap_broadcast_announcement;
ad_len++;
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
if (ARRAY_SIZE(ad_ext_uuid16) > 0) {
if (data_array_size <= ad_len) {
shell_warn(ctx_shell, "No space for AD_UUID16");
return ad_len;
}
data_array[ad_len].type = BT_DATA_UUID16_SOME;
data_array[ad_len].data_len = ARRAY_SIZE(ad_ext_uuid16);
data_array[ad_len].data = &ad_ext_uuid16[0];
ad_len++;
}
return ad_len;
}
ssize_t audio_ad_data_add(struct bt_data *data_array, const size_t data_array_size,
const bool discoverable, const bool connectable)
{
ssize_t ad_len = 0;
if (!discoverable) {
return 0;
}
if (connectable) {
ad_len += connectable_ad_data_add(data_array, data_array_size);
} else {
ad_len += nonconnectable_ad_data_add(data_array, data_array_size);
}
if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR)) {
ad_len += cap_initiator_ad_data_add(data_array, data_array_size, discoverable,
connectable);
}
return ad_len;
}
ssize_t audio_pa_data_add(struct bt_data *data_array,
const size_t data_array_size)
{
size_t ad_len = 0;
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
if (default_source.bap_source != NULL && !default_source.is_cap) {
/* Required size of the buffer depends on what has been
* configured. We just use the maximum size possible.
*/
NET_BUF_SIMPLE_DEFINE_STATIC(base_buf, UINT8_MAX);
int err;
err = bt_bap_broadcast_source_get_base(default_source.bap_source, &base_buf);
if (err != 0) {
printk("Unable to get BASE: %d\n", err);
return -1;
}
data_array[ad_len].type = BT_DATA_SVC_DATA16;
data_array[ad_len].data_len = base_buf.len;
data_array[ad_len].data = base_buf.data;
ad_len++;
} else if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR)) {
return cap_initiator_pa_data_add(data_array, data_array_size);
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
return ad_len;
}