/* Bluetooth Audio Broadcast Sink */ /* * Copyright (c) 2021-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../host/conn_internal.h" #include "../host/iso_internal.h" #include "audio_internal.h" #include "bap_iso.h" #include "bap_endpoint.h" LOG_MODULE_REGISTER(bt_bap_broadcast_sink, CONFIG_BT_BAP_BROADCAST_SINK_LOG_LEVEL); #include "common/bt_str.h" #define PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO 20 /* Set the timeout relative to interval */ #define BROADCAST_SYNC_MIN_INDEX (BIT(1)) static struct bt_bap_ep broadcast_sink_eps[CONFIG_BT_BAP_BROADCAST_SNK_COUNT] [CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT]; static struct bt_bap_broadcast_sink broadcast_sinks[CONFIG_BT_BAP_BROADCAST_SNK_COUNT]; struct codec_cap_lookup_id_data { uint8_t id; const struct bt_audio_codec_cap *codec_cap; }; static sys_slist_t sink_cbs = SYS_SLIST_STATIC_INIT(&sink_cbs); static void broadcast_sink_cleanup(struct bt_bap_broadcast_sink *sink); static bool find_recv_state_by_sink_cb(const struct bt_bap_scan_delegator_recv_state *recv_state, void *user_data) { const struct bt_bap_broadcast_sink *sink = user_data; if (atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID) && sink->bass_src_id == recv_state->src_id) { return true; } return false; } static bool find_recv_state_by_pa_sync_cb(const struct bt_bap_scan_delegator_recv_state *recv_state, void *user_data) { struct bt_le_per_adv_sync *sync = user_data; struct bt_le_per_adv_sync_info sync_info; int err; err = bt_le_per_adv_sync_get_info(sync, &sync_info); if (err != 0) { LOG_DBG("Failed to get sync info: %d", err); return false; } if (bt_addr_le_eq(&recv_state->addr, &sync_info.addr) && recv_state->adv_sid == sync_info.sid) { return true; } return false; }; static void update_recv_state_big_synced(const struct bt_bap_broadcast_sink *sink) { const struct bt_bap_scan_delegator_recv_state *recv_state; struct bt_bap_scan_delegator_mod_src_param mod_src_param = {0}; int err; recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink); if (recv_state == NULL) { LOG_WRN("Failed to find receive state for sink %p", sink); return; } mod_src_param.num_subgroups = sink->subgroup_count; for (uint8_t i = 0U; i < sink->subgroup_count; i++) { struct bt_bap_bass_subgroup *subgroup_param = &mod_src_param.subgroups[i]; const struct bt_bap_broadcast_sink_subgroup *sink_subgroup = &sink->subgroups[i]; /* Set the bis_sync value to the indexes available per subgroup */ subgroup_param->bis_sync = sink_subgroup->bis_indexes & sink->indexes_bitfield; } if (recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_BCODE_REQ) { mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_DEC; } else { mod_src_param.encrypt_state = recv_state->encrypt_state; } /* Since the mod_src_param struct is 0-initialized the metadata won't * be modified by this */ /* Copy existing unchanged data */ mod_src_param.src_id = recv_state->src_id; mod_src_param.broadcast_id = recv_state->broadcast_id; err = bt_bap_scan_delegator_mod_src(&mod_src_param); if (err != 0) { LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err); } } static void update_recv_state_big_cleared(const struct bt_bap_broadcast_sink *sink, uint8_t reason) { struct bt_bap_scan_delegator_mod_src_param mod_src_param = { 0 }; const struct bt_bap_scan_delegator_recv_state *recv_state; int err; recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink); if (recv_state == NULL) { /* This is likely due to the receive state being removed while we are BIG synced */ LOG_DBG("Could not find receive state for sink %p", sink); return; } if ((recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_BCODE_REQ || recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_DEC) && reason == BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL) { /* Sync failed due to bad broadcast code */ mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_BAD_CODE; } else { mod_src_param.encrypt_state = recv_state->encrypt_state; } if (reason != BT_HCI_ERR_LOCALHOST_TERM_CONN) { for (uint8_t i = 0U; i < recv_state->num_subgroups; i++) { mod_src_param.subgroups[i].bis_sync = BT_BAP_BIS_SYNC_FAILED; } } /* Since the metadata_len is 0 then the metadata won't be modified by the operation either*/ /* Copy existing unchanged data */ mod_src_param.num_subgroups = recv_state->num_subgroups; mod_src_param.src_id = recv_state->src_id; mod_src_param.broadcast_id = recv_state->broadcast_id; err = bt_bap_scan_delegator_mod_src(&mod_src_param); if (err != 0) { LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err); } } static void broadcast_sink_clear_big(struct bt_bap_broadcast_sink *sink, uint8_t reason) { sink->big = NULL; update_recv_state_big_cleared(sink, reason); } static struct bt_bap_broadcast_sink *broadcast_sink_lookup_iso_chan( const struct bt_iso_chan *chan) { for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sinks); i++) { for (uint8_t j = 0U; j < broadcast_sinks[i].stream_count; j++) { if (broadcast_sinks[i].bis[j].chan == chan) { return &broadcast_sinks[i]; } } } return NULL; } static void broadcast_sink_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_STREAMING) { LOG_DBG("Invalid broadcast sync endpoint state transition"); return; } break; case BT_BAP_EP_STATE_STREAMING: if (state != BT_BAP_EP_STATE_IDLE) { 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; if (state == BT_BAP_EP_STATE_IDLE) { struct bt_bap_stream *stream = ep->stream; if (stream != NULL) { bt_bap_iso_unbind_ep(ep->iso, ep); stream->ep = NULL; stream->codec_cfg = NULL; ep->stream = NULL; } } } static void broadcast_sink_iso_recv(struct bt_iso_chan *chan, const struct bt_iso_recv_info *info, struct net_buf *buf) { 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->rx.ep; size_t buf_len; 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; buf_len = net_buf_frags_len(buf); if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) { LOG_DBG("stream %p ep %p len %zu", stream, stream->ep, buf_len); } if (buf_len > stream->qos->sdu) { LOG_WRN("Received %u octets but stream %p was only configured for %u", buf_len, stream, stream->qos->sdu); } if (ops != NULL && ops->recv != NULL) { ops->recv(stream, info, buf); } else { LOG_WRN("No callback for recv set"); } } static bool broadcast_sink_is_in_state(struct bt_bap_broadcast_sink *sink, enum bt_bap_ep_state state) { struct bt_bap_stream *stream; if (sink == NULL) { LOG_DBG("sink is NULL"); return state == BT_BAP_EP_STATE_IDLE; } if (sys_slist_is_empty(&sink->streams)) { LOG_DBG("Sink does not have any streams"); return state == BT_BAP_EP_STATE_IDLE; } SYS_SLIST_FOR_EACH_CONTAINER(&sink->streams, stream, _node) { if (stream->ep != NULL && stream->ep->status.state != state) { return false; } } return true; } static void broadcast_sink_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_broadcast_sink *sink; struct bt_bap_stream *stream; struct bt_bap_ep *ep = iso->rx.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; } LOG_DBG("stream %p", stream); ops = stream->ops; if (ops != NULL && ops->connected != NULL) { ops->connected(stream); } sink = broadcast_sink_lookup_iso_chan(chan); if (sink == NULL) { LOG_ERR("Could not lookup sink by iso %p", chan); return; } broadcast_sink_set_ep_state(ep, BT_BAP_EP_STATE_STREAMING); if (ops != NULL && ops->started != NULL) { ops->started(stream); } else { LOG_WRN("No callback for started set"); } if (broadcast_sink_is_in_state(sink, BT_BAP_EP_STATE_STREAMING)) { update_recv_state_big_synced(sink); } } static void broadcast_sink_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->rx.ep; struct bt_bap_broadcast_sink *sink; 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; } LOG_DBG("stream %p ep %p reason 0x%02x", stream, ep, reason); ops = stream->ops; if (ops != NULL && ops->disconnected != NULL) { ops->disconnected(stream, reason); } broadcast_sink_set_ep_state(ep, BT_BAP_EP_STATE_IDLE); sink = broadcast_sink_lookup_iso_chan(chan); if (sink == NULL) { LOG_ERR("Could not lookup sink by iso %p", chan); } else { if (!sys_slist_find_and_remove(&sink->streams, &stream->_node)) { LOG_DBG("Could not find and remove stream %p from sink %p", stream, sink); } } 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_sink_iso_ops = { .recv = broadcast_sink_iso_recv, .connected = broadcast_sink_iso_connected, .disconnected = broadcast_sink_iso_disconnected, }; static struct bt_bap_broadcast_sink *broadcast_sink_free_get(void) { /* Find free entry */ for (int i = 0; i < ARRAY_SIZE(broadcast_sinks); i++) { if (!atomic_test_bit(broadcast_sinks[i].flags, BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED)) { broadcast_sinks[i].index = i; broadcast_sinks[i].broadcast_id = BT_BAP_INVALID_BROADCAST_ID; return &broadcast_sinks[i]; } } return NULL; } static struct bt_bap_broadcast_sink *broadcast_sink_get_by_pa(struct bt_le_per_adv_sync *sync) { for (int i = 0; i < ARRAY_SIZE(broadcast_sinks); i++) { if (broadcast_sinks[i].pa_sync == sync) { return &broadcast_sinks[i]; } } return NULL; } static struct bt_bap_broadcast_sink *broadcast_sink_get_by_big(const struct bt_iso_big *big) { for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sinks); i++) { if (broadcast_sinks[i].big == big) { return &broadcast_sinks[i]; } } return NULL; } static void broadcast_sink_add_src(struct bt_bap_broadcast_sink *sink) { struct bt_bap_scan_delegator_add_src_param add_src_param; struct bt_le_per_adv_sync_info sync_info; int err; err = bt_le_per_adv_sync_get_info(sink->pa_sync, &sync_info); __ASSERT_NO_MSG(err == 0); bt_addr_le_copy(&add_src_param.addr, &sync_info.addr); add_src_param.sid = sync_info.sid; add_src_param.broadcast_id = sink->broadcast_id; /* Will be updated when we receive the BASE */ add_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC; add_src_param.num_subgroups = 0U; err = bt_bap_scan_delegator_add_src(&add_src_param); if (err < 0) { LOG_WRN("Failed to add sync as Receive State for sink %p: %d", sink, err); } else { sink->bass_src_id = (uint8_t)err; atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID); } } static bool base_subgroup_meta_cb(const struct bt_bap_base_subgroup *subgroup, void *user_data) { struct bt_bap_scan_delegator_mod_src_param *mod_src_param = user_data; struct bt_bap_bass_subgroup *subgroup_param; uint8_t *meta; int ret; ret = bt_bap_base_get_subgroup_codec_meta(subgroup, &meta); if (ret < 0) { return false; } subgroup_param = &mod_src_param->subgroups[mod_src_param->num_subgroups++]; subgroup_param->metadata_len = (uint8_t)ret; memcpy(subgroup_param->metadata, meta, subgroup_param->metadata_len); return true; } static int update_recv_state_base_copy_meta(const struct bt_bap_base *base, struct bt_bap_scan_delegator_mod_src_param *param) { int err; err = bt_bap_base_foreach_subgroup(base, base_subgroup_meta_cb, param); if (err != 0) { LOG_DBG("Failed to parse subgroups: %d", err); return err; } return 0; } static void update_recv_state_base(const struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base) { struct bt_bap_scan_delegator_mod_src_param mod_src_param = { 0 }; const struct bt_bap_scan_delegator_recv_state *recv_state; int err; recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink); if (recv_state == NULL) { LOG_WRN("Failed to find receive state for sink %p", sink); return; } err = update_recv_state_base_copy_meta(base, &mod_src_param); if (err != 0) { LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err); return; } /* Copy existing unchanged data */ mod_src_param.src_id = recv_state->src_id; mod_src_param.encrypt_state = recv_state->encrypt_state; mod_src_param.broadcast_id = recv_state->broadcast_id; mod_src_param.num_subgroups = sink->subgroup_count; for (uint8_t i = 0U; i < sink->subgroup_count; i++) { struct bt_bap_bass_subgroup *subgroup_param = &mod_src_param.subgroups[i]; /* Leave the bis_sync unchanged */ subgroup_param->bis_sync = recv_state->subgroups[i].bis_sync; } err = bt_bap_scan_delegator_mod_src(&mod_src_param); if (err != 0) { LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err); } } static bool codec_lookup_id(const struct bt_pacs_cap *cap, void *user_data) { struct codec_cap_lookup_id_data *data = user_data; if (cap->codec_cap->id == data->id) { data->codec_cap = cap->codec_cap; return false; } return true; } struct store_base_info_data { struct bt_bap_broadcast_sink_bis bis[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT]; struct bt_bap_broadcast_sink_subgroup subgroups[CONFIG_BT_BAP_BROADCAST_SNK_SUBGROUP_COUNT]; struct bt_audio_codec_cfg *subgroup_codec_cfg; uint32_t valid_indexes_bitfield; uint8_t subgroup_count; uint8_t bis_count; }; static bool merge_bis_and_subgroup_data_cb(struct bt_data *data, void *user_data) { struct bt_audio_codec_cfg *codec_cfg = user_data; int err; err = bt_audio_codec_cfg_set_val(codec_cfg, data->type, data->data, data->data_len); if (err < 0) { LOG_DBG("Failed to set type %u with len %u in codec_cfg: %d", data->type, data->data_len, err); return false; } return true; } static bool base_subgroup_bis_index_cb(const struct bt_bap_base_subgroup_bis *bis, void *user_data) { struct bt_bap_broadcast_sink_subgroup *sink_subgroup; struct store_base_info_data *data = user_data; struct bt_bap_broadcast_sink_bis *sink_bis; if (data->bis_count == ARRAY_SIZE(data->bis)) { /* We've parsed as many subgroups as we support */ LOG_DBG("Could only store %u BIS", data->bis_count); return false; } sink_bis = &data->bis[data->bis_count]; sink_subgroup = &data->subgroups[data->subgroup_count]; sink_bis->index = bis->index; sink_subgroup->bis_indexes |= BT_ISO_BIS_INDEX_BIT(bis->index); #if CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 memcpy(&sink_bis->codec_cfg, data->subgroup_codec_cfg, sizeof(sink_bis->codec_cfg)); if (bis->data_len > 0) { /* Merge subgroup codec configuration with the BIS configuration * As per the BAP spec, if a value exist at level 2 (subgroup) and 3 (BIS), then it * is the value at level 3 that shall be used */ if (sink_bis->codec_cfg.id == BT_HCI_CODING_FORMAT_LC3) { int err; memcpy(&sink_bis->codec_cfg, data->subgroup_codec_cfg, sizeof(sink_bis->codec_cfg)); err = bt_audio_data_parse(bis->data, bis->data_len, merge_bis_and_subgroup_data_cb, &sink_bis->codec_cfg); if (err != 0) { LOG_DBG("Could not merge BIS and subgroup config in codec_cfg: %d", err); return false; } } else { /* If it is not LC3, then we don't know how to merge the subgroup and BIS * codecs, so we just append them */ if (sink_bis->codec_cfg.data_len + bis->data_len > sizeof(sink_bis->codec_cfg.data)) { LOG_DBG("Could not store BIS and subgroup config in codec_cfg (%u " "> %u)", sink_bis->codec_cfg.data_len + bis->data_len, sizeof(sink_bis->codec_cfg.data)); return false; } memcpy(&sink_bis->codec_cfg.data[sink_bis->codec_cfg.data_len], bis->data, bis->data_len); sink_bis->codec_cfg.data_len += bis->data_len; } } #endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 */ data->bis_count++; return true; } static bool base_subgroup_cb(const struct bt_bap_base_subgroup *subgroup, void *user_data) { struct bt_bap_broadcast_sink_subgroup *sink_subgroup; struct codec_cap_lookup_id_data lookup_data = {0}; struct store_base_info_data *data = user_data; struct bt_audio_codec_cfg codec_cfg; int ret; if (data->subgroup_count == ARRAY_SIZE(data->subgroups)) { /* We've parsed as many subgroups as we support */ LOG_DBG("Could only store %u subgroups", data->subgroup_count); return false; } sink_subgroup = &data->subgroups[data->subgroup_count]; ret = bt_bap_base_subgroup_codec_to_codec_cfg(subgroup, &codec_cfg); if (ret < 0) { LOG_DBG("Could not store codec_cfg: %d", ret); return false; } /* Lookup and assign path_id based on capabilities */ lookup_data.id = codec_cfg.id; bt_pacs_cap_foreach(BT_AUDIO_DIR_SINK, codec_lookup_id, &lookup_data); if (lookup_data.codec_cap == NULL) { LOG_DBG("Codec with id %u is not supported by our capabilities", lookup_data.id); } else { codec_cfg.path_id = lookup_data.codec_cap->path_id; codec_cfg.ctlr_transcode = lookup_data.codec_cap->ctlr_transcode; data->subgroup_codec_cfg = &codec_cfg; ret = bt_bap_base_subgroup_foreach_bis(subgroup, base_subgroup_bis_index_cb, data); if (ret < 0) { LOG_DBG("Could not parse BISes: %d", ret); return false; } /* Add BIS to bitfield of valid BIS indexes we support */ data->valid_indexes_bitfield |= sink_subgroup->bis_indexes; data->subgroup_count++; } return true; } static int store_base_info(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base) { /* data is static due to its size, which easily can exceed the stack size */ static struct store_base_info_data data; uint32_t pres_delay; int ret; ret = bt_bap_base_get_pres_delay(base); if (ret < 0) { LOG_DBG("Could not get presentation delay: %d", ret); return ret; } pres_delay = (uint32_t)ret; memset(&data, 0, sizeof(data)); ret = bt_bap_base_foreach_subgroup(base, base_subgroup_cb, &data); if (ret != 0) { LOG_DBG("Failed to parse all subgroups: %d", ret); return ret; } /* Ensure that we have not synced while parsing the BASE */ if (sink->big == NULL) { sink->qos_cfg.pd = pres_delay; memcpy(sink->bis, data.bis, sizeof(sink->bis)); memcpy(sink->subgroups, data.subgroups, sizeof(sink->subgroups)); sink->subgroup_count = data.subgroup_count; sink->valid_indexes_bitfield = data.valid_indexes_bitfield; } return 0; } static bool base_subgroup_bis_count_cb(const struct bt_bap_base_subgroup *subgroup, void *user_data) { uint8_t *bis_cnt = user_data; int ret; ret = bt_bap_base_get_subgroup_bis_count(subgroup); if (ret < 0) { return false; } *bis_cnt += (uint8_t)ret; return true; } static int base_get_bis_count(const struct bt_bap_base *base) { uint8_t bis_cnt = 0U; int err; err = bt_bap_base_foreach_subgroup(base, base_subgroup_bis_count_cb, &bis_cnt); if (err != 0) { LOG_DBG("Failed to parse subgroups: %d", err); return err; } return bis_cnt; } static bool pa_decode_base(struct bt_data *data, void *user_data) { struct bt_bap_broadcast_sink *sink = (struct bt_bap_broadcast_sink *)user_data; const struct bt_bap_base *base = bt_bap_base_get_base_from_ad(data); struct bt_bap_broadcast_sink_cb *listener; int base_size; int ret; /* Base is NULL if the data does not contain a valid BASE */ if (base == NULL) { return true; } if (atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED)) { ret = base_get_bis_count(base); if (ret < 0) { LOG_DBG("Invalid BASE: %d", ret); return false; } else if (ret != sink->biginfo_num_bis) { LOG_DBG("BASE contains different amount of BIS (%u) than reported by " "BIGInfo (%u)", ret, sink->biginfo_num_bis); return false; } } /* Store newest BASE info until we are BIG synced */ if (sink->big == NULL) { LOG_DBG("Updating BASE for sink %p with %d subgroups\n", sink, bt_bap_base_get_subgroup_count(base)); ret = store_base_info(sink, base); if (ret < 0) { LOG_DBG("Could not store BASE information: %d", ret); /* If it returns -ECANCELED it means that we stopped parsing ourselves due * to lack of memory. In this case we can still provide the BASE to the * application else abort */ if (ret != -ECANCELED) { return false; } } } if (atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID)) { update_recv_state_base(sink, base); } /* We provide the BASE without the service data UUID */ base_size = bt_bap_base_get_size(base); if (base_size < 0) { LOG_DBG("BASE get size failed (%d)", base_size); return false; } SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { if (listener->base_recv != NULL) { listener->base_recv(sink, base, (size_t)base_size); } } return false; } static void pa_recv(struct bt_le_per_adv_sync *sync, const struct bt_le_per_adv_sync_recv_info *info, struct net_buf_simple *buf) { struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_pa(sync); if (sink == NULL) { /* Not a PA sync that we control */ return; } if (sys_slist_is_empty(&sink_cbs)) { /* Terminate early if we do not have any broadcast sink listeners */ return; } bt_data_parse(buf, pa_decode_base, (void *)sink); } static void pa_term_cb(struct bt_le_per_adv_sync *sync, const struct bt_le_per_adv_sync_term_info *info) { struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_pa(sync); if (sink != NULL) { sink->pa_sync = NULL; } } static void update_recv_state_encryption(const struct bt_bap_broadcast_sink *sink) { struct bt_bap_scan_delegator_mod_src_param mod_src_param = { 0 }; const struct bt_bap_scan_delegator_recv_state *recv_state; int err; __ASSERT(sink->big == NULL, "Encryption state shall not be updated while synced"); recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_sink_cb, (void *)sink); if (recv_state == NULL) { LOG_WRN("Failed to find receive state for sink %p", sink); return; } /* Only change the encrypt state, and leave the rest as is */ if (atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED)) { mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_BCODE_REQ; } else { mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC; } if (mod_src_param.encrypt_state == recv_state->encrypt_state) { /* No change, abort*/ return; } /* Copy existing data */ /* TODO: Maybe we need more refined functions to set only specific fields? */ mod_src_param.src_id = recv_state->src_id; mod_src_param.broadcast_id = recv_state->broadcast_id; mod_src_param.num_subgroups = recv_state->num_subgroups; (void)memcpy(mod_src_param.subgroups, recv_state->subgroups, sizeof(recv_state->num_subgroups)); err = bt_bap_scan_delegator_mod_src(&mod_src_param); if (err != 0) { LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err); } } static void biginfo_recv(struct bt_le_per_adv_sync *sync, const struct bt_iso_biginfo *biginfo) { struct bt_bap_broadcast_sink_cb *listener; struct bt_bap_broadcast_sink *sink; sink = broadcast_sink_get_by_pa(sync); if (sink == NULL) { /* Not ours */ return; } if (sink->big != NULL) { /* Already synced - ignore */ return; } atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED); sink->iso_interval = biginfo->iso_interval; sink->biginfo_num_bis = biginfo->num_bis; if (biginfo->encryption != atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED)) { atomic_set_bit_to(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED, biginfo->encryption); if (atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID)) { update_recv_state_encryption(sink); } } sink->qos_cfg.framing = biginfo->framing; sink->qos_cfg.phy = biginfo->phy; sink->qos_cfg.sdu = biginfo->max_sdu; sink->qos_cfg.interval = biginfo->sdu_interval; SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { if (listener->syncable != NULL) { listener->syncable(sink, biginfo); } } } static uint16_t interval_to_sync_timeout(uint16_t interval) { uint32_t interval_us; uint32_t timeout; /* Add retries and convert to unit in 10's of ms */ interval_us = BT_GAP_PER_ADV_INTERVAL_TO_US(interval); timeout = BT_GAP_US_TO_PER_ADV_SYNC_TIMEOUT(interval_us) * PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO; /* Enforce restraints */ timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, BT_GAP_PER_ADV_MAX_TIMEOUT); return (uint16_t)timeout; } static void big_started_cb(struct bt_iso_big *big) { struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_big(big); struct bt_bap_broadcast_sink_cb *listener; if (sink == NULL) { /* Not one of ours */ return; } SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { if (listener->started != NULL) { listener->started(sink); } } } static void big_stopped_cb(struct bt_iso_big *big, uint8_t reason) { struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_big(big); struct bt_bap_broadcast_sink_cb *listener; if (sink == NULL) { /* Not one of ours */ return; } broadcast_sink_clear_big(sink, reason); SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { if (listener->stopped != NULL) { listener->stopped(sink, reason); } } } int bt_bap_broadcast_sink_register_cb(struct bt_bap_broadcast_sink_cb *cb) { static bool iso_big_cb_registered; CHECKIF(cb == NULL) { LOG_DBG("cb is NULL"); return -EINVAL; } if (sys_slist_find(&sink_cbs, &cb->_node, NULL)) { LOG_DBG("cb %p is already registered", cb); return -EEXIST; } if (!iso_big_cb_registered) { static struct bt_iso_big_cb big_cb = { .started = big_started_cb, .stopped = big_stopped_cb, }; const int err = bt_iso_big_register_cb(&big_cb); if (err != 0) { __ASSERT(false, "Failed to register ISO BIG callbacks: %d", err); } iso_big_cb_registered = true; } sys_slist_append(&sink_cbs, &cb->_node); return 0; } bool bt_bap_ep_is_broadcast_snk(const struct bt_bap_ep *ep) { for (int i = 0; i < ARRAY_SIZE(broadcast_sink_eps); i++) { if (PART_OF_ARRAY(broadcast_sink_eps[i], ep)) { return true; } } return false; } static void broadcast_sink_ep_init(struct bt_bap_ep *ep) { LOG_DBG("ep %p", ep); (void)memset(ep, 0, sizeof(*ep)); ep->dir = BT_AUDIO_DIR_SINK; ep->iso = NULL; } static struct bt_bap_ep *broadcast_sink_new_ep(uint8_t index) { for (size_t i = 0; i < ARRAY_SIZE(broadcast_sink_eps[index]); i++) { struct bt_bap_ep *ep = &broadcast_sink_eps[index][i]; /* If ep->stream is NULL the endpoint is unallocated */ if (ep->stream == NULL) { broadcast_sink_ep_init(ep); return ep; } } return NULL; } static int bt_bap_broadcast_sink_setup_stream(struct bt_bap_broadcast_sink *sink, struct bt_bap_stream *stream, struct bt_audio_codec_cfg *codec_cfg) { struct bt_bap_iso *iso; struct bt_bap_ep *ep; if (stream->group != NULL) { LOG_DBG("Stream %p already in group %p", stream, stream->group); return -EALREADY; } ep = broadcast_sink_new_ep(sink->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_sink_iso_ops); bt_bap_iso_bind_ep(iso, ep); bt_bap_qos_cfg_to_iso_qos(iso->chan.qos->rx, &sink->qos_cfg); bt_bap_iso_configure_data_path(ep, codec_cfg); bt_bap_iso_unref(iso); bt_bap_stream_attach(NULL, stream, ep, codec_cfg); stream->qos = &sink->qos_cfg; return 0; } static void broadcast_sink_cleanup_streams(struct bt_bap_broadcast_sink *sink) { struct bt_bap_stream *stream, *next; SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&sink->streams, stream, next, _node) { if (stream->ep != NULL) { bt_bap_iso_unbind_ep(stream->ep->iso, stream->ep); stream->ep->stream = NULL; stream->ep = NULL; } stream->qos = NULL; stream->codec_cfg = NULL; stream->group = NULL; sys_slist_remove(&sink->streams, NULL, &stream->_node); } sink->stream_count = 0; sink->indexes_bitfield = 0U; } static void broadcast_sink_cleanup(struct bt_bap_broadcast_sink *sink) { if (sink->stream_count > 0U) { broadcast_sink_cleanup_streams(sink); } (void)memset(sink, 0, sizeof(*sink)); /* also clears flags */ } static struct bt_audio_codec_cfg *codec_cfg_from_base_by_index(struct bt_bap_broadcast_sink *sink, uint8_t index) { for (size_t i = 0U; i < ARRAY_SIZE(sink->bis); i++) { struct bt_bap_broadcast_sink_bis *bis = &sink->bis[i]; if (bis->index == index) { return &bis->codec_cfg; } else if (bis->index == 0) { /* index 0 is invalid, so we can use that as a terminator in the array */ break; } } return NULL; } int bt_bap_broadcast_sink_create(struct bt_le_per_adv_sync *pa_sync, uint32_t broadcast_id, struct bt_bap_broadcast_sink **out_sink) { const struct bt_bap_scan_delegator_recv_state *recv_state; struct bt_bap_broadcast_sink *sink; CHECKIF(pa_sync == NULL) { LOG_DBG("pa_sync is NULL"); return -EINVAL; } CHECKIF(broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) { LOG_DBG("Invalid broadcast_id: 0x%X", broadcast_id); return -EINVAL; } CHECKIF(out_sink == NULL) { LOG_DBG("sink was NULL"); return -EINVAL; } sink = broadcast_sink_free_get(); if (sink == NULL) { LOG_DBG("No more free broadcast sinks"); return -ENOMEM; } sink->broadcast_id = broadcast_id; sink->pa_sync = pa_sync; recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_pa_sync_cb, (void *)pa_sync); if (recv_state == NULL) { broadcast_sink_add_src(sink); } else { /* The PA sync is known by the Scan Delegator */ if (recv_state->broadcast_id != broadcast_id) { LOG_DBG("Broadcast ID mismatch: 0x%X != 0x%X", recv_state->broadcast_id, broadcast_id); broadcast_sink_cleanup(sink); return -EINVAL; } sink->bass_src_id = recv_state->src_id; atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID); } atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED); *out_sink = sink; return 0; } int bt_bap_broadcast_sink_sync(struct bt_bap_broadcast_sink *sink, uint32_t indexes_bitfield, struct bt_bap_stream *streams[], const uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]) { struct bt_iso_big_sync_param param; struct bt_audio_codec_cfg *codec_cfgs[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT] = {NULL}; struct bt_iso_chan *bis_channels[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT]; uint8_t stream_count; int err; CHECKIF(sink == NULL) { LOG_DBG("sink is NULL"); return -EINVAL; } CHECKIF(indexes_bitfield == 0U || indexes_bitfield > BIT_MASK(BT_ISO_BIS_INDEX_MAX)) { LOG_DBG("Invalid indexes_bitfield: 0x%08X", indexes_bitfield); return -EINVAL; } CHECKIF(streams == NULL) { LOG_DBG("streams is NULL"); return -EINVAL; } if (sink->pa_sync == NULL) { LOG_DBG("Sink is not PA synced"); return -EINVAL; } if (!atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED)) { /* TODO: We could store the request to sync and start the sync * once the BIGInfo has been received, and then do the sync * then. This would be similar how LE Create Connection works. */ LOG_DBG("BIGInfo not received, cannot sync yet"); return -EAGAIN; } if (atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED) && broadcast_code == NULL) { LOG_DBG("Broadcast code required"); return -EINVAL; } /* Validate that number of bits set is less than number of streams */ if ((indexes_bitfield & sink->valid_indexes_bitfield) != indexes_bitfield) { LOG_DBG("Request BIS indexes 0x%08X contains bits not support by the Broadcast " "Sink 0x%08X", indexes_bitfield, sink->valid_indexes_bitfield); return -EINVAL; } stream_count = 0; for (int i = 1; i < BT_ISO_MAX_GROUP_ISO_COUNT; i++) { if ((indexes_bitfield & BT_ISO_BIS_INDEX_BIT(i)) != 0) { struct bt_audio_codec_cfg *codec_cfg = codec_cfg_from_base_by_index(sink, i); __ASSERT(codec_cfg != NULL, "Index %d not found in sink", i); codec_cfgs[stream_count++] = codec_cfg; if (stream_count > CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT) { LOG_DBG("Cannot sync to more than %d streams (%u was requested)", CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT, stream_count); return -EINVAL; } } } for (size_t i = 0; i < stream_count; i++) { CHECKIF(streams[i] == NULL) { LOG_DBG("streams[%zu] is NULL", i); return -EINVAL; } } sink->stream_count = 0U; for (size_t i = 0; i < stream_count; i++) { struct bt_bap_stream *stream; struct bt_audio_codec_cfg *codec_cfg; stream = streams[i]; codec_cfg = codec_cfgs[i]; err = bt_bap_broadcast_sink_setup_stream(sink, stream, codec_cfg); if (err != 0) { LOG_DBG("Failed to setup streams[%zu]: %d", i, err); broadcast_sink_cleanup_streams(sink); return err; } sink->bis[i].chan = bt_bap_stream_iso_chan_get(stream); sys_slist_append(&sink->streams, &stream->_node); sink->stream_count++; bis_channels[i] = sink->bis[i].chan; } param.bis_channels = bis_channels; param.num_bis = sink->stream_count; param.bis_bitfield = indexes_bitfield; param.mse = 0; /* Let controller decide */ param.sync_timeout = interval_to_sync_timeout(sink->iso_interval); param.encryption = atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED); if (param.encryption) { memcpy(param.bcode, broadcast_code, sizeof(param.bcode)); } else { memset(param.bcode, 0, sizeof(param.bcode)); } err = bt_iso_big_sync(sink->pa_sync, ¶m, &sink->big); if (err != 0) { broadcast_sink_cleanup_streams(sink); return err; } sink->indexes_bitfield = indexes_bitfield; for (size_t i = 0; i < stream_count; i++) { struct bt_bap_ep *ep = streams[i]->ep; ep->broadcast_sink = sink; broadcast_sink_set_ep_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED); } return 0; } int bt_bap_broadcast_sink_stop(struct bt_bap_broadcast_sink *sink) { int err; CHECKIF(sink == NULL) { LOG_DBG("sink is NULL"); return -EINVAL; } if (sys_slist_is_empty(&sink->streams)) { LOG_DBG("Source does not have any streams (already stopped)"); return -EALREADY; } if (broadcast_sink_is_in_state(sink, BT_BAP_EP_STATE_IDLE)) { LOG_DBG("Broadcast sink %p in idle state", sink); return -EBADMSG; } err = bt_iso_big_terminate(sink->big); if (err) { LOG_DBG("Failed to terminate BIG (err %d)", err); return err; } return 0; } int bt_bap_broadcast_sink_delete(struct bt_bap_broadcast_sink *sink) { CHECKIF(sink == NULL) { LOG_DBG("sink is NULL"); return -EINVAL; } if (!broadcast_sink_is_in_state(sink, BT_BAP_EP_STATE_IDLE)) { LOG_DBG("Broadcast sink %p not in idle state", sink); return -EBADMSG; } /* Reset the broadcast sink */ broadcast_sink_cleanup(sink); return 0; } static int broadcast_sink_init(void) { static struct bt_le_per_adv_sync_cb cb = { .recv = pa_recv, .biginfo = biginfo_recv, .term = pa_term_cb, }; bt_le_per_adv_sync_cb_register(&cb); return 0; } SYS_INIT(broadcast_sink_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);