Bluetooth: BAP: Unicast client Split start and connect

Removes the CIS connection establishment from bt_bap_stream_start
and move the behavior to a new funciton bt_bap_stream_connect.

This has 2 advantages:
1) The behavior of bt_bap_stream_start is much more clear and more aligned
with the spec's behavior for the receiver start ready opcode.
2) It is possible to connect streams in both the enabling
and the QoS configured state with bt_bap_stream_connect as
per the spec. This allows us to pass additional PTS test cases.

To implement this new behavior, samples and tests have been updated.

The CAP Initiator implementation has also been updated
to accomodate for the change in BAP, but the CAP
initiator implementation should work the same for application, except
that it's now possible to do unicast start on ASEs in any order
(https://github.com/zephyrproject-rtos/zephyr/issues/72138).

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2024-05-20 17:07:48 +02:00 committed by Alberto Escolar
commit d2fbeffaa9
17 changed files with 969 additions and 231 deletions

View file

@ -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 <meta>]
[vendor <meta>]]
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 : <direction: sink, source> <location bitmask>
set_context : <direction: sink, source><context bitmask> <type:
supported, available>
@ -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 <address>
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

View file

@ -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`)

View file

@ -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.
*

View file

@ -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) {

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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(&param, 1);
case BT_ISO_STATE_CONNECTING:
return -EBUSY;
case BT_ISO_STATE_CONNECTED:
return -EALREADY;
default:
return bt_iso_chan_connect(&param, 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(&param, 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;
}

View file

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

View file

@ -1,11 +1,26 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
* Copyright (c) 2023-2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/att.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/csip.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/util.h>
#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);

View file

@ -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 <zephyr/sys/check.h>
#include <zephyr/bluetooth/gatt.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/csip.h>
#include <zephyr/bluetooth/audio/tbs.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/logging/log.h>
#include <zephyr/net/buf.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include "cap_internal.h"
#include "ccid_internal.h"
#include "csip_internal.h"
#include "bap_endpoint.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_cap_initiator, CONFIG_BT_CAP_INITIATOR_LOG_LEVEL);
#include "common/bt_str.h"
@ -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.
*/

View file

@ -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 <stdbool.h>
#include <stddef.h>
#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/audio/csip.h>
#include <zephyr/types.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
@ -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 */

View file

@ -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 <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/hci_types.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include "cap_internal.h"
#include <zephyr/logging/log.h>
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);
}

View file

@ -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),

View file

@ -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__);

View file

@ -13,6 +13,7 @@
#include <zephyr/kernel.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/hci_types.h>
#include <hci_core.h>
#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;
}

View file

@ -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

View file

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