blob: d3a6087d0a869510d8a88115a52501bb51aa36c6 [file] [log] [blame]
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/pacs.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include <zephyr/bluetooth/audio/pbp.h>
#define AVAILABLE_SINK_CONTEXT (BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED | \
BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | \
BT_AUDIO_CONTEXT_TYPE_MEDIA | \
BT_AUDIO_CONTEXT_TYPE_GAME | \
BT_AUDIO_CONTEXT_TYPE_INSTRUCTIONAL)
#define SEM_TIMEOUT K_SECONDS(10)
#define PA_SYNC_SKIP 5
#define PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO 20 /* Set the timeout relative to interval */
#define INVALID_BROADCAST_ID 0xFFFFFFFF
static bool pbs_found;
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 void broadcast_scan_recv(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *ad);
static void broadcast_scan_timeout(void);
static void broadcast_pa_synced(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info);
static void broadcast_pa_recv(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_recv_info *info,
struct net_buf_simple *buf);
static void broadcast_pa_terminated(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_term_info *info);
static struct bt_le_scan_cb broadcast_scan_cb = {
.recv = broadcast_scan_recv,
.timeout = broadcast_scan_timeout
};
static struct bt_le_per_adv_sync_cb broadcast_sync_cb = {
.synced = broadcast_pa_synced,
.recv = broadcast_pa_recv,
.term = broadcast_pa_terminated,
};
static struct bt_bap_broadcast_sink *broadcast_sink;
static uint32_t bcast_id;
static struct bt_le_per_adv_sync *bcast_pa_sync;
static struct bt_bap_stream streams[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT];
struct bt_bap_stream *streams_p[ARRAY_SIZE(streams)];
static const struct bt_audio_codec_cap codec = BT_AUDIO_CODEC_CAP_LC3(
BT_AUDIO_CODEC_CAP_FREQ_48KHZ, BT_AUDIO_CODEC_CAP_DURATION_10,
BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u, (BT_AUDIO_CONTEXT_TYPE_MEDIA));
/* 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 bis_index_bitfield;
static void stream_started_cb(struct bt_bap_stream *stream)
{
printk("Stream %p started\n", stream);
}
static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason)
{
printk("Stream %p stopped with reason 0x%02X\n", stream, reason);
}
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 % 20U) == 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 struct bt_pacs_cap cap = {
.codec_cap = &codec,
};
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;
}
static void sync_broadcast_pa(const struct bt_le_scan_recv_info *info,
uint32_t broadcast_id)
{
struct bt_le_per_adv_sync_param param;
int err;
/* 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) {
printk("Could not stop scan: %d", err);
}
bt_addr_le_copy(&param.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(&param, &bcast_pa_sync);
if (err != 0) {
printk("Could not sync to PA: %d", err);
} else {
bcast_id = broadcast_id;
}
}
static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data)
{
enum bt_pbp_announcement_feature source_features;
uint32_t *broadcast_id = user_data;
struct bt_uuid_16 adv_uuid;
uint8_t *tmp_meta = NULL;
int ret;
if (data->type != BT_DATA_SVC_DATA16) {
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)) {
*broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16);
return true;
}
ret = bt_pbp_parse_announcement(data, &source_features, &tmp_meta);
if (ret >= 0) {
if (!(source_features & BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY)) {
/* This is a Standard Quality Public Broadcast Audio stream */
printk("This is a Standard Quality Public Broadcast Audio stream\n");
pbs_found = false;
return false;
}
printk("Found Suitable Public Broadcast Announcement with %d octets of metadata\n",
ret);
pbs_found = true;
/**
* Continue parsing if Broadcast Audio Announcement Service
* was not found.
*/
if (*broadcast_id == INVALID_BROADCAST_ID) {
return true;
}
}
return true;
}
static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *ad)
{
uint32_t broadcast_id;
pbs_found = false;
/* We are only interested in non-connectable periodic advertisers */
if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) || info->interval == 0) {
return;
}
broadcast_id = INVALID_BROADCAST_ID;
bt_data_parse(ad, scan_check_and_sync_broadcast, (void *)&broadcast_id);
if ((broadcast_id != INVALID_BROADCAST_ID) && pbs_found) {
sync_broadcast_pa(info, broadcast_id);
}
}
static void broadcast_scan_timeout(void)
{
printk("Broadcast scan timed out\n");
}
static bool pa_decode_base(struct bt_data *data, void *user_data)
{
const struct bt_bap_base *base = bt_bap_base_get_base_from_ad(data);
uint32_t base_bis_index_bitfield = 0U;
int err;
/* Base is NULL if the data does not contain a valid BASE */
if (base == NULL) {
return true;
}
err = bt_bap_base_get_bis_indexes(base, &base_bis_index_bitfield);
if (err != 0) {
return false;
}
bis_index_bitfield = base_bis_index_bitfield & bis_index_mask;
k_sem_give(&sem_base_received);
return false;
}
static void broadcast_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, pa_decode_base, NULL);
}
static void syncable_cb(struct bt_bap_broadcast_sink *sink, const struct bt_iso_biginfo *biginfo)
{
k_sem_give(&sem_syncable);
}
static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base,
size_t base_size)
{
k_sem_give(&sem_base_received);
}
static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = {
.syncable = syncable_cb,
.base_recv = base_recv_cb,
};
static void broadcast_pa_synced(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info)
{
if (sync == bcast_pa_sync) {
printk("PA sync %p synced for broadcast sink with broadcast ID 0x%06X\n",
sync, bcast_id);
k_sem_give(&sem_pa_synced);
}
}
static void broadcast_pa_terminated(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_term_info *info)
{
if (sync == bcast_pa_sync) {
printk("PA sync %p lost with reason %u\n", sync, info->reason);
bcast_pa_sync = NULL;
k_sem_give(&sem_pa_sync_lost);
}
}
static int reset(void)
{
if (broadcast_sink != NULL) {
int err = bt_bap_broadcast_sink_delete(broadcast_sink);
if (err) {
printk("Deleting broadcast sink failed (err %d)\n", err);
return err;
}
broadcast_sink = NULL;
}
k_sem_reset(&sem_pa_synced);
k_sem_reset(&sem_base_received);
k_sem_reset(&sem_syncable);
k_sem_reset(&sem_pa_sync_lost);
return 0;
}
int bap_broadcast_sink_init(void)
{
int err;
bt_bap_broadcast_sink_register_cb(&broadcast_sink_cbs);
bt_le_per_adv_sync_cb_register(&broadcast_sync_cb);
err = bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap);
if (err) {
printk("Capability register failed (err %d)\n", err);
return err;
}
for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) {
streams[i].ops = &stream_ops;
}
for (size_t i = 0U; i < ARRAY_SIZE(streams_p); i++) {
streams_p[i] = &streams[i];
}
return 0;
}
int bap_broadcast_sink_run(void)
{
while (true) {
int err = reset();
if (err != 0) {
printk("Resetting failed: %d - Aborting\n", err);
return err;
}
/* Register callbacks */
bt_le_scan_cb_register(&broadcast_scan_cb);
/* Start scanning */
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
if (err) {
printk("Scan start failed (err %d)\n", err);
return err;
}
/* Wait until a suitable source is found */
err = k_sem_take(&sem_pa_synced, K_FOREVER);
printk("Broadcast source PA synced, waiting for BASE\n");
/* Wait for BASE decode */
err = k_sem_take(&sem_base_received, SEM_TIMEOUT);
if (err != 0) {
printk("sem_base_received timed out\n");
return err;
}
/* Create broadcast sink */
printk("BASE received, creating broadcast sink\n");
err = bt_bap_broadcast_sink_create(bcast_pa_sync, bcast_id, &broadcast_sink);
if (err != 0) {
printk("bt_bap_broadcast_sink_create failed: %d\n", err);
return err;
}
k_sem_take(&sem_syncable, SEM_TIMEOUT);
if (err != 0) {
printk("sem_syncable timed out\n");
return err;
}
/* Sync to broadcast source */
printk("Syncing to broadcast\n");
err = bt_bap_broadcast_sink_sync(broadcast_sink, bis_index_bitfield,
streams_p, NULL);
if (err != 0) {
printk("Unable to sync to broadcast source: %d\n", err);
return err;
}
k_sem_take(&sem_pa_sync_lost, K_FOREVER);
}
return 0;
}
int main(void)
{
int err;
err = bt_enable(NULL);
if (err != 0) {
printk("Bluetooth init failed (err %d)\n", err);
return err;
}
printk("Bluetooth initialized\n");
printk("Initializing BAP Broadcast Sink\n");
err = bap_broadcast_sink_init();
if (err != 0) {
return err;
}
err = bap_broadcast_sink_run();
if (err != 0) {
return err;
}
return 0;
}