blob: 5107b8db9d19658dbe224aebb7fecd39c5984a7b [file] [log] [blame]
/*
* Copyright (c) 2022-2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/pacs.h>
BUILD_ASSERT(IS_ENABLED(CONFIG_SCAN_SELF) || IS_ENABLED(CONFIG_SCAN_OFFLOAD),
"Either SCAN_SELF or SCAN_OFFLOAD must be enabled");
#define SEM_TIMEOUT K_SECONDS(10)
#define BROADCAST_ASSISTANT_TIMEOUT K_SECONDS(120) /* 2 minutes */
#if defined(CONFIG_SCAN_SELF)
#define ADV_TIMEOUT K_SECONDS(CONFIG_SCAN_DELAY)
#else /* !CONFIG_SCAN_SELF */
#define ADV_TIMEOUT K_FOREVER
#endif /* CONFIG_SCAN_SELF */
#define SYNC_RETRY_COUNT 6 /* similar to retries for connections */
#define PA_SYNC_SKIP 5
static K_SEM_DEFINE(sem_connected, 0U, 1U);
static K_SEM_DEFINE(sem_disconnected, 0U, 1U);
static K_SEM_DEFINE(sem_broadcaster_found, 0U, 1U);
static K_SEM_DEFINE(sem_pa_synced, 0U, 1U);
static K_SEM_DEFINE(sem_base_received, 0U, 1U);
static K_SEM_DEFINE(sem_syncable, 0U, 1U);
static K_SEM_DEFINE(sem_pa_sync_lost, 0U, 1U);
static K_SEM_DEFINE(sem_broadcast_code_received, 0U, 1U);
static K_SEM_DEFINE(sem_pa_request, 0U, 1U);
static K_SEM_DEFINE(sem_past_request, 0U, 1U);
static K_SEM_DEFINE(sem_bis_sync_requested, 0U, 1U);
static K_SEM_DEFINE(sem_bis_synced, 0U, CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT);
/* Sample assumes that we only have a single Scan Delegator receive state */
static const struct bt_bap_scan_delegator_recv_state *sink_recv_state;
static struct bt_bap_broadcast_sink *broadcast_sink;
static struct bt_bap_stream streams[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT];
static struct bt_bap_stream *streams_p[ARRAY_SIZE(streams)];
static struct bt_conn *broadcast_assistant_conn;
static struct bt_le_ext_adv *ext_adv;
static struct bt_codec codec = BT_CODEC_LC3_CONFIG_16_2(BT_AUDIO_LOCATION_FRONT_LEFT,
BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED);
/* Create a mask for the maximum BIS we can sync to using the number of streams
* we have. We add an additional 1 since the bis indexes start from 1 and not
* 0.
*/
static const uint32_t bis_index_mask = BIT_MASK(ARRAY_SIZE(streams) + 1U);
static uint32_t requested_bis_sync;
static uint32_t bis_index_bitfield;
static uint8_t sink_broadcast_code[BT_AUDIO_BROADCAST_CODE_SIZE];
static void stream_started_cb(struct bt_bap_stream *stream)
{
printk("Stream %p started\n", stream);
k_sem_give(&sem_bis_synced);
}
static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason)
{
int err;
printk("Stream %p stopped with reason 0x%02X\n", stream, reason);
err = k_sem_take(&sem_bis_synced, K_NO_WAIT);
if (err != 0) {
printk("Failed to take sem_bis_synced: %d\n", err);
}
}
static void stream_recv_cb(struct bt_bap_stream *stream,
const struct bt_iso_recv_info *info,
struct net_buf *buf)
{
static uint32_t recv_cnt;
recv_cnt++;
if ((recv_cnt % 1000U) == 0U) {
printk("Received %u total ISO packets\n", recv_cnt);
}
}
static struct bt_bap_stream_ops stream_ops = {
.started = stream_started_cb,
.stopped = stream_stopped_cb,
.recv = stream_recv_cb
};
static bool scan_recv_cb(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *ad,
uint32_t broadcast_id)
{
if (broadcast_assistant_conn == NULL) {
/* Not requested by Broadcast Assistant */
k_sem_give(&sem_broadcaster_found);
return true;
} else if (sink_recv_state != NULL &&
bt_addr_le_eq(info->addr, &sink_recv_state->addr) &&
info->sid == sink_recv_state->adv_sid &&
broadcast_id == sink_recv_state->broadcast_id) {
k_sem_give(&sem_broadcaster_found);
return true;
}
return false;
}
static void scan_term_cb(int err)
{
if (err != 0) {
printk("Scan terminated with error: %d\n", err);
}
}
static void pa_synced_cb(struct bt_bap_broadcast_sink *sink,
struct bt_le_per_adv_sync *sync,
uint32_t broadcast_id)
{
if (broadcast_sink != NULL) {
printk("Unexpected PA sync\n");
return;
}
printk("PA synced for broadcast sink %p with broadcast ID 0x%06X\n",
sink, broadcast_id);
broadcast_sink = sink;
k_sem_give(&sem_pa_synced);
}
static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base)
{
uint32_t base_bis_index_bitfield = 0U;
if (k_sem_count_get(&sem_base_received) != 0U) {
return;
}
printk("Received BASE with %u subgroups from broadcast sink %p\n",
base->subgroup_count, sink);
for (size_t i = 0U; i < base->subgroup_count; i++) {
for (size_t j = 0U; j < base->subgroups[i].bis_count; j++) {
const uint8_t index = base->subgroups[i].bis_data[j].index;
base_bis_index_bitfield |= BIT(index);
}
}
bis_index_bitfield = base_bis_index_bitfield & bis_index_mask;
if (broadcast_assistant_conn == NULL) {
/* No broadcast assistant requesting anything */
requested_bis_sync = BT_BAP_BIS_SYNC_NO_PREF;
k_sem_give(&sem_bis_sync_requested);
}
k_sem_give(&sem_base_received);
}
static void syncable_cb(struct bt_bap_broadcast_sink *sink, bool encrypted)
{
k_sem_give(&sem_syncable);
if (!encrypted) {
/* Use the semaphore as a boolean */
k_sem_reset(&sem_broadcast_code_received);
k_sem_give(&sem_broadcast_code_received);
}
}
static void pa_sync_lost_cb(struct bt_bap_broadcast_sink *sink)
{
if (broadcast_sink == NULL) {
printk("Unexpected PA sync lost\n");
return;
}
printk("Sink %p disconnected\n", sink);
broadcast_sink = NULL;
k_sem_give(&sem_pa_sync_lost);
}
static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = {
.scan_recv = scan_recv_cb,
.scan_term = scan_term_cb,
.base_recv = base_recv_cb,
.syncable = syncable_cb,
.pa_synced = pa_synced_cb,
.pa_sync_lost = pa_sync_lost_cb
};
const struct bt_bap_scan_delegator_recv_state *broadcast_recv_state;
static void pa_timer_handler(struct k_work *work)
{
if (broadcast_recv_state != NULL) {
enum bt_bap_pa_state pa_state;
if (broadcast_recv_state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ) {
pa_state = BT_BAP_PA_STATE_NO_PAST;
} else {
pa_state = BT_BAP_PA_STATE_FAILED;
}
bt_bap_scan_delegator_set_pa_state(broadcast_recv_state->src_id,
pa_state);
}
printk("PA timeout\n");
}
static K_WORK_DELAYABLE_DEFINE(pa_timer, pa_timer_handler);
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 {
/* 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 */
pa_timeout = ((uint32_t)pa_interval * SYNC_RETRY_COUNT) / 10;
/* Enforce restraints */
pa_timeout = CLAMP(pa_timeout, BT_GAP_PER_ADV_MIN_TIMEOUT,
BT_GAP_PER_ADV_MAX_TIMEOUT);
}
return pa_timeout;
}
static int pa_sync_past(struct bt_conn *conn, uint16_t pa_interval)
{
struct bt_le_per_adv_sync_transfer_param param = { 0 };
int err;
param.skip = PA_SYNC_SKIP;
param.timeout = interval_to_sync_timeout(pa_interval);
err = bt_le_per_adv_sync_transfer_subscribe(conn, &param);
if (err != 0) {
printk("Could not do PAST subscribe: %d\n", err);
} else {
printk("Syncing with PAST: %d\n", err);
(void)k_work_reschedule(&pa_timer, K_MSEC(param.timeout * 10));
}
return err;
}
static int pa_sync_req_cb(struct bt_conn *conn,
const struct bt_bap_scan_delegator_recv_state *recv_state,
bool past_avail, uint16_t pa_interval)
{
int err;
sink_recv_state = recv_state;
broadcast_recv_state = recv_state;
if (recv_state->pa_sync_state == BT_BAP_PA_STATE_SYNCED ||
recv_state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ) {
/* Already syncing */
/* TODO: Terminate existing sync and then sync to new?*/
return -1;
}
if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER) && past_avail) {
err = pa_sync_past(conn, pa_interval);
k_sem_give(&sem_past_request);
} else {
/* start scan */
err = 0;
}
k_sem_give(&sem_pa_request);
return err;
}
static int pa_sync_term_req_cb(struct bt_conn *conn,
const struct bt_bap_scan_delegator_recv_state *recv_state)
{
int err;
sink_recv_state = recv_state;
err = bt_bap_broadcast_sink_delete(broadcast_sink);
if (err != 0) {
return err;
}
broadcast_sink = NULL;
return 0;
}
static void broadcast_code_cb(struct bt_conn *conn,
const struct bt_bap_scan_delegator_recv_state *recv_state,
const uint8_t broadcast_code[BT_AUDIO_BROADCAST_CODE_SIZE])
{
printk("Broadcast code received for %p\n", recv_state);
sink_recv_state = recv_state;
(void)memcpy(sink_broadcast_code, broadcast_code, BT_AUDIO_BROADCAST_CODE_SIZE);
/* Use the semaphore as a boolean */
k_sem_reset(&sem_broadcast_code_received);
k_sem_give(&sem_broadcast_code_received);
}
static int bis_sync_req_cb(struct bt_conn *conn,
const struct bt_bap_scan_delegator_recv_state *recv_state,
const uint32_t bis_sync_req[BT_BAP_SCAN_DELEGATOR_MAX_SUBGROUPS])
{
const bool bis_synced = k_sem_count_get(&sem_bis_synced) > 0U;
printk("BIS sync request received for %p: 0x%08x\n",
recv_state, bis_sync_req[0]);
/* We only care about a single subgroup in this sample */
if (bis_synced && requested_bis_sync != bis_sync_req[0]) {
/* If the BIS sync request is received while we are already
* synced, it means that the requested BIS sync has changed.
*/
int err;
/* The stream stopped callback will be called as part of this,
* and we do not need to wait for any events from the
* controller. Thus, when this returns, the `sem_bis_synced`
* is back to 0.
*/
err = bt_bap_broadcast_sink_stop(broadcast_sink);
if (err != 0) {
printk("Failed to stop Broadcast Sink: %d\n", err);
return err;
}
}
requested_bis_sync = bis_sync_req[0];
if (bis_sync_req[0] != 0) {
k_sem_give(&sem_bis_sync_requested);
}
return 0;
}
static struct bt_bap_scan_delegator_cb scan_delegator_cbs = {
.pa_sync_req = pa_sync_req_cb,
.pa_sync_term_req = pa_sync_term_req_cb,
.broadcast_code = broadcast_code_cb,
.bis_sync_req = bis_sync_req_cb,
};
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0U) {
printk("Failed to connect to %s (%u)\n", addr, err);
broadcast_assistant_conn = NULL;
return;
}
printk("Connected: %s\n", addr);
broadcast_assistant_conn = bt_conn_ref(conn);
k_sem_give(&sem_connected);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != broadcast_assistant_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(broadcast_assistant_conn);
broadcast_assistant_conn = NULL;
k_sem_give(&sem_disconnected);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
static struct bt_pacs_cap cap = {
.codec = &codec,
};
static int init(void)
{
int err;
err = bt_enable(NULL);
if (err) {
printk("Bluetooth enable failed (err %d)\n", err);
return err;
}
printk("Bluetooth initialized\n");
err = bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap);
if (err) {
printk("Capability register failed (err %d)\n", err);
return err;
}
bt_bap_broadcast_sink_register_cb(&broadcast_sink_cbs);
bt_bap_scan_delegator_register_cb(&scan_delegator_cbs);
for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) {
streams[i].ops = &stream_ops;
}
return 0;
}
static int reset(void)
{
int err;
bis_index_bitfield = 0U;
requested_bis_sync = 0U;
sink_recv_state = NULL;
(void)memset(sink_broadcast_code, 0, sizeof(sink_broadcast_code));
if (broadcast_sink != NULL) {
err = bt_bap_broadcast_sink_delete(broadcast_sink);
if (err) {
printk("Deleting broadcast sink failed (err %d)\n", err);
return err;
}
broadcast_sink = NULL;
}
if (IS_ENABLED(CONFIG_SCAN_OFFLOAD)) {
if (broadcast_assistant_conn != NULL) {
err = bt_conn_disconnect(broadcast_assistant_conn,
BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err) {
printk("Disconnecting Broadcast Assistant failed (err %d)\n",
err);
return err;
}
err = k_sem_take(&sem_disconnected, SEM_TIMEOUT);
if (err != 0) {
printk("Failed to take sem_disconnected: %d\n", err);
return err;
}
} else if (ext_adv != NULL) { /* advertising still running */
err = bt_le_ext_adv_stop(ext_adv);
if (err) {
printk("Stopping advertising set failed (err %d)\n",
err);
return err;
}
err = bt_le_ext_adv_delete(ext_adv);
if (err) {
printk("Deleting advertising set failed (err %d)\n",
err);
return err;
}
ext_adv = NULL;
}
k_sem_reset(&sem_connected);
k_sem_reset(&sem_disconnected);
k_sem_reset(&sem_pa_request);
k_sem_reset(&sem_past_request);
}
k_sem_reset(&sem_broadcaster_found);
k_sem_reset(&sem_pa_synced);
k_sem_reset(&sem_base_received);
k_sem_reset(&sem_syncable);
k_sem_reset(&sem_pa_sync_lost);
k_sem_reset(&sem_broadcast_code_received);
k_sem_reset(&sem_bis_sync_requested);
k_sem_reset(&sem_bis_synced);
return 0;
}
static int start_adv(void)
{
const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_ALL,
BT_UUID_16_ENCODE(BT_UUID_BASS_VAL),
BT_UUID_16_ENCODE(BT_UUID_PACS_VAL)),
};
int err;
/* Create a non-connectable non-scannable advertising set */
err = bt_le_ext_adv_create(BT_LE_EXT_ADV_CONN_NAME, NULL, &ext_adv);
if (err != 0) {
printk("Failed to create advertising set (err %d)\n", err);
return err;
}
err = bt_le_ext_adv_set_data(ext_adv, ad, ARRAY_SIZE(ad), NULL, 0);
if (err != 0) {
printk("Failed to set advertising data (err %d)\n", err);
return err;
}
err = bt_le_ext_adv_start(ext_adv, BT_LE_EXT_ADV_START_DEFAULT);
if (err != 0) {
printk("Failed to start advertising set (err %d)\n", err);
return err;
}
return 0;
}
static int stop_adv(void)
{
int err;
err = bt_le_ext_adv_stop(ext_adv);
if (err != 0) {
printk("Failed to stop advertising set (err %d)\n", err);
return err;
}
err = bt_le_ext_adv_delete(ext_adv);
if (err != 0) {
printk("Failed to delete advertising set (err %d)\n", err);
return err;
}
ext_adv = NULL;
return 0;
}
int main(void)
{
int err;
err = init();
if (err) {
printk("Init failed (err %d)\n", err);
return 0;
}
for (size_t i = 0U; i < ARRAY_SIZE(streams_p); i++) {
streams_p[i] = &streams[i];
}
while (true) {
err = reset();
if (err != 0) {
printk("Resetting failed: %d - Aborting\n", err);
return 0;
}
if (IS_ENABLED(CONFIG_SCAN_OFFLOAD)) {
printk("Starting advertising\n");
err = start_adv();
if (err != 0) {
printk("Unable to start advertising connectable: %d\n",
err);
return 0;
}
printk("Waiting for Broadcast Assistant\n");
err = k_sem_take(&sem_connected, ADV_TIMEOUT);
if (err != 0) {
printk("No Broadcast Assistant connected\n");
err = stop_adv();
if (err != 0) {
printk("Unable to stop advertising: %d\n",
err);
return 0;
}
} else {
/* Wait for the PA request to determine if we
* should start scanning, or wait for PAST
*/
err = k_sem_take(&sem_pa_request,
BROADCAST_ASSISTANT_TIMEOUT);
if (err != 0) {
printk("sem_pa_request timed out, resetting\n");
continue;
}
if (k_sem_take(&sem_past_request, K_NO_WAIT) == 0) {
goto wait_for_pa_sync;
} /* else continue with scanning below */
}
}
printk("Scanning for broadcast sources\n");
err = bt_bap_broadcast_sink_scan_start(BT_LE_SCAN_ACTIVE);
if (err != 0 && err != -EALREADY) {
printk("Unable to start scan for broadcast sources: %d\n",
err);
return 0;
}
err = k_sem_take(&sem_broadcaster_found, SEM_TIMEOUT);
if (err != 0) {
printk("sem_broadcaster_found timed out, resetting\n");
continue;
}
printk("Broadcast source found, waiting for PA sync\n");
wait_for_pa_sync:
err = k_sem_take(&sem_pa_synced, SEM_TIMEOUT);
if (err != 0) {
printk("sem_pa_synced timed out, resetting\n");
continue;
}
printk("Broadcast source PA synced, waiting for BASE\n");
err = k_sem_take(&sem_base_received, SEM_TIMEOUT);
if (err != 0) {
printk("sem_base_received timed out, resetting\n");
continue;
}
printk("BASE received, waiting for syncable\n");
err = k_sem_take(&sem_syncable, SEM_TIMEOUT);
if (err != 0) {
printk("sem_syncable timed out, resetting\n");
continue;
}
/* sem_broadcast_code_received is also given if the
* broadcast is not encrypted
*/
printk("Waiting for broadcast code OK\n");
err = k_sem_take(&sem_broadcast_code_received, SEM_TIMEOUT);
if (err != 0) {
printk("sem_syncable timed out, resetting\n");
continue;
}
printk("Waiting for BIS sync request\n");
err = k_sem_take(&sem_bis_sync_requested, SEM_TIMEOUT);
if (err != 0) {
printk("sem_syncable timed out, resetting\n");
continue;
}
printk("Syncing to broadcast\n");
err = bt_bap_broadcast_sink_sync(broadcast_sink,
bis_index_bitfield & requested_bis_sync,
streams_p, sink_broadcast_code);
if (err != 0) {
printk("Unable to sync to broadcast source: %d\n", err);
return 0;
}
printk("Waiting for PA disconnected\n");
k_sem_take(&sem_pa_sync_lost, K_FOREVER);
}
return 0;
}