Samples: Bluetooth: CAP Acceptor broadcast support

Add broadcast support for the CAP acceptor sample.

This adds new sample-specific Kconfig options to help
select the right Kconfig options based on whether
unicast, broadcast or both is being used.

The babblesim implemented for the broadcast has been expanded
to verify that the CAP acceptor receives the broadcast audio.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2024-05-07 15:46:21 +02:00 committed by Alberto Escolar
commit d578c598eb
15 changed files with 912 additions and 41 deletions

View file

@ -6,7 +6,9 @@ project(cap_acceptor)
target_sources(app PRIVATE
src/main.c
src/cap_acceptor_unicast.c
)
zephyr_sources_ifdef(CONFIG_SAMPLE_UNICAST src/cap_acceptor_unicast.c)
zephyr_sources_ifdef(CONFIG_SAMPLE_BROADCAST src/cap_acceptor_broadcast.c)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)

View file

@ -0,0 +1,48 @@
# Copyright (c) 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
mainmenu "Bluetooth: Common Audio Profile Acceptor sample"
config SAMPLE_UNICAST
bool "Whether or not to search for CAP acceptors for unicast audio"
default y
select BT_BAP_UNICAST_SERVER
select BT_ISO_PERIPHERAL
select BT_ASCS
select BT_PAC_SNK
select BT_PAC_SNK_LOC
select BT_PAC_SRC
select BT_PAC_SRC_LOC
select BT_CTLR_PERIPHERAL_ISO if BT_CTLR
help
If set to true, the sample will start advertising connectable for
Broadcast Assistants.
config SAMPLE_BROADCAST
bool "Whether or not to search for CAP acceptors for unicast audio"
default y if !SAMPLE_UNICAST
select BT_ISO_SYNC_RECEIVER
select BT_BAP_SCAN_DELEGATOR
select BT_BAP_BROADCAST_SINK
select BT_PAC_SNK
select BT_PAC_SNK_LOC
select BT_PER_ADV_SYNC_TRANSFER_RECEIVER if !BT_CTLR || BT_CTLR_SYNC_TRANSFER_RECEIVER_SUPPORT
select BT_CTLR_SYNC_ISO if BT_CTLR
help
If set to true, the sample will start advertising syncable audio streams
config SAMPLE_SCAN_SELF
bool "Whether to scan for Broadcast Sources without Broadcast Assistant"
depends on SAMPLE_BROADCAST
help
If set to true, the sample will start scanning for Broadcast Sources
without waiting for a Broadcast Assistant to connect.
config SAMPLE_TARGET_BROADCAST_NAME
string "Target Broadcast Device Name when self-scanning"
default ""
help
Name of target broadcast device. If not empty string, sink device
will only listen to the specified broadcast source. Not case sensitive.
source "Kconfig.zephyr"

View file

@ -9,6 +9,7 @@ Overview
Application demonstrating the CAP Acceptor functionality.
Starts by advertising for a CAP Initiator to connect and set up available streams.
It can also be configured to start scanning for broadcast audio streams by itself.
This sample can be found under :zephyr_file:`samples/bluetooth/cap_acceptor` in the Zephyr tree.

View file

@ -2,11 +2,9 @@
CONFIG_BT_LL_SW_SPLIT=y
# Zephyr Controller tested maximum advertising data that can be set in a single HCI command
CONFIG_BT_CTLR_SCAN_DATA_LEN_MAX=191
CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=191
# Enable support for central ISO in Zephyr Bluetooth Controller
CONFIG_BT_CTLR_PERIPHERAL_ISO=y
# Support the highest SDU size required by any BAP LC3 presets (155) + 8 bytes of HCI ISO Data
# packet overhead (the Packet_Sequence_Number, ISO_SDU_Length, Packet_Status_Flag fields; and
# the optional Time_Stamp field, if supplied)

View file

@ -1,23 +1,16 @@
CONFIG_BT=y
CONFIG_LOG=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_ISO_PERIPHERAL=y
CONFIG_BT_GATT_DYNAMIC_DB=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_DEVICE_NAME="CAP Acceptor"
CONFIG_BT_AUDIO=y
CONFIG_BT_SMP=y
CONFIG_BT_KEYS_OVERWRITE_OLDEST=y
# CAP
CONFIG_BT_CAP_ACCEPTOR=y
# BAP support
CONFIG_BT_BAP_UNICAST_SERVER=y
# Mandatory to support at least 1 for ASCS
CONFIG_BT_ATT_PREPARE_COUNT=1
@ -28,9 +21,3 @@ CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT=1
# Support an ISO channel per ASE
CONFIG_BT_ISO_MAX_CHAN=2
# PACS
CONFIG_BT_PAC_SNK=y
CONFIG_BT_PAC_SNK_LOC=y
CONFIG_BT_PAC_SRC=y
CONFIG_BT_PAC_SRC_LOC=y

View file

