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 <danielaandreea.dumitrache@nxp.com>
This commit is contained in:
parent
afc319e3fa
commit
3bfb2e3ab2
36 changed files with 2436 additions and 3 deletions
11
samples/bluetooth/public_broadcast_sink/CMakeLists.txt
Normal file
11
samples/bluetooth/public_broadcast_sink/CMakeLists.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(pbp_broadcast_sink)
|
||||
|
||||
target_sources(app PRIVATE
|
||||
src/main.c
|
||||
)
|
||||
|
||||
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)
|
15
samples/bluetooth/public_broadcast_sink/Kconfig.sysbuild
Normal file
15
samples/bluetooth/public_broadcast_sink/Kconfig.sysbuild
Normal file
|
@ -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 != ""
|
77
samples/bluetooth/public_broadcast_sink/README.rst
Normal file
77
samples/bluetooth/public_broadcast_sink/README.rst
Normal file
|
@ -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 <bluetooth-samples>` 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 <bluetooth-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 <nrf5340bsim>`.
|
||||
|
||||
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
|
|
@ -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
|
34
samples/bluetooth/public_broadcast_sink/prj.conf
Normal file
34
samples/bluetooth/public_broadcast_sink/prj.conf
Normal file
|
@ -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
|
27
samples/bluetooth/public_broadcast_sink/sample.yaml
Normal file
27
samples/bluetooth/public_broadcast_sink/sample.yaml
Normal file
|
@ -0,0 +1,27 @@
|
|||
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
|
||||
- nrf5340bsim_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
|
436
samples/bluetooth/public_broadcast_sink/src/main.c
Normal file
436
samples/bluetooth/public_broadcast_sink/src/main.c
Normal file
|
@ -0,0 +1,436 @@
|
|||
/*
|
||||
* Copyright 2023 NXP
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include <stddef.h>
|
||||
#include <errno.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/audio/audio.h>
|
||||
#include <zephyr/bluetooth/audio/bap.h>
|
||||
#include <zephyr/bluetooth/audio/pacs.h>
|
||||
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
|
||||
#include <zephyr/bluetooth/audio/pbp.h>
|
||||
|
||||
#define AVAILABLE_SINK_CONTEXT (BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED | \
|
||||
BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | \
|
||||
BT_AUDIO_CONTEXT_TYPE_MEDIA | \
|
||||
BT_AUDIO_CONTEXT_TYPE_GAME | \
|
||||
BT_AUDIO_CONTEXT_TYPE_INSTRUCTIONAL)
|
||||
|
||||
#define SEM_TIMEOUT K_SECONDS(10)
|
||||
#define PA_SYNC_SKIP 5
|
||||
#define 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)
|
||||
{
|
||||
const struct bt_bap_base *base = bt_bap_base_get_base_from_ad(data);
|
||||
uint32_t base_bis_index_bitfield = 0U;
|
||||
int err;
|
||||
|
||||
/* Base is NULL if the data does not contain a valid BASE */
|
||||
if (base == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
err = bt_bap_base_get_bis_indexes(base, &base_bis_index_bitfield);
|
||||
if (err != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bis_index_bitfield = base_bis_index_bitfield & bis_index_mask;
|
||||
k_sem_give(&sem_base_received);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void broadcast_pa_recv(struct bt_le_per_adv_sync *sync,
|
||||
const struct bt_le_per_adv_sync_recv_info *info,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
bt_data_parse(buf, pa_decode_base, NULL);
|
||||
}
|
||||
|
||||
static void syncable_cb(struct bt_bap_broadcast_sink *sink, bool encrypted)
|
||||
{
|
||||
k_sem_give(&sem_syncable);
|
||||
}
|
||||
|
||||
static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base,
|
||||
size_t base_size)
|
||||
{
|
||||
k_sem_give(&sem_base_received);
|
||||
}
|
||||
|
||||
static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = {
|
||||
.syncable = syncable_cb,
|
||||
.base_recv = base_recv_cb,
|
||||
};
|
||||
|
||||
static void broadcast_pa_synced(struct bt_le_per_adv_sync *sync,
|
||||
struct bt_le_per_adv_sync_synced_info *info)
|
||||
{
|
||||
if (sync == bcast_pa_sync) {
|
||||
printk("PA sync %p synced for broadcast sink with broadcast ID 0x%06X\n",
|
||||
sync, bcast_id);
|
||||
|
||||
k_sem_give(&sem_pa_synced);
|
||||
}
|
||||
}
|
||||
|
||||
static void broadcast_pa_terminated(struct bt_le_per_adv_sync *sync,
|
||||
const struct bt_le_per_adv_sync_term_info *info)
|
||||
{
|
||||
if (sync == bcast_pa_sync) {
|
||||
printk("PA sync %p lost with reason %u\n", sync, info->reason);
|
||||
bcast_pa_sync = NULL;
|
||||
|
||||
k_sem_give(&sem_pa_sync_lost);
|
||||
}
|
||||
}
|
||||
|
||||
static int reset(void)
|
||||
{
|
||||
if (broadcast_sink != NULL) {
|
||||
int err = bt_bap_broadcast_sink_delete(broadcast_sink);
|
||||
|
||||
if (err) {
|
||||
printk("Deleting broadcast sink failed (err %d)\n", err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
broadcast_sink = NULL;
|
||||
}
|
||||
k_sem_reset(&sem_pa_synced);
|
||||
k_sem_reset(&sem_base_received);
|
||||
k_sem_reset(&sem_syncable);
|
||||
k_sem_reset(&sem_pa_sync_lost);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bap_broadcast_sink_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
bt_bap_broadcast_sink_register_cb(&broadcast_sink_cbs);
|
||||
bt_le_per_adv_sync_cb_register(&broadcast_sync_cb);
|
||||
|
||||
err = bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap);
|
||||
if (err) {
|
||||
printk("Capability register failed (err %d)\n", err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) {
|
||||
streams[i].ops = &stream_ops;
|
||||
}
|
||||
|
||||
for (size_t i = 0U; i < ARRAY_SIZE(streams_p); i++) {
|
||||
streams_p[i] = &streams[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bap_broadcast_sink_run(void)
|
||||
{
|
||||
while (true) {
|
||||
int err = reset();
|
||||
|
||||
if (err != 0) {
|
||||
printk("Resetting failed: %d - Aborting\n", err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Register callbacks */
|
||||
bt_le_scan_cb_register(&broadcast_scan_cb);
|
||||
|
||||
/* Start scanning */
|
||||
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
|
||||
if (err) {
|
||||
printk("Scan start failed (err %d)\n", err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Wait until a suitable source is found */
|
||||
err = k_sem_take(&sem_pa_synced, K_FOREVER);
|
||||
printk("Broadcast source PA synced, waiting for BASE\n");
|
||||
|
||||
/* Wait for BASE decode */
|
||||
err = k_sem_take(&sem_base_received, SEM_TIMEOUT);
|
||||
if (err != 0) {
|
||||
printk("sem_base_received timed out\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Create broadcast sink */
|
||||
printk("BASE received, creating broadcast sink\n");
|
||||
err = bt_bap_broadcast_sink_create(bcast_pa_sync, bcast_id, &broadcast_sink);
|
||||
if (err != 0) {
|
||||
printk("bt_bap_broadcast_sink_create failed: %d\n", err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
k_sem_take(&sem_syncable, SEM_TIMEOUT);
|
||||
if (err != 0) {
|
||||
printk("sem_syncable timed out\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Sync to broadcast source */
|
||||
printk("Syncing to broadcast\n");
|
||||
err = bt_bap_broadcast_sink_sync(broadcast_sink, bis_index_bitfield,
|
||||
streams_p, NULL);
|
||||
if (err != 0) {
|
||||
printk("Unable to sync to broadcast source: %d\n", err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
k_sem_take(&sem_pa_sync_lost, K_FOREVER);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bt_enable(NULL);
|
||||
if (err != 0) {
|
||||
printk("Bluetooth init failed (err %d)\n", err);
|
||||
|
||||
return err;
|
||||
}
|
||||
printk("Bluetooth initialized\n");
|
||||
|
||||
printk("Initializing BAP Broadcast Sink\n");
|
||||
err = bap_broadcast_sink_init();
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = bap_broadcast_sink_run();
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
24
samples/bluetooth/public_broadcast_sink/sysbuild.cmake
Normal file
24
samples/bluetooth/public_broadcast_sink/sysbuild.cmake
Normal file
|
@ -0,0 +1,24 @@
|
|||
# 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 ""
|
||||
)
|
||||
|
||||
native_simulator_set_child_images(${DEFAULT_IMAGE} ${NET_APP})
|
||||
endif()
|
||||
|
||||
native_simulator_set_final_executable(${DEFAULT_IMAGE})
|
11
samples/bluetooth/public_broadcast_source/CMakeLists.txt
Normal file
11
samples/bluetooth/public_broadcast_source/CMakeLists.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(public_broadcast_source)
|
||||
|
||||
target_sources(app PRIVATE
|
||||
src/main.c
|
||||
)
|
||||
|
||||
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)
|
15
samples/bluetooth/public_broadcast_source/Kconfig.sysbuild
Normal file
15
samples/bluetooth/public_broadcast_source/Kconfig.sysbuild
Normal file
|
@ -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 != ""
|
77
samples/bluetooth/public_broadcast_source/README.rst
Normal file
77
samples/bluetooth/public_broadcast_source/README.rst
Normal file
|
@ -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 <bluetooth-samples>` 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 <bluetooth-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 <nrf5340bsim>`.
|
||||
|
||||
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
|
|
@ -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
|
26
samples/bluetooth/public_broadcast_source/prj.conf
Normal file
26
samples/bluetooth/public_broadcast_source/prj.conf
Normal file
|
@ -0,0 +1,26 @@
|
|||
CONFIG_MAIN_STACK_SIZE=2048
|
||||
|
||||
CONFIG_BT=y
|
||||
CONFIG_LOG=y
|
||||
CONFIG_BT_AUDIO=y
|
||||
CONFIG_BT_PERIPHERAL=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"
|
27
samples/bluetooth/public_broadcast_source/sample.yaml
Normal file
27
samples/bluetooth/public_broadcast_source/sample.yaml
Normal file
|
@ -0,0 +1,27 @@
|
|||
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
|
||||
- nrf5340bsim_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
|
424
samples/bluetooth/public_broadcast_source/src/main.c
Normal file
424
samples/bluetooth/public_broadcast_source/src/main.c
Normal file
|
@ -0,0 +1,424 @@
|
|||
/*
|
||||
* Copyright 2023 NXP
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include <stddef.h>
|
||||
#include <errno.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/audio/audio.h>
|
||||
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
|
||||
#include <zephyr/bluetooth/audio/cap.h>
|
||||
#include <zephyr/bluetooth/audio/bap.h>
|
||||
#include <zephyr/bluetooth/audio/pbp.h>
|
||||
|
||||
#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;
|
||||
}
|
24
samples/bluetooth/public_broadcast_source/sysbuild.cmake
Normal file
24
samples/bluetooth/public_broadcast_source/sysbuild.cmake
Normal file
|
@ -0,0 +1,24 @@
|
|||
# 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 ""
|
||||
)
|
||||
|
||||
native_simulator_set_child_images(${DEFAULT_IMAGE} ${NET_APP})
|
||||
endif()
|
||||
|
||||
native_simulator_set_final_executable(${DEFAULT_IMAGE})
|
|
@ -1,3 +1,5 @@
|
|||
CONFIG_MAIN_STACK_SIZE=2048
|
||||
|
||||
CONFIG_BT=y
|
||||
CONFIG_LOG=y
|
||||
CONFIG_BT_PERIPHERAL=y
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#include <zephyr/bluetooth/audio/tmap.h>
|
||||
|
||||
#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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue