Bluetooth: CAP: Commander Reception start procedure

Add the CAP commander reception start procedure which starts reception
on one or more CAP acceptors

With the implementation of broadcast reception start procedure we also need
some mockups for unit testing

Signed-off-by: Andries Kruithof <andries.kruithof@nordicsemi.no>
This commit is contained in:
Andries Kruithof 2024-02-09 13:47:52 +01:00 committed by Alberto Escolar
parent 7d5ccc083c
commit 6a0cdb4eaa
9 changed files with 403 additions and 33 deletions

View file

@ -750,6 +750,20 @@ struct bt_cap_commander_cb {
void (*microphone_gain_changed)(struct bt_conn *conn, int err);
#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */
#endif /* CONFIG_BT_MICP_MIC_CTLR */
#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT)
/**
* @brief Callback for bt_cap_commander_broadcast_reception_start().
*
* @param conn Pointer to the connection where the error
* occurred. NULL if @p err is 0 or if cancelled by
* bt_cap_commander_cancel()
* @param err 0 on success, BT_GATT_ERR() with a
* specific ATT (BT_ATT_ERR_*) error code or -ECANCELED if cancelled
* by bt_cap_commander_cancel().
*/
void (*broadcast_reception_start)(struct bt_conn *conn, int err);
#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */
};
/**
@ -842,7 +856,7 @@ struct bt_cap_commander_broadcast_reception_start_member_param {
*
* At least one bit in one of the subgroups bis_sync parameters shall be set.
*/
struct bt_bap_bass_subgroup *subgroups;
struct bt_bap_bass_subgroup subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS];
/** Number of subgroups */
size_t num_subgroups;

View file

@ -123,3 +123,16 @@ static inline const char *bt_bap_big_enc_state_str(uint8_t state)
return "unknown state";
}
}
static inline bool valid_bis_syncs(uint32_t bis_sync)
{
if (bis_sync == BT_BAP_BIS_SYNC_NO_PREF) {
return true;
}
if (bis_sync > BIT_MASK(31)) { /* Max BIS index */
return false;
}
return true;
}

View file

