blob: cc6335d1f1784492789d8de61b0c7c6a1ade4ec2 [file] [log] [blame]
/* Bluetooth Audio Broadcast Source */
/*
* Copyright (c) 2021-2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/audio/audio.h>
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_BROADCAST_SOURCE)
#define LOG_MODULE_NAME bt_audio_broadcast_source
#include "common/log.h"
#include "audio_iso.h"
#include "endpoint.h"
struct bt_audio_broadcast_subgroup {
/* The streams used to create the broadcast source */
sys_slist_t streams;
/* The codec of the subgroup */
struct bt_codec *codec;
/* List node */
sys_snode_t _node;
};
static struct bt_audio_ep broadcast_source_eps
[CONFIG_BT_AUDIO_BROADCAST_SRC_COUNT][BROADCAST_STREAM_CNT];
static struct bt_audio_broadcast_subgroup broadcast_source_subgroups
[CONFIG_BT_AUDIO_BROADCAST_SRC_COUNT][CONFIG_BT_AUDIO_BROADCAST_SRC_SUBGROUP_COUNT];
static struct bt_audio_broadcast_source broadcast_sources[CONFIG_BT_AUDIO_BROADCAST_SRC_COUNT];
/**
* 2 octets UUID
* 3 octets presentation delay
* 1 octet number of subgroups
*
* Each subgroup then has
* 1 octet of number of BIS
* 5 octets of Codec_ID
* 1 octet codec specific configuration len
* 0-n octets of codec specific configuration
* 1 octet metadata len
* 0-n octets of metadata
*
* For each BIS in the subgroup there is
* 1 octet for the BIS index
* 1 octet codec specific configuration len
* 0-n octets of codec specific configuration
*
* For a minimal BASE with 1 subgroup and 1 BIS without and other data the
* total comes to 16
*/
#define MINIMUM_BASE_SIZE 16
static void broadcast_source_set_ep_state(struct bt_audio_ep *ep, uint8_t state)
{
uint8_t old_state;
old_state = ep->status.state;
BT_DBG("ep %p id 0x%02x %s -> %s", ep, ep->status.id,
bt_audio_ep_state_str(old_state),
bt_audio_ep_state_str(state));
switch (old_state) {
case BT_AUDIO_EP_STATE_IDLE:
if (state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
BT_DBG("Invalid broadcast sync endpoint state transition");
return;
}
break;
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
if (state != BT_AUDIO_EP_STATE_IDLE &&
state != BT_AUDIO_EP_STATE_ENABLING) {
BT_DBG("Invalid broadcast sync endpoint state transition");
return;
}
break;
case BT_AUDIO_EP_STATE_ENABLING:
if (state != BT_AUDIO_EP_STATE_STREAMING) {
BT_DBG("Invalid broadcast sync endpoint state transition");
return;
}
break;
case BT_AUDIO_EP_STATE_STREAMING:
if (state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
BT_DBG("Invalid broadcast sync endpoint state transition");
return;
}
break;
default:
BT_ERR("Invalid broadcast sync endpoint state: %s",
bt_audio_ep_state_str(old_state));
return;
}
ep->status.state = state;
if (state == BT_AUDIO_EP_STATE_IDLE) {
struct bt_audio_stream *stream = ep->stream;
if (stream != NULL) {
stream->ep = NULL;
stream->codec = NULL;
ep->stream = NULL;
}
}
}
static void broadcast_source_iso_sent(struct bt_iso_chan *chan)
{
struct bt_audio_iso *iso = CONTAINER_OF(chan, struct bt_audio_iso, chan);
const struct bt_audio_stream_ops *ops;
struct bt_audio_stream *stream;
struct bt_audio_ep *ep = iso->tx.ep;
if (ep == NULL) {
BT_ERR("iso %p not bound with ep", chan);
return;
}
stream = ep->stream;
if (stream == NULL) {
BT_ERR("No stream for ep %p", ep);
return;
}
ops = stream->ops;
if (IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_STREAM_DATA)) {
BT_DBG("stream %p ep %p", stream, stream->ep);
}
if (ops != NULL && ops->sent != NULL) {
ops->sent(stream);
}
}
static void broadcast_source_iso_connected(struct bt_iso_chan *chan)
{
struct bt_audio_iso *iso = CONTAINER_OF(chan, struct bt_audio_iso, chan);
const struct bt_audio_stream_ops *ops;
struct bt_audio_stream *stream;
struct bt_audio_ep *ep = iso->tx.ep;
if (ep == NULL) {
BT_ERR("iso %p not bound with ep", chan);
return;
}
stream = ep->stream;
if (stream == NULL) {
BT_ERR("No stream for ep %p", ep);
return;
}
ops = stream->ops;
BT_DBG("stream %p ep %p", stream, ep);
broadcast_source_set_ep_state(ep, BT_AUDIO_EP_STATE_STREAMING);
if (ops != NULL && ops->started != NULL) {
ops->started(stream);
} else {
BT_WARN("No callback for connected set");
}
}
static void broadcast_source_iso_disconnected(struct bt_iso_chan *chan, uint8_t reason)
{
struct bt_audio_iso *iso = CONTAINER_OF(chan, struct bt_audio_iso, chan);
const struct bt_audio_stream_ops *ops;
struct bt_audio_stream *stream;
struct bt_audio_ep *ep = iso->tx.ep;
if (ep == NULL) {
BT_ERR("iso %p not bound with ep", chan);
return;
}
stream = ep->stream;
if (stream == NULL) {
BT_ERR("No stream for ep %p", ep);
return;
}
ops = stream->ops;
BT_DBG("stream %p ep %p reason 0x%02x", stream, stream->ep, reason);
broadcast_source_set_ep_state(ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED);
if (ops != NULL && ops->stopped != NULL) {
ops->stopped(stream);
} else {
BT_WARN("No callback for stopped set");
}
}
static struct bt_iso_chan_ops broadcast_source_iso_ops = {
.sent = broadcast_source_iso_sent,
.connected = broadcast_source_iso_connected,
.disconnected = broadcast_source_iso_disconnected,
};
bool bt_audio_ep_is_broadcast_src(const struct bt_audio_ep *ep)
{
for (int i = 0; i < ARRAY_SIZE(broadcast_source_eps); i++) {
if (PART_OF_ARRAY(broadcast_source_eps[i], ep)) {
return true;
}
}
return false;
}
static void broadcast_source_ep_init(struct bt_audio_ep *ep)
{
BT_DBG("ep %p", ep);
(void)memset(ep, 0, sizeof(*ep));
ep->dir = BT_AUDIO_DIR_SOURCE;
ep->iso = NULL;
}
static struct bt_audio_ep *broadcast_source_new_ep(uint8_t index)
{
for (size_t i = 0; i < ARRAY_SIZE(broadcast_source_eps[index]); i++) {
struct bt_audio_ep *ep = &broadcast_source_eps[index][i];
/* If ep->stream is NULL the endpoint is unallocated */
if (ep->stream == NULL) {
broadcast_source_ep_init(ep);
return ep;
}
}
return NULL;
}
static struct bt_audio_broadcast_subgroup *broadcast_source_new_subgroup(uint8_t index)
{
for (size_t i = 0; i < ARRAY_SIZE(broadcast_source_subgroups[index]); i++) {
struct bt_audio_broadcast_subgroup *subgroup =
&broadcast_source_subgroups[index][i];
if (sys_slist_is_empty(&subgroup->streams)) {
return subgroup;
}
}
return NULL;
}
static int broadcast_source_setup_stream(uint8_t index,
struct bt_audio_stream *stream,
struct bt_codec *codec,
struct bt_codec_qos *qos,
struct bt_audio_broadcast_source *source)
{
struct bt_audio_iso *iso;
struct bt_audio_ep *ep;
ep = broadcast_source_new_ep(index);
if (ep == NULL) {
BT_DBG("Could not allocate new broadcast endpoint");
return -ENOMEM;
}
iso = bt_audio_iso_new();
if (iso == NULL) {
BT_DBG("Could not allocate iso");
return -ENOMEM;
}
bt_audio_iso_init(iso, &broadcast_source_iso_ops);
bt_audio_iso_bind_ep(iso, ep);
bt_audio_codec_qos_to_iso_qos(iso->chan.qos->tx, qos);
bt_audio_codec_to_iso_path(iso->chan.qos->tx->path, codec);
bt_audio_iso_unref(iso);
bt_audio_stream_attach(NULL, stream, ep, codec);
stream->qos = qos;
ep->broadcast_source = source;
return 0;
}
static bool encode_base_subgroup(struct bt_audio_broadcast_subgroup *subgroup,
struct bt_audio_broadcast_stream_data *stream_data,
uint8_t *streams_encoded,
struct net_buf_simple *buf)
{
struct bt_audio_stream *stream;
const struct bt_codec *codec;
uint8_t stream_count;
uint8_t bis_index;
uint8_t *start;
uint8_t len;
stream_count = 0;
SYS_SLIST_FOR_EACH_CONTAINER(&subgroup->streams, stream, _node) {
stream_count++;
}
codec = subgroup->codec;
net_buf_simple_add_u8(buf, stream_count);
net_buf_simple_add_u8(buf, codec->id);
net_buf_simple_add_le16(buf, codec->cid);
net_buf_simple_add_le16(buf, codec->vid);
/* Insert codec configuration data in LTV format */
start = net_buf_simple_add(buf, sizeof(len));
for (int i = 0; i < codec->data_count; i++) {
const struct bt_data *codec_data = &codec->data[i].data;
if ((buf->size - buf->len) < (sizeof(codec_data->data_len) +
sizeof(codec_data->type) +
codec_data->data_len)) {
BT_DBG("No room for codec[%d] with len %u",
i, codec_data->data_len);
return false;
}
net_buf_simple_add_u8(buf, codec_data->data_len + sizeof(codec_data->type));
net_buf_simple_add_u8(buf, codec_data->type);
net_buf_simple_add_mem(buf, codec_data->data, codec_data->data_len);
}
/* Calculate length of codec config data */
len = net_buf_simple_tail(buf) - start - sizeof(len);
/* Update the length field */
*start = len;
if ((buf->size - buf->len) < sizeof(len)) {
BT_DBG("No room for metadata length");
return false;
}
/* Insert codec metadata in LTV format*/
start = net_buf_simple_add(buf, sizeof(len));
for (int i = 0; i < codec->meta_count; i++) {
const struct bt_data *metadata = &codec->meta[i].data;
if ((buf->size - buf->len) < (sizeof(metadata->data_len) +
sizeof(metadata->type) +
metadata->data_len)) {
BT_DBG("No room for metadata[%d] with len %u",
i, metadata->data_len);
return false;
}
net_buf_simple_add_u8(buf, metadata->data_len + sizeof(metadata->type));
net_buf_simple_add_u8(buf, metadata->type);
net_buf_simple_add_mem(buf, metadata->data, metadata->data_len);
}
/* Calculate length of codec config data */
len = net_buf_simple_tail(buf) - start - sizeof(len);
/* Update the length field */
*start = len;
/* Create BIS index bitfield */
bis_index = 0;
for (int i = 0; i < stream_count; i++) {
bis_index++;
if ((buf->size - buf->len) < (sizeof(bis_index) + sizeof(uint8_t))) {
BT_DBG("No room for BIS[%d] index", i);
return false;
}
net_buf_simple_add_u8(buf, bis_index);
if ((buf->size - buf->len) < sizeof(len)) {
BT_DBG("No room for bis codec config length");
return false;
}
/* Insert codec configuration data in LTV format */
start = net_buf_simple_add(buf, sizeof(len));
#if defined(CONFIG_BT_CODEC_MAX_DATA_COUNT)
for (size_t j = 0U; j < stream_data[i].data_count; j++) {
const struct bt_data *codec_data = &stream_data[i].data[j].data;
if ((buf->size - buf->len) < (sizeof(codec_data->data_len) +
sizeof(codec_data->type) +
codec_data->data_len)) {
BT_DBG("No room for BIS [%u] codec[%zu] with len %u",
bis_index, j, codec_data->data_len);
return false;
}
net_buf_simple_add_u8(buf, codec_data->data_len + sizeof(codec_data->type));
net_buf_simple_add_u8(buf, codec_data->type);
net_buf_simple_add_mem(buf, codec_data->data, codec_data->data_len);
}
#endif /* CONFIG_BT_CODEC_MAX_DATA_COUNT */
/* Calculate length of codec config data */
len = net_buf_simple_tail(buf) - start - sizeof(len);
/* Update the length field */
*start = len;
streams_encoded++;
}
return true;
}
static bool encode_base(struct bt_audio_broadcast_source *source,
struct net_buf_simple *buf)
{
struct bt_audio_broadcast_subgroup *subgroup;
uint8_t streams_encoded;
uint8_t subgroup_count;
/* 13 is the size of the fixed size values following this check */
if ((buf->size - buf->len) < MINIMUM_BASE_SIZE) {
return false;
}
subgroup_count = 0U;
SYS_SLIST_FOR_EACH_CONTAINER(&source->subgroups, subgroup, _node) {
subgroup_count++;
}
net_buf_simple_add_le16(buf, BT_UUID_BASIC_AUDIO_VAL);
net_buf_simple_add_le24(buf, source->qos->pd);
net_buf_simple_add_u8(buf, subgroup_count);
/* Since the `stream_data` is only stored in the broadcast source,
* we need to provide that information when encoding each subgroup
*/
streams_encoded = 0;
SYS_SLIST_FOR_EACH_CONTAINER(&source->subgroups, subgroup, _node) {
if (!encode_base_subgroup(subgroup,
&source->stream_data[streams_encoded],
&streams_encoded, buf)) {
return false;
}
}
return true;
}
static int generate_broadcast_id(struct bt_audio_broadcast_source *source)
{
bool unique;
do {
int err;
err = bt_rand(&source->broadcast_id,
BT_AUDIO_BROADCAST_ID_SIZE);
if (err) {
return err;
}
/* Ensure uniqueness */
unique = true;
for (int i = 0; i < ARRAY_SIZE(broadcast_sources); i++) {
if (&broadcast_sources[i] == source) {
continue;
}
if (broadcast_sources[i].broadcast_id == source->broadcast_id) {
unique = false;
break;
}
}
} while (!unique);
return 0;
}
static void broadcast_source_cleanup(struct bt_audio_broadcast_source *source)
{
struct bt_audio_broadcast_subgroup *subgroup, *next_subgroup;
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&source->subgroups, subgroup,
next_subgroup, _node) {
struct bt_audio_stream *stream, *next_stream;
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&subgroup->streams, stream,
next_stream, _node) {
bt_audio_iso_unbind_ep(stream->ep->iso, stream->ep);
stream->ep->stream = NULL;
stream->ep = NULL;
stream->codec = NULL;
stream->qos = NULL;
stream->group = NULL;
sys_slist_remove(&subgroup->streams, NULL,
&stream->_node);
}
sys_slist_remove(&source->subgroups, NULL, &subgroup->_node);
}
(void)memset(source, 0, sizeof(*source));
}
static bool valid_create_param(const struct bt_audio_broadcast_source_create_param *param)
{
const struct bt_codec_qos *qos;
CHECKIF(param == NULL) {
BT_DBG("param is NULL");
return false;
}
CHECKIF(param->params_count == 0U) {
BT_DBG("param->params_count is 0");
return false;
}
qos = param->qos;
CHECKIF(qos == NULL) {
BT_DBG("param->qos is NULL");
return false;
}
CHECKIF(!bt_audio_valid_qos(qos)) {
BT_DBG("param->qos is invalid");
return false;
}
for (size_t i = 0U; i < param->params_count; i++) {
const struct bt_audio_broadcast_source_subgroup_param *subgroup_param;
subgroup_param = &param->params[i];
CHECKIF(subgroup_param->params_count == 0U) {
BT_DBG("subgroup_params[%zu].count is 0", i);
return false;
}
CHECKIF(subgroup_param->codec == NULL) {
BT_DBG("subgroup_params[%zu].codec is NULL", i);
return false;
}
for (size_t j = 0U; j < subgroup_param->params_count; j++) {
const struct bt_audio_broadcast_source_stream_param *stream_param;
stream_param = &subgroup_param->params[j];
CHECKIF(stream_param->stream == NULL) {
BT_DBG("subgroup_params[%zu].stream_params[%zu]->stream is NULL",
i, j);
return false;
}
CHECKIF(stream_param->stream->group != NULL) {
BT_DBG("subgroup_params[%zu].stream_params[%zu]->stream is "
"already part of group %p",
i, j, stream_param->stream->group);
return false;
}
}
}
return true;
}
static enum bt_audio_state broadcast_source_get_state(struct bt_audio_broadcast_source *source)
{
struct bt_audio_broadcast_subgroup *subgroup;
struct bt_audio_stream *stream;
sys_snode_t *head_node;
if (source == NULL) {
BT_DBG("source is NULL");
return BT_AUDIO_EP_STATE_IDLE;
}
if (sys_slist_is_empty(&source->subgroups)) {
BT_DBG("Source does not have any streams");
return BT_AUDIO_EP_STATE_IDLE;
}
/* Get the first stream */
head_node = sys_slist_peek_head(&source->subgroups);
subgroup = CONTAINER_OF(head_node, struct bt_audio_broadcast_subgroup, _node);
head_node = sys_slist_peek_head(&subgroup->streams);
stream = CONTAINER_OF(head_node, struct bt_audio_stream, _node);
/* All streams in a broadcast source is in the same state,
* so we can just check the first stream
*/
if (stream->ep == NULL) {
BT_DBG("stream->ep is NULL");
return BT_AUDIO_EP_STATE_IDLE;
}
return stream->ep->status.state;
}
int bt_audio_broadcast_source_create(struct bt_audio_broadcast_source_create_param *param,
struct bt_audio_broadcast_source **out_source)
{
struct bt_audio_broadcast_subgroup *subgroup;
struct bt_audio_broadcast_source *source;
struct bt_codec_qos *qos;
size_t stream_count;
uint8_t index;
int err;
CHECKIF(out_source == NULL) {
BT_DBG("out_source is NULL");
return -EINVAL;
}
/* Set out_source to NULL until the source has actually been created */
*out_source = NULL;
if (!valid_create_param(param)) {
BT_DBG("Invalid parameters");
return -EINVAL;
}
source = NULL;
for (index = 0; index < ARRAY_SIZE(broadcast_sources); index++) {
if (sys_slist_is_empty(&broadcast_sources[index].subgroups)) { /* Find free entry */
source = &broadcast_sources[index];
break;
}
}
if (source == NULL) {
BT_DBG("Could not allocate any more broadcast sources");
return -ENOMEM;
}
stream_count = 0U;
qos = param->qos;
/* Go through all subgroups and streams and setup each setup with an
* endpoint
*/
for (size_t i = 0U; i < param->params_count; i++) {
const struct bt_audio_broadcast_source_subgroup_param *subgroup_param;
subgroup_param = &param->params[i];
subgroup = broadcast_source_new_subgroup(index);
if (subgroup == NULL) {
BT_DBG("Could not allocate new broadcast subgroup");
broadcast_source_cleanup(source);
return -ENOMEM;
}
subgroup->codec = subgroup_param->codec;
sys_slist_append(&source->subgroups, &subgroup->_node);
/* Check that we are not above the maximum BIS count */
if (subgroup_param->params_count + stream_count > BROADCAST_STREAM_CNT) {
BT_DBG("Cannot create broadcaster with %zu streams",
stream_count);
broadcast_source_cleanup(source);
return -ENOMEM;
}
for (size_t j = 0U; j < subgroup_param->params_count; j++) {
const struct bt_audio_broadcast_source_stream_param *stream_param;
struct bt_audio_stream *stream;
stream_param = &subgroup_param->params[j];
stream = stream_param->stream;
err = broadcast_source_setup_stream(index, stream,
subgroup_param->codec,
qos, source);
if (err != 0) {
BT_DBG("Failed to setup streams[%zu]: %d", i, err);
broadcast_source_cleanup(source);
return err;
}
/* Store the BIS specific codec configuration data in
* the broadcast source. It is stored in the broadcast
* source, instead of the stream object, as this is
* only relevant for the broadcast source, and not used
* for unicast or broadcast sink.
*/
(void)memcpy(source->stream_data[stream_count].data,
stream_param->data,
stream_param->data_count * sizeof(*stream_param->data));
source->stream_data[stream_count].data_count = stream_param->data_count;
sys_slist_append(&subgroup->streams, &stream->_node);
stream_count++;
}
}
err = generate_broadcast_id(source);
if (err != 0) {
BT_DBG("Could not generate broadcast id: %d", err);
return err;
}
/* Finalize state changes and store information */
SYS_SLIST_FOR_EACH_CONTAINER(&source->subgroups, subgroup, _node) {
struct bt_audio_stream *stream;
SYS_SLIST_FOR_EACH_CONTAINER(&subgroup->streams, stream, _node) {
broadcast_source_set_ep_state(stream->ep,
BT_AUDIO_EP_STATE_QOS_CONFIGURED);
}
}
source->qos = qos;
BT_DBG("Broadcasting with ID 0x%6X", source->broadcast_id);
*out_source = source;
return 0;
}
int bt_audio_broadcast_source_reconfig(struct bt_audio_broadcast_source *source,
struct bt_codec *codec,
struct bt_codec_qos *qos)
{
struct bt_audio_broadcast_subgroup *subgroup;
enum bt_audio_state broadcast_state;
struct bt_audio_stream *stream;
CHECKIF(source == NULL) {
BT_DBG("source is NULL");
return -EINVAL;
}
broadcast_state = broadcast_source_get_state(source);
if (broadcast_source_get_state(source) != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
BT_DBG("Broadcast source invalid state: %u",
broadcast_state);
return -EBADMSG;
}
SYS_SLIST_FOR_EACH_CONTAINER(&source->subgroups, subgroup, _node) {
SYS_SLIST_FOR_EACH_CONTAINER(&subgroup->streams, stream, _node) {
bt_audio_stream_attach(NULL, stream, stream->ep, codec);
}
}
source->qos = qos;
return 0;
}
int bt_audio_broadcast_source_start(struct bt_audio_broadcast_source *source,
struct bt_le_ext_adv *adv)
{
struct bt_iso_chan *bis[BROADCAST_STREAM_CNT];
struct bt_iso_big_create_param param = { 0 };
struct bt_audio_broadcast_subgroup *subgroup;
enum bt_audio_state broadcast_state;
struct bt_audio_stream *stream;
size_t bis_count;
int err;
CHECKIF(source == NULL) {
BT_DBG("source is NULL");
return -EINVAL;
}
broadcast_state = broadcast_source_get_state(source);
if (broadcast_source_get_state(source) != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
BT_DBG("Broadcast source invalid state: %u", broadcast_state);
return -EBADMSG;
}
bis_count = 0;
SYS_SLIST_FOR_EACH_CONTAINER(&source->subgroups, subgroup, _node) {
SYS_SLIST_FOR_EACH_CONTAINER(&subgroup->streams, stream, _node) {
bis[bis_count++] = bt_audio_stream_iso_chan_get(stream);
}
}
/* Create BIG */
param.num_bis = bis_count;
param.bis_channels = bis;
param.framing = source->qos->framing;
param.packing = 0; /* TODO: Add to QoS struct */
param.interval = source->qos->interval;
param.latency = source->qos->latency;
err = bt_iso_big_create(adv, &param, &source->big);
if (err != 0) {
BT_DBG("Failed to create BIG: %d", err);
return err;
}
SYS_SLIST_FOR_EACH_CONTAINER(&source->subgroups, subgroup, _node) {
SYS_SLIST_FOR_EACH_CONTAINER(&subgroup->streams, stream, _node) {
struct bt_audio_ep *ep = stream->ep;
broadcast_source_set_ep_state(ep, BT_AUDIO_EP_STATE_ENABLING);
}
}
return 0;
}
int bt_audio_broadcast_source_stop(struct bt_audio_broadcast_source *source)
{
enum bt_audio_state broadcast_state;
int err;
CHECKIF(source == NULL) {
BT_DBG("source is NULL");
return -EINVAL;
}
broadcast_state = broadcast_source_get_state(source);
if (broadcast_state != BT_AUDIO_EP_STATE_STREAMING &&
broadcast_state != BT_AUDIO_EP_STATE_ENABLING) {
BT_DBG("Broadcast source invalid state: %u", broadcast_state);
return -EBADMSG;
}
if (source->big == NULL) {
BT_DBG("Source is not started");
return -EALREADY;
}
err = bt_iso_big_terminate(source->big);
if (err) {
BT_DBG("Failed to terminate BIG (err %d)", err);
return err;
}
source->big = NULL;
return 0;
}
int bt_audio_broadcast_source_delete(struct bt_audio_broadcast_source *source)
{
enum bt_audio_state broadcast_state;
CHECKIF(source == NULL) {
BT_DBG("source is NULL");
return -EINVAL;
}
broadcast_state = broadcast_source_get_state(source);
if (broadcast_state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
BT_DBG("Broadcast source invalid state: %u", broadcast_state);
return -EBADMSG;
}
/* Reset the broadcast source */
broadcast_source_cleanup(source);
return 0;
}
int bt_audio_broadcast_source_get_id(const struct bt_audio_broadcast_source *source,
uint32_t *const broadcast_id)
{
CHECKIF(source == NULL) {
BT_DBG("source is NULL");
return -EINVAL;
}
CHECKIF(broadcast_id == NULL) {
BT_DBG("broadcast_id is NULL");
return -EINVAL;
}
*broadcast_id = source->broadcast_id;
return 0;
}
int bt_audio_broadcast_source_get_base(struct bt_audio_broadcast_source *source,
struct net_buf_simple *base_buf)
{
if (!encode_base(source, base_buf)) {
BT_DBG("base_buf %p with size %u not large enough",
base_buf, base_buf->size);
return -EMSGSIZE;
}
return 0;
}