| /* Bluetooth Audio Broadcast Sink */ |
| |
| /* |
| * Copyright (c) 2021-2023 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/device.h> |
| #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/bluetooth/audio/pacs.h> |
| #include <zephyr/bluetooth/audio/bap.h> |
| |
| #include "../host/conn_internal.h" |
| #include "../host/iso_internal.h" |
| |
| #include "bap_iso.h" |
| #include "bap_endpoint.h" |
| #include "audio_internal.h" |
| |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_REGISTER(bt_bap_broadcast_sink, CONFIG_BT_BAP_BROADCAST_SINK_LOG_LEVEL); |
| |
| #include "common/bt_str.h" |
| |
| #define PA_SYNC_SKIP 5 |
| #define SYNC_RETRY_COUNT 6 /* similar to retries for connections */ |
| #define BROADCAST_SYNC_MIN_INDEX (BIT(1)) |
| |
| /* any value above 0xFFFFFF is invalid, so we can just use 0xFFFFFFFF to denote |
| * invalid broadcast ID |
| */ |
| #define INVALID_BROADCAST_ID 0xFFFFFFFF |
| |
| static struct bt_bap_ep broadcast_sink_eps[CONFIG_BT_BAP_BROADCAST_SNK_COUNT] |
| [BROADCAST_SNK_STREAM_CNT]; |
| static struct bt_bap_broadcast_sink broadcast_sinks[CONFIG_BT_BAP_BROADCAST_SNK_COUNT]; |
| static struct bt_le_scan_cb broadcast_scan_cb; |
| |
| struct codec_lookup_id_data { |
| uint8_t id; |
| struct bt_codec *codec; |
| }; |
| |
| static sys_slist_t sink_cbs = SYS_SLIST_STATIC_INIT(&sink_cbs); |
| |
| static void broadcast_sink_cleanup(struct bt_bap_broadcast_sink *sink); |
| |
| static enum bt_bap_scan_delegator_iter |
| 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 BT_BAP_SCAN_DELEGATOR_ITER_STOP; |
| } |
| |
| return BT_BAP_SCAN_DELEGATOR_ITER_CONTINUE; |
| } |
| |
| static enum bt_bap_scan_delegator_iter |
| 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 BT_BAP_SCAN_DELEGATOR_ITER_CONTINUE; |
| } |
| |
| if (bt_addr_le_eq(&recv_state->addr, &sync_info.addr) && |
| recv_state->adv_sid == sync_info.sid) { |
| return BT_BAP_SCAN_DELEGATOR_ITER_STOP; |
| } |
| |
| return BT_BAP_SCAN_DELEGATOR_ITER_CONTINUE; |
| }; |
| |
| 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 }; |
| const struct bt_bap_base *base; |
| 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; |
| } |
| |
| base = &sink->base; |
| |
| mod_src_param.num_subgroups = base->subgroup_count; |
| for (uint8_t i = 0U; i < base->subgroup_count; i++) { |
| struct bt_bap_scan_delegator_subgroup *subgroup_param = &mod_src_param.subgroups[i]; |
| const struct bt_bap_base_subgroup *subgroup = &base->subgroups[i]; |
| |
| /* Update the BIS sync indexes for the subgroup */ |
| for (size_t j = 0U; j < subgroup->bis_count; j++) { |
| const struct bt_bap_base_bis_data *bis_data = &subgroup->bis_data[j]; |
| |
| subgroup_param->bis_sync |= BIT(bis_data->index); |
| } |
| } |
| |
| 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) { |
| LOG_WRN("Failed to find receive state for sink %p", sink); |
| |
| return; |
| } |
| |
| if (recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_BCODE_REQ && |
| 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; |
| } |
| |
| /* BIS syncs will be automatically cleared since the mod_src_param |
| * struct is 0-initialized |
| * |
| * Since the metadata_len is also 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) { |
| 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 = 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; |
| |
| 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 len %zu", stream, stream->ep, net_buf_frags_len(buf)); |
| } |
| |
| if (ops != NULL && ops->recv != NULL) { |
| ops->recv(stream, info, buf); |
| } else { |
| LOG_WRN("No callback for recv set"); |
| } |
| } |
| |
| 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; |
| bool all_connected; |
| |
| 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", 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 connected set"); |
| } |
| |
| all_connected = true; |
| SYS_SLIST_FOR_EACH_CONTAINER(&sink->streams, stream, _node) { |
| __ASSERT(stream->ep, "Endpoint is NULL"); |
| |
| if (stream->ep->status.state != BT_BAP_EP_STATE_STREAMING) { |
| all_connected = false; |
| break; |
| } |
| } |
| |
| if (all_connected) { |
| 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; |
| } |
| |
| ops = stream->ops; |
| |
| LOG_DBG("stream %p ep %p reason 0x%02x", stream, ep, reason); |
| |
| broadcast_sink_set_ep_state(ep, BT_BAP_EP_STATE_IDLE); |
| |
| if (ops != NULL && ops->stopped != NULL) { |
| ops->stopped(stream, reason); |
| } else { |
| LOG_WRN("No callback for stopped set"); |
| } |
| |
| sink = broadcast_sink_lookup_iso_chan(chan); |
| if (sink == NULL) { |
| LOG_ERR("Could not lookup sink by iso %p", chan); |
| return; |
| } |
| |
| if (!sys_slist_find_and_remove(&sink->streams, &stream->_node)) { |
| LOG_DBG("Could not find and remove stream %p from sink %p", stream, sink); |
| } |
| |
| /* Clear sink->big if not already cleared */ |
| if (sys_slist_is_empty(&sink->streams) && sink->big) { |
| broadcast_sink_clear_big(sink, reason); |
| } |
| } |
| |
| 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_syncing_get(void) |
| { |
| for (int i = 0; i < ARRAY_SIZE(broadcast_sinks); i++) { |
| if (atomic_test_bit(broadcast_sinks[i].flags, |
| BT_BAP_BROADCAST_SINK_FLAG_SYNCING)) { |
| return &broadcast_sinks[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct bt_bap_broadcast_sink *broadcast_sink_scanning_get(void) |
| { |
| for (int i = 0; i < ARRAY_SIZE(broadcast_sinks); i++) { |
| if (atomic_test_bit(broadcast_sinks[i].flags, |
| BT_BAP_BROADCAST_SINK_FLAG_SCANNING)) { |
| return &broadcast_sinks[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| 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 = 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_broadcast_id(uint32_t broadcast_id) |
| { |
| for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sinks); i++) { |
| if (broadcast_sinks[i].broadcast_id == broadcast_id) { |
| 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; |
| int err; |
| |
| add_src_param.pa_sync = sink->pa_sync; |
| 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 void handle_past_sync(struct bt_le_per_adv_sync *sync) |
| { |
| const struct bt_bap_scan_delegator_recv_state *recv_state; |
| |
| recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_pa_sync_cb, (void *)sync); |
| if (recv_state != NULL) { |
| /* If we receive a PAST transfer that fits a |
| * known BASS Receive State, then we create it |
| * as a Broadcast Sink |
| * |
| * The PA state in the Receive State will be |
| * updated by the Scan Delegator |
| */ |
| int err; |
| |
| err = bt_bap_broadcast_sink_create(sync, recv_state->broadcast_id); |
| if (err != 0) { |
| LOG_WRN("Failed to create Broadcast Sink: %d", err); |
| } |
| } |
| } |
| |
| static void pa_synced(struct bt_le_per_adv_sync *sync, |
| struct bt_le_per_adv_sync_synced_info *info) |
| { |
| const struct bt_bap_scan_delegator_recv_state *recv_state; |
| struct bt_bap_broadcast_sink_cb *listener; |
| struct bt_bap_broadcast_sink *sink; |
| int err; |
| |
| sink = broadcast_sink_syncing_get(); |
| if (sink == NULL || sync != sink->pa_sync) { |
| if (info->conn) { /* PAST */ |
| handle_past_sync(sync); |
| } else { |
| /* Not ours */ |
| } |
| |
| return; |
| } |
| |
| LOG_DBG("Synced to broadcast source with ID 0x%06X", sink->broadcast_id); |
| |
| atomic_clear_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SYNCING); |
| |
| err = bt_bap_broadcast_sink_scan_stop(); |
| if (err != 0 && err != -EALREADY) { |
| LOG_WRN("Failed to stop sink scan: %d", err); |
| /* Even if we cannot stop scanning here, we can still move on */ |
| } |
| |
| /* Add the PA sync to the scan delegator or modify if it already exists */ |
| recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_pa_sync_cb, (void *)sync); |
| if (recv_state == NULL) { |
| broadcast_sink_add_src(sink); |
| } else { |
| /* Set PA sync state */ |
| err = bt_bap_scan_delegator_set_pa_state(recv_state->src_id, |
| BT_BAP_PA_STATE_SYNCED); |
| if (err != 0) { |
| LOG_WRN("Failed to set PA state: %d", err); |
| } |
| } |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { |
| if (listener->pa_synced != NULL) { |
| listener->pa_synced(sink, sink->pa_sync, sink->broadcast_id); |
| } |
| } |
| |
| /* TBD: What if sync to a bad broadcast source that does not send |
| * properly formatted (or any) BASE? |
| */ |
| } |
| |
| static void pa_term(struct bt_le_per_adv_sync *sync, |
| const struct bt_le_per_adv_sync_term_info *info) |
| { |
| 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; |
| } |
| |
| LOG_DBG("PA sync with broadcast source with ID 0x%06X lost", sink->broadcast_id); |
| |
| if (sink->big != NULL) { |
| const int err = bt_iso_big_terminate(sink->big); |
| |
| if (err != 0) { |
| LOG_ERR("Failed to disconnect BIG sync: %d", err); |
| } |
| } |
| |
| broadcast_sink_cleanup(sink); |
| SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { |
| if (listener->pa_sync_lost != NULL) { |
| listener->pa_sync_lost(sink); |
| } |
| } |
| } |
| |
| static void update_recv_state_base_copy_meta(const struct bt_bap_base *base, |
| struct bt_bap_scan_delegator_mod_src_param *param) |
| { |
| for (uint8_t i = 0U; i < base->subgroup_count; i++) { |
| struct bt_bap_scan_delegator_subgroup *subgroup_param = ¶m->subgroups[i]; |
| const struct bt_bap_base_subgroup *subgroup = &base->subgroups[i]; |
| uint8_t *metadata_param = subgroup_param->metadata; |
| size_t total_len; |
| |
| /* Copy metadata into subgroup_param, changing it from an array |
| * of bt_codec_data to a uint8_t buffer |
| */ |
| total_len = 0U; |
| for (size_t j = 0; j < subgroup->codec.meta_count; j++) { |
| const struct bt_codec_data *meta = &subgroup->codec.meta[j]; |
| const struct bt_data *data = &meta->data; |
| const uint8_t len = data->data_len; |
| const uint8_t type = data->type; |
| const size_t ltv_len = sizeof(len) + sizeof(type) + len; |
| |
| if (total_len + ltv_len > sizeof(subgroup_param->metadata)) { |
| LOG_WRN("Could not fit entire metadata for subgroup[%u]", i); |
| |
| return; |
| } |
| |
| metadata_param[total_len++] = len + 1; |
| metadata_param[total_len++] = type; |
| (void)memcpy(&metadata_param[total_len], data->data, |
| len); |
| total_len += len; |
| } |
| |
| subgroup_param->metadata_len = total_len; |
| } |
| } |
| |
| static void update_recv_state_base(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; |
| const struct bt_bap_base *base; |
| 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; |
| } |
| |
| base = &sink->base; |
| |
| mod_src_param.num_subgroups = base->subgroup_count; |
| update_recv_state_base_copy_meta(base, &mod_src_param); |
| |
| /* 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; |
| |
| 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 pa_decode_base(struct bt_data *data, void *user_data) |
| { |
| struct bt_bap_broadcast_sink *sink = (struct bt_bap_broadcast_sink *)user_data; |
| struct bt_bap_broadcast_sink_cb *listener; |
| struct bt_bap_base base = { 0 }; |
| |
| if (data->type != BT_DATA_SVC_DATA16) { |
| return true; |
| } |
| |
| if (data->data_len < BT_BAP_BASE_MIN_SIZE) { |
| return true; |
| } |
| |
| if (bt_bap_decode_base(data, &base) != 0) { |
| return false; |
| } |
| |
| if (atomic_test_bit(sink->flags, |
| BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED)) { |
| uint8_t num_bis = 0; |
| |
| for (int i = 0; i < base.subgroup_count; i++) { |
| num_bis += base.subgroups[i].bis_count; |
| } |
| |
| if (num_bis > sink->biginfo_num_bis) { |
| LOG_WRN("BASE contains more BIS than reported by BIGInfo"); |
| return false; |
| } |
| } |
| |
| sink->codec_qos.pd = base.pd; |
| if (memcmp(&sink->base, &base, sizeof(base)) != 0) { |
| /* We only overwrite the sink->base data once the base has |
| * successfully been decoded to avoid overwriting it with |
| * invalid data |
| */ |
| (void)memcpy(&sink->base, &base, sizeof(base)); |
| |
| if (atomic_test_bit(sink->flags, |
| BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID)) { |
| update_recv_state_base(sink); |
| } |
| } |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { |
| if (listener->base_recv != NULL) { |
| listener->base_recv(sink, &base); |
| } |
| } |
| |
| 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 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->codec_qos.framing = biginfo->framing; |
| sink->codec_qos.phy = biginfo->phy; |
| sink->codec_qos.sdu = biginfo->max_sdu; |
| sink->codec_qos.interval = biginfo->sdu_interval; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { |
| if (listener->syncable != NULL) { |
| listener->syncable(sink, biginfo->encryption); |
| } |
| } |
| } |
| |
| static uint16_t interval_to_sync_timeout(uint16_t interval) |
| { |
| uint32_t interval_ms; |
| uint16_t timeout; |
| |
| /* Ensure that the following calculation does not overflow silently */ |
| __ASSERT(SYNC_RETRY_COUNT < 10, "SYNC_RETRY_COUNT shall be less than 10"); |
| |
| /* Add retries and convert to unit in 10's of ms */ |
| interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(interval); |
| timeout = (interval_ms * SYNC_RETRY_COUNT) / 10; |
| |
| /* Enforce restraints */ |
| timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, |
| BT_GAP_PER_ADV_MAX_TIMEOUT); |
| |
| return timeout; |
| } |
| |
| static void sync_broadcast_pa(const struct bt_le_scan_recv_info *info, |
| uint32_t broadcast_id) |
| { |
| struct bt_bap_broadcast_sink_cb *listener; |
| struct bt_le_per_adv_sync_param param; |
| struct bt_bap_broadcast_sink *sink; |
| int err; |
| |
| sink = broadcast_sink_scanning_get(); |
| /* Should never happen as we set the scanning flag before registering |
| * the scanning callbacks |
| */ |
| __ASSERT(sink != NULL, "sink is NULL"); |
| |
| /* Unregister the callbacks to prevent broadcast_scan_recv to be called again */ |
| bt_le_scan_cb_unregister(&broadcast_scan_cb); |
| err = bt_le_scan_stop(); |
| if (err != 0) { |
| LOG_ERR("Could not stop scan: %d", err); |
| } else { |
| atomic_clear_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SCANNING); |
| } |
| |
| bt_addr_le_copy(¶m.addr, info->addr); |
| param.options = 0; |
| param.sid = info->sid; |
| param.skip = PA_SYNC_SKIP; |
| param.timeout = interval_to_sync_timeout(info->interval); |
| err = bt_le_per_adv_sync_create(¶m, &sink->pa_sync); |
| if (err != 0) { |
| LOG_ERR("Could not sync to PA: %d", err); |
| broadcast_sink_cleanup(sink); |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { |
| if (listener->scan_term != NULL) { |
| listener->scan_term(err); |
| } |
| } |
| } else { |
| atomic_set_bit(sink->flags, |
| BT_BAP_BROADCAST_SINK_FLAG_SYNCING); |
| sink->broadcast_id = broadcast_id; |
| } |
| } |
| |
| static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data) |
| { |
| uint32_t *broadcast_id = user_data; |
| struct bt_uuid_16 adv_uuid; |
| |
| if (sys_slist_is_empty(&sink_cbs)) { |
| /* Terminate early if we do not have any broadcast sink listeners */ |
| return false; |
| } |
| |
| if (data->type != BT_DATA_SVC_DATA16) { |
| return true; |
| } |
| |
| if (data->data_len < BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE) { |
| return true; |
| } |
| |
| if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) { |
| return true; |
| } |
| |
| if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO)) { |
| return true; |
| } |
| |
| if (broadcast_sink_syncing_get() != NULL) { |
| /* Already syncing, can maximum sync one */ |
| return true; |
| } |
| |
| *broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16); |
| |
| /* Stop parsing */ |
| return false; |
| } |
| |
| static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, |
| struct net_buf_simple *ad) |
| { |
| struct bt_bap_broadcast_sink_cb *listener; |
| struct net_buf_simple_state state; |
| uint32_t broadcast_id; |
| |
| /* We are only interested in non-connectable periodic advertisers */ |
| if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) || |
| info->interval == 0) { |
| return; |
| } |
| |
| /* As scan_check_and_sync_broadcast modifies the AD data, |
| * we store the state before parsing it |
| */ |
| net_buf_simple_save(ad, &state); |
| broadcast_id = INVALID_BROADCAST_ID; |
| bt_data_parse(ad, scan_check_and_sync_broadcast, (void *)&broadcast_id); |
| net_buf_simple_restore(ad, &state); |
| |
| /* We check if `broadcast_id` was modified by `scan_check_and_sync_broadcast`. |
| * If it was then that means that we found a broadcast source |
| */ |
| if (broadcast_id != INVALID_BROADCAST_ID) { |
| LOG_DBG("Found broadcast source with address %s and id 0x%06X", |
| bt_addr_le_str(info->addr), broadcast_id); |
| |
| if (broadcast_sink_get_by_broadcast_id(broadcast_id) != NULL) { |
| LOG_DBG("Broadcast sink with broadcast_id 0x%X already exists", |
| broadcast_id); |
| |
| return; |
| } |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { |
| if (listener->scan_recv != NULL) { |
| bool sync_pa; |
| |
| |
| /* As the callback receiver may modify the AD |
| * data, we store the state so that we can |
| * restore it for each callback |
| */ |
| net_buf_simple_save(ad, &state); |
| |
| sync_pa = listener->scan_recv(info, ad, broadcast_id); |
| |
| if (sync_pa) { |
| sync_broadcast_pa(info, broadcast_id); |
| break; |
| } |
| |
| net_buf_simple_restore(ad, &state); |
| } |
| } |
| } |
| } |
| |
| static void broadcast_scan_timeout(void) |
| { |
| struct bt_bap_broadcast_sink_cb *listener; |
| struct bt_bap_broadcast_sink *sink; |
| |
| bt_le_scan_cb_unregister(&broadcast_scan_cb); |
| |
| sink = broadcast_sink_scanning_get(); |
| /* Should never happen as we set the scanning flag before registering |
| * the scanning callbacks |
| */ |
| __ASSERT(sink != NULL, "sink is NULL"); |
| |
| broadcast_sink_cleanup(sink); |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { |
| if (listener->scan_term != NULL) { |
| listener->scan_term(-ETIME); |
| } |
| } |
| } |
| |
| int bt_bap_broadcast_sink_register_cb(struct bt_bap_broadcast_sink_cb *cb) |
| { |
| CHECKIF(cb == NULL) { |
| LOG_DBG("cb is NULL"); |
| return -EINVAL; |
| } |
| |
| sys_slist_append(&sink_cbs, &cb->_node); |
| |
| return 0; |
| } |
| |
| int bt_bap_broadcast_sink_scan_start(const struct bt_le_scan_param *param) |
| { |
| struct bt_bap_broadcast_sink *sink; |
| int err; |
| |
| CHECKIF(param == NULL) { |
| LOG_DBG("param is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(param->timeout != 0) { |
| /* This is to avoid having to re-implement the scan timeout |
| * callback as well, and can be modified later if requested |
| */ |
| LOG_DBG("Scan param shall not have a timeout"); |
| return -EINVAL; |
| } |
| |
| if (sys_slist_is_empty(&sink_cbs)) { |
| LOG_WRN("No broadcast sink callbacks registered"); |
| return -EINVAL; |
| } |
| |
| if (broadcast_sink_scanning_get() != NULL) { |
| LOG_DBG("Already scanning"); |
| |
| return -EALREADY; |
| } |
| |
| sink = broadcast_sink_free_get(); |
| if (sink == NULL) { |
| LOG_DBG("No more free broadcast sinks"); |
| return -ENOMEM; |
| } |
| |
| /* TODO: check for scan callback */ |
| err = bt_le_scan_start(param, NULL); |
| if (err == 0) { |
| atomic_set_bit(sink->flags, |
| BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED); |
| atomic_set_bit(sink->flags, |
| BT_BAP_BROADCAST_SINK_FLAG_SCANNING); |
| |
| broadcast_scan_cb.recv = broadcast_scan_recv; |
| broadcast_scan_cb.timeout = broadcast_scan_timeout; |
| bt_le_scan_cb_register(&broadcast_scan_cb); |
| } |
| |
| return err; |
| } |
| |
| int bt_bap_broadcast_sink_scan_stop(void) |
| { |
| struct bt_bap_broadcast_sink_cb *listener; |
| struct bt_bap_broadcast_sink *sink; |
| int err; |
| |
| sink = broadcast_sink_scanning_get(); |
| if (sink == NULL) { |
| LOG_DBG("Not scanning"); |
| |
| return -EALREADY; |
| } |
| |
| if (sink->pa_sync != NULL) { |
| err = bt_le_per_adv_sync_delete(sink->pa_sync); |
| if (err != 0) { |
| LOG_DBG("Could not delete PA sync: %d", err); |
| return err; |
| } |
| } |
| |
| broadcast_sink_cleanup(sink); |
| |
| err = bt_le_scan_stop(); |
| if (err == 0) { |
| bt_le_scan_cb_unregister(&broadcast_scan_cb); |
| } |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { |
| if (listener->scan_term != NULL) { |
| listener->scan_term(err); |
| } |
| } |
| |
| return err; |
| } |
| |
| 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_codec *codec) |
| { |
| 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_audio_codec_qos_to_iso_qos(iso->chan.qos->rx, &sink->codec_qos); |
| bt_audio_codec_to_iso_path(iso->chan.qos->rx->path, codec); |
| |
| bt_bap_iso_unref(iso); |
| |
| bt_bap_stream_attach(NULL, stream, ep, codec); |
| stream->qos = &sink->codec_qos; |
| |
| 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 = NULL; |
| stream->group = NULL; |
| |
| sys_slist_remove(&sink->streams, NULL, &stream->_node); |
| } |
| |
| sink->stream_count = 0; |
| } |
| |
| static void broadcast_sink_cleanup(struct bt_bap_broadcast_sink *sink) |
| { |
| if (atomic_test_bit(sink->flags, |
| BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID)) { |
| int err; |
| |
| err = bt_bap_scan_delegator_rem_src(sink->bass_src_id); |
| if (err != 0) { |
| LOG_WRN("Failed to remove Receive State for sink %p: %d", |
| sink, err); |
| } |
| } |
| |
| if (sink->stream_count > 0U) { |
| broadcast_sink_cleanup_streams(sink); |
| } |
| |
| (void)memset(sink, 0, sizeof(*sink)); /* also clears flags */ |
| } |
| |
| static struct bt_codec *codec_from_base_by_index(struct bt_bap_base *base, uint8_t index) |
| { |
| for (size_t i = 0U; i < base->subgroup_count; i++) { |
| struct bt_bap_base_subgroup *subgroup = &base->subgroups[i]; |
| |
| for (size_t j = 0U; j < subgroup->bis_count; j++) { |
| if (subgroup->bis_data[j].index == index) { |
| return &subgroup->codec; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static bool codec_lookup_id(const struct bt_pacs_cap *cap, void *user_data) |
| { |
| struct codec_lookup_id_data *data = user_data; |
| |
| if (cap->codec->id == data->id) { |
| data->codec = cap->codec; |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int bt_bap_broadcast_sink_create(struct bt_le_per_adv_sync *pa_sync, uint32_t broadcast_id) |
| { |
| const struct bt_bap_scan_delegator_recv_state *recv_state; |
| struct bt_bap_broadcast_sink_cb *listener; |
| 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; |
| } |
| |
| if (broadcast_sink_get_by_broadcast_id(broadcast_id) != NULL) { |
| LOG_DBG("Broadcast sink with broadcast_id 0x%X already exists", |
| broadcast_id); |
| |
| return -EALREADY; |
| } |
| |
| sink = broadcast_sink_free_get(); |
| if (sink == NULL) { |
| LOG_DBG("No more free broadcast sinks"); |
| |
| return -ENOMEM; |
| } |
| |
| 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); |
| |
| return -EINVAL; |
| } |
| |
| sink->bass_src_id = recv_state->src_id; |
| } |
| |
| sink->broadcast_id = broadcast_id; |
| sink->pa_sync = pa_sync; |
| atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED); |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { |
| if (listener->pa_synced != NULL) { |
| listener->pa_synced(sink, sink->pa_sync, |
| sink->broadcast_id); |
| } |
| } |
| |
| 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[16]) |
| { |
| struct bt_iso_big_sync_param param; |
| struct bt_codec *codecs[BROADCAST_SNK_STREAM_CNT] = { NULL }; |
| uint8_t stream_count; |
| int err; |
| |
| CHECKIF(sink == NULL) { |
| LOG_DBG("sink is NULL"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(indexes_bitfield == 0) { |
| LOG_DBG("indexes_bitfield is 0"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(indexes_bitfield & BIT(0)) { |
| LOG_DBG("BIT(0) is not a valid BIS index"); |
| 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 */ |
| stream_count = 0; |
| for (int i = 1; i < BT_ISO_MAX_GROUP_ISO_COUNT; i++) { |
| if ((indexes_bitfield & BIT(i)) != 0) { |
| struct bt_codec *codec = codec_from_base_by_index(&sink->base, i); |
| struct codec_lookup_id_data lookup_data = { }; |
| |
| if (codec == NULL) { |
| LOG_DBG("Index %d not found in BASE", i); |
| return -EINVAL; |
| } |
| |
| /* Lookup and assign path_id based on capabilities */ |
| lookup_data.id = codec->id; |
| |
| bt_pacs_cap_foreach(BT_AUDIO_DIR_SINK, codec_lookup_id, |
| &lookup_data); |
| if (lookup_data.codec == NULL) { |
| LOG_DBG("Codec with id %u is not supported by our capabilities", |
| codec->id); |
| |
| return -ENOENT; |
| } |
| |
| codec->path_id = lookup_data.codec->path_id; |
| |
| codecs[stream_count++] = codec; |
| |
| if (stream_count > BROADCAST_SNK_STREAM_CNT) { |
| LOG_DBG("Cannot sync to more than %d streams", |
| BROADCAST_SNK_STREAM_CNT); |
| 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_codec *codec; |
| |
| stream = streams[i]; |
| codec = codecs[i]; |
| |
| err = bt_bap_broadcast_sink_setup_stream(sink, stream, codec); |
| if (err != 0) { |
| LOG_DBG("Failed to setup streams[%zu]: %d", i, err); |
| broadcast_sink_cleanup_streams(sink); |
| return err; |
| } |
| |
| sink->bis[i] = bt_bap_stream_iso_chan_get(stream); |
| sys_slist_append(&sink->streams, &stream->_node); |
| sink->stream_count++; |
| } |
| |
| param.bis_channels = sink->bis; |
| 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; |
| } |
| |
| 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) |
| { |
| struct bt_bap_stream *stream; |
| sys_snode_t *head_node; |
| 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; |
| } |
| |
| head_node = sys_slist_peek_head(&sink->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 -EINVAL; |
| } |
| |
| if (stream->ep->status.state != BT_BAP_EP_STATE_STREAMING && |
| stream->ep->status.state != BT_BAP_EP_STATE_QOS_CONFIGURED) { |
| LOG_DBG("Broadcast sink stream %p invalid state: %u", stream, |
| stream->ep->status.state); |
| return -EBADMSG; |
| } |
| |
| err = bt_iso_big_terminate(sink->big); |
| if (err) { |
| LOG_DBG("Failed to terminate BIG (err %d)", err); |
| return err; |
| } |
| |
| broadcast_sink_clear_big(sink, BT_HCI_ERR_LOCALHOST_TERM_CONN); |
| /* Channel states will be updated in the broadcast_sink_iso_disconnected function */ |
| |
| return 0; |
| } |
| |
| int bt_bap_broadcast_sink_delete(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)) { |
| struct bt_bap_stream *stream; |
| sys_snode_t *head_node; |
| |
| head_node = sys_slist_peek_head(&sink->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("Sink is not stopped"); |
| return -EBADMSG; |
| } |
| } |
| |
| if (sink->pa_sync == NULL) { |
| LOG_DBG("Broadcast sink is already deleted"); |
| return -EALREADY; |
| } |
| |
| err = bt_le_per_adv_sync_delete(sink->pa_sync); |
| if (err != 0) { |
| LOG_DBG("Failed to delete periodic advertising sync (err %d)", err); |
| return err; |
| } |
| |
| /* 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 = { |
| .synced = pa_synced, |
| .recv = pa_recv, |
| .term = pa_term, |
| .biginfo = biginfo_recv |
| }; |
| |
| bt_le_per_adv_sync_cb_register(&cb); |
| |
| return 0; |
| } |
| |
| SYS_INIT(broadcast_sink_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); |