zephyr/subsys/bluetooth/audio/bap_stream.c
Emil Gydesen b4f3763c31 tests: Bluetooth: Audio: Use same recv_cb for all tests
This commit changes the BSIM tests to use the same recv callback
for all tests. The purpose of this is to reduce code duplication
and make it easier to maintain the tests.

This also changes the recv_cb so that in case of any error we log
the most recently received SDU, which should provide more
information about why a test failed in case of RX error.

PBP had to be updated a bit to support the audio_stream
struct.

Also modifies a check and log in bap_stream that was less than
helpful to determine if it was the stream or the endpoint that
was NULL.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2024-11-21 14:47:49 +01:00

1015 lines
25 KiB
C

/* Bluetooth Audio Stream */
/*
* Copyright (c) 2020 Intel Corporation
* Copyright (c) 2021-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/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/hci_types.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net_buf.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/slist.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include "../host/iso_internal.h"
#include "audio_internal.h"
#include "bap_iso.h"
#include "bap_endpoint.h"
#include "bap_unicast_client_internal.h"
#include "bap_unicast_server.h"
LOG_MODULE_REGISTER(bt_bap_stream, CONFIG_BT_BAP_STREAM_LOG_LEVEL);
#if defined(CONFIG_BT_BAP_UNICAST_CLIENT) || defined(CONFIG_BT_BAP_BROADCAST_SOURCE) || \
defined(CONFIG_BT_BAP_BROADCAST_SINK)
void bt_bap_qos_cfg_to_iso_qos(struct bt_iso_chan_io_qos *io, const struct bt_bap_qos_cfg *qos_cfg)
{
io->sdu = qos_cfg->sdu;
io->phy = qos_cfg->phy;
io->rtn = qos_cfg->rtn;
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
io->burst_number = qos_cfg->burst_number;
io->max_pdu = qos_cfg->max_pdu;
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT || \
* CONFIG_BT_BAP_BROADCAST_SOURCE || \
* CONFIG_BT_BAP_BROADCAST_SINK \
*/
void bt_bap_stream_init(struct bt_bap_stream *stream)
{
struct bt_bap_stream_ops *ops;
void *user_data;
/* Save the stream->ops and stream->user_data owned by API user */
ops = stream->ops;
user_data = stream->user_data;
memset(stream, 0, sizeof(*stream));
/* Restore */
stream->ops = ops;
stream->user_data = user_data;
}
void bt_bap_stream_attach(struct bt_conn *conn, struct bt_bap_stream *stream, struct bt_bap_ep *ep,
struct bt_audio_codec_cfg *codec_cfg)
{
LOG_DBG("conn %p stream %p ep %p codec_cfg %p", (void *)conn, stream, ep, codec_cfg);
if (conn != NULL) {
__ASSERT(stream->conn == NULL || stream->conn == conn,
"stream->conn %p already attached", (void *)stream->conn);
if (stream->conn == NULL) {
stream->conn = bt_conn_ref(conn);
}
}
stream->codec_cfg = codec_cfg;
stream->ep = ep;
ep->stream = stream;
}
struct bt_iso_chan *bt_bap_stream_iso_chan_get(struct bt_bap_stream *stream)
{
if (stream != NULL && stream->ep != NULL && stream->ep->iso != NULL) {
return &stream->ep->iso->chan;
}
return NULL;
}
void bt_bap_stream_cb_register(struct bt_bap_stream *stream,
struct bt_bap_stream_ops *ops)
{
stream->ops = ops;
}
int bt_bap_ep_get_info(const struct bt_bap_ep *ep, struct bt_bap_ep_info *info)
{
enum bt_audio_dir dir;
CHECKIF(ep == NULL) {
LOG_DBG("ep is NULL");
return -EINVAL;
}
CHECKIF(info == NULL) {
LOG_DBG("info is NULL");
return -EINVAL;
}
dir = ep->dir;
info->id = ep->status.id;
info->state = ep->status.state;
info->dir = dir;
info->qos_pref = &ep->qos_pref;
if (ep->iso == NULL) {
info->paired_ep = NULL;
info->iso_chan = NULL;
} else {
info->paired_ep = bt_bap_iso_get_paired_ep(ep);
info->iso_chan = &ep->iso->chan;
}
info->can_send = false;
info->can_recv = false;
if (IS_ENABLED(CONFIG_BT_AUDIO_TX) && ep->stream != NULL) {
if (IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SOURCE) && bt_bap_ep_is_broadcast_src(ep)) {
info->can_send = true;
} else if (IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SINK) &&
bt_bap_ep_is_broadcast_snk(ep)) {
info->can_recv = true;
} else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) &&
bt_bap_ep_is_unicast_client(ep)) {
/* dir is not initialized before the connection is set */
if (ep->stream->conn != NULL) {
info->can_send = dir == BT_AUDIO_DIR_SINK;
info->can_recv = dir == BT_AUDIO_DIR_SOURCE;
}
} else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER)) {
/* dir is not initialized before the connection is set */
if (ep->stream->conn != NULL) {
info->can_send = dir == BT_AUDIO_DIR_SOURCE;
info->can_recv = dir == BT_AUDIO_DIR_SINK;
}
}
}
return 0;
}
enum bt_bap_ascs_reason bt_audio_verify_qos(const struct bt_bap_qos_cfg *qos)
{
if (qos->interval < BT_ISO_SDU_INTERVAL_MIN ||
qos->interval > BT_ISO_SDU_INTERVAL_MAX) {
LOG_DBG("Interval not within allowed range: %u (%u-%u)", qos->interval,
BT_ISO_SDU_INTERVAL_MIN, BT_ISO_SDU_INTERVAL_MAX);
return BT_BAP_ASCS_REASON_INTERVAL;
}
if (qos->framing > BT_BAP_QOS_CFG_FRAMING_FRAMED) {
LOG_DBG("Invalid Framing 0x%02x", qos->framing);
return BT_BAP_ASCS_REASON_FRAMING;
}
if (qos->phy != BT_BAP_QOS_CFG_1M && qos->phy != BT_BAP_QOS_CFG_2M &&
qos->phy != BT_BAP_QOS_CFG_CODED) {
LOG_DBG("Invalid PHY 0x%02x", qos->phy);
return BT_BAP_ASCS_REASON_PHY;
}
if (qos->sdu > BT_ISO_MAX_SDU) {
LOG_DBG("Invalid SDU %u", qos->sdu);
return BT_BAP_ASCS_REASON_SDU;
}
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) || defined(CONFIG_BT_BAP_UNICAST)
if (qos->latency < BT_ISO_LATENCY_MIN ||
qos->latency > BT_ISO_LATENCY_MAX) {
LOG_DBG("Invalid Latency %u", qos->latency);
return BT_BAP_ASCS_REASON_LATENCY;
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE || CONFIG_BT_BAP_UNICAST */
if (qos->pd > BT_AUDIO_PD_MAX) {
LOG_DBG("Invalid presentation delay %u", qos->pd);
return BT_BAP_ASCS_REASON_PD;
}
return BT_BAP_ASCS_REASON_NONE;
}
bool bt_audio_valid_codec_cfg(const struct bt_audio_codec_cfg *codec_cfg)
{
if (codec_cfg == NULL) {
LOG_DBG("codec is NULL");
return false;
}
if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) {
if (codec_cfg->cid != 0U) {
LOG_DBG("codec_cfg->cid (%u) is invalid", codec_cfg->cid);
return false;
}
if (codec_cfg->vid != 0U) {
LOG_DBG("codec_cfg->vid (%u) is invalid", codec_cfg->vid);
return false;
}
}
#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0
/* Verify that codec configuration length is 0 when using
* BT_HCI_CODING_FORMAT_TRANSPARENT as per the core spec, 5.4, Vol 4, Part E, 7.8.109
*/
if (codec_cfg->id == BT_HCI_CODING_FORMAT_TRANSPARENT && codec_cfg->data_len != 0) {
LOG_DBG("Invalid data_len %zu for codec_id %u", codec_cfg->data_len, codec_cfg->id);
return false;
}
if (codec_cfg->data_len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE) {
LOG_DBG("codec_cfg->data_len (%zu) is invalid", codec_cfg->data_len);
return false;
}
if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3 &&
!bt_audio_valid_ltv(codec_cfg->data, codec_cfg->data_len)) {
LOG_DBG("codec_cfg->data not valid LTV");
return false;
}
#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 */
#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0
if (codec_cfg->meta_len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) {
LOG_DBG("codec_cfg->meta_len (%zu) is invalid", codec_cfg->meta_len);
return false;
}
if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3 &&
!bt_audio_valid_ltv(codec_cfg->data, codec_cfg->data_len)) {
LOG_DBG("codec_cfg->meta not valid LTV");
return false;
}
#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 */
return true;
}
bool bt_bap_valid_qos_pref(const struct bt_bap_qos_cfg_pref *qos_pref)
{
const uint8_t phy_mask = BT_GAP_LE_PHY_1M | BT_GAP_LE_PHY_2M | BT_GAP_LE_PHY_CODED;
if ((qos_pref->phy & (~phy_mask)) != 0U) {
LOG_DBG("Invalid phy: %u", qos_pref->phy);
return false;
}
if (!IN_RANGE(qos_pref->latency, BT_ISO_LATENCY_MIN, BT_ISO_LATENCY_MAX)) {
LOG_DBG("Invalid latency: %u", qos_pref->latency);
return false;
}
if (qos_pref->pd_min > BT_AUDIO_PD_MAX) {
LOG_DBG("Invalid pd_min: %u", qos_pref->pd_min);
return false;
}
if (qos_pref->pd_max > BT_AUDIO_PD_MAX) {
LOG_DBG("Invalid pd_min: %u", qos_pref->pd_min);
return false;
}
if (qos_pref->pd_max < qos_pref->pd_min) {
LOG_DBG("Invalid combination of pd_min %u and pd_max: %u", qos_pref->pd_min,
qos_pref->pd_max);
return false;
}
if (qos_pref->pref_pd_min != BT_AUDIO_PD_PREF_NONE) {
/* If pref_pd_min != BT_AUDIO_PD_PREF_NONE then pd_min <= pref_pd_min <= pd_max */
if (!IN_RANGE(qos_pref->pref_pd_min, qos_pref->pd_min, qos_pref->pd_max)) {
LOG_DBG("Invalid combination of pref_pd_min %u, pd_min %u and pd_max: %u",
qos_pref->pref_pd_min, qos_pref->pd_min, qos_pref->pd_max);
return false;
}
}
if (qos_pref->pref_pd_max != BT_AUDIO_PD_PREF_NONE) {
/* If pref_pd_min == BT_AUDIO_PD_PREF_NONE then pd_min <= pref_pd_max <= pd_max
*
* If pref_pd_min != BT_AUDIO_PD_PREF_NONE then
* pd_min <= pref_pd_min <= pref_pd_max <= pd_max
*/
if (qos_pref->pref_pd_min == BT_AUDIO_PD_PREF_NONE) {
if (!IN_RANGE(qos_pref->pref_pd_max, qos_pref->pd_min, qos_pref->pd_max)) {
LOG_DBG("Invalid combination of pref_pd_max %u, pd_min %u and "
"pd_max: %u",
qos_pref->pref_pd_max, qos_pref->pd_min, qos_pref->pd_max);
return false;
}
} else {
if (!IN_RANGE(qos_pref->pref_pd_max, qos_pref->pref_pd_min,
qos_pref->pd_max)) {
LOG_DBG("Invalid combination of pref_pd_max %u, pref_pd_min %u and "
"pd_max: %u",
qos_pref->pref_pd_max, qos_pref->pd_min, qos_pref->pd_max);
return false;
}
}
}
return true;
}
#if defined(CONFIG_BT_AUDIO_TX)
static bool bt_bap_stream_can_send(const struct bt_bap_stream *stream)
{
struct bt_bap_ep_info info;
int err;
if (stream == NULL || stream->ep == NULL) {
return false;
}
err = bt_bap_ep_get_info(stream->ep, &info);
if (err != 0) {
return false;
}
return info.can_send;
}
static int bap_stream_send(struct bt_bap_stream *stream, struct net_buf *buf, uint16_t seq_num,
uint32_t ts, bool has_ts)
{
struct bt_iso_chan *iso_chan;
struct bt_bap_ep *ep;
int ret;
if (stream == NULL) {
LOG_DBG("stream is NULL");
return -EINVAL;
}
if (stream->ep == NULL) {
LOG_DBG("stream->ep %p is NULL", stream);
return -EINVAL;
}
if (!bt_bap_stream_can_send(stream)) {
LOG_DBG("Stream is not configured for TX");
return -EINVAL;
}
ep = stream->ep;
if (ep->status.state != BT_BAP_EP_STATE_STREAMING) {
LOG_DBG("Channel %p not ready for streaming (state: %s)", stream,
bt_bap_ep_state_str(ep->status.state));
return -EBADMSG;
}
iso_chan = bt_bap_stream_iso_chan_get(stream);
if (has_ts) {
ret = bt_iso_chan_send_ts(iso_chan, buf, seq_num, ts);
} else {
ret = bt_iso_chan_send(iso_chan, buf, seq_num);
}
if (ret < 0) {
return ret;
}
#if defined(CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM)
if (stream->_prev_seq_num != 0U && seq_num != 0U &&
(stream->_prev_seq_num + 1U) != seq_num) {
LOG_WRN("Unexpected seq_num diff between %u and %u for %p", stream->_prev_seq_num,
seq_num, stream);
}
stream->_prev_seq_num = seq_num;
#endif /* CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM */
return ret;
}
int bt_bap_stream_send(struct bt_bap_stream *stream, struct net_buf *buf, uint16_t seq_num)
{
return bap_stream_send(stream, buf, seq_num, 0, false);
}
int bt_bap_stream_send_ts(struct bt_bap_stream *stream, struct net_buf *buf, uint16_t seq_num,
uint32_t ts)
{
return bap_stream_send(stream, buf, seq_num, ts, true);
}
int bt_bap_stream_get_tx_sync(struct bt_bap_stream *stream, struct bt_iso_tx_info *info)
{
struct bt_iso_chan *iso_chan;
CHECKIF(stream == NULL) {
LOG_DBG("stream is null");
return -EINVAL;
}
CHECKIF(info == NULL) {
LOG_DBG("info is null");
return -EINVAL;
}
if (!bt_bap_stream_can_send(stream)) {
LOG_DBG("Stream is not configured for TX");
return -EINVAL;
}
iso_chan = bt_bap_stream_iso_chan_get(stream);
if (iso_chan == NULL) {
LOG_DBG("Could not get iso channel from stream %p", stream);
return -EINVAL;
}
return bt_iso_chan_get_tx_sync(iso_chan, info);
}
#endif /* CONFIG_BT_AUDIO_TX */
#if defined(CONFIG_BT_BAP_UNICAST)
/** Checks if the stream can terminate the CIS
*
* If the CIS is used for another stream, or if the CIS is not in the connected
* state it will return false.
*/
bool bt_bap_stream_can_disconnect(const struct bt_bap_stream *stream)
{
const struct bt_bap_ep *stream_ep;
enum bt_iso_state iso_state;
if (stream == NULL) {
return false;
}
stream_ep = stream->ep;
if (stream_ep == NULL || stream_ep->iso == NULL) {
return false;
}
iso_state = stream_ep->iso->chan.state;
if (iso_state == BT_ISO_STATE_CONNECTED || iso_state == BT_ISO_STATE_CONNECTING) {
const struct bt_bap_ep *pair_ep;
pair_ep = bt_bap_iso_get_paired_ep(stream_ep);
/* If there are no paired endpoint, or the paired endpoint is
* not in the streaming state, we can disconnect the CIS
*/
if (pair_ep == NULL || pair_ep->status.state != BT_BAP_EP_STATE_STREAMING) {
return true;
}
}
return false;
}
static bool bt_bap_stream_is_broadcast(const struct bt_bap_stream *stream)
{
return (IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SOURCE) &&
bt_bap_ep_is_broadcast_src(stream->ep)) ||
(IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SINK) && bt_bap_ep_is_broadcast_snk(stream->ep));
}
enum bt_bap_ascs_reason bt_bap_stream_verify_qos(const struct bt_bap_stream *stream,
const struct bt_bap_qos_cfg *qos)
{
const struct bt_bap_qos_cfg_pref *qos_pref = &stream->ep->qos_pref;
if (qos_pref->latency < qos->latency) {
/* Latency is a preferred value. Print debug info but do not fail. */
LOG_DBG("Latency %u higher than preferred max %u", qos->latency, qos_pref->latency);
}
if (!IN_RANGE(qos->pd, qos_pref->pd_min, qos_pref->pd_max)) {
LOG_DBG("Presentation Delay not within range: min %u max %u pd %u",
qos_pref->pd_min, qos_pref->pd_max, qos->pd);
return BT_BAP_ASCS_REASON_PD;
}
return BT_BAP_ASCS_REASON_NONE;
}
void bt_bap_stream_detach(struct bt_bap_stream *stream)
{
const bool is_broadcast = bt_bap_stream_is_broadcast(stream);
LOG_DBG("stream %p conn %p ep %p", stream, (void *)stream->conn, (void *)stream->ep);
if (stream->conn != NULL) {
bt_conn_unref(stream->conn);
stream->conn = NULL;
}
stream->codec_cfg = NULL;
stream->ep->stream = NULL;
stream->ep = NULL;
if (!is_broadcast) {
const int err = bt_bap_stream_disconnect(stream);
if (err != 0) {
LOG_DBG("Failed to disconnect stream %p: %d", stream, err);
}
}
}
int bt_bap_stream_disconnect(struct bt_bap_stream *stream)
{
struct bt_iso_chan *iso_chan;
LOG_DBG("stream %p", stream);
if (stream == NULL) {
return -EINVAL;
}
iso_chan = bt_bap_stream_iso_chan_get(stream);
if (iso_chan == NULL || iso_chan->iso == NULL) {
LOG_DBG("Not connected");
return -ENOTCONN;
}
return bt_iso_chan_disconnect(iso_chan);
}
void bt_bap_stream_reset(struct bt_bap_stream *stream)
{
LOG_DBG("stream %p", stream);
if (stream == NULL) {
return;
}
if (stream->ep != NULL && stream->ep->iso != NULL) {
bt_bap_iso_unbind_ep(stream->ep->iso, stream->ep);
}
bt_bap_stream_detach(stream);
}
static uint8_t conn_get_role(const struct bt_conn *conn)
{
struct bt_conn_info info;
int err;
err = bt_conn_get_info(conn, &info);
__ASSERT(err == 0, "Failed to get conn info");
return info.role;
}
#if defined(CONFIG_BT_BAP_UNICAST_CLIENT)
int bt_bap_stream_config(struct bt_conn *conn, struct bt_bap_stream *stream, struct bt_bap_ep *ep,
struct bt_audio_codec_cfg *codec_cfg)
{
uint8_t role;
int err;
LOG_DBG("conn %p stream %p, ep %p codec_cfg %p codec id 0x%02x "
"codec cid 0x%04x codec vid 0x%04x", (void *)conn, stream, ep,
codec_cfg, codec_cfg ? codec_cfg->id : 0, codec_cfg ? codec_cfg->cid : 0,
codec_cfg ? codec_cfg->vid : 0);
CHECKIF(conn == NULL || stream == NULL || codec_cfg == NULL || ep == NULL) {
LOG_DBG("NULL value(s) supplied)");
return -EINVAL;
}
if (stream->conn != NULL) {
LOG_DBG("Stream already configured for conn %p", (void *)stream->conn);
return -EALREADY;
}
role = conn_get_role(conn);
if (role != BT_HCI_ROLE_CENTRAL) {
LOG_DBG("Invalid conn role: %u, shall be central", role);
return -EINVAL;
}
switch (ep->status.state) {
/* Valid only if ASE_State field = 0x00 (Idle) */
case BT_BAP_EP_STATE_IDLE:
/* or 0x01 (Codec Configured) */
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
/* or 0x02 (QoS Configured) */
case BT_BAP_EP_STATE_QOS_CONFIGURED:
break;
default:
LOG_ERR("Invalid state: %s", bt_bap_ep_state_str(ep->status.state));
return -EBADMSG;
}
bt_bap_stream_attach(conn, stream, ep, codec_cfg);
err = bt_bap_unicast_client_config(stream, codec_cfg);
if (err != 0) {
LOG_DBG("Failed to configure stream: %d", err);
return err;
}
return 0;
}
int bt_bap_stream_qos(struct bt_conn *conn, struct bt_bap_unicast_group *group)
{
uint8_t role;
int err;
LOG_DBG("conn %p group %p", (void *)conn, group);
CHECKIF(conn == NULL) {
LOG_DBG("conn is NULL");
return -EINVAL;
}
CHECKIF(group == NULL) {
LOG_DBG("group is NULL");
return -EINVAL;
}
if (sys_slist_is_empty(&group->streams)) {
LOG_DBG("group stream list is empty");
return -ENOEXEC;
}
role = conn_get_role(conn);
if (role != BT_HCI_ROLE_CENTRAL) {
LOG_DBG("Invalid conn role: %u, shall be central", role);
return -EINVAL;
}
err = bt_bap_unicast_client_qos(conn, group);
if (err != 0) {
LOG_DBG("Failed to configure stream: %d", err);
return err;
}
return 0;
}
int bt_bap_stream_enable(struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len)
{
uint8_t role;
int err;
LOG_DBG("stream %p", stream);
if (stream == NULL || stream->ep == NULL || stream->conn == NULL) {
LOG_DBG("Invalid stream");
return -EINVAL;
}
role = conn_get_role(stream->conn);
if (role != BT_HCI_ROLE_CENTRAL) {
LOG_DBG("Invalid conn role: %u, shall be central", role);
return -EINVAL;
}
/* Valid for an ASE only if ASE_State field = 0x02 (QoS Configured) */
if (stream->ep->status.state != BT_BAP_EP_STATE_QOS_CONFIGURED) {
LOG_ERR("Invalid state: %s", bt_bap_ep_state_str(stream->ep->status.state));
return -EBADMSG;
}
err = bt_bap_unicast_client_enable(stream, meta, meta_len);
if (err != 0) {
LOG_DBG("Failed to enable stream: %d", err);
return err;
}
return 0;
}
int bt_bap_stream_stop(struct bt_bap_stream *stream)
{
struct bt_bap_ep *ep;
uint8_t role;
int err;
if (stream == NULL || stream->ep == NULL || stream->conn == NULL) {
LOG_DBG("Invalid stream");
return -EINVAL;
}
role = conn_get_role(stream->conn);
if (role != BT_HCI_ROLE_CENTRAL) {
LOG_DBG("Invalid conn role: %u, shall be central", role);
return -EINVAL;
}
ep = stream->ep;
switch (ep->status.state) {
/* Valid only if ASE_State field = 0x03 (Disabling) */
case BT_BAP_EP_STATE_DISABLING:
break;
default:
LOG_ERR("Invalid state: %s", bt_bap_ep_state_str(ep->status.state));
return -EBADMSG;
}
err = bt_bap_unicast_client_stop(stream);
if (err != 0) {
LOG_DBG("Stopping stream failed: %d", err);
return err;
}
return 0;
}
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
int bt_bap_stream_reconfig(struct bt_bap_stream *stream,
struct bt_audio_codec_cfg *codec_cfg)
{
uint8_t state;
uint8_t role;
int err;
LOG_DBG("stream %p codec_cfg %p", stream, codec_cfg);
CHECKIF(stream == NULL || stream->ep == NULL || stream->conn == NULL) {
LOG_DBG("Invalid stream");
return -EINVAL;
}
CHECKIF(codec_cfg == NULL) {
LOG_DBG("codec_cfg is NULL");
return -EINVAL;
}
state = stream->ep->status.state;
switch (state) {
/* Valid only if ASE_State field = 0x00 (Idle) */
case BT_BAP_EP_STATE_IDLE:
/* or 0x01 (Codec Configured) */
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
/* or 0x02 (QoS Configured) */
case BT_BAP_EP_STATE_QOS_CONFIGURED:
break;
default:
LOG_ERR("Invalid state: %s", bt_bap_ep_state_str(state));
return -EBADMSG;
}
role = conn_get_role(stream->conn);
if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && role == BT_HCI_ROLE_CENTRAL) {
err = bt_bap_unicast_client_config(stream, codec_cfg);
} else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && role == BT_HCI_ROLE_PERIPHERAL) {
err = bt_bap_unicast_server_reconfig(stream, codec_cfg);
} else {
err = -EOPNOTSUPP;
}
if (err != 0) {
LOG_DBG("reconfiguring stream failed: %d", err);
} else {
stream->codec_cfg = codec_cfg;
}
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;
uint8_t role;
int err;
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;
}
state = stream->ep->status.state;
switch (state) {
/* Valid only if ASE_State field = 0x03 (Enabling) */
case BT_BAP_EP_STATE_ENABLING:
break;
default:
LOG_ERR("Invalid state: %s", bt_bap_ep_state_str(state));
return -EBADMSG;
}
role = conn_get_role(stream->conn);
if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && role == BT_HCI_ROLE_CENTRAL) {
err = bt_bap_unicast_client_start(stream);
} else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && role == BT_HCI_ROLE_PERIPHERAL) {
err = bt_bap_unicast_server_start(stream);
} else {
err = -EOPNOTSUPP;
}
if (err != 0) {
LOG_DBG("Starting stream failed: %d", err);
return err;
}
return 0;
}
int bt_bap_stream_metadata(struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len)
{
uint8_t state;
uint8_t role;
int err;
LOG_DBG("stream %p meta_len %zu", stream, meta_len);
CHECKIF(stream == NULL || stream->ep == NULL || stream->conn == NULL) {
LOG_DBG("Invalid stream");
return -EINVAL;
}
CHECKIF((meta == NULL && meta_len != 0U) || (meta != NULL && meta_len == 0U)) {
LOG_DBG("Invalid meta (%p) or len (%zu)", meta, meta_len);
return -EINVAL;
}
state = stream->ep->status.state;
switch (state) {
/* Valid for an ASE only if ASE_State field = 0x03 (Enabling) */
case BT_BAP_EP_STATE_ENABLING:
/* or 0x04 (Streaming) */
case BT_BAP_EP_STATE_STREAMING:
break;
default:
LOG_ERR("Invalid state: %s", bt_bap_ep_state_str(state));
return -EBADMSG;
}
role = conn_get_role(stream->conn);
if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && role == BT_HCI_ROLE_CENTRAL) {
err = bt_bap_unicast_client_metadata(stream, meta, meta_len);
} else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && role == BT_HCI_ROLE_PERIPHERAL) {
err = bt_bap_unicast_server_metadata(stream, meta, meta_len);
} else {
err = -EOPNOTSUPP;
}
if (err != 0) {
LOG_DBG("Updating metadata failed: %d", err);
return err;
}
return 0;
}
int bt_bap_stream_disable(struct bt_bap_stream *stream)
{
uint8_t state;
uint8_t role;
int err;
LOG_DBG("stream %p", stream);
CHECKIF(stream == NULL || stream->ep == NULL || stream->conn == NULL) {
LOG_DBG("Invalid stream");
return -EINVAL;
}
state = stream->ep->status.state;
switch (state) {
/* Valid only if ASE_State field = 0x03 (Enabling) */
case BT_BAP_EP_STATE_ENABLING:
/* or 0x04 (Streaming) */
case BT_BAP_EP_STATE_STREAMING:
break;
default:
LOG_ERR("Invalid state: %s", bt_bap_ep_state_str(state));
return -EBADMSG;
}
role = conn_get_role(stream->conn);
if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && role == BT_HCI_ROLE_CENTRAL) {
err = bt_bap_unicast_client_disable(stream);
} else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && role == BT_HCI_ROLE_PERIPHERAL) {
err = bt_bap_unicast_server_disable(stream);
} else {
err = -EOPNOTSUPP;
}
if (err != 0) {
LOG_DBG("Disabling stream failed: %d", err);
return err;
}
return 0;
}
int bt_bap_stream_release(struct bt_bap_stream *stream)
{
uint8_t state;
uint8_t role;
int err;
LOG_DBG("stream %p", stream);
CHECKIF(stream == NULL || stream->ep == NULL || stream->conn == NULL) {
LOG_DBG("Invalid stream (ep %p, conn %p)", stream->ep, (void *)stream->conn);
return -EINVAL;
}
state = stream->ep->status.state;
switch (state) {
/* Valid only if ASE_State field = 0x01 (Codec Configured) */
case BT_BAP_EP_STATE_CODEC_CONFIGURED:
/* or 0x02 (QoS Configured) */
case BT_BAP_EP_STATE_QOS_CONFIGURED:
/* or 0x03 (Enabling) */
case BT_BAP_EP_STATE_ENABLING:
/* or 0x04 (Streaming) */
case BT_BAP_EP_STATE_STREAMING:
/* or 0x04 (Disabling) */
case BT_BAP_EP_STATE_DISABLING:
break;
default:
LOG_ERR("Invalid state: %s", bt_bap_ep_state_str(state));
return -EBADMSG;
}
role = conn_get_role(stream->conn);
if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && role == BT_HCI_ROLE_CENTRAL) {
err = bt_bap_unicast_client_release(stream);
} else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && role == BT_HCI_ROLE_PERIPHERAL) {
err = bt_bap_unicast_server_release(stream);
} else {
err = -EOPNOTSUPP;
}
if (err != 0) {
LOG_DBG("Releasing stream failed: %d", err);
return err;
}
return 0;
}
#endif /* CONFIG_BT_BAP_UNICAST */