samples: Bluetooth: Add broadcast audio sink sample

Add sample for the Basic Audio Profile (BAP) broadcast
audio sink role, that can scan for a receive audio
data over ISO from BAP broadcast sources.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2022-03-18 14:19:23 +01:00 committed by Carles Cufí
parent a84d2b2e1a
commit 9dd7efa4f1
7 changed files with 298 additions and 0 deletions

View file

@ -0,0 +1,11 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(unicast_audio_server)
target_sources(app PRIVATE
src/main.c
)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)

View file

@ -0,0 +1,24 @@
.. _bluetooth_broadcast_audio_sink:
Bluetooth: Broadcast Audio Sink
###############################
Overview
********
Application demonstrating the LE Audio broadcast sink functionality.
Starts by scanning for LE Audio broadcast sources and then synchronizes to
the first found and listens to it until the source is (potentially) stopped.
Requirements
************
* BlueZ running on the host, or
* A board with Bluetooth Low Energy 5.2 support
Building and Running
********************
This sample can be found under
:zephyr_file:`samples/bluetooth/broadcast_audio_sink` in the Zephyr tree.
See :ref:`bluetooth samples section <bluetooth-samples>` for details.

View file

@ -0,0 +1,2 @@
CONFIG_BT_CTLR_ADV_EXT=y
CONFIG_BT_CTLR_ADV_PERIODIC=y

View file

@ -0,0 +1,2 @@
CONFIG_BT_CTLR_ADV_EXT=y
CONFIG_BT_CTLR_ADV_PERIODIC=y

View file

@ -0,0 +1,6 @@
CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_AUDIO=y
CONFIG_BT_AUDIO_BROADCAST_SINK=y
CONFIG_BT_DEVICE_NAME="Broadcast Audio Sink"

View file

@ -0,0 +1,8 @@
sample:
description: Bluetooth Low Energy Audio Broadcast Sink sample
name: Bluetooth Low Energy Audio Broadcast Sink sample
tests:
sample.bluetooth.broadcast_audio_sink:
harness: bluetooth
platform_allow: qemu_cortex_m3 qemu_x86 nrf52_bsim
tags: bluetooth

View file

@ -0,0 +1,245 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <bluetooth/bluetooth.h>
#include <bluetooth/audio/audio.h>
#define SEM_TIMEOUT K_SECONDS(10)
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 struct bt_audio_broadcast_sink *broadcast_sink;
static struct bt_audio_stream streams[CONFIG_BT_AUDIO_BROADCAST_SNK_STREAM_COUNT];
/* Mandatory support preset by both source and sink */
static struct bt_audio_lc3_preset preset_16_2_1 = BT_AUDIO_LC3_BROADCAST_PRESET_16_2_1;
/* 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_audio_stream *stream)
{
printk("Stream %p started\n", stream);
}
static void stream_stopped_cb(struct bt_audio_stream *stream)
{
printk("Stream %p stopped\n", stream);
}
static void stream_recv_cb(struct bt_audio_stream *stream, 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);
}
}
struct bt_audio_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,
uint32_t broadcast_id)
{
k_sem_give(&sem_broadcaster_found);
return true;
}
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_audio_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_audio_broadcast_sink *sink,
const struct bt_audio_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;
k_sem_give(&sem_base_received);
}
static void syncable_cb(struct bt_audio_broadcast_sink *sink, bool encrypted)
{
if (encrypted) {
printk("Cannot sync to encrypted broadcast source\n");
return;
}
k_sem_give(&sem_syncable);
}
static void pa_sync_lost_cb(struct bt_audio_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_audio_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
};
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");
bt_audio_broadcast_sink_register_cb(&broadcast_sink_cbs);
for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) {
streams[i].ops = &stream_ops;
}
return 0;
}
static void reset(void)
{
bis_index_bitfield = 0U;
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);
if (broadcast_sink != NULL) {
bt_audio_broadcast_sink_delete(broadcast_sink);
broadcast_sink = NULL;
}
}
void main(void)
{
int err;
err = init();
if (err) {
printk("Init failed (err %d)\n", err);
return;
}
while (true) {
reset();
printk("Scanning for broadcast sources\n");
err = bt_audio_broadcast_sink_scan_start(BT_LE_SCAN_ACTIVE);
if (err != 0) {
printk("Unable to start scan for broadcast sources: %d\n",
err);
return;
}
/* TODO: Update K_FOREVER with a sane value, and handle error */
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");
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");
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");
k_sem_take(&sem_syncable, SEM_TIMEOUT);
if (err != 0) {
printk("sem_syncable timed out, resetting\n");
continue;
}
printk("Syncing to broadcast\n");
err = bt_audio_broadcast_sink_sync(broadcast_sink,
bis_index_bitfield,
streams,
&preset_16_2_1.codec, NULL);
if (err != 0) {
printk("Unable to sync to broadcast source: %d\n", err);
return;
}
printk("Waiting for PA disconnected\n");
k_sem_take(&sem_pa_sync_lost, K_FOREVER);
}
}