@ -88,19 +88,6 @@ static bool bits_subset_of(uint32_t a, uint32_t b)
return (((a) & (~(b))) == 0);
}
static bool valid_bis_syncs(uint32_t bis_sync)
{
if (bis_sync == BT_BAP_BIS_SYNC_NO_PREF) {
return true;
}
if (bis_sync > BIT_MASK(31)) { /* Max BIS index */
return false;
}
return true;
}
static bool bis_syncs_unique_or_no_pref(uint32_t requested_bis_syncs,
uint32_t aggregated_bis_syncs)
{

View file

@ -11,6 +11,7 @@
#include <zephyr/bluetooth/audio/micp.h>
#include <zephyr/bluetooth/audio/vcp.h>
#include <zephyr/bluetooth/audio/vocs.h>
#include "bap_internal.h"
#include "cap_internal.h"
#include "ccid_internal.h"
#include "csip_internal.h"
@ -22,17 +23,21 @@ LOG_MODULE_REGISTER(bt_cap_commander, CONFIG_BT_CAP_COMMANDER_LOG_LEVEL);
#include "common/bt_str.h"
static void cap_commander_proc_complete(void);
static const struct bt_cap_commander_cb *cap_cb;
int bt_cap_commander_register_cb(const struct bt_cap_commander_cb *cb)
{
CHECKIF(cb == NULL) {
LOG_DBG("cb is NULL");
return -EINVAL;
}
CHECKIF(cap_cb != NULL) {
LOG_DBG("callbacks already registered");
return -EALREADY;
}
@ -77,18 +82,311 @@ int bt_cap_commander_discover(struct bt_conn *conn)
return bt_cap_common_discover(conn, cap_commander_discover_complete);
}
#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT)
static struct bt_bap_broadcast_assistant_cb broadcast_assistant_cb;
static bool ba_cb_registered;
static void
copy_broadcast_reception_start_param(struct bt_bap_broadcast_assistant_add_src_param *add_src_param,
struct cap_broadcast_reception_start *start_param)
{
bt_addr_le_copy(&add_src_param->addr, &start_param->addr);
add_src_param->adv_sid = start_param->adv_sid;
add_src_param->broadcast_id = start_param->broadcast_id;
add_src_param->pa_interval = start_param->pa_interval;
add_src_param->num_subgroups = start_param->num_subgroups;
add_src_param->subgroups = start_param->subgroups;
}
static void cap_commander_ba_add_src_cb(struct bt_conn *conn, int err)
{
struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc();
struct bt_bap_broadcast_assistant_add_src_param add_src_param = {0};
LOG_DBG("conn %p", (void *)conn);
if (!bt_cap_common_conn_in_active_proc(conn)) {
/* State change happened outside of a procedure; ignore */
return;
}
if (err != 0) {
LOG_DBG("Failed to add source: %d", err);
LOG_DBG("Aborting the proc %d %d", active_proc->proc_done_cnt,
active_proc->proc_initiated_cnt);
bt_cap_common_abort_proc(conn, err);
} else {
active_proc->proc_done_cnt++;
LOG_DBG("Conn %p broadcast source added (%zu/%zu streams done)", (void *)conn,
active_proc->proc_done_cnt, active_proc->proc_cnt);
}
if (bt_cap_common_proc_is_aborted()) {
if (bt_cap_common_proc_all_handled()) {
cap_commander_proc_complete();
}
return;
}
if (!bt_cap_common_proc_is_done()) {
struct bt_cap_commander_proc_param *proc_param;
proc_param = &active_proc->proc_param.commander[active_proc->proc_done_cnt];
conn = proc_param->conn;
copy_broadcast_reception_start_param(&add_src_param,
&proc_param->broadcast_reception_start);
active_proc->proc_initiated_cnt++;
err = bt_bap_broadcast_assistant_add_src(conn, &add_src_param);
if (err != 0) {
LOG_DBG("Failed to perform broadcast reception start for conn %p: %d",
(void *)conn, err);
bt_cap_common_abort_proc(conn, err);
cap_commander_proc_complete();
}
} else {
cap_commander_proc_complete();
}
}
static int cap_commander_register_ba_cb(void)
{
int err;
err = bt_bap_broadcast_assistant_register_cb(&broadcast_assistant_cb);
if (err != 0) {
LOG_DBG("Failed to register broadcast assistant callbacks: %d", err);
return -ENOEXEC;
}
ba_cb_registered = true;
return 0;
}
static bool valid_broadcast_reception_start_param(
const struct bt_cap_commander_broadcast_reception_start_param *param)
{
uint32_t total_bis_sync = 0U;
CHECKIF(param == NULL) {
LOG_DBG("param is NULL");
return false;
}
CHECKIF(param->count == 0) {
LOG_DBG("Invalid param->count: %u", param->count);
return false;
}
CHECKIF(param->count > CONFIG_BT_MAX_CONN) {
LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", param->count,
CONFIG_BT_MAX_CONN);
return false;
}
CHECKIF(param->param == NULL) {
LOG_DBG("param->param is NULL");
return false;
}
for (size_t i = 0; i < param->count; i++) {
const struct bt_cap_commander_broadcast_reception_start_member_param *start_param =
&param->param[i];
const union bt_cap_set_member *member = &param->param[i].member;
const struct bt_cap_common_client *client =
bt_cap_common_get_client(param->type, member);
if (member == NULL) {
LOG_DBG("param->param[%zu].member is NULL", i);
return false;
}
if (client == NULL) {
LOG_DBG("Invalid param->param[%zu].member", i);
return false;
}
CHECKIF(start_param->addr.type > BT_ADDR_LE_RANDOM) {
LOG_DBG("Invalid address type %u", start_param->addr.type);
return false;
}
CHECKIF(start_param->adv_sid > BT_GAP_SID_MAX) {
LOG_DBG("param->param[%zu]->adv_sid is larger than %d", i, BT_GAP_SID_MAX);
return false;
}
CHECKIF(!IN_RANGE(start_param->pa_interval, BT_GAP_PER_ADV_MIN_INTERVAL,
BT_GAP_PER_ADV_MAX_INTERVAL)) {
LOG_DBG("param->param[%zu]->pa_interval is out of range", i);
return false;
}
CHECKIF(start_param->broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
LOG_DBG("param->param[%zu]->broadcast_id is larger than %u", i,
BT_AUDIO_BROADCAST_ID_MAX);
return false;
}
CHECKIF(start_param->num_subgroups == 0) {
LOG_DBG("param->param[%zu]->num_subgroups is 0", i);
return false;
}
CHECKIF(start_param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
LOG_DBG("Too many subgroups %u/%u", start_param->num_subgroups,
CONFIG_BT_BAP_BASS_MAX_SUBGROUPS);
return false;
}
CHECKIF(start_param->subgroups == NULL) {
LOG_DBG("param->param[%zu]->subgroup is NULL", i);
return false;
}
for (size_t j = 0U; j < start_param->num_subgroups; j++) {
const struct bt_bap_bass_subgroup *param_subgroups =
&start_param->subgroups[j];
CHECKIF(!valid_bis_syncs(param_subgroups->bis_sync)) {
LOG_DBG("param->param[%zu].subgroup[%zu].bis_sync is invalid %u", i,
j, param_subgroups->bis_sync);
}
CHECKIF((total_bis_sync & param_subgroups->bis_sync) != 0) {
LOG_DBG("param->param[%zu].subgroup[%zu].bis_sync 0x%08X has "
"duplicate bits (0x%08X) ",
i, j, param_subgroups->bis_sync, total_bis_sync);
}
total_bis_sync |= param_subgroups->bis_sync;
CHECKIF(param_subgroups->metadata_len >
CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) {
LOG_DBG("param->param[%zu].subgroup[%zu].metadata_len too long "
"%u/%u",
i, j, param_subgroups->metadata_len,
CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE);
return false;
}
#if defined(CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE)
CHECKIF(param_subgroups->metadata_len > 0 &&
!bt_audio_valid_ltv(param_subgroups->metadata,
param_subgroups->metadata_len)) {
LOG_DBG("param->param[%zu].subgroup[%zu].metadata not valid LTV", i,
j);
}
#endif
}
for (size_t j = 0U; j < i; j++) {
const union bt_cap_set_member *other = &param->param[j].member;
if (other == member) {
LOG_DBG("param->members[%zu] (%p) is duplicated by "
"param->members[%zu] (%p)",
j, other, i, member);
return false;
}
}
}
return true;
}
int bt_cap_commander_broadcast_reception_start(
const struct bt_cap_commander_broadcast_reception_start_param *param)
{
return -ENOSYS;
struct bt_bap_broadcast_assistant_add_src_param add_src_param = {0};
struct bt_cap_commander_proc_param *proc_param;
struct bt_cap_common_proc *active_proc;
struct bt_conn *conn;
int err;
if (bt_cap_common_proc_is_active()) {
LOG_DBG("A CAP procedure is already in progress");
return -EBUSY;
}
if (!valid_broadcast_reception_start_param(param)) {
return -EINVAL;
}
bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START, param->count);
broadcast_assistant_cb.add_src = cap_commander_ba_add_src_cb;
if (!ba_cb_registered && cap_commander_register_ba_cb() != 0) {
LOG_DBG("Failed to register VCP callbacks");
return -ENOEXEC;
}
active_proc = bt_cap_common_get_active_proc();
for (size_t i = 0U; i < param->count; i++) {
const struct bt_cap_commander_broadcast_reception_start_member_param *member_param =
&param->param[i];
struct bt_cap_commander_proc_param *stored_param;
struct bt_conn *member_conn =
bt_cap_common_get_member_conn(param->type, &member_param->member);
if (member_conn == NULL) {
LOG_DBG("Invalid param->members[%zu]", i);
return -EINVAL;
}
/* Store the necessary parameters as we cannot assume that the supplied parameters
* are kept valid
* TODO: consider putting this into a function
*/
stored_param = &active_proc->proc_param.commander[i];
stored_param->conn = member_conn;
bt_addr_le_copy(&stored_param->broadcast_reception_start.addr, &member_param->addr);
stored_param->broadcast_reception_start.adv_sid = member_param->adv_sid;
stored_param->broadcast_reception_start.broadcast_id = member_param->broadcast_id;
stored_param->broadcast_reception_start.pa_interval = member_param->pa_interval;
stored_param->broadcast_reception_start.num_subgroups = member_param->num_subgroups;
memcpy(stored_param->broadcast_reception_start.subgroups, member_param->subgroups,
sizeof(struct bt_bap_bass_subgroup) * add_src_param.num_subgroups);
}
active_proc->proc_initiated_cnt++;
proc_param = &active_proc->proc_param.commander[0];
conn = proc_param->conn;
copy_broadcast_reception_start_param(&add_src_param,
&proc_param->broadcast_reception_start);
/* TODO: what to do if we are adding a source that has already been added? */
err = bt_bap_broadcast_assistant_add_src(conn, &add_src_param);
if (err != 0) {
LOG_DBG("Failed to start broadcast reception for conn %p: %d", (void *)conn, err);
return -ENOEXEC;
}
return 0;
}
#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */
int bt_cap_commander_broadcast_reception_stop(
const struct bt_cap_commander_broadcast_reception_stop_param *param)
{
return -ENOSYS;
}
static void cap_commander_unicast_audio_proc_complete(void)
static void cap_commander_proc_complete(void)
{
struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc();
enum bt_cap_common_proc_type proc_type;
@ -138,6 +436,13 @@ static void cap_commander_unicast_audio_proc_complete(void)
break;
#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */
#endif /* CONFIG_BT_MICP_MIC_CTLR */
#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT)
case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START:
if (cap_cb->broadcast_reception_start != NULL) {
cap_cb->broadcast_reception_start(failed_conn, err);
}
break;
#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */
case BT_CAP_COMMON_PROC_TYPE_NONE:
default:
__ASSERT(false, "Invalid proc_type: %u", proc_type);
@ -153,7 +458,7 @@ int bt_cap_commander_cancel(void)
}
bt_cap_common_abort_proc(NULL, -ECANCELED);
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
return 0;
}
@ -265,7 +570,7 @@ static void cap_commander_vcp_vol_set_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int e
LOG_DBG("Proc is aborted");
if (bt_cap_common_proc_all_handled()) {
LOG_DBG("All handled");
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
return;
@ -282,10 +587,10 @@ static void cap_commander_vcp_vol_set_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int e
if (err != 0) {
LOG_DBG("Failed to set volume for conn %p: %d", (void *)conn, err);
bt_cap_common_abort_proc(conn, err);
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
} else {
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
}
@ -434,7 +739,7 @@ static void cap_commander_vcp_vol_mute_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int
LOG_DBG("Proc is aborted");
if (bt_cap_common_proc_all_handled()) {
LOG_DBG("All handled");
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
return;
@ -455,10 +760,10 @@ static void cap_commander_vcp_vol_mute_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int
if (err != 0) {
LOG_DBG("Failed to set volume for conn %p: %d", (void *)conn, err);
bt_cap_common_abort_proc(conn, err);
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
} else {
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
}
@ -634,7 +939,7 @@ static void cap_commander_vcp_set_offset_cb(struct bt_vocs *inst, int err)
LOG_DBG("Proc is aborted");
if (bt_cap_common_proc_all_handled()) {
LOG_DBG("All handled");
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
return;
@ -652,10 +957,10 @@ static void cap_commander_vcp_set_offset_cb(struct bt_vocs *inst, int err)
if (err != 0) {
LOG_DBG("Failed to set offset for conn %p: %d", (void *)conn, err);
bt_cap_common_abort_proc(conn, err);
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
} else {
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
}
@ -847,7 +1152,7 @@ static void cap_commander_micp_mic_mute_cb(struct bt_micp_mic_ctlr *mic_ctlr, in
LOG_DBG("Proc is aborted");
if (bt_cap_common_proc_all_handled()) {
LOG_DBG("All handled");
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
return;
@ -868,10 +1173,10 @@ static void cap_commander_micp_mic_mute_cb(struct bt_micp_mic_ctlr *mic_ctlr, in
if (err != 0) {
LOG_DBG("Failed to change mute for conn %p: %d", (void *)conn, err);
bt_cap_common_abort_proc(conn, err);
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
} else {
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
}
@ -1040,7 +1345,7 @@ static void cap_commander_micp_gain_set_cb(struct bt_aics *inst, int err)
LOG_DBG("Proc is aborted");
if (bt_cap_common_proc_all_handled()) {
LOG_DBG("All handled");
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
return;
@ -1056,10 +1361,10 @@ static void cap_commander_micp_gain_set_cb(struct bt_aics *inst, int err)
if (err != 0) {
LOG_DBG("Failed to set gain for conn %p: %d", (void *)conn, err);
bt_cap_common_abort_proc(conn, err);
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
} else {
cap_commander_unicast_audio_proc_complete();
cap_commander_proc_complete();
}
}

View file

@ -124,6 +124,7 @@ static bool active_proc_is_commander(void)
case BT_CAP_COMMON_PROC_TYPE_VOLUME_MUTE_CHANGE:
case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_GAIN_CHANGE:
case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_MUTE_CHANGE:
case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START:
return true;
default:
return false;

View file

@ -36,6 +36,7 @@ enum bt_cap_common_proc_type {
BT_CAP_COMMON_PROC_TYPE_START,
BT_CAP_COMMON_PROC_TYPE_UPDATE,
BT_CAP_COMMON_PROC_TYPE_STOP,
BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START,
BT_CAP_COMMON_PROC_TYPE_VOLUME_CHANGE,
BT_CAP_COMMON_PROC_TYPE_VOLUME_OFFSET_CHANGE,
BT_CAP_COMMON_PROC_TYPE_VOLUME_MUTE_CHANGE,
@ -70,6 +71,18 @@ struct bt_cap_initiator_proc_param {
};
};
#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT)
struct cap_broadcast_reception_start {
bt_addr_le_t addr;
uint8_t adv_sid;
uint32_t broadcast_id;
uint16_t pa_interval;
uint8_t num_subgroups;
struct bt_bap_bass_subgroup subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS];
};
#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */
struct bt_cap_commander_proc_param {
struct bt_conn *conn;
union {
@ -87,6 +100,9 @@ struct bt_cap_commander_proc_param {
struct bt_vocs *vocs;
} change_offset;
#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */
#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT)
struct cap_broadcast_reception_start broadcast_reception_start;
#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */
#if defined(CONFIG_BT_MICP_MIC_CTLR)
struct {
bool mute;

View file

@ -15,7 +15,7 @@ CONFIG_BT_AICS_CLIENT_MAX_INSTANCE_COUNT=1
CONFIG_BT_MICP_MIC_CTLR_MAX_AICS_INST=1
CONFIG_BT_CAP_COMMANDER=y
CONFIG_BT_BAP_BROADCAST_ASSISTANT=y
CONFIG_LOG=y
CONFIG_BT_CAP_COMMANDER_LOG_LEVEL_DBG=y

View file

@ -7,10 +7,14 @@
#
add_library(uut STATIC
${ZEPHYR_BASE}/subsys/bluetooth/audio/audio.c
${ZEPHYR_BASE}/subsys/bluetooth/audio/bap_iso.c
${ZEPHYR_BASE}/subsys/bluetooth/audio/bap_stream.c
${ZEPHYR_BASE}/subsys/bluetooth/audio/cap_commander.c
${ZEPHYR_BASE}/subsys/bluetooth/audio/cap_common.c
${ZEPHYR_BASE}/subsys/logging/log_minimal.c
${ZEPHYR_BASE}/subsys/net/buf_simple.c
bap_broadcast_assistant.c
aics.c
csip.c
micp.c

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "zephyr/bluetooth/audio/bap.h"
static struct bt_bap_broadcast_assistant_cb *broadcast_assistant_cbs;
int bt_bap_broadcast_assistant_register_cb(struct bt_bap_broadcast_assistant_cb *cb)
{
broadcast_assistant_cbs = cb;
return 0;
}
int bt_bap_broadcast_assistant_add_src(struct bt_conn *conn,
const struct bt_bap_broadcast_assistant_add_src_param *param)
{
/* Note that proper parameter checking is done in the caller */
zassert_not_null(conn, "conn is NULL");
zassert_not_null(param, "param is NULL");
if (broadcast_assistant_cbs->add_src != NULL) {
broadcast_assistant_cbs->add_src(conn, 0);
}
return 0;
}