From 265b80e5d29f0d1b300aefb6624740bab47c8b5c Mon Sep 17 00:00:00 2001 From: Daniela Andreea Dumitrache Date: Tue, 25 Jul 2023 13:14:18 +0300 Subject: [PATCH] Bluetooth: Audio: Add implementation for PBP and 2 sample applications. PBP API allows sources to create a Public Broadcast Announcement. PBP API allows sinks to scan for a Public Broadcast Announcement which fulfills some given requirements. public_broadcast_source application starts extended advertising and includes a Public Broadcast Announcement. The advertised broadcast audio stream quality will cycle between high and standard quality. public_broadcast_sink application scans for broadcast sources and synchronizes to the first found source which defines a Public Broadcast Announcement including a High Quality Public Broadcast Audio Stream configuration. Add bsim tests for Public Broadcast Profile APIs. Signed-off-by: Daniela Andreea Dumitrache --- include/zephyr/bluetooth/audio/pbp.h | 85 ++++ .../public_broadcast_sink/CMakeLists.txt | 12 + .../public_broadcast_sink/README.rst | 24 + .../bluetooth/public_broadcast_sink/prj.conf | 33 ++ .../public_broadcast_sink/sample.yaml | 15 + .../src/bap_broadcast_sink.c | 412 ++++++++++++++++ .../public_broadcast_sink/src/main.c | 51 ++ .../src/public_broadcast_sink.h | 21 + .../public_broadcast_source/CMakeLists.txt | 12 + .../public_broadcast_source/README.rst | 24 + .../public_broadcast_source/prj.conf | 24 + .../public_broadcast_source/sample.yaml | 14 + .../src/cap_initiator.c | 382 +++++++++++++++ .../public_broadcast_source/src/main.c | 55 +++ .../src/public_broadcast_source.h | 20 + subsys/bluetooth/audio/CMakeLists.txt | 1 + subsys/bluetooth/audio/Kconfig | 1 + subsys/bluetooth/audio/Kconfig.pbp | 25 + subsys/bluetooth/audio/pbp.c | 188 ++++++++ tests/bsim/bluetooth/audio/prj.conf | 3 + tests/bsim/bluetooth/audio/src/main.c | 4 + .../audio/src/public_broadcast_sink_test.c | 449 ++++++++++++++++++ .../audio/src/public_broadcast_source_test.c | 380 +++++++++++++++ .../bsim/bluetooth/audio/test_scripts/pbp.sh | 28 ++ 24 files changed, 2263 insertions(+) create mode 100644 include/zephyr/bluetooth/audio/pbp.h create mode 100644 samples/bluetooth/public_broadcast_sink/CMakeLists.txt create mode 100644 samples/bluetooth/public_broadcast_sink/README.rst create mode 100644 samples/bluetooth/public_broadcast_sink/prj.conf create mode 100644 samples/bluetooth/public_broadcast_sink/sample.yaml create mode 100644 samples/bluetooth/public_broadcast_sink/src/bap_broadcast_sink.c create mode 100644 samples/bluetooth/public_broadcast_sink/src/main.c create mode 100644 samples/bluetooth/public_broadcast_sink/src/public_broadcast_sink.h create mode 100644 samples/bluetooth/public_broadcast_source/CMakeLists.txt create mode 100644 samples/bluetooth/public_broadcast_source/README.rst create mode 100644 samples/bluetooth/public_broadcast_source/prj.conf create mode 100644 samples/bluetooth/public_broadcast_source/sample.yaml create mode 100644 samples/bluetooth/public_broadcast_source/src/cap_initiator.c create mode 100644 samples/bluetooth/public_broadcast_source/src/main.c create mode 100644 samples/bluetooth/public_broadcast_source/src/public_broadcast_source.h create mode 100644 subsys/bluetooth/audio/Kconfig.pbp create mode 100644 subsys/bluetooth/audio/pbp.c create mode 100644 tests/bsim/bluetooth/audio/src/public_broadcast_sink_test.c create mode 100644 tests/bsim/bluetooth/audio/src/public_broadcast_source_test.c create mode 100644 tests/bsim/bluetooth/audio/test_scripts/pbp.sh diff --git a/include/zephyr/bluetooth/audio/pbp.h b/include/zephyr/bluetooth/audio/pbp.h new file mode 100644 index 000000000000000..ee2e597185ab72a --- /dev/null +++ b/include/zephyr/bluetooth/audio/pbp.h @@ -0,0 +1,85 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_PBP_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_PBP_ + +#include + +#include + +/** @brief PBP Roles */ +enum bt_pbp_role { + BT_PBP_ROLE_PBS = BIT(0), /* Public Broadcast Source */ + BT_PBP_ROLE_PBK = BIT(1), /* Public Broadcast Sink */ + BT_PBP_ROLE_PBA = BIT(2), /* Public Broadcast Assistant */ +}; + +enum bt_pbp_encryption { + BT_PBP_STREAMS_NOT_ENCRYPTED = 0, + BT_PBP_STREAMS_ENCRYPTED = 1, +}; + +enum bt_pbp_quality { + BT_PBP_AUDIO_CONFIG_NOT_PRESENT = 0, + BT_PBP_AUDIO_CONFIG_PRESENT = 1, +}; + +enum bt_pbp_announcement_features { + BT_PBP_ANNOUNCEMENT_FEATURE_ENCRYPTION = BIT(0), + BT_PBP_ANNOUNCEMENT_FEATURE_STANDARD_QUALITY = BIT(1), + BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY = BIT(2), +}; + +/** + * @brief Sets the received PBP role(s) and Public Broadcast Announcement features. + * + * @param role PBP role(s) of the device (one or multiple). + * @param pba_params Public Broadcast Announcement features + * + */ +void bt_pbp_init_source(enum bt_pbp_role role, uint8_t pba_params); + +/** + * @brief Sets the received PBP role(s). + * + * @param role PBP role(s) of the device (one or multiple). + * + */ +void bt_pbp_set_role(enum bt_pbp_role role); + +/** + * @brief Creates a Public Broadcast Announcement based on the information received + * in bt_pbp_init_source(). + * + * @param meta_count Number of metadata fields to be included in the advertising data + * @param meta Metadata to be included in the advertising data + * @param bba_adv_data Advertising data structure to include the Public Broadcast Announcement + * + * @return 0 on success or an appropriate error code. + */ +int bt_pbp_create_public_broadcast_announcement(size_t meta_count, + const struct bt_audio_codec_data meta[], struct bt_data *pba_adv_data); + +/** + * @brief Parses the received advertising data and looks for the Public Broadcast + * Announcement Service UUID. If found, it checks the received stream configuration + * preferences against the ones advertised by the source. + * + * @param ad Advertising data to be checked + * @param sink_config_preferences Sink stream configuration preferences + * + * @return true if the stream fits the sink's preferences, false otherwise + */ +bool bt_pbp_parse_public_broadcast_announcement(struct bt_data *ad, + uint8_t sink_config_preferences); + +/** + * @brief Clear the data previously set for a Public Broadcast Announcement. + */ +void bt_pbp_clear_data_buffer(void); + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_PBP_ */ diff --git a/samples/bluetooth/public_broadcast_sink/CMakeLists.txt b/samples/bluetooth/public_broadcast_sink/CMakeLists.txt new file mode 100644 index 000000000000000..786c988e5bc2f17 --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(pbp_broadcast_sink) + +target_sources(app PRIVATE + src/main.c + src/bap_broadcast_sink.c +) + +zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) diff --git a/samples/bluetooth/public_broadcast_sink/README.rst b/samples/bluetooth/public_broadcast_sink/README.rst new file mode 100644 index 000000000000000..486abb675e2c2fe --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/README.rst @@ -0,0 +1,24 @@ +.. _bluetooth_public_broadcast_sink: + +Bluetooth: Public Broadcast Sink +################################ + +Overview +******** + +Application demonstrating the LE Public Broadcast Profile sink functionality. +Starts by scanning for LE Audio broadcast sources and then synchronizes to +the first found source which defines a Public Broadcast Announcement including +a High Quality Public Broadcast Audio Stream configuration. + +Requirements +************ + +* A board with Bluetooth Low Energy 5.2 support + +Building and Running +******************** +This sample can be found under +:zephyr_file:`samples/bluetooth/public_broadcast_sink` in the Zephyr tree. + +See :ref:`bluetooth samples section ` for details. diff --git a/samples/bluetooth/public_broadcast_sink/prj.conf b/samples/bluetooth/public_broadcast_sink/prj.conf new file mode 100644 index 000000000000000..9ec2c5e7b558c91 --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/prj.conf @@ -0,0 +1,33 @@ +CONFIG_BT=y +CONFIG_LOG=y +CONFIG_BT_PAC_SNK=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_AUDIO=y +CONFIG_UTF8=y + +CONFIG_BT_SMP=y +CONFIG_BT_KEYS_OVERWRITE_OLDEST=y +CONFIG_BT_L2CAP_TX_BUF_COUNT=20 + +# CAP +CONFIG_BT_CAP_ACCEPTOR=y + +# BAP support +CONFIG_BT_BAP_SCAN_DELEGATOR=y +CONFIG_BT_BAP_BROADCAST_SINK=y +CONFIG_BT_BAP_BROADCAST_SNK_SUBGROUP_COUNT=1 +CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT=1 + +# Support an ISO channel per ASE +CONFIG_BT_ISO_MAX_CHAN=2 + +# Sink PAC Location Support +CONFIG_BT_PAC_SNK_LOC=y + +# Generic config +CONFIG_BT_EXT_ADV=y +CONFIG_BT_DEVICE_NAME="PBP Broadcast Sink" + +# PBP Support +CONFIG_BT_PBP=y + diff --git a/samples/bluetooth/public_broadcast_sink/sample.yaml b/samples/bluetooth/public_broadcast_sink/sample.yaml new file mode 100644 index 000000000000000..66f4d06bc85a69f --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/sample.yaml @@ -0,0 +1,15 @@ +sample: + description: Bluetooth Low Energy Audio PBP Broadcast Sink sample + name: Bluetooth Low Energy Audio PBP Broadcast Sink sample +tests: + sample.bluetooth.public_broadcast_sink: + harness: bluetooth + platform_allow: + - qemu_cortex_m3 + - qemu_x86 + - nrf5340dk_nrf5340_cpuapp + integration_platforms: + - qemu_x86 + - nrf5340dk_nrf5340_cpuapp + tags: bluetooth + diff --git a/samples/bluetooth/public_broadcast_sink/src/bap_broadcast_sink.c b/samples/bluetooth/public_broadcast_sink/src/bap_broadcast_sink.c new file mode 100644 index 000000000000000..e2c78cd1c29f5ad --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/src/bap_broadcast_sink.c @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2022-2023 Nordic Semiconductor ASA + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#include +#include +#include +#include +#include +#include + +#define SEM_TIMEOUT K_SECONDS(10) +#define PA_SYNC_SKIP 5 +#define SYNC_RETRY_COUNT 6 /* similar to retries for connections */ +#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_sink_created, 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 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, +}; + +static struct bt_bap_broadcast_sink *broadcast_sink; +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 struct bt_audio_codec_cap codec = BT_AUDIO_CODEC_LC3( + BT_AUDIO_CODEC_LC3_FREQ_48KHZ, + BT_AUDIO_CODEC_LC3_DURATION_10, BT_AUDIO_CODEC_LC3_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 uint32_t broadcast_id = INVALID_BROADCAST_ID; + +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; + uint16_t timeout; + + /* 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 */ + interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(interval); + timeout = (interval_ms * SYNC_RETRY_COUNT) / 10; + + /* Enforce restraints */ + timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, + BT_GAP_PER_ADV_MAX_TIMEOUT); + + return timeout; +} + +static void sync_broadcast_pa(const struct bt_le_scan_recv_info *info) +{ + 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(¶m.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(¶m, &bcast_pa_sync); + if (err != 0) { + printk("Could not sync to PA: %d", err); + + } +} + +static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data) +{ + struct bt_uuid_16 adv_uuid; + uint8_t sink_config_preferences = BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY; + + bool pba_sync = bt_pbp_parse_public_broadcast_announcement(data, sink_config_preferences); + + if (pba_sync) { + printk("Found Suitable Public Broadcast Announcement\n"); + pbs_found = true; + + /* Continue parsing if Broadcast Audio Announcement Service was not yet found */ + if (broadcast_id == INVALID_BROADCAST_ID) { + return true; + } + return false; + } + + 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)) { + /* Save broadcast_id */ + if (broadcast_id == INVALID_BROADCAST_ID) { + broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16); + } + + /* Found Broadcast Audio and Public Broadcast Announcement Services */ + if (pbs_found) { + return false; + } + } + + /* Continue parsing */ + return true; +} + +static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *ad) +{ + struct net_buf_simple_state state; + + 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; + } + + net_buf_simple_save(ad, &state); + bt_data_parse(ad, scan_check_and_sync_broadcast, (void *)&broadcast_id); + net_buf_simple_restore(ad, &state); + + if ((broadcast_id != INVALID_BROADCAST_ID) && pbs_found) { + sync_broadcast_pa(info); + } +} + +static void broadcast_scan_timeout(void) +{ + printk("Broadcast scan timed out\n"); +} + +static bool pa_decode_base(struct bt_data *data, void *user_data) +{ + struct bt_bap_base base = { 0 }; + uint32_t base_bis_index_bitfield = 0U; + + if (data->type != BT_DATA_SVC_DATA16) { + return true; + } + + if (data->data_len < BT_BAP_BASE_MIN_SIZE) { + return true; + } + + if (bt_bap_decode_base(data, &base) != 0) { + return false; + } + + 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); + + 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 broadcast_pa_synced(struct bt_le_per_adv_sync *sync, + struct bt_le_per_adv_sync_synced_info *info) +{ + k_sem_give(&sem_pa_synced); +} + +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_sink_created); +} + +static void syncable_cb(struct bt_bap_broadcast_sink *sink, bool encrypted) +{ + k_sem_give(&sem_syncable); +} + +static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base) +{ + k_sem_give(&sem_base_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 = { + .pa_synced = pa_synced_cb, + .syncable = syncable_cb, + .base_recv = base_recv_cb, + .pa_sync_lost = pa_sync_lost_cb +}; + +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_sink_created); + 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 for PA sync */ + err = k_sem_take(&sem_pa_synced, K_FOREVER); + if (err != 0) { + printk("sem_pa_synced timed out\n"); + return err; + } + 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, broadcast_id); + err = k_sem_take(&sem_sink_created, SEM_TIMEOUT); + if (err != 0) { + printk("sem_sink_created timed out\n"); + 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; +} diff --git a/samples/bluetooth/public_broadcast_sink/src/main.c b/samples/bluetooth/public_broadcast_sink/src/main.c new file mode 100644 index 000000000000000..c7d0f3518fc3e12 --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/src/main.c @@ -0,0 +1,51 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include "public_broadcast_sink.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) + + +int main(void) +{ + int err; + enum bt_pbp_role role = BT_PBP_ROLE_PBK; + + err = bt_enable(NULL); + if (err != 0) { + printk("Bluetooth init failed (err %d)\n", err); + return err; + } + + printk("Bluetooth initialized\n"); + + printk("Initializing PBP and setting role\n"); + bt_pbp_set_role(role); + + 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; +} diff --git a/samples/bluetooth/public_broadcast_sink/src/public_broadcast_sink.h b/samples/bluetooth/public_broadcast_sink/src/public_broadcast_sink.h new file mode 100644 index 000000000000000..7655eee32e55a14 --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/src/public_broadcast_sink.h @@ -0,0 +1,21 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/** + * @brief Initialize BAP Broadcast Sink + * + * @return 0 if success, errno on failure. + */ +int bap_broadcast_sink_init(void); + +/** + * @brief Run BAP Broadcast Sink + * + * @return 0 if success, errno on failure. + */ +int bap_broadcast_sink_run(void); diff --git a/samples/bluetooth/public_broadcast_source/CMakeLists.txt b/samples/bluetooth/public_broadcast_source/CMakeLists.txt new file mode 100644 index 000000000000000..35ae2120155417c --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(public_broadcast_source) + +target_sources(app PRIVATE + src/main.c + src/cap_initiator.c +) + +zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) diff --git a/samples/bluetooth/public_broadcast_source/README.rst b/samples/bluetooth/public_broadcast_source/README.rst new file mode 100644 index 000000000000000..a7c01332bfe3d22 --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/README.rst @@ -0,0 +1,24 @@ +.. _bluetooth_public_broadcast_source: + +Bluetooth: Public Broadcast Source +################################## + +Overview +******** + +Application demonstrating the LE LE Public Broadcast Profile source functionality. +Will start advertising extended advertising and includes a Public Audio Announcement. +The advertised broadcast audio stream quality will cycle between high and standard quality +every 15 seconds. + +Requirements +************ + +* A board with Bluetooth Low Energy 5.2 support + +Building and Running +******************** +This sample can be found under +:zephyr_file:`samples/bluetooth/public_broadcast_source` in the Zephyr tree. + +See :ref:`bluetooth samples section ` for details. diff --git a/samples/bluetooth/public_broadcast_source/prj.conf b/samples/bluetooth/public_broadcast_source/prj.conf new file mode 100644 index 000000000000000..3ee51fcd2ae1dfc --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/prj.conf @@ -0,0 +1,24 @@ +CONFIG_MAIN_STACK_SIZE=2048 + +CONFIG_BT=y +CONFIG_LOG=y +CONFIG_BT_AUDIO=y + +CONFIG_BT_ISO_MAX_CHAN=2 +CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT=2 +CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT=2 +CONFIG_BT_ISO_TX_BUF_COUNT=4 + +# PBP support +CONFIG_BT_PBP=y + +# CAP support +CONFIG_BT_CAP_INITIATOR=y + +# BAP support +CONFIG_BT_BAP_BROADCAST_SOURCE=y +CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT=1 +CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT=1 + +CONFIG_BT_EXT_ADV=y +CONFIG_BT_DEVICE_NAME="PBS" diff --git a/samples/bluetooth/public_broadcast_source/sample.yaml b/samples/bluetooth/public_broadcast_source/sample.yaml new file mode 100644 index 000000000000000..4aa6667651ff0e7 --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/sample.yaml @@ -0,0 +1,14 @@ +sample: + description: Bluetooth Low Energy Public Broadcast Source sample + name: Bluetooth Low Energy Public Broadcast Source sample +tests: + sample.bluetooth.public_broadcast_source: + harness: bluetooth + platform_allow: + - qemu_cortex_m3 + - qemu_x86 + - nrf5340dk_nrf5340_cpuapp + integration_platforms: + - qemu_x86 + - nrf5340dk_nrf5340_cpuapp + tags: bluetooth diff --git a/samples/bluetooth/public_broadcast_source/src/cap_initiator.c b/samples/bluetooth/public_broadcast_source/src/cap_initiator.c new file mode 100644 index 000000000000000..844de8bc66f5554 --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/src/cap_initiator.c @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2022-2023 Nordic Semiconductor ASA + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define BROADCAST_ENQUEUE_COUNT 2U +#define MOCK_CCID 0x1234 +NET_BUF_POOL_FIXED_DEFINE(tx_pool, + (BROADCAST_ENQUEUE_COUNT * CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT), + BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), 8, NULL); + +static K_SEM_DEFINE(sem_broadcast_started, 0, 1); +static K_SEM_DEFINE(sem_broadcast_stopped, 0, 1); + +static struct bt_cap_stream broadcast_source_stream; +static struct bt_cap_stream *broadcast_stream; + +struct bt_audio_codec_data bis_codec_data = BT_AUDIO_CODEC_DATA( + BT_AUDIO_CODEC_CONFIG_LC3_FREQ, BT_AUDIO_CODEC_CONFIG_LC3_FREQ_48KHZ); + +const struct bt_audio_codec_data new_metadata[] = { + BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT, + (BT_AUDIO_CONTEXT_TYPE_MEDIA & 0xFFU), + ((BT_AUDIO_CONTEXT_TYPE_MEDIA >> 8) & 0xFFU)), + BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_CCID_LIST, + (MOCK_CCID & 0xFFU), + ((MOCK_CCID >> 8) & 0xFFU)) +}; + +const struct bt_audio_codec_data pba_metadata[] = { + BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_PROGRAM_INFO, + "PBS DEMO") +}; + +static struct bt_bap_lc3_preset broadcast_preset_48_2_1 = + BT_BAP_LC3_UNICAST_PRESET_48_2_1(BT_AUDIO_LOCATION_FRONT_LEFT, + BT_AUDIO_CONTEXT_TYPE_MEDIA); + +struct bt_cap_initiator_broadcast_stream_param stream_params; +struct bt_cap_initiator_broadcast_subgroup_param subgroup_param; +struct bt_cap_initiator_broadcast_create_param create_param; +struct bt_cap_broadcast_source *broadcast_source; +struct bt_le_ext_adv *adv; + +static const char broadcast_name[] = "PBP Source Demo"; + +static uint8_t appearance_addata[] = { + (((BT_APPEARANCE_AUDIO_SOURCE_BROADCASTING_DEVICE) >> 0) & 0xFFU), + (((BT_APPEARANCE_AUDIO_SOURCE_BROADCASTING_DEVICE) >> 8) & 0xFFU) +}; + +static void broadcast_started_cb(struct bt_bap_stream *stream) +{ + printk("Stream %p started\n", stream); + k_sem_give(&sem_broadcast_started); +} + +static void broadcast_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) +{ + if (reason == 0x16) { + printk("Stream %p ended\n", stream); + } else { + printk("Stream %p stopped with reason 0x%02X\n", stream, reason); + } + + k_sem_give(&sem_broadcast_stopped); +} + +static void broadcast_sent_cb(struct bt_bap_stream *stream) +{ + static uint8_t mock_data[CONFIG_BT_ISO_TX_MTU]; + static bool mock_data_initialized; + static uint32_t seq_num; + struct net_buf *buf; + int ret; + + if (broadcast_preset_48_2_1.qos.sdu > CONFIG_BT_ISO_TX_MTU) { + printk("Invalid SDU %u for the MTU: %d", + broadcast_preset_48_2_1.qos.sdu, CONFIG_BT_ISO_TX_MTU); + return; + } + + if (!mock_data_initialized) { + for (size_t i = 0U; i < ARRAY_SIZE(mock_data); i++) { + /* Initialize mock data */ + mock_data[i] = (uint8_t)i; + } + mock_data_initialized = true; + } + + buf = net_buf_alloc(&tx_pool, K_FOREVER); + if (buf == NULL) { + printk("Could not allocate buffer when sending on %p\n", + stream); + return; + } + + net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); + net_buf_add_mem(buf, mock_data, broadcast_preset_48_2_1.qos.sdu); + ret = bt_bap_stream_send(stream, buf, seq_num++, BT_ISO_TIMESTAMP_NONE); + if (ret < 0) { + /* This will end broadcasting on this stream. */ + net_buf_unref(buf); + return; + } +} + +static int stop_and_delete_extended_adv(struct bt_le_ext_adv *adv) +{ + int err; + + /* Stop extended advertising */ + err = bt_le_per_adv_stop(adv); + if (err) { + printk("Failed to stop periodic advertising: %d\n", err); + return err; + } + + err = bt_le_ext_adv_stop(adv); + if (err) { + printk("Failed to stop extended advertising: %d\n", err); + return err; + } + + err = bt_le_ext_adv_delete(adv); + if (err) { + printk("Failed to delete extended advertising: %d\n", err); + return err; + } + + bt_pbp_clear_data_buffer(); + + return 0; +} + +static int reset(void) +{ + k_sem_reset(&sem_broadcast_started); + k_sem_reset(&sem_broadcast_stopped); + + return 0; +} + +static struct bt_bap_stream_ops broadcast_stream_ops = {.started = broadcast_started_cb, + .stopped = broadcast_stopped_cb, + .sent = broadcast_sent_cb}; + +static int setup_extended_adv(struct bt_le_ext_adv **adv) +{ + int err; + + /* Create a non-connectable non-scannable advertising set */ + err = bt_le_ext_adv_create(BT_LE_EXT_ADV_NCONN_NAME, NULL, adv); + if (err != 0) { + printk("Unable to create extended advertising set: %d\n", err); + return err; + } + + /* Set periodic advertising parameters */ + err = bt_le_per_adv_set_param(*adv, BT_LE_PER_ADV_DEFAULT); + if (err) { + printk("Failed to set periodic advertising parameters: %d\n", + err); + return err; + } + + return 0; +} + +static int setup_extended_adv_data(struct bt_cap_broadcast_source *source, + struct bt_le_ext_adv *adv) +{ + /* Broadcast Audio Streaming Endpoint advertising data */ + NET_BUF_SIMPLE_DEFINE(ad_buf, + BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE); + NET_BUF_SIMPLE_DEFINE(base_buf, 128); + struct bt_data ext_ad[4]; + struct bt_data per_ad; + uint32_t broadcast_id; + int err; + + err = bt_cap_initiator_broadcast_get_id(source, &broadcast_id); + if (err != 0) { + printk("Unable to get broadcast ID: %d\n", err); + return err; + } + + /* Setup extended advertising data */ + ext_ad[0].type = BT_DATA_GAP_APPEARANCE; + ext_ad[0].data_len = 2; + ext_ad[0].data = appearance_addata; + /* Broadcast name AD Type */ + ext_ad[1].type = BT_DATA_BROADCAST_NAME; + ext_ad[1].data_len = ARRAY_SIZE(broadcast_name); + ext_ad[1].data = broadcast_name; + /* Broadcast Audio Announcements */ + net_buf_simple_add_le16(&ad_buf, BT_UUID_BROADCAST_AUDIO_VAL); + net_buf_simple_add_le24(&ad_buf, broadcast_id); + ext_ad[2].type = BT_DATA_SVC_DATA16; + ext_ad[2].data_len = ad_buf.len + sizeof(ext_ad[2].type); + ext_ad[2].data = ad_buf.data; + /* Public Broadcast Announcement */ + err = bt_pbp_create_public_broadcast_announcement(1, pba_metadata, &ext_ad[3]); + if (err != 0) { + printk("Failed to create public broadcast announcement!: %d\n", err); + return err; + } + err = bt_le_ext_adv_set_data(adv, ext_ad, ARRAY_SIZE(ext_ad), NULL, 0); + if (err != 0) { + printk("Failed to set extended advertising data: %d\n", err); + return err; + } + + /* Setup periodic advertising data */ + err = bt_cap_initiator_broadcast_get_base(source, &base_buf); + if (err != 0) { + printk("Failed to get encoded BASE: %d\n", err); + return err; + } + + per_ad.type = BT_DATA_SVC_DATA16; + per_ad.data_len = base_buf.len; + per_ad.data = base_buf.data; + err = bt_le_per_adv_set_data(adv, &per_ad, 1); + if (err != 0) { + printk("Failed to set periodic advertising data: %d\n", err); + return err; + } + + return 0; +} + +static int start_extended_adv(struct bt_le_ext_adv *adv) +{ + int err; + + /* Start extended advertising */ + err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); + if (err) { + printk("Failed to start extended advertising: %d\n", err); + return err; + } + + /* Enable Periodic Advertising */ + err = bt_le_per_adv_start(adv); + if (err) { + printk("Failed to enable periodic advertising: %d\n", err); + return err; + } + + return 0; +} + +int cap_initiator_init(void) +{ + if (IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SOURCE)) { + broadcast_stream = &broadcast_source_stream; + bt_bap_stream_cb_register(&broadcast_stream->bap_stream, &broadcast_stream_ops); + } + + return 0; +} + +void cap_initiator_setup(void) +{ + int err; + enum bt_pbp_role role = BT_PBP_ROLE_PBS; + uint8_t pba_params = 0; + + stream_params.stream = &broadcast_source_stream; + + stream_params.data_count = 1U; + stream_params.data = &bis_codec_data; + + subgroup_param.stream_count = 1U; + subgroup_param.stream_params = &stream_params; + subgroup_param.codec_cfg = &broadcast_preset_48_2_1.codec_cfg; + + create_param.subgroup_count = 1U; + create_param.subgroup_params = &subgroup_param; + create_param.qos = &broadcast_preset_48_2_1.qos; + create_param.packing = BT_ISO_PACKING_SEQUENTIAL; + create_param.encryption = false; + + while (true) { + err = reset(); + if (err != 0) { + printk("Resetting failed: %d - Aborting\n", err); + return; + } + + /** + * Initialize PBP and set public broadcast announcement features. + * Cycle between high and standard quality public broadcast audio. + */ + if (pba_params & BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY) { + pba_params = 0; + pba_params |= BT_PBP_ANNOUNCEMENT_FEATURE_STANDARD_QUALITY; + printk("Starting stream with standard quality!\n"); + } else { + pba_params = 0; + pba_params |= BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY; + printk("Starting stream with high quality!\n"); + } + bt_pbp_init_source(role, pba_params); + if (err != 0) { + return; + } + + err = setup_extended_adv(&adv); + if (err != 0) { + printk("Unable to setup extended advertiser: %d\n", err); + return; + } + + err = bt_cap_initiator_broadcast_audio_create(&create_param, &broadcast_source); + if (err != 0) { + printk("Unable to create broadcast source: %d\n", err); + return; + } + + err = bt_cap_initiator_broadcast_audio_start(broadcast_source, adv); + if (err != 0) { + printk("Unable to start broadcast source: %d\n", err); + return; + } + + err = setup_extended_adv_data(broadcast_source, adv); + if (err != 0) { + printk("Unable to setup extended advertising data: %d\n", err); + return; + } + + err = start_extended_adv(adv); + if (err != 0) { + printk("Unable to start extended advertiser: %d\n", err); + return; + } + k_sem_take(&sem_broadcast_started, K_FOREVER); + + /* Initialize sending */ + for (unsigned int j = 0U; j < BROADCAST_ENQUEUE_COUNT; j++) { + broadcast_sent_cb(&broadcast_stream->bap_stream); + } + + /* Keeping running for a little while */ + k_sleep(K_SECONDS(15)); + err = bt_cap_initiator_broadcast_audio_stop(broadcast_source); + if (err != 0) { + printk("Failed to stop broadcast source: %d\n", err); + return; + } + + k_sem_take(&sem_broadcast_stopped, K_FOREVER); + err = bt_cap_initiator_broadcast_audio_delete(broadcast_source); + if (err != 0) { + printk("Failed to stop broadcast source: %d\n", err); + return; + } + broadcast_source = NULL; + + err = stop_and_delete_extended_adv(adv); + if (err != 0) { + printk("Failed to stop and delete extended advertising: %d\n", err); + return; + } + } +} diff --git a/samples/bluetooth/public_broadcast_source/src/main.c b/samples/bluetooth/public_broadcast_source/src/main.c new file mode 100644 index 000000000000000..a0509fa331cd8c4 --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/src/main.c @@ -0,0 +1,55 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "public_broadcast_source.h" + +static int init(void) +{ + int err; + + err = bt_enable(NULL); + if (err != 0) { + printk("Bluetooth enable failed (err %d)\n", err); + return err; + } + + printk("Bluetooth initialized\n"); + + return 0; +} + +int main(void) +{ + int err; + + err = init(); + if (err != 0) { + return err; + } + + /* Initialize CAP Initiator */ + err = cap_initiator_init(); + if (err != 0) { + return err; + } + printk("CAP initialized\n"); + + /* Configure and start broadcast stream */ + err = cap_initiator_setup(); + if (err != 0) { + return err; + } + + return 0; +} diff --git a/samples/bluetooth/public_broadcast_source/src/public_broadcast_source.h b/samples/bluetooth/public_broadcast_source/src/public_broadcast_source.h new file mode 100644 index 000000000000000..5110b2f04cd76d7 --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/src/public_broadcast_source.h @@ -0,0 +1,20 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +/** + * @brief Initialize the CAP Initiator role + * + * @return 0 if success, errno on failure. + */ +int cap_initiator_init(void); + +/** + * @brief Setup streams for CAP Initiator + * + * @return 0 if success, errno on failure. + */ +int cap_initiator_setup(void); diff --git a/subsys/bluetooth/audio/CMakeLists.txt b/subsys/bluetooth/audio/CMakeLists.txt index 8f24c50233b62b6..897f7a1ff759c6a 100644 --- a/subsys/bluetooth/audio/CMakeLists.txt +++ b/subsys/bluetooth/audio/CMakeLists.txt @@ -61,3 +61,4 @@ zephyr_library_sources_ifdef(CONFIG_BT_CAP cap_stream.c) zephyr_library_sources_ifdef(CONFIG_BT_CAP_ACCEPTOR cap_acceptor.c) zephyr_library_sources_ifdef(CONFIG_BT_CAP_INITIATOR cap_initiator.c) zephyr_library_sources_ifdef(CONFIG_BT_TMAP tmap.c) +zephyr_library_sources_ifdef(CONFIG_BT_PBP pbp.c) diff --git a/subsys/bluetooth/audio/Kconfig b/subsys/bluetooth/audio/Kconfig index 2137151e9e72aba..ae0f199074f32a6 100644 --- a/subsys/bluetooth/audio/Kconfig +++ b/subsys/bluetooth/audio/Kconfig @@ -51,6 +51,7 @@ rsource "Kconfig.mpl" rsource "Kconfig.mctl" rsource "Kconfig.cap" rsource "Kconfig.tmap" +rsource "Kconfig.pbp" module = BT_AUDIO module-str = "Bluetooth Audio" diff --git a/subsys/bluetooth/audio/Kconfig.pbp b/subsys/bluetooth/audio/Kconfig.pbp new file mode 100644 index 000000000000000..77065b39c24ccc2 --- /dev/null +++ b/subsys/bluetooth/audio/Kconfig.pbp @@ -0,0 +1,25 @@ +# Bluetooth Audio - Public Broadcast Profile (PBP) options +# +# Copyright 2023 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# + +config BT_PBP + bool "Public Broadcast Profile [EXPERIMENTAL]" + select EXPERIMENTAL + help + Enabling this will enable PBP. + +config BT_DEBUG_PBP + bool "Public Broadcast Profile debug" + select DEPRECATED + depends on BT_PBP + help + Use this option to enable Public Broadcast Profile debug + logs for the Bluetooth Audio functionality. + +module = BT_PBP +legacy-debug-sym = BT_DEBUG_PBP +module-str = "Public Broadcast Profile" +source "subsys/bluetooth/common/Kconfig.template.log_config_bt" diff --git a/subsys/bluetooth/audio/pbp.c b/subsys/bluetooth/audio/pbp.c new file mode 100644 index 000000000000000..53002093bfe540e --- /dev/null +++ b/subsys/bluetooth/audio/pbp.c @@ -0,0 +1,188 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + + + +LOG_MODULE_REGISTER(bt_pbp, CONFIG_BT_PBP_LOG_LEVEL); + +#define TOTAL_BUF_COUNT_NEEDED 1 + +/* PBA Service UUID + Public Broadcast Announcement features + Maximum Metadata_Length */ +#define TOTAL_BUF_DATA_SIZE_NEEDED 260 + +/* PBA Service UUID + Public Broadcast Announcement features + Public_Info metadata of length 1 */ +#define MIN_PBA_SIZE 5 + +/* No audio configuration set */ +#define PBA_FEATURES_INVALID_VALUE 0 + +NET_BUF_POOL_FIXED_DEFINE(adv_data_pool, + TOTAL_BUF_COUNT_NEEDED, + TOTAL_BUF_DATA_SIZE_NEEDED, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +static uint8_t pbp_role; +static uint8_t stream_config_preferences = PBA_FEATURES_INVALID_VALUE; +static struct net_buf *pba_data_buf; + +static void scan_check(struct bt_data *data, uint8_t *source_config) +{ + struct bt_uuid_16 adv_uuid; + + if (data->type != BT_DATA_SVC_DATA16) { + return; + } + + if (data->data_len < BT_UUID_SIZE_16 + MIN_PBA_SIZE) { + return; + } + + if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) { + return; + } + + if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_PBA)) { + return; + } + + *source_config = *(data->data + BT_UUID_SIZE_16); +} + +void bt_pbp_init_source(enum bt_pbp_role role, uint8_t pba_params) +{ + pbp_role = role; + stream_config_preferences = pba_params; +} + +void bt_pbp_set_role(enum bt_pbp_role role) +{ + pbp_role = role; +} + +int bt_pbp_create_public_broadcast_announcement(size_t meta_count, + const struct bt_audio_codec_data meta[], struct bt_data *pba_adv_data) +{ + size_t i = 0; + uint16_t uuid_pba = BT_UUID_PBA_VAL; + + if (pba_adv_data == NULL) { + printk("bba_adv_data is NULL\n"); + + return -EINVAL; + } + + if (meta_count == 0) { + printk("At least the program_info type metadata should be provided!\n"); + + return -EINVAL; + } + + if (stream_config_preferences == PBA_FEATURES_INVALID_VALUE) { + printk("Public Broadcast Announcement features not set!\n"); + + return -EINVAL; + } + + if (!(pbp_role & BT_PBP_ROLE_PBS)) { + printk("Public Broadcast Source role not set!\n"); + + return -EINVAL; + } + + for (i = 0; i < meta_count; i++) { + if (meta[i].data.type == BT_AUDIO_METADATA_TYPE_PROGRAM_INFO) { + break; + } + } + + if (i == meta_count) { + printk("Program_Info metadata should be provided!\n"); + + return -EINVAL; + } + + if (!pba_data_buf) { + pba_data_buf = net_buf_alloc(&adv_data_pool, K_FOREVER); + if (pba_data_buf == NULL) { + printk("Could not allocate buffer\n"); + + return -EINVAL; + } + } + + /* Fill Announcement data */ + net_buf_add_mem(pba_data_buf, &uuid_pba, 2); + net_buf_add_mem(pba_data_buf, &stream_config_preferences, 1); + net_buf_add_mem(pba_data_buf, meta, meta_count * sizeof(*meta)); + + pba_adv_data->type = BT_DATA_SVC_DATA16; + pba_adv_data->data_len = pba_data_buf->len + sizeof(pba_adv_data->type); + pba_adv_data->data = pba_data_buf->data; + + return 0; +} + +bool bt_pbp_parse_public_broadcast_announcement(struct bt_data *ad, uint8_t sink_config_preferences) +{ + uint8_t source_config = PBA_FEATURES_INVALID_VALUE; + + if (!(pbp_role & BT_PBP_ROLE_PBK)) { + LOG_DBG("Public Broadcast Sink role not set!"); + + return -EINVAL; + } + + scan_check(ad, &source_config); + + if (source_config != PBA_FEATURES_INVALID_VALUE) { + /* Found a valid Public Broadcast Announcement - check sink requirements */ + if ((sink_config_preferences & BT_PBP_ANNOUNCEMENT_FEATURE_ENCRYPTION) && + !(source_config & BT_PBP_ANNOUNCEMENT_FEATURE_ENCRYPTION) == + BT_PBP_STREAMS_NOT_ENCRYPTED) { + printk("WRONG ENCRYPTION!\n"); + return false; + } + + if ((sink_config_preferences & BT_PBP_ANNOUNCEMENT_FEATURE_STANDARD_QUALITY) && + !(source_config & BT_PBP_ANNOUNCEMENT_FEATURE_STANDARD_QUALITY)) { + printk("NO STANDARD QUALITY !\n"); + return false; + } + + if ((sink_config_preferences & BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY) && + !(source_config & BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY)) { + printk("NO HIGH QUALITY !\n"); + return false; + } + + return true; + } + + return false; +} + +void bt_pbp_clear_data_buffer(void) +{ + if (pba_data_buf) { + net_buf_reset(pba_data_buf); + } +} diff --git a/tests/bsim/bluetooth/audio/prj.conf b/tests/bsim/bluetooth/audio/prj.conf index c516eeb1bdd090a..7325deec8043552 100644 --- a/tests/bsim/bluetooth/audio/prj.conf +++ b/tests/bsim/bluetooth/audio/prj.conf @@ -126,6 +126,9 @@ CONFIG_BT_CAP_INITIATOR=y # Telephony and Media Audio Profile CONFIG_BT_TMAP=y +# Public Broadcast Profile +CONFIG_BT_PBP=y + # DEBUGGING CONFIG_LOG=y CONFIG_LOG_FUNC_NAME_PREFIX_ERR=y diff --git a/tests/bsim/bluetooth/audio/src/main.c b/tests/bsim/bluetooth/audio/src/main.c index 40e0c116a6554ae..b5184ad71750535 100644 --- a/tests/bsim/bluetooth/audio/src/main.c +++ b/tests/bsim/bluetooth/audio/src/main.c @@ -33,6 +33,8 @@ extern struct bst_test_list *test_ias_install(struct bst_test_list *tests); extern struct bst_test_list *test_ias_client_install(struct bst_test_list *tests); extern struct bst_test_list *test_tmap_client_install(struct bst_test_list *tests); extern struct bst_test_list *test_tmap_server_install(struct bst_test_list *tests); +extern struct bst_test_list *test_public_broadcast_source_install(struct bst_test_list *tests); +extern struct bst_test_list *test_public_broadcast_sink_install(struct bst_test_list *tests); bst_test_install_t test_installers[] = { test_vcp_install, @@ -62,6 +64,8 @@ bst_test_install_t test_installers[] = { test_ias_client_install, test_tmap_server_install, test_tmap_client_install, + test_public_broadcast_source_install, + test_public_broadcast_sink_install, NULL }; diff --git a/tests/bsim/bluetooth/audio/src/public_broadcast_sink_test.c b/tests/bsim/bluetooth/audio/src/public_broadcast_sink_test.c new file mode 100644 index 000000000000000..1672f236ff905f3 --- /dev/null +++ b/tests/bsim/bluetooth/audio/src/public_broadcast_sink_test.c @@ -0,0 +1,449 @@ +/* + * Copyright 2023 Nxp + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if defined(CONFIG_BT_PBP) + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define SEM_TIMEOUT K_SECONDS(5) +#define PA_SYNC_SKIP 5 +#define SYNC_RETRY_COUNT 6 /* similar to retries for connections */ +#define INVALID_BROADCAST_ID 0xFFFFFFFF + +extern enum bst_result_t bst_result; + +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_sink_created, 0U, 1U); +static K_SEM_DEFINE(sem_syncable, 0U, 1U); +static K_SEM_DEFINE(sem_pa_sync_lost, 0U, 1U); + +static struct bt_bap_broadcast_sink *broadcast_sink; +static struct bt_le_per_adv_sync *bcast_pa_sync; + +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_audio_codec_cap codec = BT_AUDIO_CODEC_LC3( + BT_AUDIO_CODEC_LC3_FREQ_48KHZ, + BT_AUDIO_CODEC_LC3_DURATION_10, BT_AUDIO_CODEC_LC3_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 uint32_t broadcast_id = INVALID_BROADCAST_ID; + +static struct bt_pacs_cap cap = { + .codec_cap = &codec, +}; + +static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *ad); + +static struct bt_le_scan_cb broadcast_scan_cb = { + .recv = broadcast_scan_recv +}; + +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) { + FAIL("Unexpected PA sync"); + return; + } + + printk("PA synced for broadcast sink %p with broadcast ID 0x%06X\n", + sink, broadcast_id); + + broadcast_sink = sink; + + k_sem_give(&sem_sink_created); +} + +static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base) +{ + + printk("Received BASE with %u subgroups from broadcast sink %p\n", + base->subgroup_count, sink); + + k_sem_give(&sem_base_received); +} + +static void syncable_cb(struct bt_bap_broadcast_sink *sink, bool encrypted) +{ + printk("Broadcast sink %p syncable with%s encryption\n", + sink, encrypted ? "" : "out"); + k_sem_give(&sem_syncable); +} + +static void pa_sync_lost_cb(struct bt_bap_broadcast_sink *sink) +{ + if (broadcast_sink == NULL) { + FAIL("Unexpected PA sync lost"); + 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 = { + .base_recv = base_recv_cb, + .pa_synced = pa_synced_cb, + .syncable = syncable_cb, + .pa_sync_lost = pa_sync_lost_cb +}; + +static void started_cb(struct bt_bap_stream *stream) +{ + printk("Stream %p started\n", stream); +} + +static void stopped_cb(struct bt_bap_stream *stream, uint8_t reason) +{ + printk("Stream %p stopped with reason 0x%02X\n", stream, reason); +} + +static void recv_cb(struct bt_bap_stream *stream, + const struct bt_iso_recv_info *info, + struct net_buf *buf) +{ + printk("Receiving ISO packets\n"); +} + +static uint16_t interval_to_sync_timeout(uint16_t interval) +{ + uint32_t interval_ms; + uint16_t timeout; + + /* 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 */ + interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(interval); + timeout = (interval_ms * SYNC_RETRY_COUNT) / 10; + + /* Enforce restraints */ + timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, + BT_GAP_PER_ADV_MAX_TIMEOUT); + + return timeout; +} + +static bool pa_decode_base(struct bt_data *data, void *user_data) +{ + struct bt_bap_base base = { 0 }; + uint32_t base_bis_index_bitfield = 0U; + + if (data->type != BT_DATA_SVC_DATA16) { + return true; + } + + if (data->data_len < BT_BAP_BASE_MIN_SIZE) { + return true; + } + + if (bt_bap_decode_base(data, &base) != 0) { + return false; + } + + 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); + + 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 broadcast_pa_synced(struct bt_le_per_adv_sync *sync, + struct bt_le_per_adv_sync_synced_info *info) +{ + k_sem_give(&sem_pa_synced); +} + +static struct bt_bap_stream_ops stream_ops = { + .started = started_cb, + .stopped = stopped_cb, + .recv = recv_cb +}; + +static struct bt_le_per_adv_sync_cb broadcast_sync_cb = { + .synced = broadcast_pa_synced, + .recv = broadcast_pa_recv, +}; + +static int reset(void) +{ + int err; + + 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; + } + k_sem_reset(&sem_pa_synced); + k_sem_reset(&sem_base_received); + k_sem_reset(&sem_sink_created); + k_sem_reset(&sem_syncable); + k_sem_reset(&sem_pa_sync_lost); + + return 0; +} + +static int init(void) +{ + int err; + enum bt_pbp_role role = BT_PBP_ROLE_PBK; + + err = bt_enable(NULL); + if (err) { + FAIL("Bluetooth enable failed (err %d)\n", err); + return err; + } + + printk("Bluetooth initialized\n"); + + bt_pbp_set_role(role); + + 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; +} + +static void sync_broadcast_pa(const struct bt_le_scan_recv_info *info) +{ + 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(¶m.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(¶m, &bcast_pa_sync); + if (err != 0) { + printk("Could not sync to PA: %d", err); + + } +} + +static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data) +{ + struct bt_uuid_16 adv_uuid; + uint8_t sink_config_preferences = BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY; + + bool pba_sync = bt_pbp_parse_public_broadcast_announcement(data, sink_config_preferences); + + if (pba_sync) { + printk("Found Suitable Public Broadcast Announcement\n"); + pbs_found = true; + + /* Continue parsing if Broadcast Audio Announcement Service was not yet found */ + if (broadcast_id == INVALID_BROADCAST_ID) { + return true; + } + return false; + } + + 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)) { + /* Save broadcast_id */ + if (broadcast_id == INVALID_BROADCAST_ID) { + broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16); + } + + /* Found Broadcast Audio and Public Broadcast Announcement Services */ + if (pbs_found) { + return false; + } + } + + /* Continue parsing */ + return true; +} + +static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *ad) +{ + struct net_buf_simple_state state; + + 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; + } + + net_buf_simple_save(ad, &state); + bt_data_parse(ad, scan_check_and_sync_broadcast, (void *)&broadcast_id); + net_buf_simple_restore(ad, &state); + + if ((broadcast_id != INVALID_BROADCAST_ID) && pbs_found) { + sync_broadcast_pa(info); + } +} + +static void test_main(void) +{ + int count = 0; + int err; + + init(); + + while (count < 2) { + err = reset(); + if (err != 0) { + printk("Resetting failed: %d - Aborting\n", err); + return; + } + + /* 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); + break; + } + + /* Wait for PA sync */ + err = k_sem_take(&sem_pa_synced, SEM_TIMEOUT); + if (err != 0) { + printk("sem_pa_synced timed out\n"); + break; + } + 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"); + break; + } + + /* Create broadcast sink */ + err = bt_bap_broadcast_sink_create(bcast_pa_sync, broadcast_id); + err = k_sem_take(&sem_sink_created, SEM_TIMEOUT); + if (err != 0) { + printk("sem_sink_created timed out\n"); + break; + } + + k_sem_take(&sem_syncable, SEM_TIMEOUT); + if (err != 0) { + printk("sem_syncable timed out\n"); + break; + } + + /* Sync to broadcast source */ + 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); + break; + } + + k_sem_take(&sem_pa_sync_lost, SEM_TIMEOUT); + + count++; + } + + if (count == 1) { + /* Pass if we synced only with the high quality broadcast */ + PASS("Public Broadcast sink passed\n"); + } +} + +static const struct bst_test_instance test_public_broadcast_sink[] = { + { + .test_id = "public_broadcast_sink", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_main + }, + BSTEST_END_MARKER +}; + +struct bst_test_list *test_public_broadcast_sink_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_public_broadcast_sink); +} + +#else /* !CONFIG_BT_BAP_BROADCAST_SINK */ + +struct bst_test_list *test_public_broadcast_sink_install(struct bst_test_list *tests) +{ + return tests; +} + +#endif /* CONFIG_BT_BAP_BROADCAST_SINK */ diff --git a/tests/bsim/bluetooth/audio/src/public_broadcast_source_test.c b/tests/bsim/bluetooth/audio/src/public_broadcast_source_test.c new file mode 100644 index 000000000000000..73c835bc2dbaef4 --- /dev/null +++ b/tests/bsim/bluetooth/audio/src/public_broadcast_source_test.c @@ -0,0 +1,380 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if defined(CONFIG_BT_PBP) + +#include +#include +#include +#include +#include +#include + +#include "common.h" + +/* When BROADCAST_ENQUEUE_COUNT > 1 we can enqueue enough buffers to ensure that + * the controller is never idle + */ +#define BROADCAST_ENQUEUE_COUNT 2U +#define TOTAL_BUF_NEEDED (BROADCAST_ENQUEUE_COUNT * CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT) + +extern enum bst_result_t bst_result; + +BUILD_ASSERT(CONFIG_BT_ISO_TX_BUF_COUNT >= TOTAL_BUF_NEEDED, + "CONFIG_BT_ISO_TX_BUF_COUNT should be at least " + "BROADCAST_ENQUEUE_COUNT * CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT"); + +NET_BUF_POOL_FIXED_DEFINE(tx_pool, + TOTAL_BUF_NEEDED, + BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +const struct bt_audio_codec_data pba_metadata[] = { + BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_PROGRAM_INFO, + "PBS DEMO") +}; + +struct bt_audio_codec_data bis_codec_data = BT_AUDIO_CODEC_DATA( + BT_AUDIO_CODEC_CONFIG_LC3_FREQ, BT_AUDIO_CODEC_CONFIG_LC3_FREQ_48KHZ); + +static struct bt_cap_stream broadcast_source_stream; +static struct bt_cap_stream *broadcast_stream; + +static struct bt_cap_initiator_broadcast_stream_param stream_params; +static struct bt_cap_initiator_broadcast_subgroup_param subgroup_param; +static struct bt_cap_initiator_broadcast_create_param create_param; +static struct bt_cap_broadcast_source *broadcast_source; + +static struct bt_bap_lc3_preset broadcast_preset_48_2_1 = + BT_BAP_LC3_UNICAST_PRESET_48_2_1(BT_AUDIO_LOCATION_FRONT_LEFT, + BT_AUDIO_CONTEXT_TYPE_MEDIA); + +static K_SEM_DEFINE(sem_started, 0U, 1); +static K_SEM_DEFINE(sem_stopped, 0U, 1); + +struct bt_le_ext_adv *adv; + +static void started_cb(struct bt_bap_stream *stream) +{ + printk("Stream %p started\n", stream); + k_sem_give(&sem_started); +} + +static void stopped_cb(struct bt_bap_stream *stream, uint8_t reason) +{ + printk("Stream %p stopped with reason 0x%02X\n", stream, reason); + k_sem_give(&sem_stopped); +} + +static void sent_cb(struct bt_bap_stream *stream) +{ + static uint8_t mock_data[CONFIG_BT_ISO_TX_MTU]; + static bool mock_data_initialized; + static uint32_t seq_num; + struct net_buf *buf; + int ret; + + if (broadcast_preset_48_2_1.qos.sdu > CONFIG_BT_ISO_TX_MTU) { + printk("Invalid SDU %u for the MTU: %d", + broadcast_preset_48_2_1.qos.sdu, CONFIG_BT_ISO_TX_MTU); + return; + } + + if (!mock_data_initialized) { + for (size_t i = 0U; i < ARRAY_SIZE(mock_data); i++) { + /* Initialize mock data */ + mock_data[i] = (uint8_t)i; + } + mock_data_initialized = true; + } + + buf = net_buf_alloc(&tx_pool, K_FOREVER); + if (buf == NULL) { + printk("Could not allocate buffer when sending on %p\n", + stream); + return; + } + + net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); + net_buf_add_mem(buf, mock_data, broadcast_preset_48_2_1.qos.sdu); + ret = bt_bap_stream_send(stream, buf, seq_num++, BT_ISO_TIMESTAMP_NONE); + if (ret < 0) { + /* This will end broadcasting on this stream. */ + net_buf_unref(buf); + return; + } +} + +static int setup_extended_adv_data(struct bt_cap_broadcast_source *source, + struct bt_le_ext_adv *adv) +{ + /* Broadcast Audio Streaming Endpoint advertising data */ + NET_BUF_SIMPLE_DEFINE(ad_buf, + BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE); + NET_BUF_SIMPLE_DEFINE(base_buf, 128); + struct bt_data ext_ad[2]; + struct bt_data per_ad; + uint32_t broadcast_id; + int err; + + err = bt_cap_initiator_broadcast_get_id(source, &broadcast_id); + if (err != 0) { + printk("Unable to get broadcast ID: %d\n", err); + return err; + } + + /* Broadcast Audio Announcements */ + net_buf_simple_add_le16(&ad_buf, BT_UUID_BROADCAST_AUDIO_VAL); + net_buf_simple_add_le24(&ad_buf, broadcast_id); + ext_ad[0].type = BT_DATA_SVC_DATA16; + ext_ad[0].data_len = ad_buf.len + sizeof(ext_ad[0].type); + ext_ad[0].data = ad_buf.data; + /* Public Broadcast Announcement */ + err = bt_pbp_create_public_broadcast_announcement(1, pba_metadata, &ext_ad[1]); + if (err != 0) { + printk("Failed to create public broadcast announcement!: %d\n", err); + return err; + } + err = bt_le_ext_adv_set_data(adv, ext_ad, ARRAY_SIZE(ext_ad), NULL, 0); + if (err != 0) { + printk("Failed to set extended advertising data: %d\n", err); + return err; + } + + /* Setup periodic advertising data */ + err = bt_cap_initiator_broadcast_get_base(source, &base_buf); + if (err != 0) { + printk("Failed to get encoded BASE: %d\n", err); + return err; + } + + per_ad.type = BT_DATA_SVC_DATA16; + per_ad.data_len = base_buf.len; + per_ad.data = base_buf.data; + err = bt_le_per_adv_set_data(adv, &per_ad, 1); + if (err != 0) { + printk("Failed to set periodic advertising data: %d\n", err); + return err; + } + + return 0; +} + +static int start_extended_adv(struct bt_le_ext_adv *adv) +{ + int err; + + /* Start extended advertising */ + err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); + if (err) { + printk("Failed to start extended advertising: %d\n", err); + return err; + } + + /* Enable Periodic Advertising */ + err = bt_le_per_adv_start(adv); + if (err) { + printk("Failed to enable periodic advertising: %d\n", err); + return err; + } + + return 0; +} + +static int setup_extended_adv(struct bt_le_ext_adv **adv) +{ + int err; + + /* Create a non-connectable non-scannable advertising set */ + err = bt_le_ext_adv_create(BT_LE_EXT_ADV_NCONN_NAME, NULL, adv); + if (err != 0) { + printk("Unable to create extended advertising set: %d\n", err); + return err; + } + + /* Set periodic advertising parameters */ + err = bt_le_per_adv_set_param(*adv, BT_LE_PER_ADV_DEFAULT); + if (err) { + printk("Failed to set periodic advertising parameters: %d\n", + err); + return err; + } + + return 0; +} + +static int stop_extended_adv(struct bt_le_ext_adv *adv) +{ + int err; + + err = bt_le_per_adv_stop(adv); + if (err) { + printk("Failed to stop periodic advertising: %d\n", err); + return err; + } + + err = bt_le_ext_adv_stop(adv); + if (err) { + printk("Failed to stop extended advertising: %d\n", err); + return err; + } + + err = bt_le_ext_adv_delete(adv); + if (err) { + printk("Failed to delete extended advertising: %d\n", err); + return err; + } + + bt_pbp_clear_data_buffer(); + + return 0; +} + +static struct bt_bap_stream_ops broadcast_stream_ops = {.started = started_cb, + .stopped = stopped_cb, + .sent = sent_cb}; + +static void test_main(void) +{ + int err; + int count = 0; + enum bt_pbp_role role = BT_PBP_ROLE_PBS; + uint8_t pba_params = 0; + + err = bt_enable(NULL); + if (err) { + FAIL("Bluetooth enable failed (err %d)\n", err); + return; + } + + broadcast_stream = &broadcast_source_stream; + bt_bap_stream_cb_register(&broadcast_stream->bap_stream, &broadcast_stream_ops); + + stream_params.stream = &broadcast_source_stream; + + stream_params.data_count = 1U; + stream_params.data = &bis_codec_data; + + subgroup_param.stream_count = 1U; + subgroup_param.stream_params = &stream_params; + subgroup_param.codec_cfg = &broadcast_preset_48_2_1.codec_cfg; + + create_param.subgroup_count = 1U; + create_param.subgroup_params = &subgroup_param; + create_param.qos = &broadcast_preset_48_2_1.qos; + create_param.packing = BT_ISO_PACKING_SEQUENTIAL; + create_param.encryption = false; + + while (count < 2) { + k_sem_reset(&sem_started); + k_sem_reset(&sem_stopped); + + /** + * Initialize PBP and set public broadcast announcement features. + * Cycle between high and standard quality public broadcast audio. + */ + if (pba_params & BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY) { + pba_params = 0; + pba_params |= BT_PBP_ANNOUNCEMENT_FEATURE_STANDARD_QUALITY; + printk("Starting stream with standard quality!\n"); + } else { + pba_params = 0; + pba_params |= BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY; + printk("Starting stream with high quality!\n"); + } + bt_pbp_init_source(role, pba_params); + if (err != 0) { + break; + } + + err = setup_extended_adv(&adv); + if (err != 0) { + printk("Unable to setup extended advertiser: %d\n", err); + break; + } + + err = bt_cap_initiator_broadcast_audio_create(&create_param, &broadcast_source); + if (err != 0) { + printk("Unable to create broadcast source: %d\n", err); + break; + } + + err = bt_cap_initiator_broadcast_audio_start(broadcast_source, adv); + if (err != 0) { + printk("Unable to start broadcast source: %d\n", err); + break; + } + + err = setup_extended_adv_data(broadcast_source, adv); + if (err != 0) { + printk("Unable to setup extended advertising data: %d\n", err); + break; + } + + err = start_extended_adv(adv); + if (err != 0) { + printk("Unable to start extended advertiser: %d\n", err); + break; + } + k_sem_take(&sem_started, K_FOREVER); + + /* Initialize sending */ + for (unsigned int j = 0U; j < BROADCAST_ENQUEUE_COUNT; j++) { + sent_cb(&broadcast_stream->bap_stream); + } + + /* Keeping running for a little while */ + k_sleep(K_SECONDS(5)); + + err = bt_cap_initiator_broadcast_audio_stop(broadcast_source); + if (err != 0) { + printk("Failed to stop broadcast source: %d\n", err); + break; + } + + k_sem_take(&sem_stopped, K_FOREVER); + err = bt_cap_initiator_broadcast_audio_delete(broadcast_source); + if (err != 0) { + printk("Failed to stop broadcast source: %d\n", err); + break; + } + broadcast_source = NULL; + + err = stop_extended_adv(adv); + if (err != 0) { + printk("Failed to stop and delete extended advertising: %d\n", err); + break; + } + + count++; + } + + PASS("Public Broadcast source passed\n"); +} + +static const struct bst_test_instance test_pbp_broadcaster[] = { + { + .test_id = "public_broadcast_source", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_main + }, + BSTEST_END_MARKER +}; + +struct bst_test_list *test_public_broadcast_source_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_pbp_broadcaster); +} + +#else /* CONFIG_BT_PBP */ + +struct bst_test_list *test_public_broadcast_source_install(struct bst_test_list *tests) +{ + return tests; +} + +#endif /* CONFIG_BT_PBP */ diff --git a/tests/bsim/bluetooth/audio/test_scripts/pbp.sh b/tests/bsim/bluetooth/audio/test_scripts/pbp.sh new file mode 100644 index 000000000000000..472fc1a5e3d89a6 --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/pbp.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# +# Copyright 2023 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=20 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +printf "\n\n======== Public Broadcaster test =========\n\n" + +SIMULATION_ID="pbp_broadcaster" + +Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=public_broadcast_source -rs=23 + +Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=public_broadcast_sink -rs=27 + +# Simulation time should be larger than the WAIT_TIME in common.h +Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=2 -sim_length=70e6 $@ + +wait_for_background_jobs