| /* Bluetooth Audio Broadcast Source */ |
| |
| /* |
| * Copyright (c) 2021-2023 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> |
| #include <zephyr/bluetooth/audio/bap.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(bt_bap_broadcast_source, CONFIG_BT_BAP_BROADCAST_SOURCE_LOG_LEVEL); |
| |
| #include "bap_iso.h" |
| #include "bap_endpoint.h" |
| |
| struct bt_bap_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_bap_ep broadcast_source_eps[CONFIG_BT_BAP_BROADCAST_SRC_COUNT] |
| [BROADCAST_STREAM_CNT]; |
| static struct bt_bap_broadcast_subgroup |
| broadcast_source_subgroups[CONFIG_BT_BAP_BROADCAST_SRC_COUNT] |
| [CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT]; |
| static struct bt_bap_broadcast_source broadcast_sources[CONFIG_BT_BAP_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_bap_ep *ep, uint8_t state) |
| { |
| uint8_t old_state; |
| |
| old_state = ep->status.state; |
| |
| LOG_DBG("ep %p id 0x%02x %s -> %s", ep, ep->status.id, bt_bap_ep_state_str(old_state), |
| bt_bap_ep_state_str(state)); |
| |
| switch (old_state) { |
| case BT_BAP_EP_STATE_IDLE: |
| if (state != BT_BAP_EP_STATE_QOS_CONFIGURED) { |
| LOG_DBG("Invalid broadcast sync endpoint state transition"); |
| return; |
| } |
| break; |
| case BT_BAP_EP_STATE_QOS_CONFIGURED: |
| if (state != BT_BAP_EP_STATE_IDLE && state != BT_BAP_EP_STATE_ENABLING) { |
| LOG_DBG("Invalid broadcast sync endpoint state transition"); |
| return; |
| } |
| break; |
| case BT_BAP_EP_STATE_ENABLING: |
| if (state != BT_BAP_EP_STATE_STREAMING) { |
| LOG_DBG("Invalid broadcast sync endpoint state transition"); |
| return; |
| } |
| break; |
| case BT_BAP_EP_STATE_STREAMING: |
| if (state != BT_BAP_EP_STATE_QOS_CONFIGURED) { |
| LOG_DBG("Invalid broadcast sync endpoint state transition"); |
| return; |
| } |
| break; |
| default: |
| LOG_ERR("Invalid broadcast sync endpoint state: %s", |
| bt_bap_ep_state_str(old_state)); |
| return; |
| } |
| |
| ep->status.state = state; |
| } |
| |
| static void broadcast_source_set_state(struct bt_bap_broadcast_source *source, uint8_t state) |
| { |
| struct bt_bap_broadcast_subgroup *subgroup; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&source->subgroups, subgroup, _node) { |
| struct bt_bap_stream *stream; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&subgroup->streams, stream, _node) { |
| broadcast_source_set_ep_state(stream->ep, state); |
| } |
| } |
| } |
| |
| static void broadcast_source_iso_sent(struct bt_iso_chan *chan) |
| { |
| struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); |
| const struct bt_bap_stream_ops *ops; |
| struct bt_bap_stream *stream; |
| struct bt_bap_ep *ep = iso->tx.ep; |
| |
| if (ep == NULL) { |
| LOG_ERR("iso %p not bound with ep", chan); |
| return; |
| } |
| |
| stream = ep->stream; |
| if (stream == NULL) { |
| LOG_ERR("No stream for ep %p", ep); |
| return; |
| } |
| |
| ops = stream->ops; |
| |
| if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) { |
| LOG_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_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); |
| const struct bt_bap_stream_ops *ops; |
| struct bt_bap_stream *stream; |
| struct bt_bap_ep *ep = iso->tx.ep; |
| |
| if (ep == NULL) { |
| LOG_ERR("iso %p not bound with ep", chan); |
| return; |
| } |
| |
| stream = ep->stream; |
| if (stream == NULL) { |
| LOG_ERR("No stream for ep %p", ep); |
| return; |
| } |
| |
| ops = stream->ops; |
| |
| LOG_DBG("stream %p ep %p", stream, ep); |
| |
| broadcast_source_set_ep_state(ep, BT_BAP_EP_STATE_STREAMING); |
| |
| if (ops != NULL && ops->started != NULL) { |
| ops->started(stream); |
| } else { |
| LOG_WRN("No callback for connected set"); |
| } |
| } |
| |
| static void broadcast_source_iso_disconnected(struct bt_iso_chan *chan, uint8_t reason) |
| { |
| struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); |
| const struct bt_bap_stream_ops *ops; |
| struct bt_bap_stream *stream; |
| struct bt_bap_ep *ep = iso->tx.ep; |
| |
| if (ep == NULL) { |
| LOG_ERR("iso %p not bound with ep", chan); |
| return; |
| } |
| |
| stream = ep->stream; |
| if (stream == NULL) { |
| LOG_ERR("No stream for ep %p", ep); |
| return; |
| } |
| |
| ops = stream->ops; |
| |
| LOG_DBG("stream %p ep %p reason 0x%02x", stream, stream->ep, reason); |
| |
| broadcast_source_set_ep_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED); |
| |
| if (ops != NULL && ops->stopped != NULL) { |
| ops->stopped(stream, reason); |
| } else { |
| LOG_WRN("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_bap_ep_is_broadcast_src(const struct bt_bap_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_bap_ep *ep) |
| { |
| LOG_DBG("ep %p", ep); |
| |
| (void)memset(ep, 0, sizeof(*ep)); |
| ep->dir = BT_AUDIO_DIR_SOURCE; |
| ep->iso = NULL; |
| } |
| |
| static struct bt_bap_ep *broadcast_source_new_ep(uint8_t index) |
| { |
| for (size_t i = 0; i < ARRAY_SIZE(broadcast_source_eps[index]); i++) { |
| struct bt_bap_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_bap_broadcast_subgroup *broadcast_source_new_subgroup(uint8_t index) |
| { |
| for (size_t i = 0; i < ARRAY_SIZE(broadcast_source_subgroups[index]); i++) { |
| struct bt_bap_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_bap_stream *stream, |
| struct bt_codec *codec, struct bt_codec_qos *qos, |
| struct bt_bap_broadcast_source *source) |
| { |
| struct bt_bap_iso *iso; |
| struct bt_bap_ep *ep; |
| |
| ep = broadcast_source_new_ep(index); |
| if (ep == NULL) { |
| LOG_DBG("Could not allocate new broadcast endpoint"); |
| return -ENOMEM; |
| } |
| |
| iso = bt_bap_iso_new(); |
| if (iso == NULL) { |
| LOG_DBG("Could not allocate iso"); |
| return -ENOMEM; |
| } |
| |
| bt_bap_iso_init(iso, &broadcast_source_iso_ops); |
| bt_bap_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_bap_iso_unref(iso); |
| |
| bt_bap_stream_attach(NULL, stream, ep, codec); |
| stream->qos = qos; |
| ep->broadcast_source = source; |
| |
| return 0; |
| } |
| |
| static bool encode_base_subgroup(struct bt_bap_broadcast_subgroup *subgroup, |
| struct bt_audio_broadcast_stream_data *stream_data, |
| uint8_t *streams_encoded, struct net_buf_simple *buf) |
| { |
| struct bt_bap_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)) { |
| LOG_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)) { |
| LOG_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)) { |
| LOG_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))) { |
| LOG_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)) { |
| LOG_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)) { |
| LOG_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_bap_broadcast_source *source, struct net_buf_simple *buf) |
| { |
| struct bt_bap_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_bap_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_bap_broadcast_source *source) |
| { |
| struct bt_bap_broadcast_subgroup *subgroup, *next_subgroup; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&source->subgroups, subgroup, |
| next_subgroup, _node) { |
| struct bt_bap_stream *stream, *next_stream; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&subgroup->streams, stream, |
| next_stream, _node) { |
| bt_bap_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_bap_broadcast_source_create_param *param) |
| { |
| const struct bt_codec_qos *qos; |
| |
| CHECKIF(param == NULL) { |
| LOG_DBG("param is NULL"); |
| return false; |
| } |
| |
| CHECKIF(!IN_RANGE(param->params_count, 1U, CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT)) { |
| LOG_DBG("param->params_count %zu is invalid", param->params_count); |
| return false; |
| } |
| |
| CHECKIF(param->packing != BT_ISO_PACKING_SEQUENTIAL && |
| param->packing != BT_ISO_PACKING_INTERLEAVED) { |
| LOG_DBG("param->packing %u is invalid", param->packing); |
| return false; |
| } |
| |
| qos = param->qos; |
| CHECKIF(qos == NULL) { |
| LOG_DBG("param->qos is NULL"); |
| return false; |
| } |
| |
| CHECKIF(bt_audio_verify_qos(qos) != BT_BAP_ASCS_REASON_NONE) { |
| LOG_DBG("param->qos is invalid"); |
| return false; |
| } |
| |
| CHECKIF(param->qos->rtn > BT_ISO_BROADCAST_RTN_MAX) { |
| LOG_DBG("param->qos->rtn %u invalid", param->qos->rtn); |
| return false; |
| } |
| |
| CHECKIF(param->params == NULL) { |
| LOG_DBG("param->params is NULL"); |
| return false; |
| } |
| |
| CHECKIF(param->params_count == 0) { |
| LOG_DBG("param->params_count is 0"); |
| return false; |
| } |
| |
| for (size_t i = 0U; i < param->params_count; i++) { |
| const struct bt_bap_broadcast_source_subgroup_param *subgroup_param; |
| |
| subgroup_param = ¶m->params[i]; |
| |
| CHECKIF(subgroup_param->params == NULL) { |
| LOG_DBG("subgroup_params[%zu].params is NULL", i); |
| return false; |
| } |
| |
| CHECKIF(!IN_RANGE(subgroup_param->params_count, 1U, |
| CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT)) { |
| LOG_DBG("subgroup_params[%zu].count (%zu) is invalid", i, |
| subgroup_param->params_count); |
| return false; |
| } |
| |
| CHECKIF(!bt_audio_valid_codec(subgroup_param->codec)) { |
| LOG_DBG("subgroup_params[%zu].codec is invalid", i); |
| return false; |
| } |
| |
| for (size_t j = 0U; j < subgroup_param->params_count; j++) { |
| const struct bt_bap_broadcast_source_stream_param *stream_param; |
| |
| stream_param = &subgroup_param->params[j]; |
| |
| CHECKIF(stream_param->stream == NULL) { |
| LOG_DBG("subgroup_params[%zu].stream_params[%zu]->stream is NULL", |
| i, j); |
| return false; |
| } |
| |
| CHECKIF(stream_param->stream->group != NULL) { |
| LOG_DBG("subgroup_params[%zu].stream_params[%zu]->stream is " |
| "already part of group %p", |
| i, j, stream_param->stream->group); |
| return false; |
| } |
| |
| CHECKIF(stream_param->data == NULL && stream_param->data_count != 0) { |
| LOG_DBG("subgroup_params[%zu].stream_params[%zu]->data is " |
| "NULL with count %zu", |
| i, j, stream_param->data_count); |
| return false; |
| } |
| |
| #if CONFIG_BT_CODEC_MAX_DATA_COUNT > 0 |
| CHECKIF(stream_param->data_count > CONFIG_BT_CODEC_MAX_DATA_COUNT) { |
| LOG_DBG("subgroup_params[%zu].stream_params[%zu]->data_count too " |
| "large: %zu/%d", |
| i, j, stream_param->data_count, |
| CONFIG_BT_CODEC_MAX_DATA_COUNT); |
| return false; |
| } |
| |
| for (size_t k = 0U; k < stream_param->data_count; k++) { |
| CHECKIF(!(bt_audio_valid_codec_data(&stream_param->data[k]))) { |
| LOG_DBG("subgroup_params[%zu].stream_params[%zu]->data[%zu]" |
| " invalid", |
| i, j, k); |
| |
| return false; |
| } |
| } |
| } |
| #endif /* CONFIG_BT_CODEC_MAX_DATA_COUNT > 0 */ |
| } |
| |
| return true; |
| } |
| |
| static enum bt_bap_ep_state broadcast_source_get_state(struct bt_bap_broadcast_source *source) |
| { |
| struct bt_bap_broadcast_subgroup *subgroup; |
| struct bt_bap_stream *stream; |
| sys_snode_t *head_node; |
| |
| if (source == NULL) { |
| LOG_DBG("source is NULL"); |
| return BT_BAP_EP_STATE_IDLE; |
| } |
| |
| if (sys_slist_is_empty(&source->subgroups)) { |
| LOG_DBG("Source does not have any streams"); |
| return BT_BAP_EP_STATE_IDLE; |
| } |
| |
| /* Get the first stream */ |
| head_node = sys_slist_peek_head(&source->subgroups); |
| subgroup = CONTAINER_OF(head_node, struct bt_bap_broadcast_subgroup, _node); |
| |
| head_node = sys_slist_peek_head(&subgroup->streams); |
| stream = CONTAINER_OF(head_node, struct bt_bap_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) { |
| LOG_DBG("stream->ep is NULL"); |
| return BT_BAP_EP_STATE_IDLE; |
| } |
| |
| return stream->ep->status.state; |
| } |
| |
| int bt_bap_broadcast_source_create(struct bt_bap_broadcast_source_create_param *param, |
| struct bt_bap_broadcast_source **out_source) |
| { |
| struct bt_bap_broadcast_source *source; |
| struct bt_codec_qos *qos; |
| size_t stream_count; |
| uint8_t index; |
| int err; |
| |
| CHECKIF(out_source == NULL) { |
| LOG_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)) { |
| LOG_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) { |
| LOG_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_bap_broadcast_source_subgroup_param *subgroup_param; |
| struct bt_bap_broadcast_subgroup *subgroup; |
| |
| subgroup_param = ¶m->params[i]; |
| |
| subgroup = broadcast_source_new_subgroup(index); |
| if (subgroup == NULL) { |
| LOG_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) { |
| LOG_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_bap_broadcast_source_stream_param *stream_param; |
| struct bt_bap_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) { |
| LOG_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; |
| for (uint8_t i = 0U; i < stream_param->data_count; i++) { |
| source->stream_data[stream_count].data[i].data.data = |
| source->stream_data[stream_count].data[i].value; |
| } |
| |
| sys_slist_append(&subgroup->streams, &stream->_node); |
| stream_count++; |
| } |
| } |
| |
| err = generate_broadcast_id(source); |
| if (err != 0) { |
| LOG_DBG("Could not generate broadcast id: %d", err); |
| return err; |
| } |
| |
| /* Finalize state changes and store information */ |
| broadcast_source_set_state(source, BT_BAP_EP_STATE_QOS_CONFIGURED); |
| source->qos = qos; |
| source->packing = param->packing; |
| |
| source->encryption = param->encryption; |
| if (source->encryption) { |
| (void)memcpy(source->broadcast_code, param->broadcast_code, |
| sizeof(source->broadcast_code)); |
| } |
| |
| LOG_DBG("Broadcasting with ID 0x%6X", source->broadcast_id); |
| |
| *out_source = source; |
| |
| return 0; |
| } |
| |
| int bt_bap_broadcast_source_reconfig(struct bt_bap_broadcast_source *source, struct bt_codec *codec, |
| struct bt_codec_qos *qos) |
| { |
| struct bt_bap_broadcast_subgroup *subgroup; |
| enum bt_bap_ep_state broadcast_state; |
| struct bt_bap_stream *stream; |
| |
| CHECKIF(source == NULL) { |
| LOG_DBG("source is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(!bt_audio_valid_codec(codec)) { |
| LOG_DBG("codec is invalid"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(qos == NULL) { |
| LOG_DBG("qos is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(bt_audio_verify_qos(qos) != BT_BAP_ASCS_REASON_NONE) { |
| LOG_DBG("qos is invalid"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(qos->rtn > BT_ISO_BROADCAST_RTN_MAX) { |
| LOG_DBG("qos->rtn %u invalid", qos->rtn); |
| return -EINVAL; |
| } |
| |
| broadcast_state = broadcast_source_get_state(source); |
| if (broadcast_source_get_state(source) != BT_BAP_EP_STATE_QOS_CONFIGURED) { |
| LOG_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) { |
| struct bt_iso_chan_io_qos *iso_qos; |
| |
| iso_qos = stream->ep->iso->chan.qos->tx; |
| |
| bt_bap_stream_attach(NULL, stream, stream->ep, codec); |
| |
| bt_audio_codec_qos_to_iso_qos(iso_qos, qos); |
| bt_audio_codec_to_iso_path(iso_qos->path, codec); |
| stream->qos = qos; |
| } |
| } |
| |
| source->qos = qos; |
| |
| return 0; |
| } |
| |
| static void broadcast_source_store_metadata(struct bt_codec *codec, |
| const struct bt_codec_data meta[], |
| size_t meta_count) |
| { |
| size_t old_meta_count; |
| |
| old_meta_count = codec->meta_count; |
| |
| /* Update metadata */ |
| codec->meta_count = meta_count; |
| (void)memcpy(codec->meta, meta, meta_count * sizeof(*meta)); |
| if (old_meta_count > meta_count) { |
| size_t meta_count_diff = old_meta_count - meta_count; |
| |
| /* If we previously had more metadata entries we reset the |
| * data that was not overwritten by the new metadata |
| */ |
| (void)memset(&codec->meta[meta_count], |
| 0, meta_count_diff * sizeof(*meta)); |
| } |
| } |
| |
| int bt_bap_broadcast_source_update_metadata(struct bt_bap_broadcast_source *source, |
| const struct bt_codec_data meta[], size_t meta_count) |
| { |
| struct bt_bap_broadcast_subgroup *subgroup; |
| enum bt_bap_ep_state broadcast_state; |
| |
| CHECKIF(source == NULL) { |
| LOG_DBG("source is NULL"); |
| |
| return -EINVAL; |
| } |
| |
| CHECKIF((meta == NULL && meta_count != 0) || |
| (meta != NULL && meta_count == 0)) { |
| LOG_DBG("Invalid metadata combination: %p %zu", |
| meta, meta_count); |
| |
| return -EINVAL; |
| } |
| |
| CHECKIF(meta_count > CONFIG_BT_CODEC_MAX_METADATA_COUNT) { |
| LOG_DBG("Invalid meta_count: %zu (max %d)", |
| meta_count, CONFIG_BT_CODEC_MAX_METADATA_COUNT); |
| |
| return -EINVAL; |
| } |
| |
| for (size_t i = 0; i < meta_count; i++) { |
| CHECKIF(meta[i].data.data_len > sizeof(meta[i].value)) { |
| LOG_DBG("Invalid meta[%zu] data_len %u", |
| i, meta[i].data.data_len); |
| |
| return -EINVAL; |
| } |
| } |
| broadcast_state = broadcast_source_get_state(source); |
| if (broadcast_source_get_state(source) != BT_BAP_EP_STATE_STREAMING) { |
| LOG_DBG("Broadcast source invalid state: %u", broadcast_state); |
| |
| return -EBADMSG; |
| } |
| |
| /* TODO: We should probably find a way to update the metadata |
| * for each subgroup individually |
| */ |
| SYS_SLIST_FOR_EACH_CONTAINER(&source->subgroups, subgroup, _node) { |
| broadcast_source_store_metadata(subgroup->codec, meta, meta_count); |
| } |
| |
| return 0; |
| } |
| |
| int bt_bap_broadcast_source_start(struct bt_bap_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_bap_broadcast_subgroup *subgroup; |
| enum bt_bap_ep_state broadcast_state; |
| struct bt_bap_stream *stream; |
| size_t bis_count; |
| int err; |
| |
| CHECKIF(source == NULL) { |
| LOG_DBG("source is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(adv == NULL) { |
| LOG_DBG("adv is NULL"); |
| return -EINVAL; |
| } |
| |
| broadcast_state = broadcast_source_get_state(source); |
| if (broadcast_source_get_state(source) != BT_BAP_EP_STATE_QOS_CONFIGURED) { |
| LOG_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_bap_stream_iso_chan_get(stream); |
| } |
| } |
| |
| /* Create BIG */ |
| param.num_bis = bis_count; |
| param.bis_channels = bis; |
| param.framing = source->qos->framing; |
| param.packing = source->packing; |
| param.interval = source->qos->interval; |
| param.latency = source->qos->latency; |
| param.encryption = source->encryption; |
| if (param.encryption) { |
| (void)memcpy(param.bcode, source->broadcast_code, |
| sizeof(param.bcode)); |
| } |
| |
| err = bt_iso_big_create(adv, ¶m, &source->big); |
| if (err != 0) { |
| LOG_DBG("Failed to create BIG: %d", err); |
| return err; |
| } |
| |
| broadcast_source_set_state(source, BT_BAP_EP_STATE_ENABLING); |
| |
| return 0; |
| } |
| |
| int bt_bap_broadcast_source_stop(struct bt_bap_broadcast_source *source) |
| { |
| enum bt_bap_ep_state broadcast_state; |
| int err; |
| |
| CHECKIF(source == NULL) { |
| LOG_DBG("source is NULL"); |
| return -EINVAL; |
| } |
| |
| broadcast_state = broadcast_source_get_state(source); |
| if (broadcast_state != BT_BAP_EP_STATE_STREAMING && |
| broadcast_state != BT_BAP_EP_STATE_ENABLING) { |
| LOG_DBG("Broadcast source invalid state: %u", broadcast_state); |
| return -EBADMSG; |
| } |
| |
| if (source->big == NULL) { |
| LOG_DBG("Source is not started"); |
| return -EALREADY; |
| } |
| |
| err = bt_iso_big_terminate(source->big); |
| if (err) { |
| LOG_DBG("Failed to terminate BIG (err %d)", err); |
| return err; |
| } |
| |
| source->big = NULL; |
| |
| return 0; |
| } |
| |
| int bt_bap_broadcast_source_delete(struct bt_bap_broadcast_source *source) |
| { |
| enum bt_bap_ep_state broadcast_state; |
| |
| CHECKIF(source == NULL) { |
| LOG_DBG("source is NULL"); |
| return -EINVAL; |
| } |
| |
| broadcast_state = broadcast_source_get_state(source); |
| if (broadcast_state != BT_BAP_EP_STATE_QOS_CONFIGURED) { |
| LOG_DBG("Broadcast source invalid state: %u", broadcast_state); |
| return -EBADMSG; |
| } |
| |
| broadcast_source_set_state(source, BT_BAP_EP_STATE_IDLE); |
| |
| /* Reset the broadcast source */ |
| broadcast_source_cleanup(source); |
| |
| return 0; |
| } |
| |
| int bt_bap_broadcast_source_get_id(const struct bt_bap_broadcast_source *source, |
| uint32_t *const broadcast_id) |
| { |
| CHECKIF(source == NULL) { |
| LOG_DBG("source is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(broadcast_id == NULL) { |
| LOG_DBG("broadcast_id is NULL"); |
| return -EINVAL; |
| } |
| |
| *broadcast_id = source->broadcast_id; |
| |
| return 0; |
| } |
| |
| int bt_bap_broadcast_source_get_base(struct bt_bap_broadcast_source *source, |
| struct net_buf_simple *base_buf) |
| { |
| CHECKIF(source == NULL) { |
| LOG_DBG("source is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(base_buf == NULL) { |
| LOG_DBG("base_buf is NULL"); |
| return -EINVAL; |
| } |
| |
| if (!encode_base(source, base_buf)) { |
| LOG_DBG("base_buf %p with size %u not large enough", base_buf, base_buf->size); |
| |
| return -EMSGSIZE; |
| } |
| |
| return 0; |
| } |