| /* |
| * Copyright (c) 2022-2024 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <zephyr/autoconf.h> |
| #include <zephyr/bluetooth/audio/audio.h> |
| #include <zephyr/bluetooth/audio/bap.h> |
| #include <zephyr/bluetooth/audio/cap.h> |
| #include <zephyr/bluetooth/audio/ccid.h> |
| #include <zephyr/bluetooth/audio/csip.h> |
| #include <zephyr/bluetooth/audio/tbs.h> |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/gatt.h> |
| #include <zephyr/bluetooth/iso.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/net_buf.h> |
| #include <zephyr/sys/__assert.h> |
| #include <zephyr/sys/check.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/sys/util_macro.h> |
| #include <sys/errno.h> |
| |
| #include "bap_endpoint.h" |
| #include "cap_internal.h" |
| #include "csip_internal.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; |
| } |
| |
| int bt_cap_initiator_unregister_cb(const struct bt_cap_initiator_cb *cb) |
| { |
| CHECKIF(cb == NULL) { |
| LOG_DBG("cb is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(cap_cb != cb) { |
| LOG_DBG("cb is not registered"); |
| return -EINVAL; |
| } |
| |
| cap_cb = NULL; |
| |
| return 0; |
| } |
| |
| struct valid_metadata_param { |
| bool stream_context_found; |
| bool valid; |
| }; |
| |
| static bool data_func_cb(struct bt_data *data, void *user_data) |
| { |
| struct valid_metadata_param *metadata_param = (struct valid_metadata_param *)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; |
| } |
| |
| metadata_param->stream_context_found = true; |
| } else if (IS_ENABLED(CONFIG_BT_CONN) && data->type == BT_AUDIO_METADATA_TYPE_CCID_LIST) { |
| /* If the application supplies a CCID list, we verify that the CCIDs exist on our |
| * device |
| * This is guarded by CONFIG_BT_CONN as without that we do not have a GATT DB to |
| * check. |
| */ |
| for (uint8_t i = 0U; i < data->data_len; i++) { |
| const uint8_t ccid = data->data[i]; |
| if (bt_ccid_find_attr(ccid) == NULL) { |
| LOG_DBG("Unknown characteristic for CCID 0x%02X", ccid); |
| metadata_param->valid = false; |
| |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool cap_initiator_valid_metadata(const uint8_t meta[], size_t meta_len) |
| { |
| struct valid_metadata_param metadata_param = { |
| .stream_context_found = false, |
| .valid = true, |
| }; |
| int err; |
| |
| LOG_DBG("meta %p len %zu", meta, meta_len); |
| |
| err = bt_audio_data_parse(meta, meta_len, data_func_cb, &metadata_param); |
| if (err != 0 && err != -ECANCELED) { |
| return false; |
| } |
| |
| if (!metadata_param.stream_context_found) { |
| LOG_DBG("No streaming context supplied"); |
| } |
| |
| return metadata_param.stream_context_found && metadata_param.valid; |
| } |
| |
| #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_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_ISO_BROADCAST_CODE_SIZE); |
| } else { |
| memset(bap_param->broadcast_code, 0, BT_ISO_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_param bap_create_param = {0}; |
| |
| 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_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) |
| |
| static enum bt_bap_ep_state stream_get_state(const struct bt_bap_stream *bap_stream) |
| { |
| struct bt_bap_ep_info ep_info; |
| int err; |
| |
| if (bap_stream->ep == NULL) { |
| return BT_BAP_EP_STATE_IDLE; |
| } |
| |
| 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 BT_BAP_EP_STATE_IDLE; |
| } |
| |
| return ep_info.state; |
| } |
| |
| static bool stream_is_in_state(const struct bt_bap_stream *bap_stream, enum bt_bap_ep_state state) |
| { |
| if (bap_stream->conn == NULL) { |
| return state == BT_BAP_EP_STATE_IDLE; |
| } |
| |
| return stream_get_state(bap_stream) == state; |
| } |
| |
| static bool stream_is_dir(const struct bt_bap_stream *bap_stream, enum bt_audio_dir dir) |
| { |
| 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.dir == dir; |
| } |
| |
| static enum bt_iso_state bap_stream_get_iso_state(const struct bt_bap_stream *bap_stream) |
| { |
| struct bt_bap_ep_info ep_info; |
| int err; |
| |
| if (bap_stream->ep == NULL) { |
| return BT_ISO_STATE_DISCONNECTED; |
| } |
| |
| 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 BT_ISO_STATE_DISCONNECTED; |
| } |
| |
| if (ep_info.iso_chan == NULL) { |
| return BT_ISO_STATE_DISCONNECTED; |
| } |
| |
| return ep_info.iso_chan->state; |
| } |
| |
| static bool iso_is_in_state(const struct bt_cap_stream *cap_stream, enum bt_iso_state state) |
| { |
| const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream; |
| |
| return bap_stream_get_iso_state(bap_stream) == state; |
| } |
| |
| static void set_cap_stream_in_progress(struct bt_cap_stream *cap_stream, bool value) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| |
| for (size_t i = 0U; i < active_proc->proc_cnt; i++) { |
| if (cap_stream == active_proc->proc_param.initiator[i].stream) { |
| active_proc->proc_param.initiator[i].in_progress = value; |
| return; |
| } |
| } |
| |
| __ASSERT(false, "CAP stream %p not in active_proc", cap_stream); |
| } |
| |
| /** |
| * @brief Gets the next stream for the active procedure. |
| * |
| * Returns NULL if all streams are in the right state for the current step |
| */ |
| static struct bt_cap_initiator_proc_param * |
| get_proc_param_by_cap_stream(struct bt_cap_common_proc *active_proc, |
| const struct bt_cap_stream *cap_stream) |
| { |
| for (size_t i = 0U; i < active_proc->proc_cnt; i++) { |
| if (active_proc->proc_param.initiator[i].stream == cap_stream) { |
| return &active_proc->proc_param.initiator[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void update_proc_done_cnt(struct bt_cap_common_proc *active_proc) |
| { |
| const enum bt_cap_common_subproc_type subproc_type = active_proc->subproc_type; |
| const enum bt_cap_common_proc_type proc_type = active_proc->proc_type; |
| size_t proc_done_cnt = 0U; |
| |
| if (proc_type == BT_CAP_COMMON_PROC_TYPE_START) { |
| /* To support state changes by the server, we cannot rely simply on the number of |
| * BAP procedures we have initiated. For the start and stop CAP procedures we use |
| * the states to determine how far we are. |
| */ |
| for (size_t i = 0U; i < active_proc->proc_cnt; i++) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *cap_stream; |
| struct bt_bap_stream *bap_stream; |
| enum bt_bap_ep_state state; |
| |
| proc_param = &active_proc->proc_param.initiator[i]; |
| cap_stream = proc_param->stream; |
| bap_stream = &cap_stream->bap_stream; |
| |
| state = stream_get_state(bap_stream); |
| |
| switch (subproc_type) { |
| case BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG: |
| if (state > BT_BAP_EP_STATE_IDLE) { |
| proc_done_cnt++; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG: |
| if (state > BT_BAP_EP_STATE_CODEC_CONFIGURED) { |
| proc_done_cnt++; |
| } else if (state < BT_BAP_EP_STATE_CODEC_CONFIGURED) { |
| /* Unexpected state - Abort */ |
| bt_cap_common_abort_proc(bap_stream->conn, -EBADMSG); |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_ENABLE: |
| if (state > BT_BAP_EP_STATE_QOS_CONFIGURED) { |
| proc_done_cnt++; |
| } else if (state < BT_BAP_EP_STATE_QOS_CONFIGURED) { |
| /* Unexpected state - Abort */ |
| bt_cap_common_abort_proc(bap_stream->conn, -EBADMSG); |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_CONNECT: |
| if (state < BT_BAP_EP_STATE_ENABLING) { |
| /* Unexpected state - Abort */ |
| bt_cap_common_abort_proc(bap_stream->conn, -EBADMSG); |
| } else if (proc_param->start.connected) { |
| proc_done_cnt++; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_START: |
| if (state > BT_BAP_EP_STATE_ENABLING) { |
| proc_done_cnt++; |
| } else if (state < BT_BAP_EP_STATE_ENABLING || |
| !iso_is_in_state(cap_stream, BT_ISO_STATE_CONNECTED)) { |
| /* Unexpected state - Abort */ |
| bt_cap_common_abort_proc(bap_stream->conn, -EBADMSG); |
| } |
| break; |
| default: |
| __ASSERT(false, "Invalid subproc %d for %d", subproc_type, |
| proc_type); |
| } |
| } |
| } else if (proc_type == BT_CAP_COMMON_PROC_TYPE_STOP) { |
| /* To support state changes by the server, we cannot rely simply on the number of |
| * BAP procedures we have initiated. For the start and stop CAP procedures we use |
| * the states to determine how far we are. |
| */ |
| for (size_t i = 0U; i < active_proc->proc_cnt; i++) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *cap_stream; |
| struct bt_bap_stream *bap_stream; |
| enum bt_bap_ep_state state; |
| |
| proc_param = &active_proc->proc_param.initiator[i]; |
| cap_stream = proc_param->stream; |
| bap_stream = &cap_stream->bap_stream; |
| |
| state = stream_get_state(bap_stream); |
| |
| switch (subproc_type) { |
| case BT_CAP_COMMON_SUBPROC_TYPE_DISABLE: |
| if (state == BT_BAP_EP_STATE_QOS_CONFIGURED || |
| state == BT_BAP_EP_STATE_DISABLING) { |
| proc_done_cnt++; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_STOP: |
| if (state == BT_BAP_EP_STATE_QOS_CONFIGURED) { |
| proc_done_cnt++; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_RELEASE: |
| if (state == BT_BAP_EP_STATE_IDLE || |
| state == BT_BAP_EP_STATE_CODEC_CONFIGURED) { |
| proc_done_cnt++; |
| } |
| break; |
| default: |
| __ASSERT(false, "Invalid subproc %d for %d", subproc_type, |
| proc_type); |
| } |
| } |
| } else if (proc_type == BT_CAP_COMMON_PROC_TYPE_UPDATE) { |
| /* For metadata we cannot check the states for all streams, as it does not trigger a |
| * state change |
| */ |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *cap_stream; |
| struct bt_bap_stream *bap_stream; |
| enum bt_bap_ep_state state; |
| |
| proc_param = &active_proc->proc_param.initiator[active_proc->proc_done_cnt]; |
| cap_stream = proc_param->stream; |
| bap_stream = &cap_stream->bap_stream; |
| |
| state = stream_get_state(bap_stream); |
| |
| switch (subproc_type) { |
| case BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE: |
| if (state == BT_BAP_EP_STATE_ENABLING || |
| state == BT_BAP_EP_STATE_STREAMING) { |
| proc_done_cnt = active_proc->proc_done_cnt + 1U; |
| } else { |
| /* Unexpected state - Abort */ |
| bt_cap_common_abort_proc(bap_stream->conn, -EBADMSG); |
| } |
| break; |
| default: |
| __ASSERT(false, "Invalid subproc %d for %d", subproc_type, proc_type); |
| } |
| } |
| |
| active_proc->proc_done_cnt = proc_done_cnt; |
| |
| LOG_DBG("proc %d subproc %d: %zu/%zu", proc_type, subproc_type, active_proc->proc_done_cnt, |
| active_proc->proc_cnt); |
| } |
| |
| /** |
| * @brief Gets the next stream for the active procedure. |
| * |
| * Returns NULL if all streams are in the right state for the current step |
| */ |
| static struct bt_cap_initiator_proc_param * |
| get_next_proc_param(struct bt_cap_common_proc *active_proc) |
| { |
| const enum bt_cap_common_subproc_type subproc_type = active_proc->subproc_type; |
| |
| for (size_t i = 0U; i < active_proc->proc_cnt; i++) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *cap_stream; |
| struct bt_bap_stream *bap_stream; |
| enum bt_bap_ep_state state; |
| |
| proc_param = &active_proc->proc_param.initiator[i]; |
| |
| if (proc_param->in_progress) { |
| continue; |
| } |
| |
| cap_stream = proc_param->stream; |
| bap_stream = &cap_stream->bap_stream; |
| state = stream_get_state(bap_stream); |
| |
| switch (subproc_type) { |
| case BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG: |
| if (state == BT_BAP_EP_STATE_IDLE) { |
| return proc_param; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG: |
| if (state == BT_BAP_EP_STATE_CODEC_CONFIGURED) { |
| return proc_param; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_ENABLE: |
| if (state == BT_BAP_EP_STATE_QOS_CONFIGURED) { |
| return proc_param; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_CONNECT: |
| if (state == BT_BAP_EP_STATE_ENABLING && !proc_param->start.connected) { |
| return proc_param; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_START: |
| if (state == BT_BAP_EP_STATE_ENABLING) { |
| /* TODO: Add check for connected */ |
| return proc_param; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE: |
| if (state == BT_BAP_EP_STATE_ENABLING || |
| state == BT_BAP_EP_STATE_STREAMING) { |
| return proc_param; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_DISABLE: |
| if (state == BT_BAP_EP_STATE_ENABLING || |
| state == BT_BAP_EP_STATE_STREAMING) { |
| return proc_param; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_STOP: |
| if (state == BT_BAP_EP_STATE_DISABLING) { |
| return proc_param; |
| } |
| break; |
| case BT_CAP_COMMON_SUBPROC_TYPE_RELEASE: |
| if (proc_param->stop.release && state != BT_BAP_EP_STATE_IDLE && |
| state != BT_BAP_EP_STATE_CODEC_CONFIGURED) { |
| return proc_param; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void |
| bt_cap_initiator_discover_complete(struct bt_conn *conn, int err, |
| const struct bt_csip_set_coordinator_set_member *member, |
| const struct bt_csip_set_coordinator_csis_inst *csis_inst) |
| { |
| if (cap_cb && cap_cb->unicast_discovery_complete) { |
| cap_cb->unicast_discovery_complete(conn, err, member, csis_inst); |
| } |
| } |
| |
| int bt_cap_initiator_unicast_discover(struct bt_conn *conn) |
| { |
| CHECKIF(conn == NULL) { |
| LOG_DBG("NULL conn"); |
| return -EINVAL; |
| } |
| |
| return bt_cap_common_discover(conn, bt_cap_initiator_discover_complete); |
| } |
| |
| static bool valid_unicast_audio_start_param(const struct bt_cap_unicast_audio_start_param *param) |
| { |
| struct bt_bap_unicast_group *unicast_group = NULL; |
| |
| 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; |
| const struct bt_conn *member_conn = |
| bt_cap_common_get_member_conn(param->type, member); |
| |
| if (member == NULL) { |
| LOG_DBG("param->members[%zu] is NULL", i); |
| return false; |
| } |
| |
| if (member_conn == NULL) { |
| LOG_DBG("Invalid param->members[%zu]", i); |
| return false; |
| } |
| |
| 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; |
| } |
| |
| CHECKIF(cap_stream == NULL) { |
| LOG_DBG("param->streams[%zu] is NULL", i); |
| return false; |
| } |
| |
| bap_stream = &cap_stream->bap_stream; |
| |
| CHECKIF(bap_stream->group == NULL) { |
| LOG_DBG("param->streams[%zu] is not in a unicast group", i); |
| return false; |
| } |
| |
| /* Use the group of the first stream for comparison */ |
| if (unicast_group == NULL) { |
| unicast_group = bap_stream->group; |
| } else { |
| 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 void cap_initiator_unicast_audio_proc_complete(void) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| enum bt_cap_common_proc_type proc_type; |
| struct bt_conn *failed_conn; |
| int err; |
| |
| failed_conn = active_proc->failed_conn; |
| err = active_proc->err; |
| proc_type = active_proc->proc_type; |
| bt_cap_common_clear_active_proc(); |
| |
| if (cap_cb == NULL) { |
| return; |
| } |
| |
| switch (proc_type) { |
| case BT_CAP_COMMON_PROC_TYPE_START: |
| if (cap_cb->unicast_start_complete != NULL) { |
| cap_cb->unicast_start_complete(err, failed_conn); |
| } |
| break; |
| case BT_CAP_COMMON_PROC_TYPE_UPDATE: |
| if (cap_cb->unicast_update_complete != NULL) { |
| cap_cb->unicast_update_complete(err, failed_conn); |
| } |
| break; |
| case BT_CAP_COMMON_PROC_TYPE_STOP: |
| if (cap_cb->unicast_stop_complete != NULL) { |
| cap_cb->unicast_stop_complete(err, failed_conn); |
| } |
| break; |
| case BT_CAP_COMMON_PROC_TYPE_NONE: |
| default: |
| __ASSERT(false, "Invalid proc_type: %u", proc_type); |
| } |
| } |
| |
| void bt_cap_initiator_cp_cb(struct bt_cap_stream *cap_stream, enum bt_bap_ascs_rsp_code rsp_code, |
| enum bt_bap_ascs_reason reason) |
| { |
| if (!bt_cap_common_stream_in_active_proc(cap_stream)) { |
| /* State change happened outside of a procedure; ignore */ |
| return; |
| } |
| |
| LOG_DBG("cap_stream %p", cap_stream); |
| |
| set_cap_stream_in_progress(cap_stream, false); |
| |
| if (rsp_code != BT_BAP_ASCS_RSP_CODE_SUCCESS) { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| |
| /* In the case that the control point write is rejected, we will not get a ASE state |
| * change notification. This is considered an error that shall abort the current |
| * procedure. |
| */ |
| active_proc->proc_done_cnt++; |
| |
| LOG_DBG("Control point operation on stream %p failed with %d and reason %d", |
| cap_stream, rsp_code, reason); |
| |
| /* Unexpected callback - Abort */ |
| bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); |
| |
| if (bt_cap_common_proc_is_aborted()) { |
| if (bt_cap_common_proc_all_handled()) { |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| } |
| } |
| |
| static int cap_initiator_unicast_audio_configure( |
| const struct bt_cap_unicast_audio_start_param *param) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_audio_codec_cfg *codec_cfg; |
| struct bt_bap_stream *bap_stream; |
| struct bt_bap_ep *ep; |
| struct bt_conn *conn; |
| int err; |
| /** 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 < 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; |
| |
| conn = bt_cap_common_get_member_conn(param->type, member); |
| |
| /* Ensure that ops are registered before any procedures are started */ |
| bt_cap_stream_ops_register_bap(cap_stream); |
| |
| /* Store the necessary parameters as we cannot assume that the supplied parameters |
| * are kept valid |
| */ |
| active_proc->proc_param.initiator[i].stream = cap_stream; |
| active_proc->proc_param.initiator[i].start.ep = stream_param->ep; |
| active_proc->proc_param.initiator[i].start.conn = conn; |
| active_proc->proc_param.initiator[i].start.codec_cfg = stream_param->codec_cfg; |
| } |
| |
| /* Store the information about the active procedure so that we can |
| * continue the procedure after each step |
| */ |
| bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_START, param->count); |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG); |
| |
| proc_param = get_next_proc_param(active_proc); |
| if (proc_param == NULL) { |
| /* If proc_param is NULL then this step is a no-op and we can skip to the next step |
| */ |
| bt_cap_initiator_codec_configured(active_proc->proc_param.initiator[0].stream); |
| |
| return 0; |
| } |
| |
| bap_stream = &proc_param->stream->bap_stream; |
| codec_cfg = proc_param->start.codec_cfg; |
| conn = proc_param->start.conn; |
| ep = proc_param->start.ep; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| /* Since BAP operations may require a write long or a read long on the notification, |
| * we cannot assume that we can do multiple streams at once, thus do it one at a time. |
| * TODO: We should always be able to do one per ACL, so there is room for optimization. |
| * This applies to all BAP calls in this file. |
| */ |
| err = bt_bap_stream_config(conn, bap_stream, ep, codec_cfg); |
| if (err != 0) { |
| LOG_DBG("Failed to config stream %p: %d", proc_param->stream, err); |
| |
| bt_cap_common_clear_active_proc(); |
| } |
| |
| return err; |
| } |
| |
| int bt_cap_initiator_unicast_audio_start(const struct bt_cap_unicast_audio_start_param *param) |
| { |
| bool all_streaming = true; |
| |
| if (bt_cap_common_proc_is_active()) { |
| LOG_DBG("A CAP procedure is already in progress"); |
| |
| return -EBUSY; |
| } |
| |
| if (!valid_unicast_audio_start_param(param)) { |
| return -EINVAL; |
| } |
| |
| for (size_t i = 0U; i < param->count; i++) { |
| const struct bt_bap_stream *bap_stream = |
| ¶m->stream_params[i].stream->bap_stream; |
| |
| if (!stream_is_in_state(bap_stream, BT_BAP_EP_STATE_STREAMING)) { |
| all_streaming = false; |
| } |
| } |
| |
| if (all_streaming) { |
| LOG_DBG("All streams are already in the streaming state"); |
| return -EALREADY; |
| } |
| |
| 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_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_bap_unicast_group *unicast_group; |
| |
| if (!bt_cap_common_stream_in_active_proc(cap_stream)) { |
| /* State change happened outside of a procedure; ignore */ |
| return; |
| } |
| |
| LOG_DBG("cap_stream %p", cap_stream); |
| |
| if (bt_cap_common_subproc_is_type(BT_CAP_COMMON_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 (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG)) { |
| /* Unexpected callback - Abort */ |
| bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); |
| } else { |
| update_proc_done_cnt(active_proc); |
| |
| LOG_DBG("Stream %p configured (%zu/%zu streams done)", cap_stream, |
| active_proc->proc_done_cnt, active_proc->proc_cnt); |
| } |
| |
| if (bt_cap_common_proc_is_aborted()) { |
| if (bt_cap_common_proc_all_handled()) { |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| |
| if (!bt_cap_common_proc_is_done()) { |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *next_bap_stream; |
| struct bt_audio_codec_cfg *codec_cfg; |
| struct bt_conn *conn; |
| struct bt_bap_ep *ep; |
| int err; |
| |
| proc_param = get_next_proc_param(active_proc); |
| __ASSERT(proc_param != NULL, "proc is not done, but could not get next proc_param"); |
| next_cap_stream = proc_param->stream; |
| conn = proc_param->start.conn; |
| ep = proc_param->start.ep; |
| codec_cfg = proc_param->start.codec_cfg; |
| next_bap_stream = &next_cap_stream->bap_stream; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_config(conn, next_bap_stream, ep, codec_cfg); |
| if (err != 0) { |
| LOG_DBG("Failed to config stream %p: %d", next_cap_stream, err); |
| |
| bt_cap_common_abort_proc(conn, err); |
| 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->proc_cnt; i++) { |
| struct bt_conn *stream_conn = |
| active_proc->proc_param.initiator[i].stream->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(false, "[%zu]: No free conns", i); |
| return; |
| } |
| } |
| |
| /* All streams in the procedure share the same unicast group, so we just |
| * use the reference from the first stream |
| */ |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG); |
| proc_param = get_next_proc_param(active_proc); |
| if (proc_param == NULL) { |
| /* If proc_param is NULL then this step is a no-op and we can skip to the next step |
| */ |
| bt_cap_initiator_qos_configured(active_proc->proc_param.initiator[0].stream); |
| |
| return; |
| } |
| |
| unicast_group = (struct bt_bap_unicast_group *)proc_param->stream->bap_stream.group; |
| |
| 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; |
| } |
| |
| for (size_t j = 0U; j < active_proc->proc_cnt; j++) { |
| proc_param = &active_proc->proc_param.initiator[j]; |
| if (proc_param->stream->bap_stream.conn == conns[i]) { |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = false; |
| break; |
| } |
| } |
| |
| proc_param->in_progress = true; |
| |
| 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 |
| */ |
| bt_cap_common_abort_proc(conns[i], err); |
| if (i == 0U) { |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| } |
| } |
| |
| void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| |
| if (!bt_cap_common_stream_in_active_proc(cap_stream)) { |
| /* State change happened outside of a procedure; ignore */ |
| return; |
| } |
| |
| LOG_DBG("cap_stream %p", cap_stream); |
| |
| if (!(bt_cap_common_proc_is_type(BT_CAP_COMMON_PROC_TYPE_START) && |
| bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG)) && |
| !(bt_cap_common_proc_is_type(BT_CAP_COMMON_PROC_TYPE_STOP))) { |
| /* Unexpected callback - Abort */ |
| bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); |
| } else { |
| update_proc_done_cnt(active_proc); |
| |
| LOG_DBG("Stream %p QoS configured (%zu/%zu streams done)", cap_stream, |
| active_proc->proc_done_cnt, active_proc->proc_cnt); |
| } |
| |
| if (bt_cap_common_proc_is_aborted()) { |
| if (bt_cap_common_proc_all_handled()) { |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| |
| if (bt_cap_common_proc_is_type(BT_CAP_COMMON_PROC_TYPE_START)) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *bap_stream; |
| int err; |
| |
| if (!bt_cap_common_proc_is_done()) { |
| /* Not yet finished, wait for all */ |
| return; |
| } |
| |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_ENABLE); |
| proc_param = get_next_proc_param(active_proc); |
| if (proc_param == NULL) { |
| /* If proc_param is NULL then this step is a no-op and we can skip to the |
| * next step |
| */ |
| bt_cap_initiator_enabled(active_proc->proc_param.initiator[0].stream); |
| |
| return; |
| } |
| |
| next_cap_stream = proc_param->stream; |
| bap_stream = &next_cap_stream->bap_stream; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| 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", next_cap_stream, err); |
| |
| bt_cap_common_abort_proc(bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| } else if (bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_RELEASE)) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *next_bap_stream; |
| int err; |
| |
| proc_param = get_next_proc_param(active_proc); |
| __ASSERT(proc_param != NULL, "proc is not done, but could not get next proc_param"); |
| |
| next_cap_stream = proc_param->stream; |
| next_bap_stream = &next_cap_stream->bap_stream; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_release(next_bap_stream); |
| if (err != 0) { |
| LOG_DBG("Failed to release stream %p: %d", next_cap_stream, err); |
| |
| bt_cap_common_abort_proc(next_bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| } |
| } |
| |
| void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_bap_stream *bap_stream; |
| int err; |
| |
| if (!bt_cap_common_stream_in_active_proc(cap_stream)) { |
| /* State change happened outside of a procedure; ignore */ |
| return; |
| } |
| |
| LOG_DBG("cap_stream %p", cap_stream); |
| |
| if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_ENABLE)) { |
| /* Unexpected callback - Abort */ |
| bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); |
| } else { |
| update_proc_done_cnt(active_proc); |
| |
| LOG_DBG("Stream %p enabled (%zu/%zu streams done)", cap_stream, |
| active_proc->proc_done_cnt, active_proc->proc_cnt); |
| } |
| |
| if (bt_cap_common_proc_is_aborted()) { |
| if (bt_cap_common_proc_all_handled()) { |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| |
| if (!bt_cap_common_proc_is_done()) { |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *next_bap_stream; |
| |
| proc_param = get_next_proc_param(active_proc); |
| __ASSERT(proc_param != NULL, "proc is not done, but could not get next proc_param"); |
| next_cap_stream = proc_param->stream; |
| next_bap_stream = &next_cap_stream->bap_stream; |
| |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_enable(next_bap_stream, next_bap_stream->codec_cfg->meta, |
| next_bap_stream->codec_cfg->meta_len); |
| if (err != 0) { |
| LOG_DBG("Failed to enable stream %p: %d", next_cap_stream, err); |
| |
| bt_cap_common_abort_proc(next_bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_CONNECT); |
| proc_param = get_next_proc_param(active_proc); |
| if (proc_param == NULL) { |
| /* If proc_param is NULL then this step is a no-op and we can skip to the next step |
| */ |
| bt_cap_initiator_connected(active_proc->proc_param.initiator[0].stream); |
| |
| return; |
| } |
| |
| bap_stream = &proc_param->stream->bap_stream; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_connect(bap_stream); |
| if (err == -EALREADY) { |
| /* If the stream is already connected we can just call the callback directly |
| * NOTE: It's important that we do not do any additional functionality after |
| * calling this |
| */ |
| proc_param->in_progress = false; |
| bt_cap_initiator_connected(proc_param->stream); |
| } else if (err != 0) { |
| LOG_DBG("Failed to connect stream %p: %d", proc_param->stream, err); |
| |
| /* End and mark procedure as aborted. |
| * If we have sent any requests over air, we will abort |
| * once all sent requests has completed |
| */ |
| bt_cap_common_abort_proc(bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| } |
| |
| void bt_cap_initiator_connected(struct bt_cap_stream *cap_stream) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_bap_stream *bap_stream; |
| int err; |
| |
| if (!bt_cap_common_stream_in_active_proc(cap_stream)) { |
| /* State change happened outside of a procedure; ignore */ |
| return; |
| } |
| |
| LOG_DBG("cap_stream %p", cap_stream); |
| |
| set_cap_stream_in_progress(cap_stream, false); |
| |
| if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_CONNECT)) { |
| /* Unexpected callback - Abort */ |
| bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); |
| } else { |
| proc_param = get_proc_param_by_cap_stream(active_proc, cap_stream); |
| __ASSERT_NO_MSG(proc_param != NULL); |
| |
| /* Sets connected before update_proc_done_cnt as that is the only way to can track |
| * the CIS state change |
| */ |
| proc_param->start.connected = true; |
| update_proc_done_cnt(active_proc); |
| |
| LOG_DBG("Stream %p connected (%zu/%zu streams done)", cap_stream, |
| active_proc->proc_done_cnt, active_proc->proc_cnt); |
| } |
| |
| if (bt_cap_common_proc_is_aborted()) { |
| if (bt_cap_common_proc_all_handled()) { |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| |
| if (!bt_cap_common_proc_is_done()) { |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *next_bap_stream; |
| |
| proc_param = get_next_proc_param(active_proc); |
| if (proc_param != NULL) { |
| next_cap_stream = proc_param->stream; |
| next_bap_stream = &next_cap_stream->bap_stream; |
| |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_connect(next_bap_stream); |
| if (err == 0 || err == -EALREADY) { |
| if (err == -EALREADY) { |
| proc_param->in_progress = false; |
| } |
| /* Pending connected - wait for connected callback */ |
| } else if (err != 0) { |
| LOG_DBG("Failed to connect stream %p: %d", next_cap_stream, err); |
| |
| bt_cap_common_abort_proc(next_bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| } /* else pending connection - wait for connected callback */ |
| |
| return; |
| } |
| |
| /* All streams connected - Start sending the receiver start ready for all source |
| * ASEs. For sink ASEs it is the responsibility of the unicast server to do the |
| * receiver start ready operation. If there are no source ASEs then we just wait. |
| */ |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_START); |
| proc_param = get_next_proc_param(active_proc); |
| if (proc_param == NULL) { |
| /* If proc_param is NULL then this step is a no-op and we can skip to the next step |
| */ |
| bt_cap_initiator_started(active_proc->proc_param.initiator[0].stream); |
| |
| return; |
| } |
| |
| bap_stream = &proc_param->stream->bap_stream; |
| if (stream_is_dir(bap_stream, BT_AUDIO_DIR_SOURCE)) { |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_start(bap_stream); |
| if (err != 0) { |
| LOG_DBG("Failed to start stream %p: %d", proc_param->stream, err); |
| |
| bt_cap_common_abort_proc(bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| |
| return; |
| } |
| } |
| } |
| |
| void bt_cap_initiator_started(struct bt_cap_stream *cap_stream) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| |
| LOG_DBG("cap_stream %p", cap_stream); |
| |
| if (!bt_cap_common_stream_in_active_proc(cap_stream)) { |
| /* State change happened outside of a procedure; ignore */ |
| return; |
| } |
| |
| /* Streams may go into the streaming state while we are connecting or starting them */ |
| if (bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_CONNECT)) { |
| /* If we are still connecting the streams, we terminate early as to not perform any |
| * start operations until all streams are connected |
| */ |
| return; |
| } else if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_START)) { |
| /* Unexpected callback - Abort */ |
| bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); |
| } else { |
| update_proc_done_cnt(active_proc); |
| |
| LOG_DBG("Stream %p started (%zu/%zu streams done)", cap_stream, |
| active_proc->proc_done_cnt, active_proc->proc_cnt); |
| } |
| |
| if (!bt_cap_common_proc_is_done()) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *next_bap_stream; |
| |
| proc_param = get_next_proc_param(active_proc); |
| if (proc_param != NULL) { |
| next_cap_stream = proc_param->stream; |
| next_bap_stream = &next_cap_stream->bap_stream; |
| |
| if (stream_is_dir(next_bap_stream, BT_AUDIO_DIR_SOURCE)) { |
| int err; |
| |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_start(next_bap_stream); |
| if (err != 0) { |
| LOG_DBG("Failed to start stream %p: %d", next_cap_stream, |
| err); |
| |
| /* End and mark procedure as aborted. |
| * If we have sent any requests over air, we will abort |
| * once all sent requests has completed |
| */ |
| bt_cap_common_abort_proc(next_bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| |
| return; |
| } |
| } |
| } /* else await notifications from server */ |
| |
| /* Return to await for response from server */ |
| return; |
| } |
| |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| static bool can_update_metadata(const struct bt_bap_stream *bap_stream) |
| { |
| return stream_is_in_state(bap_stream, BT_BAP_EP_STATE_ENABLING) || |
| stream_is_in_state(bap_stream, BT_BAP_EP_STATE_STREAMING); |
| } |
| |
| static bool valid_unicast_audio_update_param(const struct bt_cap_unicast_audio_update_param *param) |
| { |
| struct bt_bap_unicast_group *unicast_group = NULL; |
| |
| 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_update_stream_param *stream_param = |
| ¶m->stream_params[i]; |
| const struct bt_cap_stream *cap_stream = stream_param->stream; |
| const struct bt_bap_stream *bap_stream; |
| struct bt_conn *conn; |
| |
| CHECKIF(cap_stream == NULL) { |
| LOG_DBG("param->stream_params[%zu] is NULL", i); |
| return false; |
| } |
| |
| bap_stream = &cap_stream->bap_stream; |
| conn = bap_stream->conn; |
| CHECKIF(conn == NULL) { |
| LOG_DBG("param->stream_params[%zu].stream->bap_stream.conn is NULL", i); |
| |
| return -EINVAL; |
| } |
| |
| CHECKIF(bap_stream->group == NULL) { |
| LOG_DBG("param->stream_params[%zu] is not in a unicast group", i); |
| return false; |
| } |
| |
| /* Use the group of the first stream for comparison */ |
| if (unicast_group == NULL) { |
| unicast_group = bap_stream->group; |
| } else { |
| CHECKIF(bap_stream->group != unicast_group) { |
| LOG_DBG("param->stream_params[%zu] is not in this group %p", i, |
| unicast_group); |
| return false; |
| } |
| } |
| |
| if (!can_update_metadata(bap_stream)) { |
| LOG_DBG("param->stream_params[%zu].stream is not in right state to be " |
| "updated", |
| i); |
| |
| return false; |
| } |
| |
| if (!cap_initiator_valid_metadata(stream_param->meta, stream_param->meta_len)) { |
| LOG_DBG("param->stream_params[%zu] invalid metadata", i); |
| |
| 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; |
| } |
| |
| int bt_cap_initiator_unicast_audio_update(const struct bt_cap_unicast_audio_update_param *param) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_bap_stream *bap_stream; |
| const uint8_t *meta; |
| size_t meta_len; |
| int err; |
| |
| if (bt_cap_common_proc_is_active()) { |
| LOG_DBG("A CAP procedure is already in progress"); |
| |
| return -EBUSY; |
| } |
| |
| if (!valid_unicast_audio_update_param(param)) { |
| return -EINVAL; |
| } |
| |
| for (size_t i = 0U; i < param->count; i++) { |
| const struct bt_cap_unicast_audio_update_stream_param *stream_param = |
| ¶m->stream_params[i]; |
| struct bt_cap_stream *cap_stream = stream_param->stream; |
| |
| /* Ensure that ops are registered before any procedures are started */ |
| bt_cap_stream_ops_register_bap(cap_stream); |
| |
| active_proc->proc_param.initiator[i].stream = cap_stream; |
| active_proc->proc_param.initiator[i].meta_update.meta_len = stream_param->meta_len; |
| memcpy(&active_proc->proc_param.initiator[i].meta_update.meta, stream_param->meta, |
| stream_param->meta_len); |
| } |
| |
| bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_UPDATE, param->count); |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE); |
| |
| proc_param = get_next_proc_param(active_proc); |
| __ASSERT(proc_param != NULL, "proc is not done, but could not get next proc_param"); |
| |
| bap_stream = &proc_param->stream->bap_stream; |
| meta_len = proc_param->meta_update.meta_len; |
| meta = proc_param->meta_update.meta; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_metadata(bap_stream, meta, meta_len); |
| if (err != 0) { |
| LOG_DBG("Failed to update metadata for stream %p: %d", proc_param->stream, err); |
| |
| bt_cap_common_clear_active_proc(); |
| } |
| |
| return err; |
| } |
| |
| int bt_cap_initiator_unicast_audio_cancel(void) |
| { |
| if (!bt_cap_common_proc_is_active() && !bt_cap_common_proc_is_aborted()) { |
| LOG_DBG("No CAP procedure is in progress"); |
| |
| return -EALREADY; |
| } |
| |
| bt_cap_common_abort_proc(NULL, -ECANCELED); |
| cap_initiator_unicast_audio_proc_complete(); |
| |
| return 0; |
| } |
| |
| void bt_cap_initiator_metadata_updated(struct bt_cap_stream *cap_stream) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| |
| if (!bt_cap_common_stream_in_active_proc(cap_stream)) { |
| /* State change happened outside of a procedure; ignore */ |
| return; |
| } |
| |
| if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE)) { |
| /* Unexpected callback - Abort */ |
| bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); |
| } else { |
| update_proc_done_cnt(active_proc); |
| |
| LOG_DBG("Stream %p QoS metadata updated (%zu/%zu streams done)", cap_stream, |
| active_proc->proc_done_cnt, active_proc->proc_cnt); |
| } |
| |
| if (bt_cap_common_proc_is_aborted()) { |
| if (bt_cap_common_proc_all_handled()) { |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| |
| if (!bt_cap_common_proc_is_done()) { |
| const size_t proc_done_cnt = active_proc->proc_done_cnt; |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *bap_stream; |
| const uint8_t *meta; |
| size_t meta_len; |
| int err; |
| |
| proc_param = &active_proc->proc_param.initiator[proc_done_cnt]; |
| meta_len = proc_param->meta_update.meta_len; |
| meta = proc_param->meta_update.meta; |
| next_cap_stream = proc_param->stream; |
| bap_stream = &next_cap_stream->bap_stream; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_metadata(bap_stream, meta, meta_len); |
| if (err != 0) { |
| LOG_DBG("Failed to update metadata for stream %p: %d", next_cap_stream, |
| err); |
| |
| bt_cap_common_abort_proc(bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| static bool can_release_stream(const struct bt_bap_stream *bap_stream) |
| { |
| enum bt_bap_ep_state state; |
| |
| if (bap_stream->conn == NULL) { |
| return false; |
| } |
| |
| state = stream_get_state(bap_stream); |
| |
| /* We cannot release idle endpoints. |
| * We do not know if we can release endpoints in the Codec Configured state as servers may |
| * cache it, so treat it as idle |
| */ |
| return state != BT_BAP_EP_STATE_IDLE && state != BT_BAP_EP_STATE_CODEC_CONFIGURED; |
| } |
| |
| static bool can_disable_stream(const struct bt_bap_stream *bap_stream) |
| { |
| enum bt_bap_ep_state state; |
| |
| if (bap_stream->conn == NULL) { |
| return false; |
| } |
| |
| state = stream_get_state(bap_stream); |
| |
| return state == BT_BAP_EP_STATE_STREAMING || state == BT_BAP_EP_STATE_ENABLING; |
| } |
| |
| static bool can_stop_stream(const struct bt_bap_stream *bap_stream) |
| { |
| enum bt_iso_state iso_state; |
| |
| if (bap_stream->conn == NULL) { |
| return false; |
| } |
| |
| if (stream_is_dir(bap_stream, BT_AUDIO_DIR_SINK)) { |
| return false; |
| } |
| |
| iso_state = bap_stream_get_iso_state(bap_stream); |
| if (iso_state != BT_ISO_STATE_CONNECTED && iso_state != BT_ISO_STATE_CONNECTING) { |
| return false; |
| } |
| |
| return stream_is_in_state(bap_stream, BT_BAP_EP_STATE_DISABLING); |
| } |
| |
| static bool valid_unicast_audio_stop_param(const struct bt_cap_unicast_audio_stop_param *param) |
| { |
| struct bt_bap_unicast_group *unicast_group = NULL; |
| |
| 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->streams == NULL) { |
| LOG_DBG("param->streams 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_stream *cap_stream = param->streams[i]; |
| const struct bt_bap_stream *bap_stream; |
| struct bt_conn *conn; |
| |
| CHECKIF(cap_stream == NULL) { |
| LOG_DBG("param->streams[%zu] is NULL", i); |
| return false; |
| } |
| |
| bap_stream = &cap_stream->bap_stream; |
| conn = bap_stream->conn; |
| CHECKIF(conn == NULL) { |
| LOG_DBG("param->streams[%zu]->bap_stream.conn is NULL", i); |
| |
| return -EINVAL; |
| } |
| |
| if (param->type == BT_CAP_SET_TYPE_CSIP) { |
| struct bt_cap_common_client *client = bt_cap_common_get_client_by_acl(conn); |
| |
| if (client->csis_inst == NULL) { |
| LOG_DBG("param->streams[%zu]->bap_stream.conn not part of a " |
| "coordinated set", |
| i); |
| |
| return false; |
| } |
| } |
| |
| CHECKIF(bap_stream->group == NULL) { |
| LOG_DBG("param->streams[%zu] is not in a unicast group", i); |
| return false; |
| } |
| |
| /* Use the group of the first stream for comparison */ |
| if (unicast_group == NULL) { |
| unicast_group = bap_stream->group; |
| } else { |
| 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->streams[j] == cap_stream) { |
| LOG_DBG("param->stream_params[%zu] (%p) is " |
| "duplicated by " |
| "param->stream_params[%zu] (%p)", |
| j, param->streams[j], i, cap_stream); |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| int bt_cap_initiator_unicast_audio_stop(const struct bt_cap_unicast_audio_stop_param *param) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| bool can_release = false; |
| bool can_disable = false; |
| bool can_stop = false; |
| int err; |
| |
| if (bt_cap_common_proc_is_active()) { |
| LOG_DBG("A CAP procedure is already in progress"); |
| |
| return -EBUSY; |
| } |
| |
| if (!valid_unicast_audio_stop_param(param)) { |
| return -EINVAL; |
| } |
| |
| for (size_t i = 0U; i < param->count; i++) { |
| struct bt_cap_stream *cap_stream = param->streams[i]; |
| struct bt_bap_stream *bap_stream = &cap_stream->bap_stream; |
| |
| /* Ensure that ops are registered before any procedures are started */ |
| bt_cap_stream_ops_register_bap(cap_stream); |
| |
| active_proc->proc_param.initiator[i].stream = cap_stream; |
| active_proc->proc_param.initiator[i].stop.release = param->release; |
| |
| if (!can_disable && can_disable_stream(bap_stream)) { |
| can_disable = true; |
| } |
| |
| if (!can_stop && can_stop_stream(bap_stream)) { |
| can_stop = true; |
| } |
| |
| if (!can_release && param->release && can_release_stream(bap_stream)) { |
| can_release = true; |
| } |
| } |
| |
| if (!can_disable && !can_stop && !can_release) { |
| LOG_DBG("Cannot %s any streams", !can_disable ? "disable" |
| : !can_stop ? "stop" |
| : "release"); |
| return -EALREADY; |
| } |
| |
| bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_STOP, param->count); |
| /** 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. |
| */ |
| |
| if (can_disable) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_bap_stream *bap_stream; |
| |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_DISABLE); |
| |
| proc_param = get_next_proc_param(active_proc); |
| __ASSERT(proc_param != NULL, |
| "proc is not started, but could not get next proc_param"); |
| bap_stream = &proc_param->stream->bap_stream; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_disable(bap_stream); |
| if (err != 0) { |
| LOG_DBG("Failed to disable bap_stream %p: %d", proc_param->stream, err); |
| |
| bt_cap_common_clear_active_proc(); |
| } |
| } else if (can_stop) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_bap_stream *bap_stream; |
| |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_STOP); |
| |
| proc_param = get_next_proc_param(active_proc); |
| __ASSERT(proc_param != NULL, |
| "proc is not started, but could not get next proc_param"); |
| bap_stream = &proc_param->stream->bap_stream; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_stop(bap_stream); |
| if (err != 0) { |
| LOG_DBG("Failed to stop bap_stream %p: %d", proc_param->stream, err); |
| |
| bt_cap_common_clear_active_proc(); |
| } |
| } else { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_bap_stream *bap_stream; |
| |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_RELEASE); |
| |
| proc_param = get_next_proc_param(active_proc); |
| __ASSERT(proc_param != NULL, |
| "proc is not started, but could not get next proc_param"); |
| bap_stream = &proc_param->stream->bap_stream; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_release(bap_stream); |
| if (err != 0) { |
| LOG_DBG("Failed to release bap_stream %p: %d", proc_param->stream, err); |
| |
| bt_cap_common_clear_active_proc(); |
| } |
| } |
| |
| return err; |
| } |
| |
| void bt_cap_initiator_disabled(struct bt_cap_stream *cap_stream) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| |
| if (!bt_cap_common_stream_in_active_proc(cap_stream)) { |
| /* State change happened outside of a procedure; ignore */ |
| return; |
| } |
| |
| if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_DISABLE)) { |
| /* Unexpected callback - Abort */ |
| bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); |
| } else { |
| update_proc_done_cnt(active_proc); |
| |
| LOG_DBG("Stream %p disabled (%zu/%zu streams done)", cap_stream, |
| active_proc->proc_done_cnt, active_proc->proc_cnt); |
| } |
| |
| if (bt_cap_common_proc_is_aborted()) { |
| if (bt_cap_common_proc_all_handled()) { |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| |
| if (!bt_cap_common_proc_is_done()) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *next_bap_stream; |
| int err; |
| |
| proc_param = get_next_proc_param(active_proc); |
| __ASSERT(proc_param != NULL, "proc is not done, but could not get next proc_param"); |
| next_cap_stream = proc_param->stream; |
| next_bap_stream = &next_cap_stream->bap_stream; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_disable(next_bap_stream); |
| if (err != 0) { |
| LOG_DBG("Failed to disable stream %p: %d", next_cap_stream, err); |
| |
| bt_cap_common_abort_proc(next_bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| } else { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *next_bap_stream; |
| int err; |
| |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_STOP); |
| |
| proc_param = get_next_proc_param(active_proc); |
| if (proc_param == NULL) { |
| /* If proc_param is NULL then this step is a no-op and we can skip to the |
| * next step |
| */ |
| bt_cap_initiator_stopped(active_proc->proc_param.initiator[0].stream); |
| |
| return; |
| } |
| |
| next_cap_stream = proc_param->stream; |
| next_bap_stream = &next_cap_stream->bap_stream; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_stop(next_bap_stream); |
| if (err != 0 && err != -EALREADY) { |
| LOG_DBG("Failed to stop stream %p: %d", next_cap_stream, err); |
| |
| bt_cap_common_abort_proc(next_bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| } else if (err == -EALREADY) { |
| proc_param->in_progress = false; |
| } /* else wait for server notification*/ |
| } |
| } |
| |
| void bt_cap_initiator_stopped(struct bt_cap_stream *cap_stream) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| |
| if (!bt_cap_common_stream_in_active_proc(cap_stream)) { |
| /* State change happened outside of a procedure; ignore */ |
| return; |
| } |
| |
| if (!bt_cap_common_proc_is_type(BT_CAP_COMMON_PROC_TYPE_STOP)) { |
| /* Unexpected callback - Abort */ |
| bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); |
| } else { |
| if (bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_STOP)) { |
| update_proc_done_cnt(active_proc); |
| |
| LOG_DBG("Stream %p stopped (%zu/%zu streams done)", cap_stream, |
| active_proc->proc_done_cnt, active_proc->proc_cnt); |
| } else { |
| /* We are still doing disable - Wait for those to be done, as stopped may |
| * also be called when we are disabling sink ASEs |
| */ |
| return; |
| } |
| } |
| |
| if (bt_cap_common_proc_is_aborted()) { |
| if (bt_cap_common_proc_all_handled()) { |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| |
| if (!bt_cap_common_proc_is_done()) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *next_bap_stream; |
| int err; |
| |
| proc_param = get_next_proc_param(active_proc); |
| if (proc_param != NULL) { |
| next_cap_stream = proc_param->stream; |
| next_bap_stream = &next_cap_stream->bap_stream; |
| |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_stop(next_bap_stream); |
| if (err != 0 && err != -EALREADY) { |
| LOG_DBG("Failed to stop stream %p: %d", next_cap_stream, err); |
| |
| bt_cap_common_abort_proc(next_bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| } else if (err == -EALREADY) { |
| proc_param->in_progress = false; |
| } |
| } /* else await notification from server */ |
| } else { |
| /* We are done stopping streams now - We mark the next subproc. If |
| * get_next_proc_param returns a NULL value it means that we are complete done. If |
| * it returns a non-NULL value, it means that we need to start releasing streams. |
| * However, since the QoS Configured state is better suited to trigger this, we |
| * simply wait until bt_cap_initiator_qos_configured is called. |
| */ |
| struct bt_cap_initiator_proc_param *proc_param; |
| |
| if (!bt_cap_common_proc_is_done()) { |
| /* We are still disabling or stopping some */ |
| return; |
| } |
| |
| bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_RELEASE); |
| |
| proc_param = get_next_proc_param(active_proc); |
| if (proc_param == NULL) { |
| /* If proc_param is NULL then this step is a no-op and we can finish the |
| * procedure |
| */ |
| cap_initiator_unicast_audio_proc_complete(); |
| |
| return; |
| } /* wait for bt_cap_initiator_qos_configured */ |
| } |
| } |
| |
| void bt_cap_initiator_released(struct bt_cap_stream *cap_stream) |
| { |
| struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); |
| |
| if (!bt_cap_common_stream_in_active_proc(cap_stream)) { |
| /* State change happened outside of a procedure; ignore */ |
| return; |
| } |
| |
| if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_RELEASE)) { |
| /* Unexpected callback - Abort */ |
| bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); |
| } else { |
| update_proc_done_cnt(active_proc); |
| |
| LOG_DBG("Stream %p released (%zu/%zu streams done)", cap_stream, |
| active_proc->proc_done_cnt, active_proc->proc_cnt); |
| } |
| |
| if (bt_cap_common_proc_is_aborted()) { |
| if (bt_cap_common_proc_all_handled()) { |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| |
| return; |
| } |
| |
| if (!bt_cap_common_proc_is_done()) { |
| struct bt_cap_initiator_proc_param *proc_param; |
| struct bt_cap_stream *next_cap_stream; |
| struct bt_bap_stream *next_bap_stream; |
| int err; |
| |
| proc_param = get_next_proc_param(active_proc); |
| __ASSERT(proc_param != NULL, "proc is not done, but could not get next proc_param"); |
| next_cap_stream = proc_param->stream; |
| next_bap_stream = &next_cap_stream->bap_stream; |
| active_proc->proc_initiated_cnt++; |
| proc_param->in_progress = true; |
| |
| err = bt_bap_stream_release(next_bap_stream); |
| if (err != 0) { |
| LOG_DBG("Failed to release stream %p: %d", next_cap_stream, err); |
| |
| bt_cap_common_abort_proc(next_bap_stream->conn, err); |
| cap_initiator_unicast_audio_proc_complete(); |
| } |
| } else { |
| 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 */ |