From 9dd7efa4f1574813c61fb41f2aa768469ec6b72d Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Fri, 18 Mar 2022 14:19:23 +0100 Subject: [PATCH] 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 --- .../broadcast_audio_sink/CMakeLists.txt | 11 + .../bluetooth/broadcast_audio_sink/README.rst | 24 ++ .../boards/nrf52840dk_nrf52840.conf | 2 + .../boards/nrf5340dk_nrf5340_cpuapp.conf | 2 + .../bluetooth/broadcast_audio_sink/prj.conf | 6 + .../broadcast_audio_sink/sample.yaml | 8 + .../bluetooth/broadcast_audio_sink/src/main.c | 245 ++++++++++++++++++ 7 files changed, 298 insertions(+) create mode 100644 samples/bluetooth/broadcast_audio_sink/CMakeLists.txt create mode 100644 samples/bluetooth/broadcast_audio_sink/README.rst create mode 100644 samples/bluetooth/broadcast_audio_sink/boards/nrf52840dk_nrf52840.conf create mode 100644 samples/bluetooth/broadcast_audio_sink/boards/nrf5340dk_nrf5340_cpuapp.conf create mode 100644 samples/bluetooth/broadcast_audio_sink/prj.conf create mode 100644 samples/bluetooth/broadcast_audio_sink/sample.yaml create mode 100644 samples/bluetooth/broadcast_audio_sink/src/main.c diff --git a/samples/bluetooth/broadcast_audio_sink/CMakeLists.txt b/samples/bluetooth/broadcast_audio_sink/CMakeLists.txt new file mode 100644 index 0000000000..2b20af75d5 --- /dev/null +++ b/samples/bluetooth/broadcast_audio_sink/CMakeLists.txt @@ -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) diff --git a/samples/bluetooth/broadcast_audio_sink/README.rst b/samples/bluetooth/broadcast_audio_sink/README.rst new file mode 100644 index 0000000000..68aeffd741 --- /dev/null +++ b/samples/bluetooth/broadcast_audio_sink/README.rst @@ -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 ` for details. diff --git a/samples/bluetooth/broadcast_audio_sink/boards/nrf52840dk_nrf52840.conf b/samples/bluetooth/broadcast_audio_sink/boards/nrf52840dk_nrf52840.conf new file mode 100644 index 0000000000..f294338330 --- /dev/null +++ b/samples/bluetooth/broadcast_audio_sink/boards/nrf52840dk_nrf52840.conf @@ -0,0 +1,2 @@ +CONFIG_BT_CTLR_ADV_EXT=y +CONFIG_BT_CTLR_ADV_PERIODIC=y diff --git a/samples/bluetooth/broadcast_audio_sink/boards/nrf5340dk_nrf5340_cpuapp.conf b/samples/bluetooth/broadcast_audio_sink/boards/nrf5340dk_nrf5340_cpuapp.conf new file mode 100644 index 0000000000..f294338330 --- /dev/null +++ b/samples/bluetooth/broadcast_audio_sink/boards/nrf5340dk_nrf5340_cpuapp.conf @@ -0,0 +1,2 @@ +CONFIG_BT_CTLR_ADV_EXT=y +CONFIG_BT_CTLR_ADV_PERIODIC=y diff --git a/samples/bluetooth/broadcast_audio_sink/prj.conf b/samples/bluetooth/broadcast_audio_sink/prj.conf new file mode 100644 index 0000000000..22e4e996d7 --- /dev/null +++ b/samples/bluetooth/broadcast_audio_sink/prj.conf @@ -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" diff --git a/samples/bluetooth/broadcast_audio_sink/sample.yaml b/samples/bluetooth/broadcast_audio_sink/sample.yaml new file mode 100644 index 0000000000..7a7b304ed5 --- /dev/null +++ b/samples/bluetooth/broadcast_audio_sink/sample.yaml @@ -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 diff --git a/samples/bluetooth/broadcast_audio_sink/src/main.c b/samples/bluetooth/broadcast_audio_sink/src/main.c new file mode 100644 index 0000000000..c00fb8ce5f --- /dev/null +++ b/samples/bluetooth/broadcast_audio_sink/src/main.c @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#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); + } +}