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();