Bluetooth: Audio: broadcast assistant sync to PA

broadcast_audio_assistant application sync to PA and obtain the correct
subgroup information used by BASS.

Signed-off-by: Ping Wang <pinw@demant.com>
This commit is contained in:
Ping Wang 2024-02-19 09:23:44 +01:00 committed by Anas Nashif
commit f1d174e0d3
2 changed files with 188 additions and 21 deletions

View file

@ -11,4 +11,6 @@ CONFIG_BT_CTLR_SCAN_DATA_LEN_MAX=191
CONFIG_BT_TINYCRYPT_ECC=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_BAP_BASS_MAX_SUBGROUPS=2
CONFIG_BT_BAP_BROADCAST_ASSISTANT=y
CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE=255

View file

@ -17,7 +17,8 @@
#include <zephyr/sys/byteorder.h>
#define NAME_LEN 30
#define PA_SYNC_SKIP 5
#define PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO 20 /* Set the timeout relative to interval */
/* Broadcast IDs are 24bit, so this is out of valid range */
#define INVALID_BROADCAST_ID 0xFFFFFFFFU
@ -39,15 +40,23 @@ static uint32_t selected_broadcast_id;
static uint8_t selected_sid;
static uint16_t selected_pa_interval;
static bt_addr_le_t selected_addr;
static struct bt_le_per_adv_sync *pa_sync;
static uint8_t received_base[UINT8_MAX];
static uint8_t received_base_size;
static struct bt_bap_bass_subgroup
bass_subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS];
static bool scanning_for_broadcast_source;
static struct k_mutex base_store_mutex;
static K_SEM_DEFINE(sem_source_discovered, 0, 1);
static K_SEM_DEFINE(sem_sink_discovered, 0, 1);
static K_SEM_DEFINE(sem_sink_connected, 0, 1);
static K_SEM_DEFINE(sem_sink_disconnected, 0, 1);
static K_SEM_DEFINE(sem_security_updated, 0, 1);
static K_SEM_DEFINE(sem_bass_discovered, 0, 1);
static K_SEM_DEFINE(sem_pa_synced, 0, 1);
static K_SEM_DEFINE(sem_received_base_subgroups, 0, 1);
static bool device_found(struct bt_data *data, void *user_data)
{
@ -121,6 +130,88 @@ static bool device_found(struct bt_data *data, void *user_data)
}
}
static bool base_store(struct bt_data *data, void *user_data)
{
const struct bt_bap_base *base = bt_bap_base_get_base_from_ad(data);
uint8_t base_size;
int base_subgroup_count;
/* Base is NULL if the data does not contain a valid BASE */
if (base == NULL) {
return true;
}
/* Can not fit all the received subgroups with the size CONFIG_BT_BAP_BASS_MAX_SUBGROUPS */
base_subgroup_count = bt_bap_base_get_subgroup_count(base);
if (base_subgroup_count < 0 || base_subgroup_count > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
printk("Got invalid subgroup count: %d\n", base_subgroup_count);
return true;
}
base_size = data->data_len - BT_UUID_SIZE_16; /* the BASE comes after the UUID */
/* Compare BASE and copy if different */
k_mutex_lock(&base_store_mutex, K_FOREVER);
if (base_size != received_base_size || memcmp(base, received_base, base_size) != 0) {
(void)memcpy(received_base, base, base_size);
received_base_size = base_size;
}
k_mutex_unlock(&base_store_mutex);
/* Stop parsing */
k_sem_give(&sem_received_base_subgroups);
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)
{
bt_data_parse(buf, base_store, NULL);
}
static bool add_pa_sync_base_subgroup_bis_cb(const struct bt_bap_base_subgroup_bis *bis,
void *user_data)
{
struct bt_bap_bass_subgroup *subgroup_param = user_data;
subgroup_param->bis_sync |= BIT(bis->index);
return true;
}
static bool add_pa_sync_base_subgroup_cb(const struct bt_bap_base_subgroup *subgroup,
void *user_data)
{
struct bt_bap_broadcast_assistant_add_src_param *param = user_data;
struct bt_bap_bass_subgroup *subgroup_param;
uint8_t *data;
int ret;
ret = bt_bap_base_get_subgroup_codec_meta(subgroup, &data);
if (ret < 0) {
return false;
}
subgroup_param = param->subgroups;
if (ret > ARRAY_SIZE(subgroup_param->metadata)) {
printk("Cannot fit %d octets into subgroup param with size %zu", ret,
ARRAY_SIZE(subgroup_param->metadata));
return false;
}
ret = bt_bap_base_subgroup_foreach_bis(subgroup, add_pa_sync_base_subgroup_bis_cb,
subgroup_param);
if (ret < 0) {
return false;
}
param->num_subgroups++;
return true;
}
static bool is_substring(const char *substr, const char *str)
{
const size_t str_len = strlen(str);
@ -143,6 +234,41 @@ static bool is_substring(const char *substr, const char *str)
return false;
}
static uint16_t interval_to_sync_timeout(uint16_t pa_interval)
{
uint16_t pa_timeout;
if (pa_interval == BT_BAP_PA_INTERVAL_UNKNOWN) {
/* Use maximum value to maximize chance of success */
pa_timeout = BT_GAP_PER_ADV_MAX_TIMEOUT;
} else {
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(pa_interval);
timeout = (interval_ms * PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO) / 10;
/* Enforce restraints */
pa_timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, BT_GAP_PER_ADV_MAX_TIMEOUT);
}
return pa_timeout;
}
static int pa_sync_create(void)
{
struct bt_le_per_adv_sync_param create_params = {0};
bt_addr_le_copy(&create_params.addr, &selected_addr);
create_params.options = BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE;
create_params.sid = selected_sid;
create_params.skip = PA_SYNC_SKIP;
create_params.timeout = interval_to_sync_timeout(selected_pa_interval);
return bt_le_per_adv_sync_create(&create_params, &pa_sync);
}
static void scan_recv_cb(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *ad)
{
@ -168,6 +294,7 @@ static void scan_recv_cb(const struct bt_le_scan_recv_info *info,
printk(" Broadcast Name: %s\n", sr_info.broadcast_name);
printk(" Broadcast ID: 0x%06x\n\n", sr_info.broadcast_id);
#if defined(CONFIG_SELECT_SOURCE_NAME)
if (strlen(CONFIG_SELECT_SOURCE_NAME) > 0U) {
/* Compare names with CONFIG_SELECT_SOURCE_NAME */
if (is_substring(CONFIG_SELECT_SOURCE_NAME, sr_info.bt_name) ||
@ -180,6 +307,7 @@ static void scan_recv_cb(const struct bt_le_scan_recv_info *info,
return;
}
}
#endif /* CONFIG_SELECT_SOURCE_NAME */
err = bt_le_scan_stop();
if (err != 0) {
@ -199,6 +327,9 @@ static void scan_recv_cb(const struct bt_le_scan_recv_info *info,
bt_addr_le_copy(&selected_addr, info->addr);
k_sem_give(&sem_source_discovered);
printk("Attempting to PA sync to the broadcaster with id 0x%06X\n",
selected_broadcast_id);
}
} else {
/* Scan for and connect to Broadcast Sink */
@ -369,11 +500,27 @@ static void bap_broadcast_assistant_add_src_cb(struct bt_conn *conn, int err)
}
}
static void pa_sync_synced_cb(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info)
{
if (sync == pa_sync) {
printk("PA sync %p synced for broadcast sink with broadcast ID 0x%06X\n", sync,
selected_broadcast_id);
k_sem_give(&sem_pa_synced);
}
}
static struct bt_bap_broadcast_assistant_cb ba_cbs = {
.discover = bap_broadcast_assistant_discover_cb,
.add_src = bap_broadcast_assistant_add_src_cb,
};
static struct bt_le_per_adv_sync_cb pa_synced_cb = {
.synced = pa_sync_synced_cb,
.recv = pa_recv,
};
static void reset(void)
{
printk("\n\nReset...\n\n");
@ -390,6 +537,8 @@ static void reset(void)
k_sem_reset(&sem_sink_disconnected);
k_sem_reset(&sem_security_updated);
k_sem_reset(&sem_bass_discovered);
k_sem_reset(&sem_pa_synced);
k_sem_reset(&sem_received_base_subgroups);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
@ -401,7 +550,6 @@ BT_CONN_CB_DEFINE(conn_callbacks) = {
int main(void)
{
int err;
struct bt_bap_bass_subgroup subgroup = { 0 };
struct bt_bap_broadcast_assistant_add_src_param param = { 0 };
err = bt_enable(NULL);
@ -412,8 +560,11 @@ int main(void)
printk("Bluetooth initialized\n");
bt_le_scan_cb_register(&scan_callbacks);
bt_bap_broadcast_assistant_register_cb(&ba_cbs);
bt_le_per_adv_sync_cb_register(&pa_synced_cb);
bt_le_scan_cb_register(&scan_callbacks);
k_mutex_init(&base_store_mutex);
while (true) {
scan_for_broadcast_sink();
@ -467,31 +618,45 @@ int main(void)
scan_for_broadcast_source();
/* FIX NEEDED: It should be valid to assign BT_BAP_BIS_SYNC_NO_PREF
* to bis_sync, but currently (2024-01-30), the broadcast_audio_sink
* sample seems to reject it (err=19) while other sinks don't.
*
* Also, if the source contains more than one stream (e.g. stereo),
* some sinks have been observed to have issues. In this case,
* set only one bit in bis_sync, e.g. subgroup.bis_sync = BIT(1).
*
* When PA sync and BASE is parsed (see note in the scan_recv_cb function),
* the available bits can be used for proper selection.
*/
subgroup.bis_sync = BT_BAP_BIS_SYNC_NO_PREF;
printk("Scan stopped, attempting to PA sync to the broadcaster with id 0x%06X\n",
selected_broadcast_id);
err = pa_sync_create();
if (err != 0) {
printk("Could not create Broadcast PA sync: %d, resetting\n", err);
return -ETIMEDOUT;
}
printk("Waiting for PA synced\n");
err = k_sem_take(&sem_pa_synced, K_FOREVER);
if (err != 0) {
printk("sem_pa_synced timed out, resetting\n");
return err;
}
memset(bass_subgroups, 0, sizeof(bass_subgroups));
bt_addr_le_copy(&param.addr, &selected_addr);
param.adv_sid = selected_sid;
param.pa_interval = selected_pa_interval;
param.broadcast_id = selected_broadcast_id;
param.pa_sync = true;
param.subgroups = bass_subgroups;
/* TODO: Obtain the and set the correct subgroup information.
* See above in the broadcast audio source discovery part
* of the scan_recv_cb function.
*/
param.num_subgroups = 1;
param.subgroups = &subgroup;
/* Wait to receive subgroups */
err = k_sem_take(&sem_received_base_subgroups, K_FOREVER);
if (err != 0) {
printk("Failed to take sem_received_base_subgroups (err %d)\n", err);
return err;
}
k_mutex_lock(&base_store_mutex, K_FOREVER);
err = bt_bap_base_foreach_subgroup((const struct bt_bap_base *)received_base,
add_pa_sync_base_subgroup_cb, &param);
k_mutex_unlock(&base_store_mutex);
if (err < 0) {
printk("Could not add BASE to params %d\n", err);
continue;
}
err = bt_bap_broadcast_assistant_add_src(broadcast_sink_conn, &param);
if (err) {