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:
parent
8c6e3a6d41
commit
8f0e648e48
|
@ -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
|
||||
*************
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue