Bluetooth: CAP: Shell: Add proper broadcast commands

Adds additional broadcast commands and modifies the existing
ones to use the CAP API.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2024-02-05 12:50:51 +01:00 committed by Fabio Baltieri
parent 8c6e3a6d41
commit 8f0e648e48
4 changed files with 264 additions and 19 deletions

View file

@ -74,6 +74,7 @@ Using the CAP Initiator
When the Bluetooth stack has been initialized (:code:`bt init`), the Initiator can discover CAS and
the optionally included CSIS instance by calling (:code:`cap_initiator discover`).
The CAP initiator also supports broadcast audio as a source.
.. code-block:: console
@ -164,6 +165,53 @@ used.
uart:~$ cap_initiator unicast_stop all
Unicast stop completed
When doing broadcast
--------------------
To start a broadcast as the CAP initiator there are a few steps to be done:
1. Create and configure an extended advertising set with periodic advertising
2. Create and configure a broadcast source
3. Setup extended and periodic advertising data
The following commands will setup a CAP broadcast source using the 16_2_1 preset (defined by BAP):
.. code-block:: console
bt init
bap init
bt adv-create nconn-nscan ext-adv name
bt per-adv-param
cap_initiator ac_12 16_2_1
bt adv-data discov
bt per-adv-data
cap_initiator broadcast_start
The broadcast source is created by the :code:`cap_initiator ac_12`, :code:`cap_initiator ac_13`,
and :code:`cap_initiator ac_14` commands, configuring the broadcast source for the defined audio
configurations from BAP. The broadcast source can then be stopped with
:code:`cap_initiator broadcast_stop` or deleted with :code:`cap_initiator broadcast_delete`.
The metadata of the broadcast source can be updated at any time, including when it is already
streaming. To update the metadata the :code:`cap_initiator broadcast_update` command can be used.
The command takes an array of data, and the only requirement (besides having valid data) is that the
streaming context shall be set. For example to set the streaming context to media, the command can
be used as
.. code-block:: console
cap_initiator broadcast_update 03020400
CAP Broadcast source updated with new metadata. Update the advertised base via `bt per-adv-data`
bt per-adv-data
The :code:`bt per-adv-data` command should be used afterwards to update the data is the advertised
BASE. The data must be little-endian, so in the above example the metadata :code:`03020400` is
setting the metadata entry with :code:`03` as the length, :code:`02` as the type (streaming context)
and :code:`0400` as the value :code:`BT_AUDIO_CONTEXT_TYPE_MEDIA`
(which has the numeric value of 0x).
CAP Commander
*************

View file

