Refactors teh BIS bitfield values used for ISO and BAP. Previously BIT(1) meant BIS index 1, which was a Zephyr choice in the early days of ISO, as the BT Core spec did not use a bitfield for BIS indexes. Later the BASS specification came along and defined that BIT(0) meant BIS index 1, which meant that we had to shift BIS bitfields between BAP and ISO. This commit refactors the ISO layer to use BIT(0) for Index 1 now, which means that there is no longer a need for conversion between the BAP and ISO layers, and that we can use a value range defined by a BT Core spec (BASS). Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
1357 lines
36 KiB
C
1357 lines
36 KiB
C
/* Bluetooth Audio Broadcast Sink */
|
|
|
|
/*
|
|
* Copyright (c) 2021-2023 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include <zephyr/autoconf.h>
|
|
#include <zephyr/bluetooth/addr.h>
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/gap.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 <zephyr/bluetooth/hci_types.h>
|
|
#include <zephyr/bluetooth/iso.h>
|
|
#include <zephyr/bluetooth/uuid.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/net/buf.h>
|
|
#include <zephyr/sys/__assert.h>
|
|
#include <zephyr/sys/atomic.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/sys/check.h>
|
|
#include <zephyr/sys/slist.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/sys/util_macro.h>
|
|
|
|
#include "../host/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))
|
|
|
|
/* 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]
|
|
[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;
|
|
}
|
|
|
|
/* 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 == 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");
|
|
}
|
|
}
|
|
|
|
/** Gets the "highest" state of all BIS in the broadcast sink */
|
|
static enum bt_bap_ep_state broadcast_sink_get_state(struct bt_bap_broadcast_sink *sink)
|
|
{
|
|
enum bt_bap_ep_state state = BT_BAP_EP_STATE_IDLE;
|
|
struct bt_bap_stream *stream;
|
|
|
|
if (sink == NULL) {
|
|
LOG_DBG("sink is NULL");
|
|
|
|
return state;
|
|
}
|
|
|
|
if (sys_slist_is_empty(&sink->streams)) {
|
|
LOG_DBG("Sink does not have any streams");
|
|
|
|
return state;
|
|
}
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&sink->streams, stream, _node) {
|
|
if (stream->ep != NULL) {
|
|
state = MAX(state, stream->ep->status.state);
|
|
}
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
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_get_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);
|
|
}
|
|
|
|
/* Clear sink->big if not already cleared */
|
|
if (sys_slist_is_empty(&sink->streams) && sink->big) {
|
|
broadcast_sink_clear_big(sink, reason);
|
|
}
|
|
}
|
|
|
|
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 = 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 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 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];
|
|
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;
|
|
}
|
|
|
|
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->codec_qos.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->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);
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint16_t interval_to_sync_timeout(uint16_t interval)
|
|
{
|
|
uint32_t interval_ms;
|
|
uint32_t timeout;
|
|
|
|
/* Add retries and convert to unit in 10's of ms */
|
|
interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(interval);
|
|
timeout = (interval_ms * PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO) / 10;
|
|
|
|
/* Enforce restraints */
|
|
timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, BT_GAP_PER_ADV_MAX_TIMEOUT);
|
|
|
|
return (uint16_t)timeout;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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_audio_codec_qos_to_iso_qos(iso->chan.qos->rx, &sink->codec_qos);
|
|
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->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_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[16])
|
|
{
|
|
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)
|
|
{
|
|
enum bt_bap_ep_state state;
|
|
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;
|
|
}
|
|
|
|
state = broadcast_sink_get_state(sink);
|
|
if (state != BT_BAP_EP_STATE_STREAMING && state != BT_BAP_EP_STATE_QOS_CONFIGURED) {
|
|
LOG_DBG("Broadcast sink %p invalid state: %u", sink, 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)
|
|
{
|
|
enum bt_bap_ep_state state;
|
|
|
|
CHECKIF(sink == NULL) {
|
|
LOG_DBG("sink is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
state = broadcast_sink_get_state(sink);
|
|
if (state != BT_BAP_EP_STATE_IDLE) {
|
|
LOG_DBG("Broadcast sink %p invalid state: %u", sink, state);
|
|
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);
|