Remove the qos field from bt_cap_unicast_audio_start_stream_param as it was not used. The QOS values are set when creating the unicast group, and not when starting the streams. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
1446 lines
37 KiB
C
1446 lines
37 KiB
C
/*
|
|
* Copyright (c) 2022-2023 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/sys/check.h>
|
|
#include <zephyr/bluetooth/gatt.h>
|
|
#include <zephyr/bluetooth/audio/audio.h>
|
|
#include <zephyr/bluetooth/audio/cap.h>
|
|
#include <zephyr/bluetooth/audio/tbs.h>
|
|
#include "cap_internal.h"
|
|
#include "csip_internal.h"
|
|
#include "bap_endpoint.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_REGISTER(bt_cap_initiator, CONFIG_BT_CAP_INITIATOR_LOG_LEVEL);
|
|
|
|
#include "common/bt_str.h"
|
|
|
|
static const struct bt_cap_initiator_cb *cap_cb;
|
|
|
|
int bt_cap_initiator_register_cb(const struct bt_cap_initiator_cb *cb)
|
|
{
|
|
CHECKIF(cb == NULL) {
|
|
LOG_DBG("cb is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(cap_cb != NULL) {
|
|
LOG_DBG("callbacks already registered");
|
|
return -EALREADY;
|
|
}
|
|
|
|
cap_cb = cb;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool data_func_cb(struct bt_data *data, void *user_data)
|
|
{
|
|
bool *stream_context_found = (bool *)user_data;
|
|
|
|
LOG_DBG("type %u len %u data %s", data->type, data->data_len,
|
|
bt_hex(data->data, data->data_len));
|
|
|
|
if (data->type == BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT) {
|
|
if (data->data_len != 2) { /* Stream context size */
|
|
return false;
|
|
}
|
|
|
|
*stream_context_found = true;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool cap_initiator_valid_metadata(const uint8_t meta[], size_t meta_len)
|
|
{
|
|
bool stream_context_found = false;
|
|
int err;
|
|
|
|
LOG_DBG("meta %p len %zu", meta, meta_len);
|
|
|
|
err = bt_audio_data_parse(meta, meta_len, data_func_cb, &stream_context_found);
|
|
if (err != 0 && err != -ECANCELED) {
|
|
return false;
|
|
}
|
|
|
|
if (!stream_context_found) {
|
|
LOG_DBG("No streaming context supplied");
|
|
}
|
|
|
|
return stream_context_found;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE)
|
|
static struct bt_cap_broadcast_source {
|
|
struct bt_bap_broadcast_source *bap_broadcast;
|
|
} broadcast_sources[CONFIG_BT_BAP_BROADCAST_SRC_COUNT];
|
|
|
|
static bool cap_initiator_broadcast_audio_start_valid_param(
|
|
const struct bt_cap_initiator_broadcast_create_param *param)
|
|
{
|
|
|
|
for (size_t i = 0U; i < param->subgroup_count; i++) {
|
|
const struct bt_cap_initiator_broadcast_subgroup_param *subgroup_param;
|
|
const struct bt_audio_codec_cfg *codec_cfg;
|
|
bool valid_metadata;
|
|
|
|
subgroup_param = ¶m->subgroup_params[i];
|
|
codec_cfg = subgroup_param->codec_cfg;
|
|
|
|
/* Streaming Audio Context shall be present in CAP */
|
|
|
|
CHECKIF(codec_cfg == NULL) {
|
|
LOG_DBG("subgroup[%zu]->codec_cfg is NULL", i);
|
|
return false;
|
|
}
|
|
|
|
valid_metadata =
|
|
cap_initiator_valid_metadata(codec_cfg->meta, codec_cfg->meta_len);
|
|
|
|
CHECKIF(!valid_metadata) {
|
|
LOG_DBG("Invalid metadata supplied for subgroup[%zu]", i);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void cap_initiator_broadcast_to_bap_broadcast_param(
|
|
const struct bt_cap_initiator_broadcast_create_param *cap_param,
|
|
struct bt_bap_broadcast_source_create_param *bap_param,
|
|
struct bt_bap_broadcast_source_subgroup_param
|
|
bap_subgroup_params[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT],
|
|
struct bt_bap_broadcast_source_stream_param
|
|
bap_stream_params[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT])
|
|
{
|
|
size_t stream_cnt = 0U;
|
|
|
|
bap_param->params_count = cap_param->subgroup_count;
|
|
bap_param->params = bap_subgroup_params;
|
|
bap_param->qos = cap_param->qos;
|
|
bap_param->packing = cap_param->packing;
|
|
bap_param->encryption = cap_param->encryption;
|
|
if (bap_param->encryption) {
|
|
memcpy(bap_param->broadcast_code, cap_param->broadcast_code,
|
|
BT_AUDIO_BROADCAST_CODE_SIZE);
|
|
} else {
|
|
memset(bap_param->broadcast_code, 0, BT_AUDIO_BROADCAST_CODE_SIZE);
|
|
}
|
|
|
|
for (size_t i = 0U; i < bap_param->params_count; i++) {
|
|
const struct bt_cap_initiator_broadcast_subgroup_param *cap_subgroup_param =
|
|
&cap_param->subgroup_params[i];
|
|
struct bt_bap_broadcast_source_subgroup_param *bap_subgroup_param =
|
|
&bap_param->params[i];
|
|
|
|
bap_subgroup_param->codec_cfg = cap_subgroup_param->codec_cfg;
|
|
bap_subgroup_param->params_count = cap_subgroup_param->stream_count;
|
|
bap_subgroup_param->params = &bap_stream_params[stream_cnt];
|
|
|
|
for (size_t j = 0U; j < bap_subgroup_param->params_count; j++, stream_cnt++) {
|
|
const struct bt_cap_initiator_broadcast_stream_param *cap_stream_param =
|
|
&cap_subgroup_param->stream_params[j];
|
|
struct bt_bap_broadcast_source_stream_param *bap_stream_param =
|
|
&bap_subgroup_param->params[j];
|
|
|
|
bap_stream_param->stream = &cap_stream_param->stream->bap_stream;
|
|
#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0
|
|
bap_stream_param->data_len = cap_stream_param->data_len;
|
|
/* We do not need to copy the data, as that is the same type of struct, so
|
|
* we can just point to the CAP parameter data
|
|
*/
|
|
bap_stream_param->data = cap_stream_param->data;
|
|
#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 */
|
|
}
|
|
}
|
|
}
|
|
|
|
int bt_cap_initiator_broadcast_audio_create(
|
|
const struct bt_cap_initiator_broadcast_create_param *param,
|
|
struct bt_cap_broadcast_source **broadcast_source)
|
|
{
|
|
struct bt_bap_broadcast_source_subgroup_param
|
|
bap_subgroup_params[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT];
|
|
struct bt_bap_broadcast_source_stream_param
|
|
bap_stream_params[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT];
|
|
struct bt_bap_broadcast_source_create_param bap_create_param;
|
|
|
|
CHECKIF(param == NULL) {
|
|
LOG_DBG("param is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(broadcast_source == NULL) {
|
|
LOG_DBG("source is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cap_initiator_broadcast_audio_start_valid_param(param)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(broadcast_sources); i++) {
|
|
if (broadcast_sources[i].bap_broadcast == NULL) {
|
|
*broadcast_source = &broadcast_sources[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
cap_initiator_broadcast_to_bap_broadcast_param(param, &bap_create_param,
|
|
bap_subgroup_params, bap_stream_params);
|
|
|
|
return bt_bap_broadcast_source_create(&bap_create_param,
|
|
&(*broadcast_source)->bap_broadcast);
|
|
}
|
|
|
|
int bt_cap_initiator_broadcast_audio_start(struct bt_cap_broadcast_source *broadcast_source,
|
|
struct bt_le_ext_adv *adv)
|
|
{
|
|
CHECKIF(adv == NULL) {
|
|
LOG_DBG("adv is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(broadcast_source == NULL) {
|
|
LOG_DBG("broadcast_source is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return bt_bap_broadcast_source_start(broadcast_source->bap_broadcast, adv);
|
|
}
|
|
|
|
int bt_cap_initiator_broadcast_audio_update(struct bt_cap_broadcast_source *broadcast_source,
|
|
const uint8_t meta[], size_t meta_len)
|
|
{
|
|
CHECKIF(broadcast_source == NULL) {
|
|
LOG_DBG("broadcast_source is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(meta == NULL) {
|
|
LOG_DBG("meta is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cap_initiator_valid_metadata(meta, meta_len)) {
|
|
LOG_DBG("Invalid metadata");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return bt_bap_broadcast_source_update_metadata(broadcast_source->bap_broadcast, meta,
|
|
meta_len);
|
|
}
|
|
|
|
int bt_cap_initiator_broadcast_audio_stop(struct bt_cap_broadcast_source *broadcast_source)
|
|
{
|
|
CHECKIF(broadcast_source == NULL) {
|
|
LOG_DBG("broadcast_source is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return bt_bap_broadcast_source_stop(broadcast_source->bap_broadcast);
|
|
}
|
|
|
|
int bt_cap_initiator_broadcast_audio_delete(struct bt_cap_broadcast_source *broadcast_source)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(broadcast_source == NULL) {
|
|
LOG_DBG("broadcast_source is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = bt_bap_broadcast_source_delete(broadcast_source->bap_broadcast);
|
|
if (err == 0) {
|
|
broadcast_source->bap_broadcast = NULL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_cap_initiator_broadcast_get_id(const struct bt_cap_broadcast_source *broadcast_source,
|
|
uint32_t *const broadcast_id)
|
|
{
|
|
CHECKIF(broadcast_source == NULL) {
|
|
LOG_DBG("broadcast_source is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return bt_bap_broadcast_source_get_id(broadcast_source->bap_broadcast, broadcast_id);
|
|
}
|
|
|
|
int bt_cap_initiator_broadcast_get_base(struct bt_cap_broadcast_source *broadcast_source,
|
|
struct net_buf_simple *base_buf)
|
|
{
|
|
CHECKIF(broadcast_source == NULL) {
|
|
LOG_DBG("broadcast_source is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return bt_bap_broadcast_source_get_base(broadcast_source->bap_broadcast, base_buf);
|
|
}
|
|
|
|
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
|
|
|
|
#if defined(CONFIG_BT_BAP_UNICAST_CLIENT)
|
|
enum {
|
|
CAP_UNICAST_PROC_STATE_ACTIVE,
|
|
CAP_UNICAST_PROC_STATE_ABORTED,
|
|
|
|
CAP_UNICAST_PROC_STATE_FLAG_NUM,
|
|
} cap_unicast_proc_state;
|
|
|
|
enum cap_unicast_proc_type {
|
|
CAP_UNICAST_PROC_TYPE_NONE,
|
|
CAP_UNICAST_PROC_TYPE_START,
|
|
CAP_UNICAST_PROC_TYPE_UPDATE,
|
|
CAP_UNICAST_PROC_TYPE_STOP,
|
|
};
|
|
|
|
enum cap_unicast_subproc_type {
|
|
CAP_UNICAST_SUBPROC_TYPE_NONE,
|
|
CAP_UNICAST_SUBPROC_TYPE_CODEC_CONFIG,
|
|
CAP_UNICAST_SUBPROC_TYPE_QOS_CONFIG,
|
|
CAP_UNICAST_SUBPROC_TYPE_ENABLE,
|
|
CAP_UNICAST_SUBPROC_TYPE_START,
|
|
CAP_UNICAST_SUBPROC_TYPE_META_UPDATE,
|
|
CAP_UNICAST_SUBPROC_TYPE_RELEASE,
|
|
};
|
|
|
|
struct cap_unicast_proc {
|
|
ATOMIC_DEFINE(proc_state_flags, CAP_UNICAST_PROC_STATE_FLAG_NUM);
|
|
/* Total number of streams in the procedure*/
|
|
size_t stream_cnt;
|
|
/* Number of streams where a subprocedure have been started */
|
|
size_t stream_initiated_cnt;
|
|
/* Number of streams done with the procedure */
|
|
size_t stream_done_cnt;
|
|
enum cap_unicast_proc_type proc_type;
|
|
enum cap_unicast_subproc_type subproc_type;
|
|
int err;
|
|
struct bt_conn *failed_conn;
|
|
struct bt_cap_stream *streams[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT];
|
|
struct bt_bap_unicast_group *unicast_group;
|
|
};
|
|
|
|
struct cap_unicast_client {
|
|
struct bt_conn *conn;
|
|
struct bt_gatt_discover_params param;
|
|
uint16_t csis_start_handle;
|
|
const struct bt_csip_set_coordinator_csis_inst *csis_inst;
|
|
bool cas_found;
|
|
};
|
|
|
|
static struct cap_unicast_client bt_cap_unicast_clients[CONFIG_BT_MAX_CONN];
|
|
static const struct bt_uuid *cas_uuid = BT_UUID_CAS;
|
|
static struct cap_unicast_proc active_proc;
|
|
|
|
static void cap_set_subproc(enum cap_unicast_subproc_type subproc_type)
|
|
{
|
|
active_proc.stream_done_cnt = 0U;
|
|
active_proc.stream_initiated_cnt = 0U;
|
|
active_proc.subproc_type = subproc_type;
|
|
}
|
|
|
|
static bool cap_proc_is_active(void)
|
|
{
|
|
return atomic_test_bit(active_proc.proc_state_flags, CAP_UNICAST_PROC_STATE_ACTIVE);
|
|
}
|
|
|
|
static bool cap_proc_is_aborted(void)
|
|
{
|
|
return atomic_test_bit(active_proc.proc_state_flags, CAP_UNICAST_PROC_STATE_ABORTED);
|
|
}
|
|
|
|
static bool cap_proc_all_streams_handled(void)
|
|
{
|
|
return active_proc.stream_done_cnt == active_proc.stream_initiated_cnt;
|
|
}
|
|
|
|
static bool cap_proc_is_done(void)
|
|
{
|
|
return active_proc.stream_done_cnt == active_proc.stream_cnt;
|
|
}
|
|
|
|
static void cap_abort_proc(struct bt_conn *conn, int err)
|
|
{
|
|
if (cap_proc_is_aborted()) {
|
|
/* no-op */
|
|
return;
|
|
}
|
|
|
|
active_proc.err = err;
|
|
active_proc.failed_conn = conn;
|
|
atomic_set_bit(active_proc.proc_state_flags, CAP_UNICAST_PROC_STATE_ABORTED);
|
|
}
|
|
|
|
static bool cap_conn_in_active_proc(const struct bt_conn *conn)
|
|
{
|
|
if (!cap_proc_is_active()) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0U; i < active_proc.stream_initiated_cnt; i++) {
|
|
if (active_proc.streams[i]->bap_stream.conn == conn) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void cap_initiator_disconnected(struct bt_conn *conn, uint8_t reason)
|
|
{
|
|
struct cap_unicast_client *client;
|
|
|
|
client = &bt_cap_unicast_clients[bt_conn_index(conn)];
|
|
|
|
if (client->conn != NULL) {
|
|
bt_conn_unref(client->conn);
|
|
}
|
|
(void)memset(client, 0, sizeof(*client));
|
|
|
|
if (cap_conn_in_active_proc(conn)) {
|
|
cap_abort_proc(conn, -ENOTCONN);
|
|
}
|
|
}
|
|
|
|
BT_CONN_CB_DEFINE(conn_callbacks) = {
|
|
.disconnected = cap_initiator_disconnected,
|
|
};
|
|
|
|
static struct cap_unicast_client *lookup_unicast_client_by_csis(
|
|
const struct bt_csip_set_coordinator_csis_inst *csis_inst)
|
|
{
|
|
if (csis_inst == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
for (size_t i = 0U; i < ARRAY_SIZE(bt_cap_unicast_clients); i++) {
|
|
struct cap_unicast_client *client = &bt_cap_unicast_clients[i];
|
|
|
|
if (client->csis_inst == csis_inst) {
|
|
return client;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void csis_client_discover_cb(struct bt_conn *conn,
|
|
const struct bt_csip_set_coordinator_set_member *member,
|
|
int err, size_t set_count)
|
|
{
|
|
struct cap_unicast_client *client;
|
|
|
|
if (err != 0) {
|
|
LOG_DBG("CSIS client discover failed: %d", err);
|
|
|
|
if (cap_cb && cap_cb->unicast_discovery_complete) {
|
|
cap_cb->unicast_discovery_complete(conn, err, NULL);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
client = &bt_cap_unicast_clients[bt_conn_index(conn)];
|
|
client->csis_inst = bt_csip_set_coordinator_csis_inst_by_handle(
|
|
conn, client->csis_start_handle);
|
|
|
|
if (member == NULL || set_count == 0 || client->csis_inst == NULL) {
|
|
LOG_ERR("Unable to find CSIS for CAS");
|
|
|
|
if (cap_cb && cap_cb->unicast_discovery_complete) {
|
|
cap_cb->unicast_discovery_complete(conn, -ENODATA,
|
|
NULL);
|
|
}
|
|
} else {
|
|
LOG_DBG("Found CAS with CSIS");
|
|
if (cap_cb && cap_cb->unicast_discovery_complete) {
|
|
cap_cb->unicast_discovery_complete(conn, 0,
|
|
client->csis_inst);
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint8_t cap_unicast_discover_included_cb(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
struct bt_gatt_discover_params *params)
|
|
{
|
|
params->func = NULL;
|
|
|
|
if (attr == NULL) {
|
|
LOG_DBG("CAS CSIS include not found");
|
|
|
|
if (cap_cb && cap_cb->unicast_discovery_complete) {
|
|
cap_cb->unicast_discovery_complete(conn, 0, NULL);
|
|
}
|
|
} else {
|
|
const struct bt_gatt_include *included_service = attr->user_data;
|
|
struct cap_unicast_client *client = CONTAINER_OF(params,
|
|
struct cap_unicast_client,
|
|
param);
|
|
|
|
/* If the remote CAS includes CSIS, we first check if we
|
|
* have already discovered it, and if so we can just retrieve it
|
|
* and forward it to the application. If not, then we start
|
|
* CSIS discovery
|
|
*/
|
|
client->csis_start_handle = included_service->start_handle;
|
|
client->csis_inst = bt_csip_set_coordinator_csis_inst_by_handle(
|
|
conn, client->csis_start_handle);
|
|
|
|
if (client->csis_inst == NULL) {
|
|
static struct bt_csip_set_coordinator_cb csis_client_cb = {
|
|
.discover = csis_client_discover_cb
|
|
};
|
|
static bool csis_cbs_registered;
|
|
int err;
|
|
|
|
LOG_DBG("CAS CSIS not known, discovering");
|
|
|
|
if (!csis_cbs_registered) {
|
|
bt_csip_set_coordinator_register_cb(&csis_client_cb);
|
|
csis_cbs_registered = true;
|
|
}
|
|
|
|
err = bt_csip_set_coordinator_discover(conn);
|
|
if (err != 0) {
|
|
LOG_DBG("Discover failed (err %d)", err);
|
|
if (cap_cb && cap_cb->unicast_discovery_complete) {
|
|
cap_cb->unicast_discovery_complete(conn,
|
|
err,
|
|
NULL);
|
|
}
|
|
}
|
|
} else if (cap_cb && cap_cb->unicast_discovery_complete) {
|
|
LOG_DBG("Found CAS with CSIS");
|
|
cap_cb->unicast_discovery_complete(conn, 0,
|
|
client->csis_inst);
|
|
}
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static uint8_t cap_unicast_discover_cas_cb(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
struct bt_gatt_discover_params *params)
|
|
{
|
|
params->func = NULL;
|
|
|
|
if (attr == NULL) {
|
|
if (cap_cb && cap_cb->unicast_discovery_complete) {
|
|
cap_cb->unicast_discovery_complete(conn, -ENODATA,
|
|
NULL);
|
|
}
|
|
} else {
|
|
const struct bt_gatt_service_val *prim_service = attr->user_data;
|
|
struct cap_unicast_client *client = CONTAINER_OF(params,
|
|
struct cap_unicast_client,
|
|
param);
|
|
int err;
|
|
|
|
client->cas_found = true;
|
|
client->conn = bt_conn_ref(conn);
|
|
|
|
if (attr->handle == prim_service->end_handle) {
|
|
LOG_DBG("Found CAS without CSIS");
|
|
cap_cb->unicast_discovery_complete(conn, 0, NULL);
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
LOG_DBG("Found CAS, discovering included CSIS");
|
|
|
|
params->uuid = NULL;
|
|
params->start_handle = attr->handle + 1;
|
|
params->end_handle = prim_service->end_handle;
|
|
params->type = BT_GATT_DISCOVER_INCLUDE;
|
|
params->func = cap_unicast_discover_included_cb;
|
|
|
|
err = bt_gatt_discover(conn, params);
|
|
if (err != 0) {
|
|
LOG_DBG("Discover failed (err %d)", err);
|
|
|
|
params->func = NULL;
|
|
if (cap_cb && cap_cb->unicast_discovery_complete) {
|
|
cap_cb->unicast_discovery_complete(conn, err,
|
|
NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
int bt_cap_initiator_unicast_discover(struct bt_conn *conn)
|
|
{
|
|
struct bt_gatt_discover_params *param;
|
|
int err;
|
|
|
|
CHECKIF(conn == NULL) {
|
|
LOG_DBG("NULL conn");
|
|
return -EINVAL;
|
|
}
|
|
|
|
param = &bt_cap_unicast_clients[bt_conn_index(conn)].param;
|
|
|
|
/* use param->func to tell if a client is "busy" */
|
|
if (param->func != NULL) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
param->func = cap_unicast_discover_cas_cb;
|
|
param->uuid = cas_uuid;
|
|
param->type = BT_GATT_DISCOVER_PRIMARY;
|
|
param->start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
|
|
param->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
|
|
|
|
err = bt_gatt_discover(conn, param);
|
|
if (err != 0) {
|
|
param->func = NULL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static bool cap_stream_in_active_proc(const struct bt_cap_stream *cap_stream)
|
|
{
|
|
if (!cap_proc_is_active()) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0U; i < active_proc.stream_cnt; i++) {
|
|
if (active_proc.streams[i] == cap_stream) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool valid_unicast_audio_start_param(const struct bt_cap_unicast_audio_start_param *param,
|
|
struct bt_bap_unicast_group *unicast_group)
|
|
{
|
|
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->stream_params == NULL) {
|
|
LOG_DBG("param->stream_params is NULL");
|
|
return false;
|
|
}
|
|
|
|
CHECKIF(param->count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) {
|
|
LOG_DBG("param->count (%zu) is larger than "
|
|
"CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT (%d)",
|
|
param->count,
|
|
CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT);
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0U; i < param->count; i++) {
|
|
const struct bt_cap_unicast_audio_start_stream_param *stream_param =
|
|
¶m->stream_params[i];
|
|
const union bt_cap_set_member *member = &stream_param->member;
|
|
const struct bt_cap_stream *cap_stream = stream_param->stream;
|
|
const struct bt_audio_codec_cfg *codec_cfg = stream_param->codec_cfg;
|
|
const struct bt_bap_stream *bap_stream;
|
|
|
|
CHECKIF(stream_param->codec_cfg == NULL) {
|
|
LOG_DBG("param->stream_params[%zu].codec_cfg is NULL", i);
|
|
return false;
|
|
}
|
|
|
|
CHECKIF(!cap_initiator_valid_metadata(codec_cfg->meta, codec_cfg->meta_len)) {
|
|
LOG_DBG("param->stream_params[%zu].codec_cfg is invalid", i);
|
|
return false;
|
|
}
|
|
|
|
CHECKIF(stream_param->ep == NULL) {
|
|
LOG_DBG("param->stream_params[%zu].ep is NULL", i);
|
|
return false;
|
|
}
|
|
|
|
CHECKIF(member == NULL) {
|
|
LOG_DBG("param->stream_params[%zu].member is NULL", i);
|
|
return false;
|
|
}
|
|
|
|
if (param->type == BT_CAP_SET_TYPE_AD_HOC) {
|
|
struct cap_unicast_client *client;
|
|
|
|
CHECKIF(member->member == NULL) {
|
|
LOG_DBG("param->members[%zu] is NULL", i);
|
|
return false;
|
|
}
|
|
|
|
client = &bt_cap_unicast_clients[bt_conn_index(member->member)];
|
|
|
|
if (!client->cas_found) {
|
|
LOG_DBG("CAS was not found for param->members[%zu]", i);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (param->type == BT_CAP_SET_TYPE_CSIP) {
|
|
struct cap_unicast_client *client;
|
|
|
|
CHECKIF(member->csip == NULL) {
|
|
LOG_DBG("param->csip.set[%zu] is NULL", i);
|
|
return false;
|
|
}
|
|
|
|
client = lookup_unicast_client_by_csis(member->csip);
|
|
if (client == NULL) {
|
|
LOG_DBG("CSIS was not found for param->members[%zu]", i);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CHECKIF(cap_stream == NULL) {
|
|
LOG_DBG("param->streams[%zu] is NULL", i);
|
|
return false;
|
|
}
|
|
|
|
bap_stream = &cap_stream->bap_stream;
|
|
|
|
CHECKIF(bap_stream->ep != NULL) {
|
|
LOG_DBG("param->streams[%zu] is already started", i);
|
|
return false;
|
|
}
|
|
|
|
CHECKIF(bap_stream->group == NULL) {
|
|
LOG_DBG("param->streams[%zu] is not in a unicast group", i);
|
|
return false;
|
|
}
|
|
|
|
CHECKIF(bap_stream->group != unicast_group) {
|
|
LOG_DBG("param->streams[%zu] is not in this group %p", i, unicast_group);
|
|
return false;
|
|
}
|
|
|
|
for (size_t j = 0U; j < i; j++) {
|
|
if (param->stream_params[j].stream == cap_stream) {
|
|
LOG_DBG("param->stream_params[%zu] (%p) is "
|
|
"duplicated by "
|
|
"param->stream_params[%zu] (%p)",
|
|
j, param->stream_params[j].stream,
|
|
i, cap_stream);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int cap_initiator_unicast_audio_configure(
|
|
const struct bt_cap_unicast_audio_start_param *param)
|
|
{
|
|
/** TODO: If this is a CSIP set, then the order of the procedures may
|
|
* not match the order in the parameters, and the CSIP ordered access
|
|
* procedure should be used.
|
|
*/
|
|
|
|
/* Store the information about the active procedure so that we can
|
|
* continue the procedure after each step
|
|
*/
|
|
atomic_set_bit(active_proc.proc_state_flags, CAP_UNICAST_PROC_STATE_ACTIVE);
|
|
active_proc.stream_cnt = param->count;
|
|
|
|
cap_set_subproc(CAP_UNICAST_SUBPROC_TYPE_CODEC_CONFIG);
|
|
|
|
for (size_t i = 0U; i < param->count; i++) {
|
|
struct bt_cap_unicast_audio_start_stream_param *stream_param =
|
|
¶m->stream_params[i];
|
|
union bt_cap_set_member *member = &stream_param->member;
|
|
struct bt_cap_stream *cap_stream = stream_param->stream;
|
|
struct bt_bap_stream *bap_stream = &cap_stream->bap_stream;
|
|
struct bt_bap_ep *ep = stream_param->ep;
|
|
struct bt_audio_codec_cfg *codec_cfg = stream_param->codec_cfg;
|
|
struct bt_conn *conn;
|
|
int err;
|
|
|
|
if (param->type == BT_CAP_SET_TYPE_AD_HOC) {
|
|
conn = member->member;
|
|
} else {
|
|
struct cap_unicast_client *client;
|
|
|
|
/* We have verified that `client` wont be NULL in
|
|
* `valid_unicast_audio_start_param`.
|
|
*/
|
|
client = lookup_unicast_client_by_csis(member->csip);
|
|
__ASSERT(client != NULL, "client is NULL");
|
|
conn = client->conn;
|
|
}
|
|
|
|
/* Ensure that ops are registered before any procedures are started */
|
|
bt_cap_stream_ops_register_bap(cap_stream);
|
|
|
|
active_proc.streams[i] = cap_stream;
|
|
|
|
err = bt_bap_stream_config(conn, bap_stream, ep, codec_cfg);
|
|
if (err != 0) {
|
|
LOG_DBG("bt_bap_stream_config failed for param->stream_params[%zu]: %d",
|
|
i, err);
|
|
|
|
if (i > 0U) {
|
|
cap_abort_proc(bap_stream->conn, err);
|
|
} else {
|
|
(void)memset(&active_proc, 0, sizeof(active_proc));
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
active_proc.stream_initiated_cnt++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cap_initiator_unicast_audio_proc_complete(void)
|
|
{
|
|
struct bt_bap_unicast_group *unicast_group;
|
|
enum cap_unicast_proc_type proc_type;
|
|
struct bt_conn *failed_conn;
|
|
int err;
|
|
|
|
unicast_group = active_proc.unicast_group;
|
|
failed_conn = active_proc.failed_conn;
|
|
err = active_proc.err;
|
|
proc_type = active_proc.proc_type;
|
|
(void)memset(&active_proc, 0, sizeof(active_proc));
|
|
|
|
if (cap_cb == NULL) {
|
|
return;
|
|
}
|
|
|
|
switch (proc_type) {
|
|
case CAP_UNICAST_PROC_TYPE_START:
|
|
if (cap_cb->unicast_start_complete != NULL) {
|
|
cap_cb->unicast_start_complete(unicast_group, err, failed_conn);
|
|
}
|
|
break;
|
|
case CAP_UNICAST_PROC_TYPE_UPDATE:
|
|
if (cap_cb->unicast_update_complete != NULL) {
|
|
cap_cb->unicast_update_complete(err, failed_conn);
|
|
}
|
|
break;
|
|
case CAP_UNICAST_PROC_TYPE_STOP:
|
|
if (cap_cb->unicast_stop_complete != NULL) {
|
|
cap_cb->unicast_stop_complete(unicast_group, err, failed_conn);
|
|
}
|
|
break;
|
|
case CAP_UNICAST_PROC_TYPE_NONE:
|
|
default:
|
|
__ASSERT(false, "Invalid proc_type: %u", proc_type);
|
|
}
|
|
}
|
|
|
|
int bt_cap_initiator_unicast_audio_start(const struct bt_cap_unicast_audio_start_param *param,
|
|
struct bt_bap_unicast_group *unicast_group)
|
|
{
|
|
if (cap_proc_is_active()) {
|
|
LOG_DBG("A CAP procedure is already in progress");
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
CHECKIF(unicast_group == NULL) {
|
|
LOG_DBG("unicast_group is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!valid_unicast_audio_start_param(param, unicast_group)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
active_proc.unicast_group = unicast_group;
|
|
active_proc.proc_type = CAP_UNICAST_PROC_TYPE_START;
|
|
|
|
return cap_initiator_unicast_audio_configure(param);
|
|
}
|
|
|
|
void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream)
|
|
{
|
|
struct bt_conn *conns[MIN(CONFIG_BT_MAX_CONN,
|
|
CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT)];
|
|
struct bt_bap_unicast_group *unicast_group;
|
|
|
|
if (!cap_stream_in_active_proc(cap_stream)) {
|
|
/* State change happened outside of a procedure; ignore */
|
|
return;
|
|
}
|
|
|
|
if (active_proc.subproc_type == CAP_UNICAST_SUBPROC_TYPE_RELEASE) {
|
|
/* When releasing a stream, it may go into the codec configured state if
|
|
* the unicast server caches the configuration - We treat it as a release
|
|
*/
|
|
bt_cap_initiator_released(cap_stream);
|
|
return;
|
|
} else if (active_proc.subproc_type != CAP_UNICAST_SUBPROC_TYPE_CODEC_CONFIG) {
|
|
/* Unexpected callback - Abort */
|
|
cap_abort_proc(cap_stream->bap_stream.conn, -EBADMSG);
|
|
} else {
|
|
active_proc.stream_done_cnt++;
|
|
|
|
LOG_DBG("Stream %p configured (%zu/%zu streams done)",
|
|
cap_stream, active_proc.stream_done_cnt,
|
|
active_proc.stream_cnt);
|
|
|
|
if (!cap_proc_is_done()) {
|
|
/* Not yet finished, wait for all */
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (cap_proc_is_aborted()) {
|
|
if (cap_proc_all_streams_handled()) {
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* The QoS Configure procedure works on a set of connections and a
|
|
* unicast group, so we generate a list of unique connection pointers
|
|
* for the procedure
|
|
*/
|
|
(void)memset(conns, 0, sizeof(conns));
|
|
for (size_t i = 0U; i < active_proc.stream_cnt; i++) {
|
|
struct bt_conn *stream_conn = active_proc.streams[i]->bap_stream.conn;
|
|
struct bt_conn **free_conn = NULL;
|
|
bool already_added = false;
|
|
|
|
for (size_t j = 0U; j < ARRAY_SIZE(conns); j++) {
|
|
if (stream_conn == conns[j]) {
|
|
already_added = true;
|
|
break;
|
|
} else if (conns[j] == NULL && free_conn == NULL) {
|
|
free_conn = &conns[j];
|
|
}
|
|
}
|
|
|
|
if (already_added) {
|
|
continue;
|
|
}
|
|
|
|
if (free_conn != NULL) {
|
|
*free_conn = stream_conn;
|
|
} else {
|
|
__ASSERT_PRINT("No free conns");
|
|
}
|
|
}
|
|
|
|
/* All streams in the procedure share the same unicast group, so we just
|
|
* use the reference from the first stream
|
|
*/
|
|
unicast_group = (struct bt_bap_unicast_group *)active_proc.streams[0]->bap_stream.group;
|
|
cap_set_subproc(CAP_UNICAST_SUBPROC_TYPE_QOS_CONFIG);
|
|
|
|
for (size_t i = 0U; i < ARRAY_SIZE(conns); i++) {
|
|
int err;
|
|
|
|
/* When conns[i] is NULL, we have QoS Configured all unique connections */
|
|
if (conns[i] == NULL) {
|
|
break;
|
|
}
|
|
|
|
err = bt_bap_stream_qos(conns[i], unicast_group);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to set stream QoS for conn %p and group %p: %d",
|
|
(void *)conns[i], unicast_group, err);
|
|
|
|
/* End or mark procedure as aborted.
|
|
* If we have sent any requests over air, we will abort
|
|
* once all sent requests has completed
|
|
*/
|
|
cap_abort_proc(conns[i], err);
|
|
if (i == 0U) {
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
active_proc.stream_initiated_cnt++;
|
|
}
|
|
}
|
|
|
|
void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream)
|
|
{
|
|
if (!cap_stream_in_active_proc(cap_stream)) {
|
|
/* State change happened outside of a procedure; ignore */
|
|
return;
|
|
}
|
|
|
|
if (active_proc.subproc_type != CAP_UNICAST_SUBPROC_TYPE_QOS_CONFIG) {
|
|
/* Unexpected callback - Abort */
|
|
cap_abort_proc(cap_stream->bap_stream.conn, -EBADMSG);
|
|
} else {
|
|
active_proc.stream_done_cnt++;
|
|
|
|
LOG_DBG("Stream %p QoS configured (%zu/%zu streams done)",
|
|
cap_stream, active_proc.stream_done_cnt,
|
|
active_proc.stream_cnt);
|
|
|
|
if (!cap_proc_is_done()) {
|
|
/* Not yet finished, wait for all */
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (cap_proc_is_aborted()) {
|
|
if (cap_proc_all_streams_handled()) {
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
cap_set_subproc(CAP_UNICAST_SUBPROC_TYPE_ENABLE);
|
|
|
|
for (size_t i = 0U; i < active_proc.stream_cnt; i++) {
|
|
struct bt_cap_stream *cap_stream_active = active_proc.streams[i];
|
|
struct bt_bap_stream *bap_stream = &cap_stream_active->bap_stream;
|
|
int err;
|
|
|
|
/* TODO: Add metadata */
|
|
err = bt_bap_stream_enable(bap_stream, bap_stream->codec_cfg->meta,
|
|
bap_stream->codec_cfg->meta_len);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to enable stream %p: %d",
|
|
cap_stream_active, err);
|
|
|
|
/* End or mark procedure as aborted.
|
|
* If we have sent any requests over air, we will abort
|
|
* once all sent requests has completed
|
|
*/
|
|
cap_abort_proc(bap_stream->conn, err);
|
|
if (i == 0U) {
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream)
|
|
{
|
|
struct bt_bap_stream *bap_stream;
|
|
int err;
|
|
|
|
if (!cap_stream_in_active_proc(cap_stream)) {
|
|
/* State change happened outside of a procedure; ignore */
|
|
return;
|
|
}
|
|
|
|
if (active_proc.subproc_type != CAP_UNICAST_SUBPROC_TYPE_ENABLE) {
|
|
/* Unexpected callback - Abort */
|
|
cap_abort_proc(cap_stream->bap_stream.conn, -EBADMSG);
|
|
} else {
|
|
active_proc.stream_done_cnt++;
|
|
|
|
LOG_DBG("Stream %p enabled (%zu/%zu streams done)",
|
|
cap_stream, active_proc.stream_done_cnt,
|
|
active_proc.stream_cnt);
|
|
|
|
if (!cap_proc_is_done()) {
|
|
/* Not yet finished, wait for all */
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (cap_proc_is_aborted()) {
|
|
if (cap_proc_all_streams_handled()) {
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
cap_set_subproc(CAP_UNICAST_SUBPROC_TYPE_START);
|
|
|
|
bap_stream = &active_proc.streams[0]->bap_stream;
|
|
|
|
/* Since bt_bap_stream_start connects the ISO, we can, at this point,
|
|
* only do this one by one due to a restriction in the ISO layer
|
|
* (maximum 1 outstanding ISO connection request at any one time).
|
|
*/
|
|
err = bt_bap_stream_start(bap_stream);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to start stream %p: %d", active_proc.streams[0], err);
|
|
|
|
/* End and mark procedure as aborted.
|
|
* If we have sent any requests over air, we will abort
|
|
* once all sent requests has completed
|
|
*/
|
|
cap_abort_proc(bap_stream->conn, err);
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
void bt_cap_initiator_started(struct bt_cap_stream *cap_stream)
|
|
{
|
|
if (!cap_stream_in_active_proc(cap_stream)) {
|
|
/* State change happened outside of a procedure; ignore */
|
|
return;
|
|
}
|
|
|
|
if (active_proc.subproc_type != CAP_UNICAST_SUBPROC_TYPE_START) {
|
|
/* Unexpected callback - Abort */
|
|
cap_abort_proc(cap_stream->bap_stream.conn, -EBADMSG);
|
|
} else {
|
|
active_proc.stream_done_cnt++;
|
|
|
|
LOG_DBG("Stream %p started (%zu/%zu streams done)",
|
|
cap_stream, active_proc.stream_done_cnt,
|
|
active_proc.stream_cnt);
|
|
}
|
|
|
|
/* Since bt_bap_stream_start connects the ISO, we can, at this point,
|
|
* only do this one by one due to a restriction in the ISO layer
|
|
* (maximum 1 outstanding ISO connection request at any one time).
|
|
*/
|
|
if (!cap_proc_is_done()) {
|
|
struct bt_cap_stream *next = active_proc.streams[active_proc.stream_done_cnt];
|
|
struct bt_bap_stream *bap_stream = &next->bap_stream;
|
|
int err;
|
|
|
|
/* Not yet finished, start next */
|
|
err = bt_bap_stream_start(bap_stream);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to start stream %p: %d", next, err);
|
|
|
|
/* End and mark procedure as aborted.
|
|
* If we have sent any requests over air, we will abort
|
|
* once all sent requests has completed
|
|
*/
|
|
cap_abort_proc(bap_stream->conn, err);
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
} else {
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
}
|
|
|
|
static bool can_update_metadata(const struct bt_bap_stream *bap_stream)
|
|
{
|
|
struct bt_bap_ep_info ep_info;
|
|
int err;
|
|
|
|
if (bap_stream->conn == NULL) {
|
|
return false;
|
|
}
|
|
|
|
err = bt_bap_ep_get_info(bap_stream->ep, &ep_info);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to get endpoint info %p: %d", bap_stream, err);
|
|
|
|
return false;
|
|
}
|
|
|
|
return ep_info.state == BT_BAP_EP_STATE_ENABLING ||
|
|
ep_info.state == BT_BAP_EP_STATE_STREAMING;
|
|
}
|
|
|
|
int bt_cap_initiator_unicast_audio_update(const struct bt_cap_unicast_audio_update_param params[],
|
|
size_t count)
|
|
{
|
|
CHECKIF(params == NULL) {
|
|
LOG_DBG("params is NULL");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(count == 0) {
|
|
LOG_DBG("count is 0");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cap_proc_is_active()) {
|
|
LOG_DBG("A CAP procedure is already in progress");
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
for (size_t i = 0U; i < count; i++) {
|
|
CHECKIF(params[i].stream == NULL) {
|
|
LOG_DBG("params[%zu].stream is NULL", i);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(params[i].stream->bap_stream.conn == NULL) {
|
|
LOG_DBG("params[%zu].stream->bap_stream.conn is NULL", i);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(!cap_initiator_valid_metadata(params[i].meta,
|
|
params[i].meta_len)) {
|
|
LOG_DBG("params[%zu].meta is invalid", i);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (size_t j = 0U; j < i; j++) {
|
|
if (params[j].stream == params[i].stream) {
|
|
LOG_DBG("param.streams[%zu] is duplicated by param.streams[%zu]",
|
|
j, i);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (!can_update_metadata(¶ms[i].stream->bap_stream)) {
|
|
LOG_DBG("params[%zu].stream is not in right state to be updated", i);
|
|
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
atomic_set_bit(active_proc.proc_state_flags,
|
|
CAP_UNICAST_PROC_STATE_ACTIVE);
|
|
active_proc.stream_cnt = count;
|
|
active_proc.proc_type = CAP_UNICAST_PROC_TYPE_UPDATE;
|
|
|
|
cap_set_subproc(CAP_UNICAST_SUBPROC_TYPE_META_UPDATE);
|
|
|
|
/** TODO: If this is a CSIP set, then the order of the procedures may
|
|
* not match the order in the parameters, and the CSIP ordered access
|
|
* procedure should be used.
|
|
*/
|
|
for (size_t i = 0U; i < count; i++) {
|
|
int err;
|
|
|
|
active_proc.streams[i] = params[i].stream;
|
|
|
|
err = bt_bap_stream_metadata(¶ms[i].stream->bap_stream, params[i].meta,
|
|
params[i].meta_len);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to update metadata for stream %p: %d",
|
|
params[i].stream, err);
|
|
|
|
if (i > 0U) {
|
|
cap_abort_proc(params[i].stream->bap_stream.conn, err);
|
|
} else {
|
|
(void)memset(&active_proc, 0,
|
|
sizeof(active_proc));
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
active_proc.stream_initiated_cnt++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_cap_initiator_unicast_audio_cancel(void)
|
|
{
|
|
if (!cap_proc_is_active() && !cap_proc_is_aborted()) {
|
|
LOG_DBG("No CAP procedure is in progress");
|
|
|
|
return -EALREADY;
|
|
}
|
|
|
|
cap_abort_proc(NULL, -ECANCELED);
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void bt_cap_initiator_metadata_updated(struct bt_cap_stream *cap_stream)
|
|
{
|
|
if (!cap_stream_in_active_proc(cap_stream)) {
|
|
/* State change happened outside of a procedure; ignore */
|
|
return;
|
|
}
|
|
|
|
if (active_proc.subproc_type != CAP_UNICAST_SUBPROC_TYPE_META_UPDATE) {
|
|
/* Unexpected callback - Abort */
|
|
cap_abort_proc(cap_stream->bap_stream.conn, -EBADMSG);
|
|
} else {
|
|
active_proc.stream_done_cnt++;
|
|
|
|
LOG_DBG("Stream %p QoS metadata updated (%zu/%zu streams done)",
|
|
cap_stream, active_proc.stream_done_cnt,
|
|
active_proc.stream_cnt);
|
|
}
|
|
|
|
if (!cap_proc_is_done()) {
|
|
/* Not yet finished, wait for all */
|
|
return;
|
|
} else if (cap_proc_is_aborted()) {
|
|
if (cap_proc_all_streams_handled()) {
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
|
|
static bool can_release(const struct bt_bap_stream *bap_stream)
|
|
{
|
|
struct bt_bap_ep_info ep_info;
|
|
int err;
|
|
|
|
if (bap_stream->conn == NULL) {
|
|
return false;
|
|
}
|
|
|
|
err = bt_bap_ep_get_info(bap_stream->ep, &ep_info);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to get endpoint info %p: %d", bap_stream, err);
|
|
|
|
return false;
|
|
}
|
|
|
|
return ep_info.state != BT_BAP_EP_STATE_IDLE;
|
|
}
|
|
|
|
int bt_cap_initiator_unicast_audio_stop(struct bt_bap_unicast_group *unicast_group)
|
|
{
|
|
struct bt_bap_stream *bap_stream;
|
|
size_t stream_cnt;
|
|
|
|
if (cap_proc_is_active()) {
|
|
LOG_DBG("A CAP procedure is already in progress");
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
CHECKIF(unicast_group == NULL) {
|
|
LOG_DBG("unicast_group is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
stream_cnt = 0U;
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&unicast_group->streams, bap_stream, _node) {
|
|
if (can_release(bap_stream)) {
|
|
stream_cnt++;
|
|
}
|
|
}
|
|
|
|
if (stream_cnt == 0U) {
|
|
LOG_DBG("All streams are already stopped");
|
|
|
|
return -EALREADY;
|
|
}
|
|
|
|
atomic_set_bit(active_proc.proc_state_flags,
|
|
CAP_UNICAST_PROC_STATE_ACTIVE);
|
|
active_proc.stream_cnt = stream_cnt;
|
|
active_proc.unicast_group = unicast_group;
|
|
active_proc.proc_type = CAP_UNICAST_PROC_TYPE_STOP;
|
|
|
|
cap_set_subproc(CAP_UNICAST_SUBPROC_TYPE_RELEASE);
|
|
|
|
/** TODO: If this is a CSIP set, then the order of the procedures may
|
|
* not match the order in the parameters, and the CSIP ordered access
|
|
* procedure should be used.
|
|
*/
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&unicast_group->streams, bap_stream, _node) {
|
|
struct bt_cap_stream *cap_stream =
|
|
CONTAINER_OF(bap_stream, struct bt_cap_stream, bap_stream);
|
|
int err;
|
|
|
|
if (!can_release(bap_stream)) {
|
|
continue;
|
|
}
|
|
|
|
active_proc.streams[active_proc.stream_initiated_cnt] = cap_stream;
|
|
|
|
err = bt_bap_stream_release(bap_stream);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to stop bap_stream %p: %d", bap_stream, err);
|
|
|
|
if (active_proc.stream_initiated_cnt > 0U) {
|
|
cap_abort_proc(bap_stream->conn, err);
|
|
} else {
|
|
(void)memset(&active_proc, 0,
|
|
sizeof(active_proc));
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
active_proc.stream_initiated_cnt++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void bt_cap_initiator_released(struct bt_cap_stream *cap_stream)
|
|
{
|
|
if (!cap_stream_in_active_proc(cap_stream)) {
|
|
/* State change happened outside of a procedure; ignore */
|
|
return;
|
|
}
|
|
|
|
if (active_proc.subproc_type != CAP_UNICAST_SUBPROC_TYPE_RELEASE) {
|
|
/* Unexpected callback - Abort */
|
|
cap_abort_proc(cap_stream->bap_stream.conn, -EBADMSG);
|
|
} else {
|
|
active_proc.stream_done_cnt++;
|
|
|
|
LOG_DBG("Stream %p released (%zu/%zu streams done)",
|
|
cap_stream, active_proc.stream_done_cnt,
|
|
active_proc.stream_cnt);
|
|
}
|
|
|
|
if (!cap_proc_is_done()) {
|
|
/* Not yet finished, wait for all */
|
|
return;
|
|
} else if (cap_proc_is_aborted()) {
|
|
if (cap_proc_all_streams_handled()) {
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
cap_initiator_unicast_audio_proc_complete();
|
|
}
|
|
|
|
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
|
|
|
|
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) && defined(CONFIG_BT_BAP_UNICAST_CLIENT)
|
|
|
|
int bt_cap_initiator_unicast_to_broadcast(
|
|
const struct bt_cap_unicast_to_broadcast_param *param,
|
|
struct bt_cap_broadcast_source **source)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int bt_cap_initiator_broadcast_to_unicast(const struct bt_cap_broadcast_to_unicast_param *param,
|
|
struct bt_bap_unicast_group **unicast_group)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE && CONFIG_BT_BAP_UNICAST_CLIENT */
|