@ -33,6 +33,9 @@ ssize_t csis_ad_data_add(struct bt_data *data, const size_t data_size, const boo
size_t cap_acceptor_ad_data_add(struct bt_data data[], size_t data_size, bool discoverable);
size_t gmap_ad_data_add(struct bt_data data[], size_t data_size);
size_t pbp_ad_data_add(struct bt_data data[], size_t data_size);
ssize_t cap_initiator_ad_data_add(struct bt_data *data_array, const size_t data_array_size,
const bool discoverable, const bool connectable);
ssize_t cap_initiator_pa_data_add(struct bt_data *data_array, const size_t data_array_size);
#if defined(CONFIG_BT_AUDIO)
/* Must guard before including audio.h as audio.h uses Kconfigs guarded by
@ -91,6 +94,7 @@ struct shell_stream {
};
struct broadcast_source {
bool is_cap;
union {
struct bt_bap_broadcast_source *bap_source;
struct bt_cap_broadcast_source *cap_source;

View file

@ -2723,7 +2723,7 @@ static int cmd_start_broadcast(const struct shell *sh, size_t argc,
return -ENOEXEC;
}
if (default_source.bap_source == NULL) {
if (default_source.bap_source == NULL || default_source.is_cap) {
shell_info(sh, "Broadcast source not created");
return -ENOEXEC;
}
@ -2741,7 +2741,7 @@ static int cmd_stop_broadcast(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (default_source.bap_source == NULL) {
if (default_source.bap_source == NULL || default_source.is_cap) {
shell_info(sh, "Broadcast source not created");
return -ENOEXEC;
}
@ -2760,7 +2760,7 @@ static int cmd_delete_broadcast(const struct shell *sh, size_t argc,
{
int err;
if (default_source.bap_source == NULL) {
if (default_source.bap_source == NULL || default_source.is_cap) {
shell_info(sh, "Broadcast source not created");
return -ENOEXEC;
}
@ -3659,7 +3659,7 @@ static ssize_t nonconnectable_ad_data_add(struct bt_data *data_array,
}
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
if (default_source.bap_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),
};
@ -3699,15 +3699,24 @@ static ssize_t nonconnectable_ad_data_add(struct bt_data *data_array,
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) {
return connectable_ad_data_add(data_array, data_array_size);
ad_len += connectable_ad_data_add(data_array, data_array_size);
} else {
return nonconnectable_ad_data_add(data_array, data_array_size);
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,
@ -3716,7 +3725,7 @@ ssize_t audio_pa_data_add(struct bt_data *data_array,
size_t ad_len = 0;
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
if (default_source.bap_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.
*/
@ -3734,6 +3743,8 @@ ssize_t audio_pa_data_add(struct bt_data *data_array,
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 */

View file

@ -1086,11 +1086,116 @@ static int cmd_cap_ac_11_ii(const struct shell *sh, size_t argc, char **argv)
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
static int cmd_broadcast_start(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.cap_source == NULL || !default_source.is_cap) {
shell_info(sh, "CAP Broadcast source not created");
return -ENOEXEC;
}
err = bt_cap_initiator_broadcast_audio_start(default_source.cap_source,
adv_sets[selected_adv]);
if (err != 0) {
shell_error(sh, "Unable to start broadcast source: %d", err);
return -ENOEXEC;
}
return 0;
}
static int cmd_broadcast_update(const struct shell *sh, size_t argc, char *argv[])
{
uint8_t meta[CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE];
size_t len;
int err;
if (default_source.cap_source == NULL || !default_source.is_cap) {
shell_info(sh, "CAP Broadcast source not created");
return -ENOEXEC;
}
len = hex2bin(argv[1], strlen(argv[1]), meta, sizeof(meta));
if (len == 0) {
shell_print(sh, "Unable to parse metadata (len was %zu, max len is %d)",
strlen(argv[1]) / 2U + strlen(argv[1]) % 2U,
CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE);
return -ENOEXEC;
}
err = bt_cap_initiator_broadcast_audio_update(default_source.cap_source, meta, len);
if (err != 0) {
shell_error(sh, "Unable to update broadcast source: %d", err);
return -ENOEXEC;
}
shell_print(sh, "CAP Broadcast source updated with new metadata. Update the advertised "
"base via `bt per-adv-data`");
return 0;
}
static int cmd_broadcast_stop(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (default_source.cap_source == NULL || !default_source.is_cap) {
shell_info(sh, "CAP Broadcast source not created");
return -ENOEXEC;
}
err = bt_cap_initiator_broadcast_audio_stop(default_source.cap_source);
if (err != 0) {
shell_error(sh, "Unable to stop broadcast source: %d", err);
return -ENOEXEC;
}
return 0;
}
static int cmd_broadcast_delete(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (default_source.cap_source == NULL || !default_source.is_cap) {
shell_info(sh, "CAP Broadcast source not created");
return -ENOEXEC;
}
err = bt_cap_initiator_broadcast_audio_delete(default_source.cap_source);
if (err != 0) {
shell_error(sh, "Unable to stop broadcast source: %d", err);
return -ENOEXEC;
}
default_source.cap_source = NULL;
default_source.is_cap = false;
return 0;
}
int cap_ac_broadcast(const struct shell *sh, size_t argc, char **argv,
const struct bap_broadcast_ac_param *param)
{
/* TODO: Use CAP API when the CAP shell has broadcast support */
struct bt_bap_broadcast_source_stream_param stream_params[BAP_UNICAST_AC_MAX_SRC] = {0};
struct bt_cap_initiator_broadcast_stream_param stream_params[BAP_UNICAST_AC_MAX_SRC] = {0};
uint8_t stereo_data[] = {
BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_CHAN_ALLOC,
BT_AUDIO_LOCATION_FRONT_RIGHT | BT_AUDIO_LOCATION_FRONT_LEFT)};
@ -1098,13 +1203,13 @@ int cap_ac_broadcast(const struct shell *sh, size_t argc, char **argv,
BT_AUDIO_LOCATION_FRONT_RIGHT)};
uint8_t left_data[] = {BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_CHAN_ALLOC,
BT_AUDIO_LOCATION_FRONT_LEFT)};
struct bt_bap_broadcast_source_subgroup_param subgroup_param = {0};
struct bt_bap_broadcast_source_param create_param = {0};
struct bt_cap_initiator_broadcast_subgroup_param subgroup_param = {0};
struct bt_cap_initiator_broadcast_create_param create_param = {0};
const struct named_lc3_preset *named_preset;
struct bt_le_ext_adv *adv;
int err;
if (default_source.bap_source != NULL) {
if (default_source.cap_source != NULL) {
shell_error(sh, "Broadcast Source already created, please delete first");
return -ENOEXEC;
}
@ -1125,7 +1230,7 @@ int cap_ac_broadcast(const struct shell *sh, size_t argc, char **argv,
default_source.qos.sdu *= param->chan_cnt;
for (size_t i = 0U; i < param->stream_cnt; i++) {
stream_params[i].stream = &broadcast_source_streams[i].stream.bap_stream;
stream_params[i].stream = &broadcast_source_streams[i].stream;
if (param->stream_cnt == 1U) {
stream_params[i].data_len = ARRAY_SIZE(stereo_data);
@ -1139,14 +1244,14 @@ int cap_ac_broadcast(const struct shell *sh, size_t argc, char **argv,
}
}
subgroup_param.params_count = param->stream_cnt;
subgroup_param.params = stream_params;
subgroup_param.stream_count = param->stream_cnt;
subgroup_param.stream_params = stream_params;
subgroup_param.codec_cfg = &default_source.codec_cfg;
create_param.params_count = 1U;
create_param.params = &subgroup_param;
create_param.subgroup_count = 1U;
create_param.subgroup_params = &subgroup_param;
create_param.qos = &default_source.qos;
err = bt_bap_broadcast_source_create(&create_param, &default_source.bap_source);
err = bt_cap_initiator_broadcast_audio_create(&create_param, &default_source.cap_source);
if (err != 0) {
shell_error(sh, "Failed to create broadcast source: %d", err);
return -ENOEXEC;
@ -1156,9 +1261,11 @@ int cap_ac_broadcast(const struct shell *sh, size_t argc, char **argv,
* periodic advertising data, the broadcast source needs to be created but not started.
*/
shell_print(sh,
"Broadcast source for %s created. Start via `bap start_broadcast`, and "
"update/set the base via `bt per-adv data`",
"CAP Broadcast source for %s created. "
"Start via `cap_initiator broadcast_start`, "
"and update / set the base via `bt per-adv data`",
param->name);
default_source.is_cap = true;
return 0;
}
@ -1283,6 +1390,10 @@ SHELL_STATIC_SUBCMD_SET_CREATE(
#endif /* UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED */
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
SHELL_CMD_ARG(broadcast_start, NULL, "", cmd_broadcast_start, 1, 0),
SHELL_CMD_ARG(broadcast_update, NULL, "<meta>", cmd_broadcast_update, 2, 0),
SHELL_CMD_ARG(broadcast_stop, NULL, "", cmd_broadcast_stop, 1, 0),
SHELL_CMD_ARG(broadcast_delete, NULL, "", cmd_broadcast_delete, 1, 0),
SHELL_CMD_ARG(ac_12, NULL, "<preset>", cmd_cap_ac_12, 2, 0),
#if CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT > 1
SHELL_CMD_ARG(ac_13, NULL, "<preset>", cmd_cap_ac_13, 2, 0),
@ -1294,3 +1405,74 @@ SHELL_STATIC_SUBCMD_SET_CREATE(
SHELL_CMD_ARG_REGISTER(cap_initiator, &cap_initiator_cmds,
"Bluetooth CAP initiator shell commands",
cmd_cap_initiator, 1, 1);
static ssize_t nonconnectable_ad_data_add(struct bt_data *data_array, const size_t data_array_size)
{
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
if (default_source.cap_source != NULL && default_source.is_cap) {
static uint8_t ad_cap_broadcast_announcement[5] = {
BT_UUID_16_ENCODE(BT_UUID_BROADCAST_AUDIO_VAL),
};
uint32_t broadcast_id;
int err;
err = bt_cap_initiator_broadcast_get_id(default_source.cap_source, &broadcast_id);
if (err != 0) {
printk("Unable to get broadcast ID: %d\n", err);
return -1;
}
sys_put_le24(broadcast_id, &ad_cap_broadcast_announcement[2]);
data_array[0].type = BT_DATA_SVC_DATA16;
data_array[0].data_len = ARRAY_SIZE(ad_cap_broadcast_announcement);
data_array[0].data = ad_cap_broadcast_announcement;
return 1;
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
return 0;
}
ssize_t cap_initiator_ad_data_add(struct bt_data *data_array, const size_t data_array_size,
const bool discoverable, const bool connectable)
{
if (!discoverable) {
return 0;
}
if (!connectable) {
return nonconnectable_ad_data_add(data_array, data_array_size);
}
return 0;
}
ssize_t cap_initiator_pa_data_add(struct bt_data *data_array, const size_t data_array_size)
{
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
if (default_source.cap_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_cap_initiator_broadcast_get_base(default_source.cap_source, &base_buf);
if (err != 0) {
printk("Unable to get BASE: %d\n", err);
return -1;
}
data_array[0].type = BT_DATA_SVC_DATA16;
data_array[0].data_len = base_buf.len;
data_array[0].data = base_buf.data;
return 1;
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
return 0;
}