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:
parent
f23d06e344
commit
f1d174e0d3
2 changed files with 188 additions and 21 deletions
|
@ -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
|
||||
|
|
|
@ -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(¶m.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, ¶m);
|
||||
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, ¶m);
|
||||
if (err) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue