| /* |
| * Copyright (c) 2022-2024 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <stddef.h> |
| #include <stdbool.h> |
| #include <stdint.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/conn.h> |
| #include <zephyr/bluetooth/hci_types.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 "cap_internal.h" |
| |
| LOG_MODULE_REGISTER(bt_cap_stream, CONFIG_BT_CAP_STREAM_LOG_LEVEL); |
| |
| static bool stream_is_central(struct bt_bap_stream *bap_stream) |
| { |
| if (IS_ENABLED(CONFIG_BT_CONN)) { |
| struct bt_conn_info info; |
| int err; |
| |
| if (bap_stream->conn == NULL) { |
| return false; |
| } |
| |
| err = bt_conn_get_info(bap_stream->conn, &info); |
| if (err == 0 && info.role == BT_HCI_ROLE_CENTRAL) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| #if defined(CONFIG_BT_BAP_UNICAST) |
| static void cap_stream_configured_cb(struct bt_bap_stream *bap_stream, |
| const struct bt_bap_qos_cfg_pref *pref) |
| { |
| struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, |
| struct bt_cap_stream, |
| bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| LOG_DBG("%p", cap_stream); |
| |
| if (ops != NULL && ops->configured != NULL) { |
| ops->configured(bap_stream, pref); |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && |
| stream_is_central(bap_stream)) { |
| bt_cap_initiator_codec_configured(cap_stream); |
| } |
| } |
| |
| static void cap_stream_qos_set_cb(struct bt_bap_stream *bap_stream) |
| { |
| struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, |
| struct bt_cap_stream, |
| bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| LOG_DBG("%p", cap_stream); |
| |
| if (ops != NULL && ops->qos_set != NULL) { |
| ops->qos_set(bap_stream); |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && |
| stream_is_central(bap_stream)) { |
| bt_cap_initiator_qos_configured(cap_stream); |
| } |
| } |
| |
| static void cap_stream_enabled_cb(struct bt_bap_stream *bap_stream) |
| { |
| struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, |
| struct bt_cap_stream, |
| bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| LOG_DBG("%p", cap_stream); |
| |
| if (ops != NULL && ops->enabled != NULL) { |
| ops->enabled(bap_stream); |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && |
| stream_is_central(bap_stream)) { |
| bt_cap_initiator_enabled(cap_stream); |
| } |
| } |
| |
| static void cap_stream_metadata_updated_cb(struct bt_bap_stream *bap_stream) |
| { |
| struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, |
| struct bt_cap_stream, |
| bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| LOG_DBG("%p", cap_stream); |
| |
| if (ops != NULL && ops->metadata_updated != NULL) { |
| ops->metadata_updated(bap_stream); |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && |
| stream_is_central(bap_stream)) { |
| bt_cap_initiator_metadata_updated(cap_stream); |
| } |
| } |
| |
| static void cap_stream_disabled_cb(struct bt_bap_stream *bap_stream) |
| { |
| struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, |
| struct bt_cap_stream, |
| bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| LOG_DBG("%p", cap_stream); |
| |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && |
| stream_is_central(bap_stream)) { |
| bt_cap_initiator_disabled(cap_stream); |
| } |
| |
| if (ops != NULL && ops->disabled != NULL) { |
| ops->disabled(bap_stream); |
| } |
| } |
| |
| static void cap_stream_released_cb(struct bt_bap_stream *bap_stream) |
| { |
| struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, |
| struct bt_cap_stream, |
| bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| LOG_DBG("%p", cap_stream); |
| |
| if (ops != NULL && ops->released != NULL) { |
| ops->released(bap_stream); |
| } |
| |
| /* Here we cannot use stream_is_central as bap_stream->conn is NULL, so fall back to |
| * a more generic, less accurate check |
| */ |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT)) { |
| bt_cap_initiator_released(cap_stream); |
| } |
| } |
| |
| #endif /* CONFIG_BT_BAP_UNICAST */ |
| |
| static void cap_stream_started_cb(struct bt_bap_stream *bap_stream) |
| { |
| struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, |
| struct bt_cap_stream, |
| bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| LOG_DBG("%p", cap_stream); |
| |
| if (ops != NULL && ops->started != NULL) { |
| ops->started(bap_stream); |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && |
| stream_is_central(bap_stream)) { |
| bt_cap_initiator_started(cap_stream); |
| } |
| } |
| |
| static void cap_stream_stopped_cb(struct bt_bap_stream *bap_stream, uint8_t reason) |
| { |
| struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, |
| struct bt_cap_stream, |
| bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| LOG_DBG("%p", cap_stream); |
| |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && |
| stream_is_central(bap_stream)) { |
| bt_cap_initiator_stopped(cap_stream); |
| } |
| |
| if (ops != NULL && ops->stopped != NULL) { |
| ops->stopped(bap_stream, reason); |
| } |
| } |
| |
| #if defined(CONFIG_BT_AUDIO_RX) |
| static void cap_stream_recv_cb(struct bt_bap_stream *bap_stream, |
| const struct bt_iso_recv_info *info, struct net_buf *buf) |
| { |
| struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, |
| struct bt_cap_stream, |
| bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| if (ops != NULL && ops->recv != NULL) { |
| ops->recv(bap_stream, info, buf); |
| } |
| } |
| #endif /* CONFIG_BT_AUDIO_RX */ |
| |
| #if defined(CONFIG_BT_AUDIO_TX) |
| static void cap_stream_sent_cb(struct bt_bap_stream *bap_stream) |
| { |
| struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, |
| struct bt_cap_stream, |
| bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| if (ops != NULL && ops->sent != NULL) { |
| ops->sent(bap_stream); |
| } |
| } |
| #endif /* CONFIG_BT_AUDIO_TX */ |
| |
| static void cap_stream_connected_cb(struct bt_bap_stream *bap_stream) |
| { |
| struct bt_cap_stream *cap_stream = |
| CONTAINER_OF(bap_stream, struct bt_cap_stream, bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| if (ops != NULL && ops->connected != NULL) { |
| ops->connected(bap_stream); |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && |
| stream_is_central(bap_stream)) { |
| bt_cap_initiator_connected(cap_stream); |
| } |
| } |
| |
| static void cap_stream_disconnected_cb(struct bt_bap_stream *bap_stream, uint8_t reason) |
| { |
| struct bt_cap_stream *cap_stream = |
| CONTAINER_OF(bap_stream, struct bt_cap_stream, bap_stream); |
| struct bt_bap_stream_ops *ops = cap_stream->ops; |
| |
| if (ops != NULL && ops->disconnected != NULL) { |
| ops->disconnected(bap_stream, reason); |
| } |
| } |
| |
| static struct bt_bap_stream_ops bap_stream_ops = { |
| #if defined(CONFIG_BT_BAP_UNICAST) |
| .configured = cap_stream_configured_cb, |
| .qos_set = cap_stream_qos_set_cb, |
| .enabled = cap_stream_enabled_cb, |
| .metadata_updated = cap_stream_metadata_updated_cb, |
| .disabled = cap_stream_disabled_cb, |
| .released = cap_stream_released_cb, |
| #endif /* CONFIG_BT_BAP_UNICAST */ |
| .started = cap_stream_started_cb, |
| .stopped = cap_stream_stopped_cb, |
| #if defined(CONFIG_BT_AUDIO_RX) |
| .recv = cap_stream_recv_cb, |
| #endif /* CONFIG_BT_AUDIO_RX */ |
| #if defined(CONFIG_BT_AUDIO_TX) |
| .sent = cap_stream_sent_cb, |
| #endif /* CONFIG_BT_AUDIO_TX */ |
| .connected = cap_stream_connected_cb, |
| .disconnected = cap_stream_disconnected_cb, |
| }; |
| |
| static void unicast_client_cp_cb(struct bt_bap_stream *bap_stream, |
| enum bt_bap_ascs_rsp_code rsp_code, enum bt_bap_ascs_reason reason) |
| { |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && |
| stream_is_central(bap_stream)) { |
| struct bt_cap_stream *cap_stream = |
| CONTAINER_OF(bap_stream, struct bt_cap_stream, bap_stream); |
| |
| bt_cap_initiator_cp_cb(cap_stream, rsp_code, reason); |
| } |
| } |
| |
| void bt_cap_stream_ops_register_bap(struct bt_cap_stream *cap_stream) |
| { |
| bt_bap_stream_cb_register(&cap_stream->bap_stream, &bap_stream_ops); |
| |
| if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT)) { |
| /* The CAP initiator can use the same callback for all of these as the result is the |
| * same: Abort current procedure |
| */ |
| static struct bt_bap_unicast_client_cb unicast_client_cb = { |
| .config = unicast_client_cp_cb, |
| .qos = unicast_client_cp_cb, |
| .enable = unicast_client_cp_cb, |
| .start = unicast_client_cp_cb, |
| .stop = unicast_client_cp_cb, |
| .disable = unicast_client_cp_cb, |
| .metadata = unicast_client_cp_cb, |
| .release = unicast_client_cp_cb, |
| }; |
| int err; |
| |
| err = bt_bap_unicast_client_register_cb(&unicast_client_cb); |
| __ASSERT_NO_MSG(err == 0 || err == -EEXIST); |
| } |
| } |
| |
| void bt_cap_stream_ops_register(struct bt_cap_stream *stream, |
| struct bt_bap_stream_ops *ops) |
| { |
| stream->ops = ops; |
| |
| /* CAP basically just forwards the BAP callbacks after doing what it (CAP) needs to do, |
| * so we can just always register the BAP callbacks here |
| * |
| * It is, however, only the CAP Initiator Unicast that depend on the callbacks being set in |
| * order to work, so for the CAP Initiator Unicast we need an additional register to ensure |
| * correctness. |
| */ |
| |
| bt_cap_stream_ops_register_bap(stream); |
| } |
| |
| #if defined(CONFIG_BT_AUDIO_TX) |
| int bt_cap_stream_send(struct bt_cap_stream *stream, struct net_buf *buf, uint16_t seq_num) |
| { |
| CHECKIF(stream == NULL) { |
| LOG_DBG("stream is NULL"); |
| |
| return -EINVAL; |
| } |
| |
| return bt_bap_stream_send(&stream->bap_stream, buf, seq_num); |
| } |
| |
| int bt_cap_stream_send_ts(struct bt_cap_stream *stream, struct net_buf *buf, uint16_t seq_num, |
| uint32_t ts) |
| { |
| CHECKIF(stream == NULL) { |
| LOG_DBG("stream is NULL"); |
| |
| return -EINVAL; |
| } |
| |
| return bt_bap_stream_send_ts(&stream->bap_stream, buf, seq_num, ts); |
| } |
| |
| int bt_cap_stream_get_tx_sync(struct bt_cap_stream *stream, struct bt_iso_tx_info *info) |
| { |
| CHECKIF(stream == NULL) { |
| LOG_DBG("stream is NULL"); |
| |
| return -EINVAL; |
| } |
| |
| return bt_bap_stream_get_tx_sync(&stream->bap_stream, info); |
| } |
| #endif /* CONFIG_BT_AUDIO_TX */ |