@ -38,6 +38,14 @@ struct peer_config {
*/
int init_cap_acceptor_unicast(struct peer_config *peer);
/**
* @brief Initialize the unicast part of the CAP Acceptor
*
* @retval 0 if success
* @retval -ENOEXEC if callbacks failed to be registered
*/
int init_cap_acceptor_broadcast(void);
/**
* @brief Request to allocate a CAP stream
*

View file

@ -0,0 +1,762 @@
/** @file
* @brief Bluetooth Common Audio Profile (CAP) Acceptor broadcast.
*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_core.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/toolchain.h>
#include "cap_acceptor.h"
LOG_MODULE_REGISTER(cap_acceptor_broadcast, LOG_LEVEL_INF);
#define NAME_LEN sizeof(CONFIG_SAMPLE_TARGET_BROADCAST_NAME) + 1
#define PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO 20 /* Set the timeout relative to interval */
#define PA_SYNC_SKIP 5
enum broadcast_flag {
FLAG_BROADCAST_SYNC_REQUESTED,
FLAG_BROADCAST_CODE_REQUIRED,
FLAG_BROADCAST_CODE_RECEIVED,
FLAG_BROADCAST_SYNCABLE,
FLAG_BROADCAST_SYNCING,
FLAG_BROADCAST_SYNCED,
FLAG_BASE_RECEIVED,
FLAG_PA_SYNCING,
FLAG_PA_SYNCED,
FLAG_SCANNING,
FLAG_NUM,
};
ATOMIC_DEFINE(flags, FLAG_NUM);
static struct broadcast_sink {
const struct bt_bap_scan_delegator_recv_state *req_recv_state;
uint8_t sink_broadcast_code[BT_AUDIO_BROADCAST_CODE_SIZE];
struct bt_bap_broadcast_sink *bap_broadcast_sink;
struct bt_cap_stream broadcast_stream;
struct bt_le_per_adv_sync *pa_sync;
uint8_t received_base[UINT8_MAX];
uint32_t requested_bis_sync;
uint32_t broadcast_id;
} broadcast_sink;
uint64_t total_broadcast_rx_iso_packet_count; /* This value is exposed to test code */
/** Starts scanning if it passes a series of check to determine if we are in the right state */
static int check_start_scan(void)
{
int err;
if (atomic_test_bit(flags, FLAG_SCANNING)) {
return -EALREADY;
}
if (atomic_test_bit(flags, FLAG_PA_SYNCED)) {
return -EALREADY;
}
if (atomic_test_bit(flags, FLAG_BROADCAST_SYNCED)) {
return -EALREADY;
}
err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL);
if (err != 0) {
LOG_ERR("Unable to start scan for CAP initiators: %d", err);
return err;
}
atomic_set_bit(flags, FLAG_SCANNING);
return 0;
}
static void broadcast_stream_started_cb(struct bt_bap_stream *bap_stream)
{
LOG_INF("Started bap_stream %p", bap_stream);
total_broadcast_rx_iso_packet_count = 0U;
atomic_clear_bit(flags, FLAG_BROADCAST_SYNCING);
atomic_set_bit(flags, FLAG_BROADCAST_SYNCED);
}
static void broadcast_stream_stopped_cb(struct bt_bap_stream *bap_stream, uint8_t reason)
{
LOG_INF("Stopped bap_stream %p with reason 0x%02X", bap_stream, reason);
atomic_clear_bit(flags, FLAG_BROADCAST_SYNCING);
atomic_clear_bit(flags, FLAG_BROADCAST_SYNCED);
if (IS_ENABLED(CONFIG_SAMPLE_SCAN_SELF)) {
(void)check_start_scan();
}
}
static void broadcast_stream_recv_cb(struct bt_bap_stream *bap_stream,
const struct bt_iso_recv_info *info, struct net_buf *buf)
{
/* Triggered every time we receive an HCI data packet from the controller.
* A call to this does not indicate valid data
* (see the `info->flags` for which flags to check),
*/
if ((total_broadcast_rx_iso_packet_count % 100U) == 0U) {
LOG_INF("Received %llu HCI ISO data packets", total_broadcast_rx_iso_packet_count);
}
total_broadcast_rx_iso_packet_count++;
}
static int create_broadcast_sink(void)
{
int err;
if (broadcast_sink.bap_broadcast_sink != NULL) {
return -EALREADY;
}
LOG_INF("Creating broadcast sink for broadcast ID 0x%06X", broadcast_sink.broadcast_id);
err = bt_bap_broadcast_sink_create(broadcast_sink.pa_sync, broadcast_sink.broadcast_id,
&broadcast_sink.bap_broadcast_sink);
if (err != 0) {
LOG_ERR("Failed to create broadcast sink: %d\n", err);
return err;
}
return 0;
}
/** Performs a series of checks to see if we are ready to sync the broadcast sink */
static void check_sync_broadcast(void)
{
struct bt_bap_stream *sync_stream = &broadcast_sink.broadcast_stream.bap_stream;
uint32_t sync_bitfield;
int err;
if (!atomic_test_bit(flags, FLAG_BASE_RECEIVED)) {
LOG_DBG("FLAG_BASE_RECEIVED");
return;
}
if (!atomic_test_bit(flags, FLAG_BROADCAST_SYNCABLE)) {
LOG_DBG("FLAG_BROADCAST_SYNCABLE");
return;
}
if (atomic_test_bit(flags, FLAG_BROADCAST_CODE_REQUIRED) &&
!atomic_test_bit(flags, FLAG_BROADCAST_CODE_RECEIVED)) {
LOG_DBG("FLAG_BROADCAST_CODE_REQUIRED");
return;
}
if (!atomic_test_bit(flags, FLAG_BROADCAST_SYNC_REQUESTED)) {
LOG_DBG("FLAG_BROADCAST_SYNC_REQUESTED");
return;
}
if (!atomic_test_bit(flags, FLAG_PA_SYNCED)) {
LOG_DBG("FLAG_PA_SYNCED");
return;
}
if (atomic_test_bit(flags, FLAG_BROADCAST_SYNCED) ||
atomic_test_bit(flags, FLAG_BROADCAST_SYNCING)) {
LOG_DBG("FLAG_BROADCAST_SYNCED");
return;
}
if (broadcast_sink.requested_bis_sync == BT_BAP_BIS_SYNC_NO_PREF) {
uint32_t base_bis;
/* Get the first BIS index from the BASE */
err = bt_bap_base_get_bis_indexes(
(struct bt_bap_base *)broadcast_sink.received_base, &base_bis);
if (err != 0) {
LOG_ERR("Failed to get BIS indexes from BASE: %d", err);
return;
}
sync_bitfield = 0U;
for (uint8_t i = BT_ISO_BIS_INDEX_MIN; i < BT_ISO_BIS_INDEX_MAX; i++) {
if (base_bis & BT_ISO_BIS_INDEX_BIT(i)) {
sync_bitfield = BT_ISO_BIS_INDEX_BIT(i);
break;
}
}
} else {
sync_bitfield = broadcast_sink.requested_bis_sync;
}
LOG_INF("Syncing to broadcast with bitfield 0x%08X", sync_bitfield);
/* Sync the BIG */
err = bt_bap_broadcast_sink_sync(broadcast_sink.bap_broadcast_sink, sync_bitfield,
&sync_stream, broadcast_sink.sink_broadcast_code);
if (err != 0) {
LOG_ERR("Failed to sync the broadcast sink: %d", err);
} else {
atomic_set_bit(flags, FLAG_BROADCAST_SYNCING);
}
}
static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base,
size_t base_size)
{
memcpy(broadcast_sink.received_base, base, base_size);
if (!atomic_test_and_set_bit(flags, FLAG_BASE_RECEIVED)) {
LOG_INF("BASE received");
check_sync_broadcast();
}
}
static void syncable_cb(struct bt_bap_broadcast_sink *sink, const struct bt_iso_biginfo *biginfo)
{
if (!biginfo->encryption) {
atomic_clear_bit(flags, FLAG_BROADCAST_CODE_REQUIRED);
} else {
atomic_set_bit(flags, FLAG_BROADCAST_CODE_REQUIRED);
}
if (!atomic_test_and_set_bit(flags, FLAG_BROADCAST_SYNCABLE)) {
LOG_INF("BIGInfo received");
check_sync_broadcast();
}
}
static void pa_timer_handler(struct k_work *work)
{
atomic_clear_bit(flags, FLAG_PA_SYNCING);
if (broadcast_sink.pa_sync != NULL) {
int err;
err = bt_le_per_adv_sync_delete(broadcast_sink.pa_sync);
if (err != 0) {
LOG_ERR("Failed to delete PA sync: %d", err);
}
}
if (broadcast_sink.req_recv_state != NULL) {
enum bt_bap_pa_state pa_state;
if (broadcast_sink.req_recv_state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ) {
pa_state = BT_BAP_PA_STATE_NO_PAST;
} else {
pa_state = BT_BAP_PA_STATE_FAILED;
}
bt_bap_scan_delegator_set_pa_state(broadcast_sink.req_recv_state->src_id, pa_state);
}
LOG_INF("PA sync timeout");
}
static K_WORK_DELAYABLE_DEFINE(pa_timer, pa_timer_handler);
static uint16_t interval_to_sync_timeout(uint16_t pa_interval)
{
uint16_t pa_timeout;
if (pa_interval == BT_BAP_PA_INTERVAL_UNKNOWN) {
/* Use maximum value to maximize chance of success */
pa_timeout = BT_GAP_PER_ADV_MAX_TIMEOUT;
} else {
uint32_t interval_ms;
uint32_t timeout;
/* Add retries and convert to unit in 10's of ms */
interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(pa_interval);
timeout = (interval_ms * PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO) / 10;
/* Enforce restraints */
pa_timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, BT_GAP_PER_ADV_MAX_TIMEOUT);
}
return pa_timeout;
}
static int pa_sync_with_past(struct bt_conn *conn,
const struct bt_bap_scan_delegator_recv_state *recv_state,
uint16_t pa_interval)
{
struct bt_le_per_adv_sync_transfer_param param = {0};
int err;
param.skip = PA_SYNC_SKIP;
param.timeout = interval_to_sync_timeout(pa_interval);
err = bt_le_per_adv_sync_transfer_subscribe(conn, &param);
if (err != 0) {
LOG_ERR("Could not do PAST subscribe: %d", err);
return err;
}
err = bt_bap_scan_delegator_set_pa_state(recv_state->src_id, BT_BAP_PA_STATE_INFO_REQ);
if (err != 0) {
LOG_ERR("Failed to set PA state to BT_BAP_PA_STATE_INFO_REQ: %d", err);
return err;
}
k_work_reschedule(&pa_timer, K_MSEC(param.timeout * 10));
return 0;
}
static int pa_sync_without_past(const bt_addr_le_t *addr, uint8_t adv_sid, uint16_t pa_interval)
{
struct bt_le_per_adv_sync_param param = {0};
int err;
bt_addr_le_copy(&param.addr, addr);
param.options = BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE;
param.sid = adv_sid;
param.skip = PA_SYNC_SKIP;
param.timeout = interval_to_sync_timeout(pa_interval);
err = bt_le_per_adv_sync_create(&param, &broadcast_sink.pa_sync);
if (err != 0) {
LOG_ERR("Failed to create PA sync: %d", err);
return err;
}
k_work_reschedule(&pa_timer, K_MSEC(param.timeout * 10));
return 0;
}
static int pa_sync_req_cb(struct bt_conn *conn,
const struct bt_bap_scan_delegator_recv_state *recv_state,
bool past_avail, uint16_t pa_interval)
{
LOG_INF("Received request to sync to PA (PAST %savailble): %u", past_avail ? "" : "not ",
recv_state->pa_sync_state);
broadcast_sink.req_recv_state = recv_state;
if (recv_state->pa_sync_state == BT_BAP_PA_STATE_SYNCED ||
recv_state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ ||
broadcast_sink.pa_sync != NULL) {
/* Already syncing */
LOG_WRN("Rejecting PA sync request because we are already syncing or synced");
return -EALREADY;
}
if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER) && past_avail) {
int err;
err = pa_sync_with_past(conn, recv_state, pa_interval);
if (err != 0) {
return err;
}
LOG_INF("Syncing with PAST");
} else {
int err;
err = pa_sync_without_past(&recv_state->addr, recv_state->adv_sid, pa_interval);
if (err != 0) {
return err;
}
LOG_INF("Syncing without PAST");
}
broadcast_sink.broadcast_id = recv_state->broadcast_id;
atomic_set_bit(flags, FLAG_PA_SYNCING);
return 0;
}
static int pa_sync_term_req_cb(struct bt_conn *conn,
const struct bt_bap_scan_delegator_recv_state *recv_state)
{
int err;
broadcast_sink.req_recv_state = recv_state;
err = bt_le_per_adv_sync_delete(broadcast_sink.pa_sync);
if (err != 0) {
LOG_ERR("Failed to delete PA sync: %d", err);
return err;
}
k_work_cancel_delayable(&pa_timer);
broadcast_sink.pa_sync = NULL;
return 0;
}
static void broadcast_code_cb(struct bt_conn *conn,
const struct bt_bap_scan_delegator_recv_state *recv_state,
const uint8_t broadcast_code[BT_AUDIO_BROADCAST_CODE_SIZE])
{
LOG_INF("Broadcast code received for %p", recv_state);
broadcast_sink.req_recv_state = recv_state;
(void)memcpy(broadcast_sink.sink_broadcast_code, broadcast_code,
BT_AUDIO_BROADCAST_CODE_SIZE);
atomic_set_bit(flags, FLAG_BROADCAST_CODE_RECEIVED);
}
static uint32_t get_req_bis_sync(const uint32_t bis_sync_req[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS])
{
uint32_t bis_sync = 0U;
for (int i = 0; i < CONFIG_BT_BAP_BASS_MAX_SUBGROUPS; i++) {
bis_sync |= bis_sync_req[i];
}
return bis_sync;
}
static int bis_sync_req_cb(struct bt_conn *conn,
const struct bt_bap_scan_delegator_recv_state *recv_state,
const uint32_t bis_sync_req[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS])
{
const uint32_t new_bis_sync_req = get_req_bis_sync(bis_sync_req);
LOG_INF("BIS sync request received for %p: 0x%08x", recv_state, bis_sync_req[0]);
if (new_bis_sync_req != BT_BAP_BIS_SYNC_NO_PREF && POPCOUNT(new_bis_sync_req) > 1U) {
LOG_WRN("Rejecting BIS sync request for 0x%08X as we do not support that",
new_bis_sync_req);
return -ENOMEM;
}
if (broadcast_sink.requested_bis_sync != new_bis_sync_req) {
return 0; /* no op */
}
if (atomic_test_bit(flags, FLAG_BROADCAST_SYNCED)) {
/* If the BIS sync request is received while we are already
* synced, it means that the requested BIS sync has changed.
*/
int err;
/* The stream stopped callback will be called as part of this,
* and we do not need to wait for any events from the
* controller. Thus, when this returns, the broadcast sink is stopped
*/
err = bt_bap_broadcast_sink_stop(broadcast_sink.bap_broadcast_sink);
if (err != 0) {
LOG_ERR("Failed to stop Broadcast Sink: %d", err);
return err;
}
err = bt_bap_broadcast_sink_delete(broadcast_sink.bap_broadcast_sink);
if (err != 0) {
LOG_ERR("Failed to delete Broadcast Sink: %d", err);
return err;
}
broadcast_sink.bap_broadcast_sink = NULL;
atomic_clear_bit(flags, FLAG_BROADCAST_SYNCED);
}
broadcast_sink.requested_bis_sync = new_bis_sync_req;
if (broadcast_sink.requested_bis_sync != 0U) {
atomic_set_bit(flags, FLAG_BROADCAST_SYNC_REQUESTED);
check_sync_broadcast();
} else {
atomic_clear_bit(flags, FLAG_BROADCAST_SYNC_REQUESTED);
}
return 0;
}
static void bap_pa_sync_synced_cb(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info)
{
if (sync == broadcast_sink.pa_sync ||
(broadcast_sink.req_recv_state != NULL &&
bt_addr_le_eq(info->addr, &broadcast_sink.req_recv_state->addr) &&
info->sid == broadcast_sink.req_recv_state->adv_sid)) {
int err;
LOG_INF("PA sync %p synced for broadcast sink", (void *)sync);
if (broadcast_sink.pa_sync == NULL) {
broadcast_sink.pa_sync = sync;
}
k_work_cancel_delayable(&pa_timer);
atomic_set_bit(flags, FLAG_PA_SYNCED);
atomic_clear_bit(flags, FLAG_PA_SYNCING);
if (IS_ENABLED(CONFIG_SAMPLE_SCAN_SELF)) {
int err;
err = bt_le_scan_stop();
if (err != 0) {
LOG_ERR("Unable to stop scanning: %d", err);
} else {
atomic_clear_bit(flags, FLAG_SCANNING);
}
}
err = create_broadcast_sink();
if (err != 0) {
LOG_ERR("Failed to create broadcast sink: %d", err);
} else {
check_sync_broadcast();
}
}
}
static void bap_pa_sync_terminated_cb(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_term_info *info)
{
if (sync == broadcast_sink.pa_sync) {
int err;
LOG_INF("PA sync %p lost with reason %u", (void *)sync, info->reason);
/* Without PA we cannot sync to any new BIG - Clear data */
broadcast_sink.requested_bis_sync = 0;
broadcast_sink.pa_sync = NULL;
k_work_cancel_delayable(&pa_timer);
atomic_clear_bit(flags, FLAG_BROADCAST_SYNCABLE);
atomic_clear_bit(flags, FLAG_PA_SYNCED);
atomic_clear_bit(flags, FLAG_PA_SYNCING);
atomic_clear_bit(flags, FLAG_BASE_RECEIVED);
atomic_clear_bit(flags, FLAG_BROADCAST_CODE_REQUIRED);
atomic_clear_bit(flags, FLAG_BROADCAST_CODE_RECEIVED);
atomic_clear_bit(flags, FLAG_BROADCAST_SYNC_REQUESTED);
err = bt_bap_scan_delegator_set_pa_state(broadcast_sink.req_recv_state->src_id,
BT_BAP_PA_STATE_NOT_SYNCED);
if (err != 0) {
LOG_ERR("Failed to set PA state to BT_BAP_PA_STATE_NOT_SYNCED: %d", err);
}
if (IS_ENABLED(CONFIG_SAMPLE_SCAN_SELF)) {
(void)check_start_scan();
}
}
}
static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data)
{
const struct bt_le_scan_recv_info *info = user_data;
struct bt_le_per_adv_sync_param param = {0};
char le_addr[BT_ADDR_LE_STR_LEN];
struct bt_uuid_16 adv_uuid;
uint32_t broadcast_id;
int err;
if (data->type != BT_DATA_SVC_DATA16) {
return true;
}
if (data->data_len < BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE) {
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)) {
return true;
}
broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16);
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
printk("Found broadcaster with ID 0x%06X and addr %s and sid 0x%02X\n", broadcast_id,
le_addr, info->sid);
bt_addr_le_copy(&param.addr, info->addr);
param.options = BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE;
param.sid = info->sid;
param.skip = PA_SYNC_SKIP;
param.timeout = interval_to_sync_timeout(info->interval);
err = bt_le_per_adv_sync_create(&param, &broadcast_sink.pa_sync);
if (err != 0) {
LOG_ERR("Failed to create PA sync: %d", err);
} else {
LOG_INF("Syncing without PAST from scan");
broadcast_sink.broadcast_id = broadcast_id;
atomic_set_bit(flags, FLAG_PA_SYNCING);
k_work_reschedule(&pa_timer, K_MSEC(param.timeout * 10));
/* Since we are scanning ourselves, we consider this as broadcast sync has been
* requested
*/
broadcast_sink.requested_bis_sync = BT_BAP_BIS_SYNC_NO_PREF;
atomic_set_bit(flags, FLAG_BROADCAST_SYNC_REQUESTED);
}
/* Stop parsing */
return false;
}
static bool is_substring(const char *substr, const char *str)
{
const size_t str_len = strlen(str);
const size_t sub_str_len = strlen(substr);
if (sub_str_len > str_len) {
return false;
}
for (size_t pos = 0; pos < str_len; pos++) {
if (pos + sub_str_len > str_len) {
return false;
}
if (strncasecmp(substr, &str[pos], sub_str_len) == 0) {
return true;
}
}
return false;
}
static bool data_cb(struct bt_data *data, void *user_data)
{
char *name = user_data;
switch (data->type) {
case BT_DATA_NAME_SHORTENED:
case BT_DATA_NAME_COMPLETE:
case BT_DATA_BROADCAST_NAME:
memcpy(name, data->data, MIN(data->data_len, NAME_LEN - 1));
return false;
default:
return true;
}
}
static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad)
{
if (atomic_test_bit(flags, FLAG_PA_SYNCED) || atomic_test_bit(flags, FLAG_PA_SYNCING) ||
atomic_test_bit(flags, FLAG_BROADCAST_SYNCED) ||
atomic_test_bit(flags, FLAG_BROADCAST_SYNCING)) {
/* If we are already synced or syncing, we do not care about scan reports */
return;
}
/* Only consider advertisers with periodic advertising */
if (info->interval != 0U) {
/* call to bt_data_parse consumes netbufs so shallow clone for verbose output */
/* If broadcast_sink.req_recv_state is NULL then we have been requested by a
* broadcast assistant to sync to a specific broadcast source. In that case we do
* not apply our own broadcast name filter.
*/
if (broadcast_sink.req_recv_state != NULL &&
strlen(CONFIG_SAMPLE_TARGET_BROADCAST_NAME) > 0U) {
struct net_buf_simple buf_copy;
char name[NAME_LEN] = {0};
net_buf_simple_clone(ad, &buf_copy);
bt_data_parse(&buf_copy, data_cb, name);
if (!(is_substring(CONFIG_SAMPLE_TARGET_BROADCAST_NAME, name))) {
return;
}
}
bt_data_parse(ad, scan_check_and_sync_broadcast, (void *)info);
}
}
int init_cap_acceptor_broadcast(void)
{
static bool cbs_registered;
int err;
if (!cbs_registered) {
static struct bt_bap_scan_delegator_cb scan_delegator_cbs = {
.pa_sync_req = pa_sync_req_cb,
.pa_sync_term_req = pa_sync_term_req_cb,
.broadcast_code = broadcast_code_cb,
.bis_sync_req = bis_sync_req_cb,
};
static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = {
.base_recv = base_recv_cb,
.syncable = syncable_cb,
};
static struct bt_bap_stream_ops broadcast_stream_ops = {
.started = broadcast_stream_started_cb,
.stopped = broadcast_stream_stopped_cb,
.recv = broadcast_stream_recv_cb,
};
static struct bt_le_per_adv_sync_cb bap_pa_sync_cb = {
.synced = bap_pa_sync_synced_cb,
.term = bap_pa_sync_terminated_cb,
};
static struct bt_le_scan_cb bap_scan_cb = {
.recv = broadcast_scan_recv,
};
err = bt_bap_broadcast_sink_register_cb(&broadcast_sink_cbs);
if (err != 0) {
LOG_ERR("Failed to register BAP broadcast sink callbacks: %d", err);
return -ENOEXEC;
}
bt_cap_stream_ops_register(&broadcast_sink.broadcast_stream, &broadcast_stream_ops);
bt_bap_scan_delegator_register_cb(&scan_delegator_cbs);
bt_le_per_adv_sync_cb_register(&bap_pa_sync_cb);
if (IS_ENABLED(CONFIG_SAMPLE_SCAN_SELF)) {
bt_le_scan_cb_register(&bap_scan_cb);
}
cbs_registered = true;
}
if (IS_ENABLED(CONFIG_SAMPLE_SCAN_SELF)) {
err = check_start_scan();
if (err != 0) {
LOG_ERR("Unable to start scan for CAP initiators: %d", err);
return err;
}
}
return 0;
}

View file

@ -43,7 +43,8 @@ LOG_MODULE_REGISTER(cap_acceptor_unicast, LOG_LEVEL_INF);
static const struct bt_audio_codec_qos_pref qos_pref = BT_AUDIO_CODEC_QOS_PREF(
UNFRAMED_SUPPORTED, PREF_PHY, RTN, LATENCY, MIN_PD, MAX_PD, MIN_PD, MAX_PD);
uint64_t total_rx_iso_packet_count; /* This value is exposed to test code */
uint64_t total_unicast_rx_iso_packet_count; /* This value is exposed to test code */
uint64_t total_unicast_tx_iso_packet_count; /* This value is exposed to test code */
static bool log_codec_cfg_cb(struct bt_data *data, void *user_data)
{
@ -295,7 +296,7 @@ static void unicast_stream_enabled_cb(struct bt_bap_stream *bap_stream)
static void unicast_stream_started_cb(struct bt_bap_stream *bap_stream)
{
LOG_INF("Started bap_stream %p", bap_stream);
total_rx_iso_packet_count = 0U;
total_unicast_rx_iso_packet_count = 0U;
}
static void unicast_stream_metadata_updated_cb(struct bt_bap_stream *bap_stream)
@ -331,11 +332,22 @@ static void unicast_stream_recv_cb(struct bt_bap_stream *bap_stream,
* (see the `info->flags` for which flags to check),
*/
if ((total_rx_iso_packet_count % 100U) == 0U) {
LOG_INF("Received %llu HCI ISO data packets", total_rx_iso_packet_count);
if ((total_unicast_rx_iso_packet_count % 100U) == 0U) {
LOG_INF("Received %llu HCI ISO data packets", total_unicast_rx_iso_packet_count);
}
total_rx_iso_packet_count++;
total_unicast_rx_iso_packet_count++;
}
static void unicast_stream_sent_cb(struct bt_bap_stream *stream)
{
/* Triggered every time we have sent an HCI data packet to the controller */
if ((total_unicast_tx_iso_packet_count % 100U) == 0U) {
LOG_INF("Sent %llu HCI ISO data packets", total_unicast_tx_iso_packet_count);
}
total_unicast_tx_iso_packet_count++;
}
static void tx_thread_func(void *arg1, void *arg2, void *arg3)
@ -397,6 +409,7 @@ int init_cap_acceptor_unicast(struct peer_config *peer)
.stopped = unicast_stream_stopped_cb,
.released = unicast_stream_released_cb,
.recv = unicast_stream_recv_cb,
.sent = unicast_stream_sent_cb,
};
static bool cbs_registered;

View file

@ -41,9 +41,10 @@ LOG_MODULE_REGISTER(cap_acceptor, LOG_LEVEL_INF);
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_SOME, BT_UUID_16_ENCODE(BT_UUID_ASCS_VAL),
BT_UUID_16_ENCODE(BT_UUID_CAS_VAL)),
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
BT_DATA_BYTES(BT_DATA_UUID16_SOME,
BT_UUID_16_ENCODE(BT_UUID_ASCS_VAL),
BT_UUID_16_ENCODE(BT_UUID_CAS_VAL)),
BT_DATA_BYTES(BT_DATA_SVC_DATA16,
BT_UUID_16_ENCODE(BT_UUID_CAS_VAL),
BT_AUDIO_UNICAST_ANNOUNCEMENT_TARGETED),
@ -55,6 +56,10 @@ static const struct bt_data ad[] = {
BT_BYTES_LIST_LE16(SOURCE_CONTEXT),
0x00, /* Metadata length */),
))
IF_ENABLED(CONFIG_BT_BAP_SCAN_DELEGATOR,
(BT_DATA_BYTES(BT_DATA_SVC_DATA16,
BT_UUID_16_ENCODE(BT_UUID_BASS_VAL)),
))
};
static struct bt_le_ext_adv *adv;
@ -308,6 +313,13 @@ int main(void)
}
}
if (IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SINK)) {
err = init_cap_acceptor_broadcast();
if (err != 0) {
return 0;
}
}
LOG_INF("CAP Acceptor initialized");
while (true) {
@ -342,7 +354,5 @@ int main(void)
}
}
/* TODO: Add CAP acceptor broadcast support */
return 0;
}

View file

@ -9,9 +9,11 @@ set(cap_acceptor_path ${ZEPHYR_BASE}/samples/bluetooth/cap_acceptor)
target_sources(app PRIVATE
${cap_acceptor_path}/src/main.c
${cap_acceptor_path}/src/cap_acceptor_unicast.c
)
zephyr_sources_ifdef(CONFIG_SAMPLE_UNICAST ${cap_acceptor_path}/src/cap_acceptor_unicast.c)
zephyr_sources_ifdef(CONFIG_SAMPLE_BROADCAST ${cap_acceptor_path}/src/cap_acceptor_broadcast.c)
target_sources(app PRIVATE
src/cap_acceptor_sample_test.c
src/test_main.c

View file

@ -0,0 +1,4 @@
# Copyright (c) 2024 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
source "${ZEPHYR_BASE}/samples/bluetooth/cap_acceptor/Kconfig"

View file

@ -5,6 +5,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/util_macro.h>
#include "bs_types.h"
#include "bs_tracing.h"
#include "bs_utils.h"
@ -43,18 +45,39 @@ static void test_cap_acceptor_sample_tick(bs_time_t HW_device_time)
* we consider the test failed
*/
extern uint64_t total_rx_iso_packet_count;
if (IS_ENABLED(CONFIG_SAMPLE_UNICAST)) {
extern uint64_t total_unicast_rx_iso_packet_count;
extern uint64_t total_unicast_tx_iso_packet_count;
bs_trace_info_time(2, "%" PRIu64 " packets received, expected >= %i\n",
total_rx_iso_packet_count, PASS_THRESHOLD);
bs_trace_info_time(2, "%" PRIu64 " unicast packets received, expected >= %i\n",
total_unicast_tx_iso_packet_count, PASS_THRESHOLD);
bs_trace_info_time(2, "%" PRIu64 " unicast packets sent, expected >= %i\n",
total_unicast_tx_iso_packet_count, PASS_THRESHOLD);
if (total_rx_iso_packet_count >= PASS_THRESHOLD) {
PASS("cap_acceptor PASSED\n");
} else {
FAIL("cap_acceptor FAILED (Did not pass after %i seconds)\n", WAIT_TIME);
if (total_unicast_rx_iso_packet_count < PASS_THRESHOLD ||
total_unicast_tx_iso_packet_count < PASS_THRESHOLD) {
FAIL("cap_acceptor FAILED with(Did not pass after %d seconds)\n ",
WAIT_TIME);
return;
}
}
if (IS_ENABLED(CONFIG_SAMPLE_BROADCAST)) {
extern uint64_t total_broadcast_rx_iso_packet_count;
bs_trace_info_time(2, "%" PRIu64 " broadcast packets received, expected >= %i\n",
total_broadcast_rx_iso_packet_count, PASS_THRESHOLD);
if (total_broadcast_rx_iso_packet_count < PASS_THRESHOLD) {
FAIL("cap_acceptor FAILED with (Did not pass after %d seconds)\n ",
WAIT_TIME);
return;
}
}
PASS("cap_acceptor PASSED\n");
}
static const struct bst_test_instance test_sample[] = {
{
.test_id = "cap_acceptor",

View file

@ -17,9 +17,10 @@ cd ${BSIM_OUT_PATH}/bin
Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_samples_cap_initiator_broadcast_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=0 -RealEncryption=1 -testid=cap_initiator
# TODO: Add CAP acceptor
Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_samples_cap_acceptor_broadcast_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=1 -RealEncryption=1 -testid=cap_acceptor
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \
-D=1 -sim_length=20e6 $@ -argschannel -at=40
-D=2 -sim_length=20e6 $@ -argschannel -at=40
wait_for_background_jobs #Wait for all programs in background and return != 0 if any fails

View file

@ -17,7 +17,7 @@ cd ${BSIM_OUT_PATH}/bin
Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_samples_cap_initiator_unicast_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=0 -RealEncryption=1 -testid=cap_initiator
Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_samples_cap_acceptor_prj_conf \
Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_samples_cap_acceptor_unicast_prj_conf \
-v=${verbosity_level} -s=${simulation_id} -d=1 -RealEncryption=1 -testid=cap_acceptor
Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \

View file

@ -24,7 +24,13 @@ if [ "${BOARD_TS}" == "nrf5340bsim_nrf5340_cpuapp" ]; then
exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile
app=tests/bsim/bluetooth/audio_samples/cap/initiator \
sample=${ZEPHYR_BASE}/samples/bluetooth/cap_initiator \
cmake_extra_args="-DCONFIG_SAMPLE_UNICAST=n" \
cmake_args="-DCONFIG_SAMPLE_UNICAST=n" \
conf_file=${sample}/prj.conf \
conf_overlay=${sample}/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf \
exe_name=bs_${BOARD_TS}_${app}_broadcast_prj_conf sysbuild=1 compile
app=tests/bsim/bluetooth/audio_samples/cap/acceptor \
sample=${ZEPHYR_BASE}/samples/bluetooth/cap_acceptor \
cmake_args="-DCONFIG_SAMPLE_SCAN_SELF=y -DCONFIG_SAMPLE_UNICAST=n" \
conf_file=${sample}/prj.conf \
conf_overlay=${sample}/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf \
exe_name=bs_${BOARD_TS}_${app}_broadcast_prj_conf sysbuild=1 compile
@ -37,7 +43,7 @@ if [ "${BOARD_TS}" == "nrf5340bsim_nrf5340_cpuapp" ]; then
sample=${ZEPHYR_BASE}/samples/bluetooth/cap_acceptor \
conf_file=${sample}/prj.conf \
conf_overlay=${sample}/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf \
exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile
exe_name=bs_${BOARD_TS}_${app}_unicast_prj_conf sysbuild=1 compile
else
app=samples/bluetooth/bap_unicast_server conf_overlay=overlay-bt_ll_sw_split.conf \
exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile
@ -54,7 +60,13 @@ else
exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile
app=tests/bsim/bluetooth/audio_samples/cap/initiator \
sample=${ZEPHYR_BASE}/samples/bluetooth/cap_initiator \
cmake_extra_args="-DCONFIG_SAMPLE_UNICAST=n" \
cmake_args="-DCONFIG_SAMPLE_UNICAST=n" \
conf_file=${sample}/prj.conf \
conf_overlay=${sample}/overlay-bt_ll_sw_split.conf \
exe_name=bs_${BOARD_TS}_${app}_broadcast_prj_conf sysbuild=1 compile
app=tests/bsim/bluetooth/audio_samples/cap/acceptor \
sample=${ZEPHYR_BASE}/samples/bluetooth/cap_acceptor \
cmake_args="-DCONFIG_SAMPLE_SCAN_SELF=y -DCONFIG_SAMPLE_UNICAST=n" \
conf_file=${sample}/prj.conf \
conf_overlay=${sample}/overlay-bt_ll_sw_split.conf \
exe_name=bs_${BOARD_TS}_${app}_broadcast_prj_conf sysbuild=1 compile
@ -67,7 +79,7 @@ else
sample=${ZEPHYR_BASE}/samples/bluetooth/cap_acceptor \
conf_file=${sample}/prj.conf \
conf_overlay=${sample}/overlay-bt_ll_sw_split.conf \
exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile
exe_name=bs_${BOARD_TS}_${app}_unicast_prj_conf sysbuild=1 compile
fi
wait_for_background_jobs