From 12f6104b8d298d4bd7590b8890ef5f67cd90069e 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 dedicated sample apps. PBP API allows sources to create a Public Broadcast Announcement. PBP API to parse a Public Broadcast Announcement. 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. Add shell implementation for Public Broadcast Profile APIs. Signed-off-by: Daniela Andreea Dumitrache --- doc/connectivity/bluetooth/api/index.rst | 1 + doc/connectivity/bluetooth/api/shell/pbp.rst | 20 + include/zephyr/bluetooth/audio/pbp.h | 78 +++ .../public_broadcast_sink/CMakeLists.txt | 11 + .../public_broadcast_sink/Kconfig.sysbuild | 15 + .../public_broadcast_sink/README.rst | 77 +++ .../overlay-bt_ll_sw_split.conf | 16 + .../bluetooth/public_broadcast_sink/prj.conf | 34 ++ .../public_broadcast_sink/sample.yaml | 26 + .../public_broadcast_sink/src/main.c | 444 ++++++++++++++++++ .../public_broadcast_sink/sysbuild.cmake | 44 ++ .../public_broadcast_source/CMakeLists.txt | 11 + .../public_broadcast_source/Kconfig.sysbuild | 15 + .../public_broadcast_source/README.rst | 77 +++ .../overlay-bt_ll_sw_split.conf | 24 + .../public_broadcast_source/prj.conf | 25 + .../public_broadcast_source/sample.yaml | 26 + .../public_broadcast_source/src/main.c | 424 +++++++++++++++++ .../public_broadcast_source/sysbuild.cmake | 44 ++ samples/bluetooth/tmap_bms/prj.conf | 2 + .../bluetooth/tmap_bms/src/cap_initiator.c | 8 +- subsys/bluetooth/audio/CMakeLists.txt | 1 + subsys/bluetooth/audio/Kconfig | 1 + subsys/bluetooth/audio/Kconfig.pbp | 16 + subsys/bluetooth/audio/pbp.c | 84 ++++ subsys/bluetooth/audio/shell/CMakeLists.txt | 4 + subsys/bluetooth/audio/shell/audio.h | 1 + subsys/bluetooth/audio/shell/pbp.c | 91 ++++ tests/bluetooth/shell/audio.conf | 3 + tests/bsim/bluetooth/audio/prj.conf | 3 + tests/bsim/bluetooth/audio/src/common.h | 2 + tests/bsim/bluetooth/audio/src/main.c | 4 + .../src/pbp_public_broadcast_sink_test.c | 443 +++++++++++++++++ .../src/pbp_public_broadcast_source_test.c | 394 ++++++++++++++++ .../bsim/bluetooth/audio/test_scripts/pbp.sh | 28 ++ 35 files changed, 2494 insertions(+), 3 deletions(-) create mode 100644 doc/connectivity/bluetooth/api/shell/pbp.rst 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/Kconfig.sysbuild create mode 100644 samples/bluetooth/public_broadcast_sink/README.rst create mode 100644 samples/bluetooth/public_broadcast_sink/overlay-bt_ll_sw_split.conf 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/main.c create mode 100644 samples/bluetooth/public_broadcast_sink/sysbuild.cmake create mode 100644 samples/bluetooth/public_broadcast_source/CMakeLists.txt create mode 100644 samples/bluetooth/public_broadcast_source/Kconfig.sysbuild create mode 100644 samples/bluetooth/public_broadcast_source/README.rst create mode 100644 samples/bluetooth/public_broadcast_source/overlay-bt_ll_sw_split.conf 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/main.c create mode 100644 samples/bluetooth/public_broadcast_source/sysbuild.cmake create mode 100644 subsys/bluetooth/audio/Kconfig.pbp create mode 100644 subsys/bluetooth/audio/pbp.c create mode 100644 subsys/bluetooth/audio/shell/pbp.c create mode 100644 tests/bsim/bluetooth/audio/src/pbp_public_broadcast_sink_test.c create mode 100644 tests/bsim/bluetooth/audio/src/pbp_public_broadcast_source_test.c create mode 100755 tests/bsim/bluetooth/audio/test_scripts/pbp.sh diff --git a/doc/connectivity/bluetooth/api/index.rst b/doc/connectivity/bluetooth/api/index.rst index 51c3638611400c0..21e4df206a4b3b0 100644 --- a/doc/connectivity/bluetooth/api/index.rst +++ b/doc/connectivity/bluetooth/api/index.rst @@ -38,3 +38,4 @@ Bluetooth APIs shell/iso.rst shell/mcp.rst shell/tmap.rst + shell/pbp.rst diff --git a/doc/connectivity/bluetooth/api/shell/pbp.rst b/doc/connectivity/bluetooth/api/shell/pbp.rst new file mode 100644 index 000000000000000..ba3e0f459fd5e49 --- /dev/null +++ b/doc/connectivity/bluetooth/api/shell/pbp.rst @@ -0,0 +1,20 @@ +Bluetooth: Public Broadcast Profile Shell +######################################### + +This document describes how to run the Public Broadcast Profile functionality. +PBP does not have an associated service. Its purpose is to enable a faster, more +efficient discovery of Broadcast Sources that are transmitting audio with commonly used codec configurations. + +Using the PBP Shell +******************* + +When the Bluetooth stack has been initialized (:code:`bt init`), the Public Broadcast Profile is ready to run. +To set the Public Broadcast Announcement features call :code:`pbp set_features`. + +.. code-block:: console + + + pbp --help + pbp - Bluetooth PBP shell commands + Subcommands: + set_features :Set the Public Broadcast Announcement features diff --git a/include/zephyr/bluetooth/audio/pbp.h b/include/zephyr/bluetooth/audio/pbp.h new file mode 100644 index 000000000000000..ddc6ce01f213886 --- /dev/null +++ b/include/zephyr/bluetooth/audio/pbp.h @@ -0,0 +1,78 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_PBP_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_PBP_ + +/** + * @brief Public Broadcast Profile (PBP) + * + * @defgroup bt_pbp Public Broadcast Profile (PBP) + * + * @ingroup bluetooth + * @{ + * + * [Experimental] Users should note that the APIs can change + * as a part of ongoing development. + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Public Broadcast Announcement features */ +enum bt_pbp_announcement_feature { + /** Broadcast Streams encryption status */ + BT_PBP_ANNOUNCEMENT_FEATURE_ENCRYPTION = BIT(0), + /** Standard Quality Public Broadcast Audio configuration */ + BT_PBP_ANNOUNCEMENT_FEATURE_STANDARD_QUALITY = BIT(1), + /** High Quality Public Broadcast Audio configuration */ + BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY = BIT(2), +}; + +/** + * @brief Creates a Public Broadcast Announcement based on the information received + * in the features parameter. + * + * @param meta Metadata to be included in the advertising data + * @param meta_len Size of the metadata fields to be included in the advertising data + * @param features Public Broadcast Announcement features + * @param pba_data_buf Pointer to store the PBA advertising data. Buffer size needs to be + * meta_len + 4 (service UUID + PBA feature + metadata length). + * + * @return 0 on success or an appropriate error code. + */ +int bt_pbp_get_announcement(const uint8_t meta[], size_t meta_len, + enum bt_pbp_announcement_feature features, + struct net_buf_simple *pba_data_buf); + +/** + * @brief Parses the received advertising data corresponding to a Public Broadcast + * Announcement. Returns the advertised Public BroadcastAnnouncement features. + * and metadata. + * + * @param data Advertising data to be checked + * @param source_features Sink stream configuration preferences + * @param meta Pointer to copy the metadata present in the advertising data + * + * @return parsed metadata length on success or an appropriate error code + */ +uint8_t bt_pbp_parse_announcement(struct bt_data *data, + enum bt_pbp_announcement_feature *source_features, + uint8_t *meta); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#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..74bed078bddb843 --- /dev/null +++ b/samples/bluetooth/public_broadcast_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(pbp_broadcast_sink) + +target_sources(app PRIVATE + src/main.c +) + +zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) diff --git a/samples/bluetooth/public_broadcast_sink/Kconfig.sysbuild b/samples/bluetooth/public_broadcast_sink/Kconfig.sysbuild new file mode 100644 index 000000000000000..f434010f81d27ce --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/Kconfig.sysbuild @@ -0,0 +1,15 @@ +# Copyright 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +source "share/sysbuild/Kconfig" + +config NET_CORE_BOARD + string + default "nrf5340dk_nrf5340_cpunet" if $(BOARD) = "nrf5340dk_nrf5340_cpuapp" + default "nrf5340_audio_dk_nrf5340_cpunet" if $(BOARD) = "nrf5340_audio_dk_nrf5340_cpuapp" + default "nrf5340bsim_nrf5340_cpunet" if $(BOARD) = "nrf5340bsim_nrf5340_cpuapp" + +config NET_CORE_IMAGE_HCI_IPC + bool "HCI IPC image on network core" + default y + depends on NET_CORE_BOARD != "" diff --git a/samples/bluetooth/public_broadcast_sink/README.rst b/samples/bluetooth/public_broadcast_sink/README.rst new file mode 100644 index 000000000000000..5f47bcd05abafdf --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/README.rst @@ -0,0 +1,77 @@ +.. zephyr:code-sample:: bluetooth_public_broadcast_sink + :name: Bluetooth: Public Broadcast Sink + :relevant-api: bluetooth + + 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. + +This sample can be found under +:zephyr_file:`samples/bluetooth/public_broadcast_sink` in the Zephyr tree. + +Check the :ref:`bluetooth samples section ` for general information. + +Requirements +************ + +* BlueZ running on the host, or +* A board with Bluetooth Low Energy 5.2 support + +Building and Running +******************** + +When building targeting an nrf52 series board with the Zephyr Bluetooth Controller, +use `-DOVERLAY_CONFIG=overlay-bt_ll_sw_split.conf` to enable the required ISO +feature support. + +Building for an nrf5340dk +------------------------- + +You can build both the application core image and an appropriate controller image for the network +core with: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/public_broadcast_sink/ + :board: nrf5340dk_nrf5340_cpuapp + :goals: build + :west-args: --sysbuild + +If you prefer to only build the application core image, you can do so by doing instead: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/public_broadcast_sink/ + :board: nrf5340dk_nrf5340_cpuapp + :goals: build + +In that case you can pair this application core image with the +:ref:`hci_ipc sample ` +:zephyr_file:`samples/bluetooth/hci_ipc/nrf5340_cpunet_iso-bt_ll_sw_split.conf` configuration. + +Building for a simulated nrf5340bsim +------------------------------------ + +Similarly to how you would for real HW, you can do: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/public_broadcast_sink/ + :board: nrf5340bsim_nrf5340_cpuapp + :goals: build + :west-args: --sysbuild + +Note this will produce a Linux executable in `./build/zephyr/zephyr.exe`. +For more information, check :ref:`this board documentation `. + +Building for a simulated nrf52_bsim +----------------------------------- + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/public_broadcast_sink/ + :board: nrf52_bsim + :goals: build + :gen-args: -DOVERLAY_CONFIG=overlay-bt_ll_sw_split.conf diff --git a/samples/bluetooth/public_broadcast_sink/overlay-bt_ll_sw_split.conf b/samples/bluetooth/public_broadcast_sink/overlay-bt_ll_sw_split.conf new file mode 100644 index 000000000000000..e336dae38e1f15b --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/overlay-bt_ll_sw_split.conf @@ -0,0 +1,16 @@ +# Zephyr Bluetooth Controller +CONFIG_BT_LL_SW_SPLIT=y + +# Enable support for Broadcast ISO Sync +CONFIG_BT_CTLR_SYNC_ISO=y + +# Supports the highest SDU size required by any BAP LC3 presets (155) +CONFIG_BT_CTLR_SYNC_ISO_PDU_LEN_MAX=155 + +# Supports the highest advertising data that is set in a single HCI command in +# Zephyr Bluetooth Controller +CONFIG_BT_CTLR_SCAN_DATA_LEN_MAX=191 + +# Number of supported streams +CONFIG_BT_CTLR_SYNC_ISO_STREAM_MAX=2 +CONFIG_BT_CTLR_ISOAL_SINKS=2 diff --git a/samples/bluetooth/public_broadcast_sink/prj.conf b/samples/bluetooth/public_broadcast_sink/prj.conf new file mode 100644 index 000000000000000..0feae717a7777a3 --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/prj.conf @@ -0,0 +1,34 @@ +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 +CONFIG_BT_HCI_ACL_FLOW_CONTROL=n +CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE=196 + +# 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..96e06fda4e679f1 --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/sample.yaml @@ -0,0 +1,26 @@ +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 + sysbuild: true + sample.bluetooth.public_broadcast_sink.bt_ll_sw_split: + harness: bluetooth + platform_allow: + - nrf52_bsim + - nrf52833dk_nrf52820 + - nrf52833dk_nrf52833 + integration_platforms: + - nrf52_bsim + - nrf52833dk_nrf52833 + extra_args: OVERLAY_CONFIG=overlay-bt_ll_sw_split.conf + tags: bluetooth 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..20a41d7eedae14b --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/src/main.c @@ -0,0 +1,444 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 SYNC_RETRY_COUNT 6 /* similar to retries for connections */ +#define INVALID_BROADCAST_ID 0xFFFFFFFF + +static bool pbs_found; +static uint8_t meta[CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE]; + +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_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 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, + 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(¶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); + } else { + bcast_id = broadcast_id; + } +} + +static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data) +{ + uint32_t *broadcast_id = user_data; + struct bt_uuid_16 adv_uuid; + enum bt_pbp_announcement_feature source_features = 0U; + + memset(meta, 0, ARRAY_SIZE(meta)); + + 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; + } + + if (!bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_PBA)) { + bt_pbp_parse_announcement(data, &source_features, meta); + 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 true; + } + printk("Found Suitable Public Broadcast Announcement\n"); + pbs_found = true; + + /** + * Continue parsing if Broadcast Audio Announcement Service + * was not found. + */ + if (*broadcast_id == INVALID_BROADCAST_ID) { + return true; + } + + return false; + } + + 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) +{ + 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 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 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; +} diff --git a/samples/bluetooth/public_broadcast_sink/sysbuild.cmake b/samples/bluetooth/public_broadcast_sink/sysbuild.cmake new file mode 100644 index 000000000000000..ed30d7f31f3d7f6 --- /dev/null +++ b/samples/bluetooth/public_broadcast_sink/sysbuild.cmake @@ -0,0 +1,44 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +if(SB_CONFIG_NET_CORE_IMAGE_HCI_IPC) + # For builds in the nrf5340, we build the netcore image with the controller + + set(NET_APP hci_ipc) + set(NET_APP_SRC_DIR ${ZEPHYR_BASE}/samples/bluetooth/${NET_APP}) + + ExternalZephyrProject_Add( + APPLICATION ${NET_APP} + SOURCE_DIR ${NET_APP_SRC_DIR} + BOARD ${SB_CONFIG_NET_CORE_BOARD} + ) + + set(${NET_APP}_CONF_FILE + ${NET_APP_SRC_DIR}/nrf5340_cpunet_iso-bt_ll_sw_split.conf + CACHE INTERNAL "" + ) + + # Let's build the net core library first + add_dependencies(${DEFAULT_IMAGE} ${NET_APP}) + + if("${BOARD}" STREQUAL "nrf5340bsim_nrf5340_cpuapp") + # For the simulated board, the application core build will produce the final executable + # for that, we give it the path to the netcore image + set(NET_LIBRARY_PATH ${CMAKE_BINARY_DIR}/${NET_APP}/zephyr/zephyr.elf) + set_property(TARGET ${DEFAULT_IMAGE} APPEND_STRING PROPERTY CONFIG + "CONFIG_NATIVE_SIMULATOR_EXTRA_IMAGE_PATHS=\"${NET_LIBRARY_PATH}\"\n" + ) + endif() +endif() + +if(("${BOARD}" MATCHES "native") OR ("${BOARD}" MATCHES "bsim")) + # Let's meet the expectation of finding the final executable in zephyr/zephyr.exe + add_custom_target(final_executable + ALL + COMMAND + ${CMAKE_COMMAND} -E copy + ${CMAKE_BINARY_DIR}/${DEFAULT_IMAGE}/zephyr/zephyr.exe + ${CMAKE_BINARY_DIR}/zephyr/zephyr.exe + DEPENDS ${DEFAULT_IMAGE} + ) +endif() diff --git a/samples/bluetooth/public_broadcast_source/CMakeLists.txt b/samples/bluetooth/public_broadcast_source/CMakeLists.txt new file mode 100644 index 000000000000000..6331703e2d89a5b --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/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(public_broadcast_source) + +target_sources(app PRIVATE + src/main.c +) + +zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) diff --git a/samples/bluetooth/public_broadcast_source/Kconfig.sysbuild b/samples/bluetooth/public_broadcast_source/Kconfig.sysbuild new file mode 100644 index 000000000000000..f434010f81d27ce --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/Kconfig.sysbuild @@ -0,0 +1,15 @@ +# Copyright 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +source "share/sysbuild/Kconfig" + +config NET_CORE_BOARD + string + default "nrf5340dk_nrf5340_cpunet" if $(BOARD) = "nrf5340dk_nrf5340_cpuapp" + default "nrf5340_audio_dk_nrf5340_cpunet" if $(BOARD) = "nrf5340_audio_dk_nrf5340_cpuapp" + default "nrf5340bsim_nrf5340_cpunet" if $(BOARD) = "nrf5340bsim_nrf5340_cpuapp" + +config NET_CORE_IMAGE_HCI_IPC + bool "HCI IPC image on network core" + default y + depends on NET_CORE_BOARD != "" diff --git a/samples/bluetooth/public_broadcast_source/README.rst b/samples/bluetooth/public_broadcast_source/README.rst new file mode 100644 index 000000000000000..9111cd3f614c13c --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/README.rst @@ -0,0 +1,77 @@ +.. zephyr:code-sample:: bluetooth_public_broadcast_source + :name: Bluetooth: Public Broadcast Source + :relevant-api: bluetooth + + Bluetooth: Public Broadcast Source + +Overview +******** + +Application demonstrating the LE Public Broadcast Profile source functionality. +Will start advertising extended advertising and includes a Broadcast Audio Announcement. +The advertised broadcast audio stream quality will cycle between high and standard quality +every 15 seconds. + +This sample can be found under +:zephyr_file:`samples/bluetooth/public_broadcast_source` in the Zephyr tree. + +Check the :ref:`bluetooth samples section ` for general information. + +Requirements +************ + +* BlueZ running on the host, or +* A board with Bluetooth Low Energy 5.2 support + +Building and Running +******************** + +When building targeting an nrf52 series board with the Zephyr Bluetooth Controller, +use `-DOVERLAY_CONFIG=overlay-bt_ll_sw_split.conf` to enable the required ISO +feature support. + +Building for an nrf5340dk +------------------------- + +You can build both the application core image and an appropriate controller image for the network +core with: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/public_broadcast_source/ + :board: nrf5340dk_nrf5340_cpuapp + :goals: build + :west-args: --sysbuild + +If you prefer to only build the application core image, you can do so by doing instead: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/public_broadcast_source/ + :board: nrf5340dk_nrf5340_cpuapp + :goals: build + +In that case you can pair this application core image with the +:ref:`hci_ipc sample ` +:zephyr_file:`samples/bluetooth/hci_ipc/nrf5340_cpunet_iso-bt_ll_sw_split.conf` configuration. + +Building for a simulated nrf5340bsim +------------------------------------ + +Similarly to how you would for real HW, you can do: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/public_broadcast_source/ + :board: nrf5340bsim_nrf5340_cpuapp + :goals: build + :west-args: --sysbuild + +Note this will produce a Linux executable in `./build/zephyr/zephyr.exe`. +For more information, check :ref:`this board documentation `. + +Building for a simulated nrf52_bsim +----------------------------------- + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/public_broadcast_source/ + :board: nrf52_bsim + :goals: build + :gen-args: -DOVERLAY_CONFIG=overlay-bt_ll_sw_split.conf diff --git a/samples/bluetooth/public_broadcast_source/overlay-bt_ll_sw_split.conf b/samples/bluetooth/public_broadcast_source/overlay-bt_ll_sw_split.conf new file mode 100644 index 000000000000000..c73ff9c3ea9b88f --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/overlay-bt_ll_sw_split.conf @@ -0,0 +1,24 @@ +# Zephyr Bluetooth Controller +CONFIG_BT_LL_SW_SPLIT=y + +# Zephyr Controller tested maximum advertising data that can be set in a single HCI command +CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=191 + +# Enable support for Broadcast ISO in Zephyr Bluetooth Controller +CONFIG_BT_CTLR_ADV_ISO=y + +# Sufficient ISO PDU length for any BAP LC3 presets (155) +CONFIG_BT_CTLR_ADV_ISO_PDU_LEN_MAX=155 + +# Number of supported streams +CONFIG_BT_CTLR_ADV_ISO_STREAM_MAX=2 +CONFIG_BT_CTLR_ISOAL_SOURCES=2 + +# FIXME: Host needs CONFIG_BT_ISO_TX_MTU + 4 bytes for sequence number, and optionally +# additional + 4 bytes for timestamp when not using BT_ISO_TIMESTAMP_NONE in bt_iso_chan_send(), +# otherwise Host tries to fragment ISO data. +# When Host is fixed, CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE can inherit the +# CONFIG_BT_ISO_TX_MTU value. +# +# Supports the highest SDU size required by any BAP LC3 presets (155) +CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE=163 diff --git a/samples/bluetooth/public_broadcast_source/prj.conf b/samples/bluetooth/public_broadcast_source/prj.conf new file mode 100644 index 000000000000000..06b446f358f60a3 --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/prj.conf @@ -0,0 +1,25 @@ +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 +CONFIG_BT_HCI_ACL_FLOW_CONTROL=n + +# 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..d7eac76bcc60806 --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/sample.yaml @@ -0,0 +1,26 @@ +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 + sysbuild: true + sample.bluetooth.public_broadcast_source.bt_ll_sw_split: + harness: bluetooth + platform_allow: + - nrf52_bsim + - nrf52833dk_nrf52820 + - nrf52833dk_nrf52833 + integration_platforms: + - nrf52_bsim + - nrf52833dk_nrf52833 + extra_args: OVERLAY_CONFIG=overlay-bt_ll_sw_split.conf + tags: bluetooth 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..c719810e243586d --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/src/main.c @@ -0,0 +1,424 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BROADCAST_ENQUEUE_COUNT 2U + +/* PBS ASCII text */ +#define PBS_DEMO 'P', 'B', 'P' + +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; + +static uint8_t bis_codec_data[] = {BT_AUDIO_CODEC_DATA( + BT_AUDIO_CODEC_CONFIG_LC3_FREQ, BT_BYTES_LIST_LE16(BT_AUDIO_CODEC_CONFIG_LC3_FREQ_48KHZ))}; + + +const uint8_t pba_metadata[] = { + BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_PROGRAM_INFO, PBS_DEMO) +}; + +static uint8_t appearance_addata[] = { + BT_BYTES_LIST_LE16(BT_APPEARANCE_AUDIO_SOURCE_BROADCASTING_DEVICE) +}; + +static const char broadcast_name[] = "PBP Source 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 *ext_adv; + +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 == BT_HCI_ERR_LOCALHOST_TERM_CONN) { + 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 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); + NET_BUF_SIMPLE_DEFINE(pbp_ad_buf, BT_UUID_SIZE_16 + 1 + ARRAY_SIZE(pba_metadata)); + static enum bt_pbp_announcement_feature pba_params; + 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 Announcement */ + 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; + + /** + * Create a Public Broadcast Announcement + * 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"); + } + err = bt_pbp_get_announcement(&pba_metadata[1], ARRAY_SIZE(pba_metadata) - 1, + pba_params, &pbp_ad_buf); + if (err != 0) { + printk("Failed to create public broadcast announcement!: %d\n", err); + + return err; + } + ext_ad[3].type = BT_DATA_SVC_DATA16; + ext_ad[3].data_len = pbp_ad_buf.len; + ext_ad[3].data = pbp_ad_buf.data; + 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 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; + } + + return 0; +} + +static int reset(void) +{ + k_sem_reset(&sem_broadcast_started); + k_sem_reset(&sem_broadcast_stopped); + + 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; + + stream_params.stream = &broadcast_source_stream; + stream_params.data_len = ARRAY_SIZE(bis_codec_data); + 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; + } + + err = setup_extended_adv(&ext_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, ext_adv); + if (err != 0) { + printk("Unable to start broadcast source: %d\n", err); + + return; + } + + err = setup_extended_adv_data(broadcast_source, ext_adv); + if (err != 0) { + printk("Unable to setup extended advertising data: %d\n", err); + + return; + } + + err = start_extended_adv(ext_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(ext_adv); + if (err != 0) { + printk("Failed to stop and delete extended advertising: %d\n", err); + + return; + } + } +} + + +int main(void) +{ + int err; + + err = bt_enable(NULL); + if (err != 0) { + printk("Bluetooth enable failed (err %d)\n", err); + + return err; + } + + printk("Bluetooth initialized\n"); + + /* Initialize CAP Initiator */ + err = cap_initiator_init(); + if (err != 0) { + return err; + } + + printk("CAP initialized\n"); + + /* Configure and start broadcast stream */ + cap_initiator_setup(); + + return 0; +} diff --git a/samples/bluetooth/public_broadcast_source/sysbuild.cmake b/samples/bluetooth/public_broadcast_source/sysbuild.cmake new file mode 100644 index 000000000000000..8ae171ddefac52e --- /dev/null +++ b/samples/bluetooth/public_broadcast_source/sysbuild.cmake @@ -0,0 +1,44 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +if(NOT("${SB_CONFIG_NET_CORE_BOARD}" STREQUAL "")) + # For builds in the nrf5340, we build the netcore image with the controller + + set(NET_APP hci_ipc) + set(NET_APP_SRC_DIR ${ZEPHYR_BASE}/samples/bluetooth/${NET_APP}) + + ExternalZephyrProject_Add( + APPLICATION ${NET_APP} + SOURCE_DIR ${NET_APP_SRC_DIR} + BOARD ${SB_CONFIG_NET_CORE_BOARD} + ) + + set(${NET_APP}_CONF_FILE + ${NET_APP_SRC_DIR}/nrf5340_cpunet_iso-bt_ll_sw_split.conf + CACHE INTERNAL "" + ) + + # Let's build the net core library first + add_dependencies(${DEFAULT_IMAGE} ${NET_APP}) + + if("${BOARD}" STREQUAL "nrf5340bsim_nrf5340_cpuapp") + # For the simulated board, the application core build will produce the final executable + # for that, we give it the path to the netcore image + set(NET_LIBRARY_PATH ${CMAKE_BINARY_DIR}/${NET_APP}/zephyr/zephyr.elf) + set_property(TARGET ${DEFAULT_IMAGE} APPEND_STRING PROPERTY CONFIG + "CONFIG_NATIVE_SIMULATOR_EXTRA_IMAGE_PATHS=\"${NET_LIBRARY_PATH}\"\n" + ) + endif() +endif() + +if(("${BOARD}" MATCHES "native") OR ("${BOARD}" MATCHES "bsim")) + # Let's meet the expectation of finding the final executable in zephyr/zephyr.exe + add_custom_target(final_executable + ALL + COMMAND + ${CMAKE_COMMAND} -E copy + ${CMAKE_BINARY_DIR}/${DEFAULT_IMAGE}/zephyr/zephyr.exe + ${CMAKE_BINARY_DIR}/zephyr/zephyr.exe + DEPENDS ${DEFAULT_IMAGE} + ) +endif() \ No newline at end of file diff --git a/samples/bluetooth/tmap_bms/prj.conf b/samples/bluetooth/tmap_bms/prj.conf index ab3641a7a0fc0d6..be8acce18802d4d 100644 --- a/samples/bluetooth/tmap_bms/prj.conf +++ b/samples/bluetooth/tmap_bms/prj.conf @@ -1,3 +1,5 @@ +CONFIG_MAIN_STACK_SIZE=2048 + CONFIG_BT=y CONFIG_LOG=y CONFIG_BT_PERIPHERAL=y diff --git a/samples/bluetooth/tmap_bms/src/cap_initiator.c b/samples/bluetooth/tmap_bms/src/cap_initiator.c index af946f8ba945e4e..1e50977eab52126 100644 --- a/samples/bluetooth/tmap_bms/src/cap_initiator.c +++ b/samples/bluetooth/tmap_bms/src/cap_initiator.c @@ -16,7 +16,7 @@ #include #define BROADCAST_ENQUEUE_COUNT 2U -#define MOCK_CCID 0xAB + 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); @@ -32,8 +32,7 @@ static uint8_t bis_codec_data[] = {BT_AUDIO_CODEC_DATA( static const uint8_t new_metadata[] = { BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT, - BT_BYTES_LIST_LE16(BT_AUDIO_CONTEXT_TYPE_MEDIA)), - BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_CCID_LIST, MOCK_CCID), + BT_BYTES_LIST_LE16(BT_AUDIO_CONTEXT_TYPE_MEDIA)) }; static struct bt_bap_lc3_preset broadcast_preset_48_2_1 = @@ -60,6 +59,7 @@ static void broadcast_started_cb(struct bt_bap_stream *stream) static void broadcast_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_broadcast_stopped); } @@ -151,11 +151,13 @@ static int setup_extended_adv_data(struct bt_cap_broadcast_source *source, ext_ad[0].type = BT_DATA_SVC_DATA16; ext_ad[0].data_len = ARRAY_SIZE(tmap_addata); ext_ad[0].data = tmap_addata; + /* Broadcast Audio Announcement */ net_buf_simple_add_le16(&ad_buf, BT_UUID_BROADCAST_AUDIO_VAL); net_buf_simple_add_le24(&ad_buf, broadcast_id); ext_ad[1].type = BT_DATA_SVC_DATA16; ext_ad[1].data_len = ad_buf.len + sizeof(ext_ad[1].type); ext_ad[1].data = ad_buf.data; + 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); diff --git a/subsys/bluetooth/audio/CMakeLists.txt b/subsys/bluetooth/audio/CMakeLists.txt index 2ae08c2d7b5c038..48d013f78cc0fc6 100644 --- a/subsys/bluetooth/audio/CMakeLists.txt +++ b/subsys/bluetooth/audio/CMakeLists.txt @@ -62,3 +62,4 @@ 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_CAP_COMMANDER cap_commander.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 7e7bbfd09516e63..aa1478b64711fc0 100644 --- a/subsys/bluetooth/audio/Kconfig +++ b/subsys/bluetooth/audio/Kconfig @@ -60,6 +60,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..9c43f98446b050f --- /dev/null +++ b/subsys/bluetooth/audio/Kconfig.pbp @@ -0,0 +1,16 @@ +# 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. + +module = BT_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..d53988387f7f6bb --- /dev/null +++ b/subsys/bluetooth/audio/pbp.c @@ -0,0 +1,84 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_pbp, CONFIG_BT_PBP_LOG_LEVEL); + +/* PBA Service UUID + Public Broadcast Announcement features + Metadata Length */ +#define MIN_PBA_SIZE (BT_UUID_SIZE_16 + 1 + 1) + +int bt_pbp_get_announcement(const uint8_t meta[], size_t meta_len, + enum bt_pbp_announcement_feature features, + struct net_buf_simple *pba_data_buf) +{ + CHECKIF(pba_data_buf == NULL) { + LOG_DBG("No buffer provided for advertising data!\n"); + + return -EINVAL; + } + + CHECKIF((meta == NULL && meta_len != 0) || (meta != NULL && meta_len == 0)) { + LOG_DBG("Invalid metadata combination: %p %zu", meta, meta_len); + + return -EINVAL; + } + + CHECKIF(pba_data_buf->size < (meta_len + MIN_PBA_SIZE)) { + LOG_DBG("Buffer size needs to be at least %d!\n", meta_len + MIN_PBA_SIZE); + + return -EINVAL; + } + + /* Fill Announcement data */ + net_buf_simple_add_le16(pba_data_buf, BT_UUID_PBA_VAL); + net_buf_simple_add_u8(pba_data_buf, features); + net_buf_simple_add_u8(pba_data_buf, meta_len); + net_buf_simple_add_mem(pba_data_buf, meta, meta_len); + + return 0; +} + +uint8_t bt_pbp_parse_announcement(struct bt_data *data, + enum bt_pbp_announcement_feature *source_features, + uint8_t *meta) +{ + uint8_t meta_len = 0; + struct bt_uuid_16 adv_uuid; + + CHECKIF(!data || !source_features || !meta) { + return -EINVAL; + } + + if (data->data_len < MIN_PBA_SIZE) { + return -EBADMSG; + } + + if (data->type != BT_DATA_SVC_DATA16) { + return -EINVAL; + } + + if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) { + return -EINVAL; + } + + if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_PBA)) { + return -EBADMSG; + } + + /* Copy source features, metadata length and metadata from the Announcement */ + *source_features = data->data[BT_UUID_SIZE_16]; + meta_len = data->data[BT_UUID_SIZE_16 + sizeof(uint8_t)]; + memcpy(meta, data->data + MIN_PBA_SIZE, meta_len); + + return meta_len; +} diff --git a/subsys/bluetooth/audio/shell/CMakeLists.txt b/subsys/bluetooth/audio/shell/CMakeLists.txt index a5230278330d426..2644dda603db332 100644 --- a/subsys/bluetooth/audio/shell/CMakeLists.txt +++ b/subsys/bluetooth/audio/shell/CMakeLists.txt @@ -81,3 +81,7 @@ zephyr_library_sources_ifdef( CONFIG_BT_BAP_BROADCAST_ASSISTANT bap_broadcast_assistant.c ) +zephyr_library_sources_ifdef( + CONFIG_BT_PBP + pbp.c + ) diff --git a/subsys/bluetooth/audio/shell/audio.h b/subsys/bluetooth/audio/shell/audio.h index 8beae301036dd4a..f224df786adda9e 100644 --- a/subsys/bluetooth/audio/shell/audio.h +++ b/subsys/bluetooth/audio/shell/audio.h @@ -29,6 +29,7 @@ ssize_t audio_ad_data_add(struct bt_data *data, const size_t data_size, const bo ssize_t audio_pa_data_add(struct bt_data *data_array, const size_t data_array_size); ssize_t csis_ad_data_add(struct bt_data *data, const size_t data_size, const bool discoverable); size_t cap_acceptor_ad_data_add(struct bt_data data[], size_t data_size, bool discoverable); +size_t pbp_ad_data_add(struct bt_data data[], size_t data_size); #if defined(CONFIG_BT_AUDIO) /* Must guard before including audio.h as audio.h uses Kconfigs guarded by diff --git a/subsys/bluetooth/audio/shell/pbp.c b/subsys/bluetooth/audio/shell/pbp.c new file mode 100644 index 000000000000000..9a680d72d73570b --- /dev/null +++ b/subsys/bluetooth/audio/shell/pbp.c @@ -0,0 +1,91 @@ +/** @file + * @brief Bluetooth Public Broadcast Profile shell + */ + +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "shell/bt.h" + +#define PBS_DEMO 'P', 'B', 'P' + +const uint8_t pba_metadata[] = { + BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_PROGRAM_INFO, PBS_DEMO) +}; + +enum bt_pbp_announcement_feature pbp_features; +extern const struct shell *ctx_shell; + +static int cmd_pbp_set_features(const struct shell *sh, size_t argc, char **argv) +{ + int err = 0; + enum bt_pbp_announcement_feature features; + + features = shell_strtoul(argv[1], 16, &err); + if (err != 0) { + shell_error(sh, "Could not parse received features: %d", err); + + return -ENOEXEC; + } + + pbp_features = features; + + return err; +} + +size_t pbp_ad_data_add(struct bt_data data[], size_t data_size) +{ + int err; + + /** + * Public Broadcast Announcement Service UUID + + * + Public Broadcast Announcement features + Metadata Size + */ + NET_BUF_SIMPLE_DEFINE(pbp_ad_buf, BT_UUID_SIZE_16 + 1 + ARRAY_SIZE(pba_metadata)); + + err = bt_pbp_get_announcement(pba_metadata, + ARRAY_SIZE(pba_metadata), + pbp_features, + &pbp_ad_buf); + + if (err != 0) { + shell_info(ctx_shell, "Create Public Broadcast Announcement"); + } else { + shell_error(ctx_shell, "Failed to create Public Broadcast Announcement: %d", err); + } + + __ASSERT(data_size > 0, "No space for Public Broadcast Announcement"); + data[0].type = BT_DATA_SVC_DATA16; + data[0].data_len = pbp_ad_buf.len; + data[0].data = pbp_ad_buf.data; + + return 1U; +} + +static int cmd_pbp(const struct shell *sh, size_t argc, char **argv) +{ + if (argc > 1) { + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + } else { + shell_error(sh, "%s missing subcomand", argv[0]); + } + + return -ENOEXEC; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(pbp_cmds, + SHELL_CMD_ARG(set_features, NULL, + "Set the Public Broadcast Announcement features", + cmd_pbp_set_features, 2, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_CMD_ARG_REGISTER(pbp, &pbp_cmds, "Bluetooth pbp shell commands", cmd_pbp, 1, 1); diff --git a/tests/bluetooth/shell/audio.conf b/tests/bluetooth/shell/audio.conf index 8cf39af0bdb2874..cd30740a0bde65b 100644 --- a/tests/bluetooth/shell/audio.conf +++ b/tests/bluetooth/shell/audio.conf @@ -158,6 +158,9 @@ CONFIG_BT_CAP_COMMANDER=y # Telephone and Media Audio Profile CONFIG_BT_TMAP=y +# Public Broadcast Profile +CONFIG_BT_PBP=y + CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE=y CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE=y CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE=y diff --git a/tests/bsim/bluetooth/audio/prj.conf b/tests/bsim/bluetooth/audio/prj.conf index 8f387c8016fa0e6..7667764f4c74776 100644 --- a/tests/bsim/bluetooth/audio/prj.conf +++ b/tests/bsim/bluetooth/audio/prj.conf @@ -144,6 +144,9 @@ CONFIG_BT_PAC_SNK_LOC_WRITEABLE=y CONFIG_BT_PAC_SRC_LOC_WRITEABLE=y CONFIG_BT_GATT_AUTO_DISCOVER_CCC=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/common.h b/tests/bsim/bluetooth/audio/src/common.h index cc0b8d11a9654ed..7f853ccc9d2cee9 100644 --- a/tests/bsim/bluetooth/audio/src/common.h +++ b/tests/bsim/bluetooth/audio/src/common.h @@ -87,6 +87,8 @@ static const uint8_t mock_iso_data[] = { #define SYNC_RETRY_COUNT 6 /* similar to retries for connections */ #define PA_SYNC_SKIP 5 +#define PBP_STREAMS_TO_SEND 2 + extern struct bt_le_scan_cb common_scan_cb; extern const struct bt_data ad[AD_SIZE]; extern struct bt_conn *default_conn; diff --git a/tests/bsim/bluetooth/audio/src/main.c b/tests/bsim/bluetooth/audio/src/main.c index f4ef629454564dd..1124b66bd3958b9 100644 --- a/tests/bsim/bluetooth/audio/src/main.c +++ b/tests/bsim/bluetooth/audio/src/main.c @@ -35,6 +35,8 @@ extern struct bst_test_list *test_tmap_client_install(struct bst_test_list *test extern struct bst_test_list *test_tmap_server_install(struct bst_test_list *tests); extern struct bst_test_list *test_pacs_notify_client_install(struct bst_test_list *tests); extern struct bst_test_list *test_pacs_notify_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); extern struct bst_test_list *test_csip_notify_client_install(struct bst_test_list *tests); extern struct bst_test_list *test_csip_notify_server_install(struct bst_test_list *tests); @@ -68,6 +70,8 @@ bst_test_install_t test_installers[] = { test_tmap_client_install, test_pacs_notify_client_install, test_pacs_notify_server_install, + test_public_broadcast_source_install, + test_public_broadcast_sink_install, test_csip_notify_client_install, test_csip_notify_server_install, NULL diff --git a/tests/bsim/bluetooth/audio/src/pbp_public_broadcast_sink_test.c b/tests/bsim/bluetooth/audio/src/pbp_public_broadcast_sink_test.c new file mode 100644 index 000000000000000..bff336e034eb095 --- /dev/null +++ b/tests/bsim/bluetooth/audio/src/pbp_public_broadcast_sink_test.c @@ -0,0 +1,443 @@ +/* + * 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(1.5) +#define PA_SYNC_SKIP 5 +#define SYNC_RETRY_COUNT 6 /* similar to retries for connections */ + +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_syncable, 0U, 1U); +static K_SEM_DEFINE(sem_pa_sync_lost, 0U, 1U); +static K_SEM_DEFINE(sem_data_received, 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 const struct bt_audio_codec_cap codec = + BT_AUDIO_CODEC_CAP_LC3(BT_AUDIO_CODEC_LC3_FREQ_16KHZ | BT_AUDIO_CODEC_LC3_FREQ_24KHZ + | BT_AUDIO_CODEC_LC3_FREQ_48KHZ, + BT_AUDIO_CODEC_LC3_DURATION_10, + BT_AUDIO_CODEC_LC3_CHAN_COUNT_SUPPORT(1), 40u, 155u, 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 base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base) +{ + + printk("Received BASE with %u subgroups from 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) +{ + k_sem_give(&sem_syncable); +} + +static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = { + .base_recv = base_recv_cb, + .syncable = syncable_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) +{ + static uint32_t recv_cnt; + + recv_cnt++; + if (recv_cnt >= MIN_SEND_COUNT) { + k_sem_give(&sem_data_received); + } + 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 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 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, + .term = broadcast_pa_terminated, +}; + +static int reset(void) +{ + int err; + + 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) { + err = bt_bap_broadcast_sink_delete(broadcast_sink); + if (err) { + printk("Deleting broadcast sink failed (err %d)\n", err); + + return err; + } + + broadcast_sink = NULL; + } + + return 0; +} + +static int init(void) +{ + int err; + + err = bt_enable(NULL); + if (err) { + FAIL("Bluetooth enable failed (err %d)\n", err); + + return err; + } + + printk("Bluetooth initialized\n"); + + 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 source_features = 0U; + uint8_t meta[50]; + + 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; + } + } + + if (!bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_PBA)) { + bt_pbp_parse_announcement(data, &source_features, meta); + if (!(source_features & BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY)) { + /* This is a Standard Quality Public Broadcast Audio stream - do not sync */ + printk("This is a Standard Quality Public Broadcast Audio stream\n"); + pbs_found = false; + + return true; + } + + printk("Found Suitable Public Broadcast Announcement\n"); + pbs_found = true; + + /* Continue parsing if Broadcast Audio Announcement Service was not found */ + if (broadcast_id == INVALID_BROADCAST_ID) { + return true; + } + + return false; + } + + /* Continue parsing */ + return true; +} + +static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *ad) +{ + 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; + } + + bt_data_parse(ad, scan_check_and_sync_broadcast, (void *)&broadcast_id); + + 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 < PBP_STREAMS_TO_SEND) { + err = reset(); + if (err != 0) { + printk("Resetting failed: %d\n", err); + break; + } + + /* 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; + } + + /* 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, &broadcast_sink); + if (err != 0) { + printk("Sink not created!\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; + } + + /* Wait for data */ + k_sem_take(&sem_data_received, SEM_TIMEOUT); + + /* Wait for the stream to end */ + k_sem_take(&sem_pa_sync_lost, SEM_TIMEOUT); + + count++; + } + + if (count == PBP_STREAMS_TO_SEND - 1) { + /* Pass if we synced only with the high quality broadcast */ + PASS("Public Broadcast sink passed\n"); + } else { + FAIL("Public Broadcast sink failed\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_PBP */ + +struct bst_test_list *test_public_broadcast_sink_install(struct bst_test_list *tests) +{ + return tests; +} + +#endif /* CONFIG_BT_PBP */ diff --git a/tests/bsim/bluetooth/audio/src/pbp_public_broadcast_source_test.c b/tests/bsim/bluetooth/audio/src/pbp_public_broadcast_source_test.c new file mode 100644 index 000000000000000..4602e4963b276be --- /dev/null +++ b/tests/bsim/bluetooth/audio/src/pbp_public_broadcast_source_test.c @@ -0,0 +1,394 @@ +/* + * 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 BUF_NEEDED (BROADCAST_ENQUEUE_COUNT * CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT) +/* PBS ASCII text */ +#define PBS_DEMO 'P', 'B', 'P' +#define SEM_TIMEOUT K_SECONDS(2) + +extern enum bst_result_t bst_result; + +BUILD_ASSERT(CONFIG_BT_ISO_TX_BUF_COUNT >= 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, + BUF_NEEDED, + BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +const uint8_t pba_metadata[] = { + BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_PROGRAM_INFO, PBS_DEMO)}; + +static uint8_t bis_codec_data[] = { + BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CONFIG_LC3_FREQ, + BT_BYTES_LIST_LE16(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(pbp_ad_buf, BT_UUID_SIZE_16 + 1 + ARRAY_SIZE(pba_metadata)); + NET_BUF_SIMPLE_DEFINE(base_buf, 128); + static enum bt_pbp_announcement_feature pba_params; + 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; + + /** + * Create a Public Broadcast Announcement + * 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"); + } + + err = bt_pbp_get_announcement(pba_metadata, ARRAY_SIZE(pba_metadata) - 1, + pba_params, &pbp_ad_buf); + if (err != 0) { + printk("Failed to create public broadcast announcement!: %d\n", err); + + return err; + } + ext_ad[1].type = BT_DATA_SVC_DATA16; + ext_ad[1].data_len = pbp_ad_buf.len; + ext_ad[1].data = pbp_ad_buf.data; + + 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; + } + + 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; + + 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_len = ARRAY_SIZE(bis_codec_data); + 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 < PBP_STREAMS_TO_SEND) { + k_sem_reset(&sem_started); + k_sem_reset(&sem_stopped); + + err = setup_extended_adv(&adv); + if (err != 0) { + printk("Unable to setup extended advertiser: %d\n", err); + FAIL("Public Broadcast source failed\n"); + } + + err = bt_cap_initiator_broadcast_audio_create(&create_param, &broadcast_source); + if (err != 0) { + printk("Unable to create broadcast source: %d\n", err); + FAIL("Public Broadcast source failed\n"); + } + + err = bt_cap_initiator_broadcast_audio_start(broadcast_source, adv); + if (err != 0) { + printk("Unable to start broadcast source: %d\n", err); + FAIL("Public Broadcast source failed\n"); + } + + err = setup_extended_adv_data(broadcast_source, adv); + if (err != 0) { + printk("Unable to setup extended advertising data: %d\n", err); + FAIL("Public Broadcast source failed\n"); + } + + err = start_extended_adv(adv); + if (err != 0) { + printk("Unable to start extended advertiser: %d\n", err); + FAIL("Public Broadcast source failed\n"); + } + + k_sem_take(&sem_started, SEM_TIMEOUT); + + /* 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(3)); + + err = bt_cap_initiator_broadcast_audio_stop(broadcast_source); + if (err != 0) { + printk("Failed to stop broadcast source: %d\n", err); + FAIL("Public Broadcast source failed\n"); + } + + k_sem_take(&sem_stopped, SEM_TIMEOUT); + err = bt_cap_initiator_broadcast_audio_delete(broadcast_source); + if (err != 0) { + printk("Failed to stop broadcast source: %d\n", err); + FAIL("Public Broadcast source failed\n"); + } + + broadcast_source = NULL; + + err = stop_extended_adv(adv); + if (err != 0) { + printk("Failed to stop and delete extended advertising: %d\n", err); + FAIL("Public Broadcast source failed\n"); + } + + 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 100755 index 000000000000000..d20310d702353a8 --- /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=27 + +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