diff --git a/doc/connectivity/bluetooth/api/audio/shell/bap.rst b/doc/connectivity/bluetooth/api/audio/shell/bap.rst index 8d01943eb3b..6b2b75ecda4 100644 --- a/doc/connectivity/bluetooth/api/audio/shell/bap.rst +++ b/doc/connectivity/bluetooth/api/audio/shell/bap.rst @@ -34,6 +34,7 @@ Commands stream_qos : interval [framing] [latency] [pd] [sdu] [phy] [rtn] qos : Send QoS configure for Unicast Group enable : [context] + connect : Connect the CIS of the stream stop list print_ase_info : Print ASE info for default connection @@ -62,10 +63,8 @@ Commands [extended ] [vendor ]] send : Send to Audio Stream [data] - start_sine : Start sending a LC3 encoded sine wave [all] - stop_sine : Stop sending a LC3 encoded sine wave [all] - recv_stats : Sets or gets the receive statistics reporting interval - in # of packets + bap_stats : Sets or gets the statistics reporting interval in # of + packets set_location : set_context : @@ -80,19 +79,19 @@ Commands "config","discover","idle/codec-configured/qos-configured","codec-configured" "qos","config","codec-configured/qos-configured","qos-configured" "enable","qos","qos-configured","enabling" - "[start]","enable","enabling","streaming" + "connect","qos/enable","qos-configured/enabling","qos-configured/enabling" + "[start]","enable/connect","enabling","streaming" "disable","enable", "enabling/streaming","disabling" "[stop]","disable","disabling","qos-configure/idle" "release","config","any","releasing/codec-configure/idle" "list","none","any","none" "select_unicast","none","any","none" - "connect","discover","idle/codec-configured/qos-configured","codec-configured" "send","enable","streaming","none" Example Central *************** -Connect and establish a stream: +Connect and establish a sink stream: .. code-block:: console @@ -104,8 +103,9 @@ Connect and establish a stream: uart:~$ bap config sink 0 uart:~$ bap qos uart:~$ bap enable + uart:~$ bap connect -Or using connect command: +Connect and establish a source stream: .. code-block:: console @@ -113,8 +113,12 @@ Or using connect command: uart:~$ bap init uart:~$ bt connect
uart:~$ gatt exchange-mtu - uart:~$ bap discover sink - uart:~$ bap connect sink 0 + uart:~$ bap discover source + uart:~$ bap config source 0 + uart:~$ bap qos + uart:~$ bap enable + uart:~$ bap connect + uart:~$ bap start Disconnect and release: @@ -479,8 +483,7 @@ parameters. Enable ****** -The :code:`enable` command attempts to enable the stream previously configured, -if the remote peer accepts then the ISO connection procedure is also initiated. +The :code:`enable` command attempts to enable the stream previously configured. .. csv-table:: State Machine Transitions :header: "Depends", "Allowed States", "Next States" @@ -493,17 +496,33 @@ if the remote peer accepts then the ISO connection procedure is also initiated. uart:~$ bap enable [context] uart:~$ bap enable Media -Start -***** +Connect +******* -The :code:`start` command is only necessary when acting as a sink as it -indicates to the source the stack is ready to start receiving data. +The :code:`connect` command attempts to connect the stream previously configured. +Sink streams will have to be started by the unicast server, and source streams will have to be +started by the unicast client. .. csv-table:: State Machine Transitions :header: "Depends", "Allowed States", "Next States" :widths: auto - "enable","enabling","streaming" + "qos/enable","qos-configured/enabling","qos-configured/enabling" + +.. code-block:: console + + uart:~$ bap connect + +Start +***** + +The :code:`start` command is only necessary when starting a source stream. + +.. csv-table:: State Machine Transitions + :header: "Depends", "Allowed States", "Next States" + :widths: auto + + "enable/connect","enabling","streaming" .. code-block:: console diff --git a/doc/releases/migration-guide-3.7.rst b/doc/releases/migration-guide-3.7.rst index 6c170c300ce..0fa08a6af9d 100644 --- a/doc/releases/migration-guide-3.7.rst +++ b/doc/releases/migration-guide-3.7.rst @@ -447,6 +447,10 @@ Bluetooth Audio This needs to be added to all instances of CAP discovery callback functions defined. (:github:`72797`) +* :c:func:`bt_bap_stream_start` no longer connects the CIS. To connect the CIS, + the :c:func:`bt_bap_stream_connect` shall now be called before :c:func:`bt_bap_stream_start`. + (:github:`73032`) + * All occurrences of ``set_sirk`` have been changed to just ``sirk`` as the ``s`` in ``sirk`` stands for set. (:github:`73413`) diff --git a/include/zephyr/bluetooth/audio/bap.h b/include/zephyr/bluetooth/audio/bap.h index 4e67d594877..32d9840c5be 100644 --- a/include/zephyr/bluetooth/audio/bap.h +++ b/include/zephyr/bluetooth/audio/bap.h @@ -2,7 +2,7 @@ * @brief Header for Bluetooth BAP. * * Copyright (c) 2020 Bose Corporation - * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * Copyright (c) 2021-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -715,20 +715,43 @@ int bt_bap_stream_metadata(struct bt_bap_stream *stream, const uint8_t meta[], s */ int bt_bap_stream_disable(struct bt_bap_stream *stream); +/** + * @brief Connect unicast audio stream + * + * This procedure is used by a unicast client to connect the connected isochronous stream (CIS) + * associated with the audio stream. If two audio streams share a CIS, then this only needs to be + * done once for those streams. This can only be done for streams in the QoS configured or enabled + * states. + * + * The bt_bap_stream_ops.connected() callback will be called on the streams once this has finished. + * + * This shall only be called for unicast streams, and only as the unicast client + * (@kconfig{CONFIG_BT_BAP_UNICAST_CLIENT}). + * + * @param stream Stream object + * + * @retval 0 in case of success + * @retval -EINVAL if the stream, endpoint, ISO channel or connection is NULL + * @retval -EBADMSG if the stream or ISO channel is in an invalid state for connection + * @retval -EOPNOTSUPP if the role of the stream is not @ref BT_HCI_ROLE_CENTRAL + * @retval -EALREADY if the ISO channel is already connecting or connected + * @retval -EBUSY if another ISO channel is connecting + * @retval -ENOEXEC if otherwise rejected by the ISO layer + */ +int bt_bap_stream_connect(struct bt_bap_stream *stream); + /** * @brief Start Audio Stream * * This procedure is used by a unicast client or unicast server to make a stream start streaming. * - * For the unicast client, this will connect the CIS for the stream before - * sending the start command. + * For the unicast client, this will send the receiver start ready command to the unicast server for + * @ref BT_AUDIO_DIR_SOURCE ASEs. The CIS is required to be connected first by + * bt_bap_stream_connect() before the command can be sent. * - * For the unicast server, this will put a @ref BT_AUDIO_DIR_SINK stream into the streaming state if - * the CIS is connected (initialized by the unicast client). If the CIS is not connected yet, the - * stream will go into the streaming state as soon as the CIS is connected. - * @ref BT_AUDIO_DIR_SOURCE streams will go into the streaming state when the unicast client sends - * the Receiver Start Ready operation, which will trigger the @ref bt_bap_unicast_server_cb.start() - * callback. + * For the unicast server, this will execute the receiver start ready command on the unicast server + * for @ref BT_AUDIO_DIR_SINK ASEs. If the CIS is not connected yet, the stream will go into the + * streaming state as soon as the CIS is connected. * * This shall only be called for unicast streams. * diff --git a/samples/bluetooth/bap_unicast_client/src/main.c b/samples/bluetooth/bap_unicast_client/src/main.c index 170808db24a..96b36094b6c 100644 --- a/samples/bluetooth/bap_unicast_client/src/main.c +++ b/samples/bluetooth/bap_unicast_client/src/main.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * Copyright (c) 2021-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -59,6 +59,7 @@ static K_SEM_DEFINE(sem_stream_configured, 0, 1); static K_SEM_DEFINE(sem_stream_qos, 0, ARRAY_SIZE(sinks) + ARRAY_SIZE(sources)); static K_SEM_DEFINE(sem_stream_enabled, 0, 1); static K_SEM_DEFINE(sem_stream_started, 0, 1); +static K_SEM_DEFINE(sem_stream_connected, 0, 1); #define AUDIO_DATA_TIMEOUT_US 1000000UL /* Send data every 1 second */ @@ -518,9 +519,9 @@ static void stream_enabled(struct bt_bap_stream *stream) k_sem_give(&sem_stream_enabled); } -static void stream_started(struct bt_bap_stream *stream) +static void stream_connected_cb(struct bt_bap_stream *stream) { - printk("Audio Stream %p started\n", stream); + printk("Audio Stream %p connected\n", stream); /* Reset sequence number for sinks */ for (size_t i = 0U; i < configured_sink_stream_count; i++) { @@ -530,6 +531,13 @@ static void stream_started(struct bt_bap_stream *stream) } } + k_sem_give(&sem_stream_connected); +} + +static void stream_started(struct bt_bap_stream *stream) +{ + printk("Audio Stream %p started\n", stream); + k_sem_give(&sem_stream_started); } @@ -576,7 +584,8 @@ static struct bt_bap_stream_ops stream_ops = { .disabled = stream_disabled, .stopped = stream_stopped, .released = stream_released, - .recv = stream_recv + .recv = stream_recv, + .connected = stream_connected_cb, }; static void add_remote_source(struct bt_bap_ep *ep) @@ -1018,17 +1027,62 @@ static int enable_streams(void) return 0; } -static int start_streams(void) +static int connect_streams(void) { for (size_t i = 0U; i < configured_stream_count; i++) { int err; - err = bt_bap_stream_start(&streams[i]); - if (err != 0) { + k_sem_reset(&sem_stream_connected); + + err = bt_bap_stream_connect(&streams[i]); + if (err == -EALREADY) { + /* We have already connected a paired stream */ + continue; + } else if (err != 0) { printk("Unable to start stream: %d\n", err); return err; } + err = k_sem_take(&sem_stream_connected, K_FOREVER); + if (err != 0) { + printk("failed to take sem_stream_connected (err %d)\n", err); + return err; + } + } + + return 0; +} + +static enum bt_audio_dir stream_dir(const struct bt_bap_stream *stream) +{ + struct bt_bap_ep_info ep_info; + int err; + + err = bt_bap_ep_get_info(stream->ep, &ep_info); + if (err != 0) { + printk("Failed to get ep info for %p: %d\n", stream, err); + __ASSERT_NO_MSG(false); + + return 0; + } + + return ep_info.dir; +} + +static int start_streams(void) +{ + for (size_t i = 0U; i < configured_stream_count; i++) { + struct bt_bap_stream *stream = &streams[i]; + int err; + + if (stream_dir(stream) == BT_AUDIO_DIR_SOURCE) { + err = bt_bap_stream_start(&streams[i]); + if (err != 0) { + printk("Unable to start stream: %d\n", err); + return err; + } + } /* Sink streams are started by the unicast server */ + err = k_sem_take(&sem_stream_started, K_FOREVER); if (err != 0) { printk("failed to take sem_stream_started (err %d)\n", err); @@ -1051,6 +1105,7 @@ static void reset_data(void) k_sem_reset(&sem_stream_qos); k_sem_reset(&sem_stream_enabled); k_sem_reset(&sem_stream_started); + k_sem_reset(&sem_stream_connected); configured_sink_stream_count = 0; configured_source_stream_count = 0; @@ -1131,6 +1186,13 @@ int main(void) } printk("Streams enabled\n"); + printk("Connecting streams\n"); + err = connect_streams(); + if (err != 0) { + return 0; + } + printk("Streams connected\n"); + printk("Starting streams\n"); err = start_streams(); if (err != 0) { diff --git a/samples/bluetooth/tmap_peripheral/src/bap_unicast_sr.c b/samples/bluetooth/tmap_peripheral/src/bap_unicast_sr.c index 523b3e1d82a..5e3ce9566f1 100644 --- a/samples/bluetooth/tmap_peripheral/src/bap_unicast_sr.c +++ b/samples/bluetooth/tmap_peripheral/src/bap_unicast_sr.c @@ -1,7 +1,7 @@ /** @file * @brief Bluetooth Basic Audio Profile (BAP) Unicast Server role. * - * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * Copyright (c) 2021-2024 Nordic Semiconductor ASA * Copyright (c) 2022 Codecoup * Copyright (c) 2023 NXP * @@ -293,10 +293,23 @@ static void stream_recv(struct bt_bap_stream *stream, const struct bt_iso_recv_i static void stream_enabled(struct bt_bap_stream *stream) { - const int err = bt_bap_stream_start(stream); + struct bt_bap_ep_info ep_info; + int err; + err = bt_bap_ep_get_info(stream->ep, &ep_info); if (err != 0) { - printk("Failed to start stream %p: %d", stream, err); + printk("Failed to get ep info: %d\n", err); + return; + } + + /* The unicast server is responsible for starting the sink streams */ + if (ep_info.dir == BT_AUDIO_DIR_SINK) { + /* Automatically do the receiver start ready operation */ + err = bt_bap_stream_start(stream); + + if (err != 0) { + printk("Failed to start stream %p: %d", stream, err); + } } } diff --git a/subsys/bluetooth/audio/bap_stream.c b/subsys/bluetooth/audio/bap_stream.c index 6ac3fd8c5f5..0894af516a1 100644 --- a/subsys/bluetooth/audio/bap_stream.c +++ b/subsys/bluetooth/audio/bap_stream.c @@ -2,7 +2,7 @@ /* * Copyright (c) 2020 Intel Corporation - * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * Copyright (c) 2021-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -476,6 +476,8 @@ int bt_bap_stream_disconnect(struct bt_bap_stream *stream) iso_chan = bt_bap_stream_iso_chan_get(stream); if (iso_chan == NULL || iso_chan->iso == NULL) { + LOG_DBG("Not connected"); + return -ENOTCONN; } @@ -720,6 +722,40 @@ int bt_bap_stream_reconfig(struct bt_bap_stream *stream, return 0; } +#if defined(CONFIG_BT_BAP_UNICAST_CLIENT) +int bt_bap_stream_connect(struct bt_bap_stream *stream) +{ + uint8_t state; + + LOG_DBG("stream %p ep %p", stream, stream == NULL ? NULL : stream->ep); + + CHECKIF(stream == NULL || stream->ep == NULL || stream->conn == NULL) { + LOG_DBG("Invalid stream"); + return -EINVAL; + } + + /* Valid only after the CIS ID has been assigned in QoS configured state and while we are + * not streaming + */ + state = stream->ep->status.state; + switch (state) { + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_ENABLING: + break; + default: + LOG_ERR("Invalid state: %s", bt_bap_ep_state_str(state)); + return -EBADMSG; + } + + /* Only a unicast client can connect a stream */ + if (conn_get_role(stream->conn) == BT_HCI_ROLE_CENTRAL) { + return bt_bap_unicast_client_connect(stream); + } else { + return -EOPNOTSUPP; + } +} +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ + int bt_bap_stream_start(struct bt_bap_stream *stream) { uint8_t state; diff --git a/subsys/bluetooth/audio/bap_unicast_client.c b/subsys/bluetooth/audio/bap_unicast_client.c index b6c579d423b..3de389618f4 100644 --- a/subsys/bluetooth/audio/bap_unicast_client.c +++ b/subsys/bluetooth/audio/bap_unicast_client.c @@ -324,17 +324,6 @@ static void unicast_client_ep_iso_connected(struct bt_bap_ep *ep) if (stream_ops != NULL && stream_ops->connected != NULL) { stream_ops->connected(stream); } - - if (ep->receiver_ready && ep->dir == BT_AUDIO_DIR_SOURCE) { - const int err = unicast_client_send_start(ep); - - if (err != 0) { - LOG_DBG("Failed to send start: %d", err); - - /* TBD: Should we release the stream then? */ - ep->receiver_ready = false; - } - } } static void unicast_client_iso_connected(struct bt_iso_chan *chan) @@ -577,6 +566,8 @@ static void unicast_client_ep_idle_state(struct bt_bap_ep *ep) struct bt_bap_stream *stream = ep->stream; const struct bt_bap_stream_ops *ops; + ep->receiver_ready = false; + if (stream == NULL) { return; } @@ -662,6 +653,8 @@ static void unicast_client_ep_config_state(struct bt_bap_ep *ep, struct net_buf_ struct bt_bap_stream *stream; void *cc; + ep->receiver_ready = false; + if (client_ep->release_requested) { LOG_DBG("Released was requested, change local state to idle"); ep->reason = BT_HCI_ERR_LOCALHOST_TERM_CONN; @@ -736,6 +729,8 @@ static void unicast_client_ep_qos_state(struct bt_bap_ep *ep, struct net_buf_sim struct bt_ascs_ase_status_qos *qos; struct bt_bap_stream *stream; + ep->receiver_ready = false; + if (buf->len < sizeof(*qos)) { LOG_ERR("QoS status too short"); return; @@ -920,6 +915,8 @@ static void unicast_client_ep_disabling_state(struct bt_bap_ep *ep, struct net_b struct bt_ascs_ase_status_disable *disable; struct bt_bap_stream *stream; + ep->receiver_ready = false; + if (buf->len < sizeof(*disable)) { LOG_ERR("Disabling status too short"); return; @@ -947,6 +944,8 @@ static void unicast_client_ep_releasing_state(struct bt_bap_ep *ep, struct net_b { struct bt_bap_stream *stream; + ep->receiver_ready = false; + stream = ep->stream; if (stream == NULL) { LOG_ERR("No stream active for endpoint"); @@ -1020,7 +1019,6 @@ static void unicast_client_ep_set_status(struct bt_bap_ep *ep, struct net_buf_si switch (status->state) { case BT_BAP_EP_STATE_IDLE: - ep->receiver_ready = false; unicast_client_ep_idle_state(ep); break; case BT_BAP_EP_STATE_CODEC_CONFIGURED: @@ -1041,8 +1039,6 @@ static void unicast_client_ep_set_status(struct bt_bap_ep *ep, struct net_buf_si return; } - ep->receiver_ready = false; - unicast_client_ep_config_state(ep, buf); break; case BT_BAP_EP_STATE_QOS_CONFIGURED: @@ -1083,8 +1079,6 @@ static void unicast_client_ep_set_status(struct bt_bap_ep *ep, struct net_buf_si } } - ep->receiver_ready = false; - unicast_client_ep_qos_state(ep, buf, old_state); break; case BT_BAP_EP_STATE_ENABLING: @@ -1141,8 +1135,6 @@ static void unicast_client_ep_set_status(struct bt_bap_ep *ep, struct net_buf_si return; } - ep->receiver_ready = false; - unicast_client_ep_disabling_state(ep, buf); break; case BT_BAP_EP_STATE_RELEASING: @@ -1170,8 +1162,6 @@ static void unicast_client_ep_set_status(struct bt_bap_ep *ep, struct net_buf_si return; } - ep->receiver_ready = false; - unicast_client_ep_releasing_state(ep, buf); break; } @@ -2251,34 +2241,6 @@ static int unicast_client_cig_terminate(struct bt_bap_unicast_group *group) return bt_iso_cig_terminate(group->cig); } -static int unicast_client_stream_connect(struct bt_bap_stream *stream) -{ - struct bt_iso_connect_param param; - struct bt_iso_chan *iso_chan; - - iso_chan = bt_bap_stream_iso_chan_get(stream); - - LOG_DBG("stream %p iso %p", stream, iso_chan); - - if (stream == NULL || iso_chan == NULL) { - return -EINVAL; - } - - param.acl = stream->conn; - param.iso_chan = iso_chan; - - switch (iso_chan->state) { - case BT_ISO_STATE_DISCONNECTED: - return bt_iso_chan_connect(¶m, 1); - case BT_ISO_STATE_CONNECTING: - return -EBUSY; - case BT_ISO_STATE_CONNECTED: - return -EALREADY; - default: - return bt_iso_chan_connect(¶m, 1); - } -} - static int unicast_group_add_iso(struct bt_bap_unicast_group *group, struct bt_bap_iso *iso) { struct bt_iso_chan **chan_slot = NULL; @@ -3019,6 +2981,55 @@ int bt_bap_unicast_client_metadata(struct bt_bap_stream *stream, const uint8_t m return bt_bap_unicast_client_ep_send(stream->conn, ep, buf); } +int bt_bap_unicast_client_connect(struct bt_bap_stream *stream) +{ + struct bt_iso_connect_param param; + struct bt_iso_chan *iso_chan; + enum bt_iso_state iso_state; + + LOG_DBG("stream %p", stream); + + if (stream == NULL) { + LOG_DBG("stream is NULL"); + + return -EINVAL; + } + + iso_chan = bt_bap_stream_iso_chan_get(stream); + if (iso_chan == NULL) { + LOG_DBG("iso_chan is NULL"); + + return -EINVAL; + } + + LOG_DBG("stream %p iso %p", stream, iso_chan); + + param.acl = stream->conn; + param.iso_chan = iso_chan; + + iso_state = iso_chan->state; + if (iso_state == BT_ISO_STATE_DISCONNECTED) { + const int err = bt_iso_chan_connect(¶m, 1); + + if (err == 0 || err == -EBUSY) { /* Expected / known return values */ + return err; + } + + /* unknown return value*/ + LOG_DBG("Unexpected err %d from bt_iso_chan_connect", err); + + return -ENOEXEC; + } else if (iso_state == BT_ISO_STATE_CONNECTING || iso_state == BT_ISO_STATE_CONNECTED) { + LOG_DBG("iso_chan %p already connecting or connected (%u)", iso_chan, iso_state); + + return -EALREADY; + } + + LOG_DBG("iso_chan %p in invalid state state (%u), cannot connect", iso_chan, iso_state); + + return -EBADMSG; +} + int bt_bap_unicast_client_start(struct bt_bap_stream *stream) { struct bt_bap_ep *ep = stream->ep; @@ -3032,37 +3043,21 @@ int bt_bap_unicast_client_start(struct bt_bap_stream *stream) return -ENOTCONN; } - /* If an ASE is in the Enabling state, and if the Unicast Client has - * not yet established a CIS for that ASE, the Unicast Client shall - * attempt to establish a CIS by using the Connected Isochronous Stream - * Central Establishment procedure. - */ - err = unicast_client_stream_connect(stream); - if (err == 0) { - if (ep->dir == BT_AUDIO_DIR_SOURCE) { - /* Set bool and wait for ISO to be connected to send the - * receiver start ready - */ - ep->receiver_ready = true; - } - } else if (err == -EALREADY) { - LOG_DBG("ISO %p already connected", bt_bap_stream_iso_chan_get(stream)); + /* As per the ASCS spec, only source streams can be started by the client */ + if (ep->dir == BT_AUDIO_DIR_SINK) { + LOG_DBG("Stream %p is not a source stream", stream); - if (ep->dir == BT_AUDIO_DIR_SOURCE) { - ep->receiver_ready = true; + return -EINVAL; + } - err = unicast_client_send_start(ep); - if (err != 0) { - LOG_DBG("Failed to send start: %d", err); + ep->receiver_ready = true; - /* TBD: Should we release the stream then? */ - ep->receiver_ready = false; + err = unicast_client_send_start(ep); + if (err != 0) { + LOG_DBG("Failed to send start: %d", err); - return err; - } - } - } else { - LOG_DBG("unicast_client_stream_connect failed: %d", err); + /* TBD: Should we release the stream then? */ + ep->receiver_ready = false; return err; } diff --git a/subsys/bluetooth/audio/bap_unicast_client_internal.h b/subsys/bluetooth/audio/bap_unicast_client_internal.h index c6ada94863f..56542365ac6 100644 --- a/subsys/bluetooth/audio/bap_unicast_client_internal.h +++ b/subsys/bluetooth/audio/bap_unicast_client_internal.h @@ -20,6 +20,7 @@ int bt_bap_unicast_client_metadata(struct bt_bap_stream *stream, const uint8_t m int bt_bap_unicast_client_disable(struct bt_bap_stream *stream); int bt_bap_unicast_client_start(struct bt_bap_stream *stream); +int bt_bap_unicast_client_connect(struct bt_bap_stream *stream); int bt_bap_unicast_client_stop(struct bt_bap_stream *stream); diff --git a/subsys/bluetooth/audio/cap_common.c b/subsys/bluetooth/audio/cap_common.c index 18249f1f967..56e7741d2ee 100644 --- a/subsys/bluetooth/audio/cap_common.c +++ b/subsys/bluetooth/audio/cap_common.c @@ -1,11 +1,26 @@ /* - * Copyright (c) 2023 Nordic Semiconductor ASA + * Copyright (c) 2023-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include #include "cap_internal.h" #include "csip_internal.h" @@ -30,6 +45,8 @@ void bt_cap_common_clear_active_proc(void) void bt_cap_common_start_proc(enum bt_cap_common_proc_type proc_type, size_t proc_cnt) { + LOG_DBG("Setting proc to %d for %zu streams", proc_type, proc_cnt); + atomic_set_bit(active_proc.proc_state_flags, BT_CAP_COMMON_PROC_STATE_ACTIVE); active_proc.proc_cnt = proc_cnt; active_proc.proc_type = proc_type; @@ -40,6 +57,8 @@ void bt_cap_common_start_proc(enum bt_cap_common_proc_type proc_type, size_t pro #if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) void bt_cap_common_set_subproc(enum bt_cap_common_subproc_type subproc_type) { + LOG_DBG("Setting subproc to %d", subproc_type); + active_proc.proc_done_cnt = 0U; active_proc.proc_initiated_cnt = 0U; active_proc.subproc_type = subproc_type; @@ -103,6 +122,8 @@ void bt_cap_common_abort_proc(struct bt_conn *conn, int err) return; } + LOG_DBG("Aborting proc %d for %p: %d", active_proc.proc_type, (void *)conn, err); + active_proc.err = err; active_proc.failed_conn = conn; atomic_set_bit(active_proc.proc_state_flags, BT_CAP_COMMON_PROC_STATE_ABORTED); diff --git a/subsys/bluetooth/audio/cap_initiator.c b/subsys/bluetooth/audio/cap_initiator.c index 55fd6886689..3fd5199d5c7 100644 --- a/subsys/bluetooth/audio/cap_initiator.c +++ b/subsys/bluetooth/audio/cap_initiator.c @@ -1,21 +1,35 @@ /* - * Copyright (c) 2022-2023 Nordic Semiconductor ASA + * Copyright (c) 2022-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ -#include -#include +#include +#include +#include +#include + +#include #include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "cap_internal.h" #include "ccid_internal.h" #include "csip_internal.h" #include "bap_endpoint.h" -#include - LOG_MODULE_REGISTER(bt_cap_initiator, CONFIG_BT_CAP_INITIATOR_LOG_LEVEL); #include "common/bt_str.h" @@ -313,6 +327,285 @@ int bt_cap_initiator_broadcast_get_base(struct bt_cap_broadcast_source *broadcas #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; + + 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 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; + struct bt_bap_ep_info ep_info; + int err; + + 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; + } + + if (ep_info.iso_chan == NULL) { + return state == BT_ISO_STATE_DISCONNECTED; + } + + return state == ep_info.iso_chan->state; +} + +/** + * @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++) { + const 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++) { + const 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_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 + */ + const 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; + + proc_param = &active_proc->proc_param.initiator[i]; + cap_stream = proc_param->stream; + bap_stream = &cap_stream->bap_stream; + + switch (subproc_type) { + case BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG: + if (stream_is_in_state(bap_stream, BT_BAP_EP_STATE_IDLE)) { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG: + if (stream_is_in_state(bap_stream, BT_BAP_EP_STATE_CODEC_CONFIGURED)) { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_ENABLE: + if (stream_is_in_state(bap_stream, BT_BAP_EP_STATE_QOS_CONFIGURED)) { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_CONNECT: + if (stream_is_in_state(bap_stream, BT_BAP_EP_STATE_ENABLING) && + !proc_param->start.connected) { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_START: + if (stream_is_in_state(bap_stream, BT_BAP_EP_STATE_ENABLING)) { + /* TODO: Add check for connected */ + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE: + if (stream_is_in_state(bap_stream, BT_BAP_EP_STATE_ENABLING) || + stream_is_in_state(bap_stream, BT_BAP_EP_STATE_STREAMING)) { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_RELEASE: + if (!stream_is_in_state(bap_stream, BT_BAP_EP_STATE_IDLE)) { + 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, @@ -522,7 +815,15 @@ static int cap_initiator_unicast_audio_configure( 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 = &active_proc->proc_param.initiator[0]; + 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; @@ -571,6 +872,8 @@ void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream) 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 @@ -581,7 +884,7 @@ void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream) /* Unexpected callback - Abort */ bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); } else { - active_proc->proc_done_cnt++; + 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); @@ -596,20 +899,20 @@ void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream) } if (!bt_cap_common_proc_is_done()) { - const size_t proc_done_cnt = active_proc->proc_done_cnt; struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; struct bt_audio_codec_cfg *codec_cfg; - struct bt_bap_stream *bap_stream; struct bt_conn *conn; struct bt_bap_ep *ep; int err; - proc_param = &active_proc->proc_param.initiator[proc_done_cnt]; + 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; - bap_stream = &next_cap_stream->bap_stream; + next_bap_stream = &next_cap_stream->bap_stream; active_proc->proc_initiated_cnt++; /* Since BAP operations may require a write long or a read long on the notification, @@ -618,7 +921,7 @@ void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream) * TODO: We should always be able to do one per ACL, so there is room for * optimization. */ - err = bt_bap_stream_config(conn, bap_stream, ep, codec_cfg); + 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); @@ -663,9 +966,17 @@ void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream) /* All streams in the procedure share the same unicast group, so we just * use the reference from the first stream */ - proc_param = &active_proc->proc_param.initiator[0]; - unicast_group = (struct bt_bap_unicast_group *)proc_param->stream->bap_stream.group; 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; @@ -709,11 +1020,13 @@ void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream) return; } + LOG_DBG("cap_stream %p", cap_stream); + if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG)) { /* Unexpected callback - Abort */ bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); } else { - active_proc->proc_done_cnt++; + 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); @@ -733,7 +1046,15 @@ void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream) } bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_ENABLE); - proc_param = &active_proc->proc_param.initiator[0]; + 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++; @@ -764,11 +1085,13 @@ void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream) 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 { - active_proc->proc_done_cnt++; + 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); @@ -783,9 +1106,13 @@ void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream) } if (!bt_cap_common_proc_is_done()) { - struct bt_cap_stream *next_cap_stream = - active_proc->proc_param.initiator[active_proc->proc_done_cnt].stream; - struct bt_bap_stream *next_bap_stream = &next_cap_stream->bap_stream; + 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++; @@ -807,17 +1134,27 @@ void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream) return; } - bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_START); - proc_param = &active_proc->proc_param.initiator[0]; + 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; - /* 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. - */ - err = bt_bap_stream_start(bap_stream); - if (err != 0) { - LOG_DBG("Failed to start stream %p: %d", proc_param->stream, err); + 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 + */ + 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 @@ -825,75 +1162,174 @@ void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream) */ 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); + + 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); + __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++; + + err = bt_bap_stream_connect(next_bap_stream); + if (err == 0 || err == -EALREADY) { + /* 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(); + } + + 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)) { + /* 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. + */ + 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; } - if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_START)) { + /* 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_START) && + !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 { - active_proc->proc_done_cnt++; + 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); } - /* 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 (!bt_cap_common_proc_is_done()) { - struct bt_cap_stream *next_cap_stream = - active_proc->proc_param.initiator[active_proc->proc_done_cnt].stream; - struct bt_bap_stream *bap_stream = &next_cap_stream->bap_stream; - int err; + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; - /* Not yet finished, start next */ - err = bt_bap_stream_start(bap_stream); - if (err != 0) { - LOG_DBG("Failed to start stream %p: %d", next_cap_stream, 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; - /* End and mark procedure as aborted. - * If we have sent any requests over air, we will abort - * once all sent requests has completed + if (stream_is_dir(next_bap_stream, BT_AUDIO_DIR_SOURCE)) { + int err; + + /* 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. */ - bt_cap_common_abort_proc(bap_stream->conn, err); - cap_initiator_unicast_audio_proc_complete(); - } - } else { - cap_initiator_unicast_audio_proc_complete(); + 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) { - 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; + 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) @@ -1019,7 +1455,7 @@ int bt_cap_initiator_unicast_audio_update(const struct bt_cap_unicast_audio_upda 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 = &active_proc->proc_param.initiator[0]; + proc_param = get_next_proc_param(active_proc); bap_stream = &proc_param->stream->bap_stream; meta_len = proc_param->meta_update.meta_len; meta = proc_param->meta_update.meta; @@ -1062,7 +1498,7 @@ void bt_cap_initiator_metadata_updated(struct bt_cap_stream *cap_stream) /* Unexpected callback - Abort */ bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); } else { - active_proc->proc_done_cnt++; + 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); @@ -1116,21 +1552,11 @@ void bt_cap_initiator_metadata_updated(struct bt_cap_stream *cap_stream) 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; + return !stream_is_in_state(bap_stream, BT_BAP_EP_STATE_IDLE); } static bool valid_unicast_audio_stop_param(const struct bt_cap_unicast_audio_stop_param *param) @@ -1244,7 +1670,7 @@ int bt_cap_initiator_unicast_audio_stop(const struct bt_cap_unicast_audio_stop_p * not match the order in the parameters, and the CSIP ordered access * procedure should be used. */ - proc_param = &active_proc->proc_param.initiator[0]; + proc_param = get_next_proc_param(active_proc); bap_stream = &proc_param->stream->bap_stream; active_proc->proc_initiated_cnt++; @@ -1271,7 +1697,7 @@ void bt_cap_initiator_released(struct bt_cap_stream *cap_stream) /* Unexpected callback - Abort */ bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); } else { - active_proc->proc_done_cnt++; + 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); @@ -1292,9 +1718,9 @@ void bt_cap_initiator_released(struct bt_cap_stream *cap_stream) int err; active_proc->proc_initiated_cnt++; - /* 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. + /* 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. */ diff --git a/subsys/bluetooth/audio/cap_internal.h b/subsys/bluetooth/audio/cap_internal.h index e201657bcad..e460fce98dc 100644 --- a/subsys/bluetooth/audio/cap_internal.h +++ b/subsys/bluetooth/audio/cap_internal.h @@ -1,11 +1,17 @@ /* Bluetooth Audio Common Audio Profile internal header */ /* - * Copyright (c) 2022-2023 Nordic Semiconductor ASA + * Copyright (c) 2022-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ +#include +#include + +#include +#include +#include #include #include #include @@ -20,6 +26,7 @@ void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream); void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream); void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream); void bt_cap_initiator_started(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_connected(struct bt_cap_stream *cap_stream); void bt_cap_initiator_metadata_updated(struct bt_cap_stream *cap_stream); void bt_cap_initiator_released(struct bt_cap_stream *cap_stream); void bt_cap_stream_ops_register_bap(struct bt_cap_stream *cap_stream); @@ -49,6 +56,7 @@ enum bt_cap_common_subproc_type { BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG, BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG, BT_CAP_COMMON_SUBPROC_TYPE_ENABLE, + BT_CAP_COMMON_SUBPROC_TYPE_CONNECT, BT_CAP_COMMON_SUBPROC_TYPE_START, BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE, BT_CAP_COMMON_SUBPROC_TYPE_RELEASE, @@ -61,6 +69,7 @@ struct bt_cap_initiator_proc_param { struct bt_conn *conn; struct bt_bap_ep *ep; struct bt_audio_codec_cfg *codec_cfg; + bool connected; } start; struct { /** Codec Specific Capabilities Metadata count */ diff --git a/subsys/bluetooth/audio/cap_stream.c b/subsys/bluetooth/audio/cap_stream.c index c2999534f81..46009b36a47 100644 --- a/subsys/bluetooth/audio/cap_stream.c +++ b/subsys/bluetooth/audio/cap_stream.c @@ -1,18 +1,48 @@ /* - * Copyright (c) 2022-2023 Nordic Semiconductor ASA + * Copyright (c) 2022-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ +#include +#include +#include +#include + +#include +#include #include +#include +#include +#include +#include #include +#include +#include #include "cap_internal.h" -#include - LOG_MODULE_REGISTER(bt_cap_stream, CONFIG_BT_CAP_STREAM_LOG_LEVEL); +static bool is_cap_initiator_unicast(struct bt_bap_stream *bap_stream) +{ + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT)) { + 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_audio_codec_qos_pref *pref) @@ -24,7 +54,7 @@ static void cap_stream_configured_cb(struct bt_bap_stream *bap_stream, LOG_DBG("%p", cap_stream); - if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR)) { + if (is_cap_initiator_unicast(bap_stream)) { bt_cap_initiator_codec_configured(cap_stream); } @@ -42,7 +72,7 @@ static void cap_stream_qos_set_cb(struct bt_bap_stream *bap_stream) LOG_DBG("%p", cap_stream); - if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR)) { + if (is_cap_initiator_unicast(bap_stream)) { bt_cap_initiator_qos_configured(cap_stream); } @@ -60,7 +90,7 @@ static void cap_stream_enabled_cb(struct bt_bap_stream *bap_stream) LOG_DBG("%p", cap_stream); - if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR)) { + if (is_cap_initiator_unicast(bap_stream)) { bt_cap_initiator_enabled(cap_stream); } @@ -78,7 +108,7 @@ static void cap_stream_metadata_updated_cb(struct bt_bap_stream *bap_stream) LOG_DBG("%p", cap_stream); - if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR)) { + if (is_cap_initiator_unicast(bap_stream)) { bt_cap_initiator_metadata_updated(cap_stream); } @@ -110,7 +140,10 @@ static void cap_stream_released_cb(struct bt_bap_stream *bap_stream) LOG_DBG("%p", cap_stream); - if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR)) { + /* Here we cannot use is_cap_initiator_unicast 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); } @@ -130,7 +163,7 @@ static void cap_stream_started_cb(struct bt_bap_stream *bap_stream) LOG_DBG("%p", cap_stream); - if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && IS_ENABLED(CONFIG_BT_BAP_UNICAST)) { + if (is_cap_initiator_unicast(bap_stream)) { bt_cap_initiator_started(cap_stream); } @@ -188,6 +221,10 @@ static void cap_stream_connected_cb(struct bt_bap_stream *bap_stream) CONTAINER_OF(bap_stream, struct bt_cap_stream, bap_stream); struct bt_bap_stream_ops *ops = cap_stream->ops; + if (is_cap_initiator_unicast(bap_stream)) { + bt_cap_initiator_connected(cap_stream); + } + if (ops != NULL && ops->connected != NULL) { ops->connected(bap_stream); } diff --git a/subsys/bluetooth/audio/shell/bap.c b/subsys/bluetooth/audio/shell/bap.c index b045ecbcda4..b560ba3c714 100644 --- a/subsys/bluetooth/audio/shell/bap.c +++ b/subsys/bluetooth/audio/shell/bap.c @@ -1550,6 +1550,24 @@ static int cmd_stop(const struct shell *sh, size_t argc, char *argv[]) return 0; } + +static int cmd_connect(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + if (default_stream == NULL) { + shell_error(sh, "No stream selected"); + return -ENOEXEC; + } + + err = bt_bap_stream_connect(default_stream); + if (err) { + shell_error(sh, "Unable to connect stream"); + return -ENOEXEC; + } + + return 0; +} #endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ static int cmd_metadata(const struct shell *sh, size_t argc, char *argv[]) @@ -3949,6 +3967,7 @@ SHELL_STATIC_SUBCMD_SET_CREATE( cmd_config, 3, 4), SHELL_CMD_ARG(stream_qos, NULL, "interval [framing] [latency] [pd] [sdu] [phy] [rtn]", cmd_stream_qos, 2, 6), + SHELL_CMD_ARG(connect, NULL, "Connect the CIS of the stream", cmd_connect, 1, 0), SHELL_CMD_ARG(qos, NULL, "Send QoS configure for Unicast Group", cmd_qos, 1, 0), SHELL_CMD_ARG(enable, NULL, "[context]", cmd_enable, 1, 1), SHELL_CMD_ARG(stop, NULL, NULL, cmd_stop, 1, 0), diff --git a/tests/bluetooth/audio/mocks/src/bap_unicast_client.c b/tests/bluetooth/audio/mocks/src/bap_unicast_client.c index 58933b85197..ef9ac1328b8 100644 --- a/tests/bluetooth/audio/mocks/src/bap_unicast_client.c +++ b/tests/bluetooth/audio/mocks/src/bap_unicast_client.c @@ -44,6 +44,12 @@ int bt_bap_unicast_client_disable(struct bt_bap_stream *stream) return 0; } +int bt_bap_unicast_client_connect(struct bt_bap_stream *stream) +{ + zassert_unreachable("Unexpected call to '%s()' occurred", __func__); + return 0; +} + int bt_bap_unicast_client_start(struct bt_bap_stream *stream) { zassert_unreachable("Unexpected call to '%s()' occurred", __func__); diff --git a/tests/bluetooth/tester/src/btp_bap_unicast.c b/tests/bluetooth/tester/src/btp_bap_unicast.c index 06b9219697c..a7df01c543f 100644 --- a/tests/bluetooth/tester/src/btp_bap_unicast.c +++ b/tests/bluetooth/tester/src/btp_bap_unicast.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "bap_endpoint.h" @@ -636,6 +637,26 @@ static void stream_started(struct bt_bap_stream *stream) btp_send_ascs_ase_state_changed_ev(stream->conn, u_stream->ase_id, info.state); } +static void stream_connected(struct bt_bap_stream *stream) +{ + struct bt_conn_info conn_info; + struct bt_bap_ep_info ep_info; + int err; + + LOG_DBG("Connected stream %p", stream); + + (void)bt_bap_ep_get_info(stream->ep, &ep_info); + (void)bt_conn_get_info(stream->conn, &conn_info); + if (conn_info.role == BT_HCI_ROLE_CENTRAL && ep_info.dir == BT_AUDIO_DIR_SOURCE) { + /* Automatically do the receiver start ready operation for source ASEs as the client + */ + err = bt_bap_stream_start(stream); + if (err != 0) { + LOG_ERR("Failed to start stream %p", stream); + } + } +} + static void stream_stopped(struct bt_bap_stream *stream, uint8_t reason) { struct btp_bap_unicast_stream *u_stream = stream_bap_to_unicast(stream); @@ -703,6 +724,7 @@ static struct bt_bap_stream_ops stream_ops = { .stopped = stream_stopped, .recv = stream_recv, .sent = stream_sent, + .connected = stream_connected, }; struct btp_bap_unicast_stream *btp_bap_unicast_stream_alloc( @@ -1419,7 +1441,7 @@ uint8_t btp_ascs_receiver_start_ready(const void *cmd, uint16_t cmd_len, LOG_DBG("Starting stream %p, ep %u, dir %u", bap_stream, cp->ase_id, info.dir); while (true) { - err = bt_bap_stream_start(bap_stream); + err = bt_bap_stream_connect(bap_stream); if (err == -EBUSY) { /* TODO: How to determine if a controller is ready again after * bt_bap_stream_start? In AC 6(i) tests the PTS sends Receiver Start Ready @@ -1427,8 +1449,8 @@ uint8_t btp_ascs_receiver_start_ready(const void *cmd, uint16_t cmd_len, */ k_sleep(K_MSEC(1000)); continue; - } else if (err != 0) { - LOG_DBG("Could not start stream: %d", err); + } else if (err != 0 && err != -EALREADY) { + LOG_DBG("Could not connect stream: %d", err); return BTP_STATUS_FAILED; } diff --git a/tests/bsim/bluetooth/audio/prj.conf b/tests/bsim/bluetooth/audio/prj.conf index 90e5924bf6c..ac1809dbf20 100644 --- a/tests/bsim/bluetooth/audio/prj.conf +++ b/tests/bsim/bluetooth/audio/prj.conf @@ -159,6 +159,7 @@ CONFIG_BT_PBP=y # DEBUGGING CONFIG_LOG=y +CONFIG_BT_ISO_LOG_LEVEL_DBG=y CONFIG_LOG_FUNC_NAME_PREFIX_ERR=y CONFIG_LOG_FUNC_NAME_PREFIX_WRN=y CONFIG_BT_VCP_VOL_REND_LOG_LEVEL_DBG=y diff --git a/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c b/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c index e2cacc854af..ac4e459f4f1 100644 --- a/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c @@ -752,6 +752,64 @@ static void metadata_update_streams(size_t stream_cnt) } } +static int connect_stream(struct bt_bap_stream *stream) +{ + int err; + + UNSET_FLAG(flag_stream_started); + + do { + err = bt_bap_stream_connect(stream); + if (err == -EALREADY) { + SET_FLAG(flag_stream_started); + } else if (err != 0) { + FAIL("Could not start stream %p: %d\n", stream, err); + return err; + } + } while (err == -EBUSY); + + WAIT_FOR_FLAG(flag_stream_started); + + return 0; +} + +static void connect_streams(void) +{ + struct bt_bap_stream *source_stream; + struct bt_bap_stream *sink_stream; + + /* We only support a single CIS so far, so only start one. We can use the group pair + * params to start both a sink and source stream that use the same CIS + */ + + source_stream = pair_params[0].rx_param == NULL ? NULL : pair_params[0].rx_param->stream; + sink_stream = pair_params[0].tx_param == NULL ? NULL : pair_params[0].tx_param->stream; + + UNSET_FLAG(flag_stream_connected); + + if (sink_stream != NULL) { + const int err = connect_stream(sink_stream); + + if (err != 0) { + FAIL("Unable to connect sink: %d", err); + + return; + } + } + + if (source_stream != NULL) { + const int err = connect_stream(source_stream); + + if (err != 0) { + FAIL("Unable to connect source stream: %d", err); + + return; + } + } + + WAIT_FOR_FLAG(flag_stream_connected); +} + static int start_stream(struct bt_bap_stream *stream) { int err; @@ -776,26 +834,8 @@ static int start_stream(struct bt_bap_stream *stream) static void start_streams(void) { struct bt_bap_stream *source_stream; - struct bt_bap_stream *sink_stream; - - /* We only support a single CIS so far, so only start one. We can use the group pair - * params to start both a sink and source stream that use the same CIS - */ source_stream = pair_params[0].rx_param == NULL ? NULL : pair_params[0].rx_param->stream; - sink_stream = pair_params[0].tx_param == NULL ? NULL : pair_params[0].tx_param->stream; - - UNSET_FLAG(flag_stream_connected); - - if (sink_stream != NULL) { - const int err = start_stream(sink_stream); - - if (err != 0) { - FAIL("Unable to start sink: %d", err); - - return; - } - } if (source_stream != NULL) { const int err = start_stream(source_stream); @@ -806,8 +846,6 @@ static void start_streams(void) return; } } - - WAIT_FOR_FLAG(flag_stream_connected); } static void transceive_streams(void) @@ -1051,6 +1089,9 @@ static void test_main(void) printk("Metadata update streams\n"); metadata_update_streams(stream_cnt); + printk("Connecting streams\n"); + connect_streams(); + printk("Starting streams\n"); start_streams(); @@ -1109,6 +1150,9 @@ static void test_main_acl_disconnect(void) printk("Metadata update streams\n"); metadata_update_streams(stream_cnt); + printk("Connecting streams\n"); + connect_streams(); + printk("Starting streams\n"); start_streams();