Samples: Bluetooth: Add CAP acceptor unicast sample

Adds a CAP acceptor unicast sample that simply
uses the CAP acceptor implementation to setup
streams with a CAP initiator.

To keep it simple no audio or encoding support
has been added.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2024-05-01 11:36:23 +02:00 committed by Alberto Escolar
parent a712315793
commit daf7d8066a
10 changed files with 964 additions and 0 deletions

View File

@ -0,0 +1,12 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(cap_acceptor)
target_sources(app PRIVATE
src/main.c
src/cap_acceptor_unicast.c
)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)

View 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"
default "nrf5340_audio_dk/nrf5340/cpunet" if "$(BOARD)" = "nrf5340_audio_dk"
default "nrf5340bsim/nrf5340/cpunet" if $(BOARD_TARGET_STRING) = "NRF5340BSIM_NRF5340_CPUAPP"
config NET_CORE_IMAGE_HCI_IPC
bool "HCI IPC image on network core"
default y
depends on NET_CORE_BOARD != ""

View File

@ -0,0 +1,74 @@
.. zephyr:code-sample:: bluetooth_cap_acceptor
:name: Bluetooth: Common Audio Profile Acceptor
:relevant-api: bt_cap bt_bap bluetooth
CAP Acceptor sample that advertises audio availability to CAP Initiators.
Overview
********
Application demonstrating the CAP Acceptor functionality.
Starts by advertising for a CAP Initiator to connect and set up available streams.
This sample can be found under :zephyr_file:`samples/bluetooth/cap_acceptor` 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/cap_acceptor/
: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/cap_acceptor/
: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/cap_acceptor/
: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/cap_acceptor/
:board: nrf52_bsim
:goals: build
:gen-args: -DOVERLAY_CONFIG=overlay-bt_ll_sw_split.conf

View File

@ -0,0 +1,13 @@
# 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 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)
CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE=163

View File

@ -0,0 +1,36 @@
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
# Support an ISO channel per ASE
CONFIG_BT_ASCS=y
CONFIG_BT_ASCS_ASE_SNK_COUNT=1
CONFIG_BT_ASCS_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

@ -0,0 +1,30 @@
sample:
description: Bluetooth Low Energy Common Audio Profile Acceptor sample
name: Bluetooth Low Energy Common Audio Profile Acceptor sample
tests:
sample.bluetooth.cap_acceptor:
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.cap_acceptor.bt_ll_sw_split:
harness: bluetooth
platform_allow:
- nrf52_bsim
- nrf52833dk/nrf52833
- nrf52840dk/nrf52840
- nrf52840dongle/nrf52840
integration_platforms:
- nrf52_bsim
- nrf52833dk/nrf52833
- nrf52840dk/nrf52840
- nrf52840dongle/nrf52840
extra_args: OVERLAY_CONFIG=overlay-bt_ll_sw_split.conf
tags: bluetooth

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/kernel.h>
#define SINK_CONTEXT BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED
#define SOURCE_CONTEXT BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED
/** Struct to contain information for a specific peer (CAP) device */
struct peer_config {
/** Stream for the source endpoint */
struct bt_cap_stream source_stream;
/** Stream for the sink endpoint */
struct bt_cap_stream sink_stream;
/** Semaphore to help wait for a release operation if the source stream is not idle */
struct k_sem source_stream_sem;
/** Semaphore to help wait for a release operation if the sink stream is not idle */
struct k_sem sink_stream_sem;
/** ACL connection object for the peer device */
struct bt_conn *conn;
};
/**
* @brief Initialize the unicast part of the CAP Acceptor
*
* @param peer Pointer to the specific peer to initialize the CAP Acceptor for
*
* @retval 0 if success
* @retval -ENOEXEC if callbacks failed to be registered
*/
int init_cap_acceptor_unicast(struct peer_config *peer);
/**
* @brief Request to allocate a CAP stream
*
* @param dir Audio direction of the stream to allocate
*
* @retval Pointer to the allocated CAP stream
* @retval NULL if no more CAP streams for the @p dir could be allocated
*/
struct bt_cap_stream *stream_alloc(enum bt_audio_dir dir);
/**
* Notify about a released stream
* This is used to handle some state checks in the main.c file.
*
* @param cap_stream Pointer to the stream that was released
*/
void stream_released(const struct bt_cap_stream *cap_stream);

View File

@ -0,0 +1,356 @@
/** @file
* @brief Bluetooth Common Audio Profile (CAP) Acceptor unicast.
*
* Copyright (c) 2021-2024 Nordic Semiconductor ASA
* Copyright (c) 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/lc3.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/hci_types.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_core.h>
#include <zephyr/net/buf.h>
#include <zephyr/sys/util.h>
#include "cap_acceptor.h"
LOG_MODULE_REGISTER(cap_acceptor_unicast, LOG_LEVEL_INF);
#define PREF_PHY BT_GAP_LE_PHY_2M
#define MIN_PD 20000U
#define MAX_PD 40000U
#define UNFRAMED_SUPPORTED true
#define LATENCY 20U
#define RTN 2U
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);
static bool log_codec_cfg_cb(struct bt_data *data, void *user_data)
{
const char *str = (const char *)user_data;
LOG_DBG("\t%s: type 0x%02x value_len %u", str, data->type, data->data_len);
LOG_HEXDUMP_DBG(data->data, data->data_len, "\t\tdata");
return true;
}
static void log_codec_cfg(const struct bt_audio_codec_cfg *codec_cfg)
{
LOG_INF("codec_cfg 0x%02x cid 0x%04x vid 0x%04x count %u", codec_cfg->id, codec_cfg->cid,
codec_cfg->vid, codec_cfg->data_len);
if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) {
enum bt_audio_location chan_allocation;
int ret;
/* LC3 uses the generic LTV format - other codecs might do as well */
bt_audio_data_parse(codec_cfg->data, codec_cfg->data_len, log_codec_cfg_cb, "data");
ret = bt_audio_codec_cfg_get_freq(codec_cfg);
if (ret > 0) {
LOG_INF("\tFrequency: %d Hz", bt_audio_codec_cfg_freq_to_freq_hz(ret));
}
ret = bt_audio_codec_cfg_get_frame_dur(codec_cfg);
if (ret > 0) {
LOG_INF("\tFrame Duration: %d us",
bt_audio_codec_cfg_frame_dur_to_frame_dur_us(ret));
}
if (bt_audio_codec_cfg_get_chan_allocation(codec_cfg, &chan_allocation) == 0) {
LOG_INF("\tChannel allocation: 0x%08X", chan_allocation);
}
ret = bt_audio_codec_cfg_get_octets_per_frame(codec_cfg);
if (ret > 0) {
LOG_INF("\tOctets per frame: %d", ret);
}
LOG_INF("\tFrames per SDU: %d",
bt_audio_codec_cfg_get_frame_blocks_per_sdu(codec_cfg, true));
} else {
LOG_HEXDUMP_DBG(codec_cfg->data, codec_cfg->data_len, "data");
}
bt_audio_data_parse(codec_cfg->meta, codec_cfg->meta_len, log_codec_cfg_cb, "meta");
}
static void log_qos(const struct bt_audio_codec_qos *qos)
{
LOG_INF("QoS: interval %u framing 0x%02x phy 0x%02x sdu %u rtn %u latency %u pd %u",
qos->interval, qos->framing, qos->phy, qos->sdu, qos->rtn, qos->latency, qos->pd);
}
static int unicast_server_config_cb(struct bt_conn *conn, const struct bt_bap_ep *ep,
enum bt_audio_dir dir,
const struct bt_audio_codec_cfg *codec_cfg,
struct bt_bap_stream **bap_stream,
struct bt_audio_codec_qos_pref *const pref,
struct bt_bap_ascs_rsp *rsp)
{
struct bt_cap_stream *cap_stream;
LOG_INF("ASE Codec Config: conn %p ep %p dir %u", (void *)conn, (void *)ep, dir);
log_codec_cfg(codec_cfg);
cap_stream = stream_alloc(dir);
if (cap_stream == NULL) {
LOG_WRN("No streams available");
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_NO_MEM, BT_BAP_ASCS_REASON_NONE);
return -ENOMEM;
}
*bap_stream = &cap_stream->bap_stream;
LOG_INF("ASE Codec Config bap_stream %p", *bap_stream);
*pref = qos_pref;
return 0;
}
static int unicast_server_reconfig_cb(struct bt_bap_stream *bap_stream, enum bt_audio_dir dir,
const struct bt_audio_codec_cfg *codec_cfg,
struct bt_audio_codec_qos_pref *const pref,
struct bt_bap_ascs_rsp *rsp)
{
LOG_INF("ASE Codec Reconfig: bap_stream %p", bap_stream);
log_codec_cfg(codec_cfg);
*pref = qos_pref;
return 0;
}
static int unicast_server_qos_cb(struct bt_bap_stream *bap_stream,
const struct bt_audio_codec_qos *qos, struct bt_bap_ascs_rsp *rsp)
{
LOG_INF("QoS: bap_stream %p qos %p", bap_stream, qos);
log_qos(qos);
return 0;
}
static int unicast_server_enable_cb(struct bt_bap_stream *bap_stream, const uint8_t meta[],
size_t meta_len, struct bt_bap_ascs_rsp *rsp)
{
LOG_INF("Enable: bap_stream %p meta_len %zu", bap_stream, meta_len);
return 0;
}
static int unicast_server_start_cb(struct bt_bap_stream *bap_stream, struct bt_bap_ascs_rsp *rsp)
{
LOG_INF("Start: bap_stream %p", bap_stream);
return 0;
}
struct data_func_param {
struct bt_bap_ascs_rsp *rsp;
bool stream_context_present;
};
static bool data_func_cb(struct bt_data *data, void *user_data)
{
struct data_func_param *func_param = (struct data_func_param *)user_data;
if (data->type == BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT) {
func_param->stream_context_present = true;
}
return true;
}
static int unicast_server_metadata_cb(struct bt_bap_stream *bap_stream, const uint8_t meta[],
size_t meta_len, struct bt_bap_ascs_rsp *rsp)
{
struct data_func_param func_param = {
.rsp = rsp,
.stream_context_present = false,
};
int err;
LOG_INF("Metadata: bap_stream %p meta_len %zu", bap_stream, meta_len);
err = bt_audio_data_parse(meta, meta_len, data_func_cb, &func_param);
if (err != 0) {
return err;
}
if (!func_param.stream_context_present) {
LOG_ERR("Stream audio context not present");
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED,
BT_BAP_ASCS_REASON_NONE);
return -EINVAL;
}
return 0;
}
static int unicast_server_disable_cb(struct bt_bap_stream *bap_stream, struct bt_bap_ascs_rsp *rsp)
{
LOG_INF("Disable: bap_stream %p", bap_stream);
return 0;
}
static int unicast_server_stop_cb(struct bt_bap_stream *bap_stream, struct bt_bap_ascs_rsp *rsp)
{
LOG_INF("Stop: bap_stream %p", bap_stream);
return 0;
}
static int unicast_server_release_cb(struct bt_bap_stream *bap_stream, struct bt_bap_ascs_rsp *rsp)
{
LOG_INF("Release: bap_stream %p", bap_stream);
return 0;
}
static const struct bt_bap_unicast_server_cb unicast_server_cb = {
.config = unicast_server_config_cb,
.reconfig = unicast_server_reconfig_cb,
.qos = unicast_server_qos_cb,
.enable = unicast_server_enable_cb,
.start = unicast_server_start_cb,
.metadata = unicast_server_metadata_cb,
.disable = unicast_server_disable_cb,
.stop = unicast_server_stop_cb,
.release = unicast_server_release_cb,
};
static void unicast_stream_configured_cb(struct bt_bap_stream *bap_stream,
const struct bt_audio_codec_qos_pref *pref)
{
LOG_INF("Configured bap_stream %p", bap_stream);
/* TODO: The preference should be used/taken into account when
* setting the QoS
*/
LOG_INF("Local preferences: unframed %s, phy %u, rtn %u, latency %u, pd_min %u, pd_max "
"%u, pref_pd_min %u, pref_pd_max %u",
pref->unframed_supported ? "supported" : "not supported", pref->phy, pref->rtn,
pref->latency, pref->pd_min, pref->pd_max, pref->pref_pd_min, pref->pref_pd_max);
}
static void unicast_stream_qos_set_cb(struct bt_bap_stream *bap_stream)
{
LOG_INF("QoS set bap_stream %p", bap_stream);
}
static void unicast_stream_enabled_cb(struct bt_bap_stream *bap_stream)
{
struct bt_bap_ep_info ep_info;
int err;
LOG_INF("Enabled bap_stream %p", bap_stream);
err = bt_bap_ep_get_info(bap_stream->ep, &ep_info);
if (err != 0) {
LOG_ERR("Failed to get ep info: %d", err);
return;
}
if (ep_info.dir == BT_AUDIO_DIR_SINK) {
/* Automatically do the receiver start ready operation */
err = bt_bap_stream_start(bap_stream);
if (err != 0) {
LOG_ERR("Failed to start: %d", err);
return;
}
}
}
static void unicast_stream_started_cb(struct bt_bap_stream *bap_stream)
{
LOG_INF("Started bap_stream %p", bap_stream);
}
static void unicast_stream_metadata_updated_cb(struct bt_bap_stream *bap_stream)
{
LOG_INF("Metadata updated bap_stream %p", bap_stream);
}
static void unicast_stream_disabled_cb(struct bt_bap_stream *bap_stream)
{
LOG_INF("Disabled bap_stream %p", bap_stream);
}
static void unicast_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);
}
static void unicast_stream_released_cb(struct bt_bap_stream *bap_stream)
{
struct bt_cap_stream *cap_stream =
CONTAINER_OF(bap_stream, struct bt_cap_stream, bap_stream);
LOG_INF("Released bap_stream %p", bap_stream);
stream_released(cap_stream);
}
static void unicast_stream_recv_cb(struct bt_bap_stream *bap_stream,
const struct bt_iso_recv_info *info, struct net_buf *buf)
{
/* TODO: Add test code */
}
int init_cap_acceptor_unicast(struct peer_config *peer)
{
static struct bt_bap_stream_ops unicast_stream_ops = {
.configured = unicast_stream_configured_cb,
.qos_set = unicast_stream_qos_set_cb,
.enabled = unicast_stream_enabled_cb,
.started = unicast_stream_started_cb,
.metadata_updated = unicast_stream_metadata_updated_cb,
.disabled = unicast_stream_disabled_cb,
.stopped = unicast_stream_stopped_cb,
.released = unicast_stream_released_cb,
.recv = unicast_stream_recv_cb,
};
static bool cbs_registered;
if (!cbs_registered) {
int err;
err = bt_bap_unicast_server_register_cb(&unicast_server_cb);
if (err != 0) {
LOG_ERR("Failed to register BAP unicast server callbacks: %d", err);
return -ENOEXEC;
}
}
bt_cap_stream_ops_register(&peer->source_stream, &unicast_stream_ops);
bt_cap_stream_ops_register(&peer->sink_stream, &unicast_stream_ops);
return 0;
}

View File

@ -0,0 +1,348 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <autoconf.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/lc3.h>
#include <zephyr/bluetooth/audio/pacs.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/byteorder.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/hci_types.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_core.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include "cap_acceptor.h"
LOG_MODULE_REGISTER(cap_acceptor, LOG_LEVEL_INF);
#define SUPPORTED_DURATION (BT_AUDIO_CODEC_CAP_DURATION_7_5 | BT_AUDIO_CODEC_CAP_DURATION_10)
#define MAX_CHAN_PER_STREAM BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(2)
#define SUPPORTED_FREQ BT_AUDIO_CODEC_CAP_FREQ_ANY
#define SEM_TIMEOUT K_SECONDS(5)
#define MAX_SDU 155U
#define MIN_SDU 30U
#define FRAMES_PER_SDU 2
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_SVC_DATA16,
BT_UUID_16_ENCODE(BT_UUID_CAS_VAL),
BT_AUDIO_UNICAST_ANNOUNCEMENT_TARGETED),
IF_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER,
(BT_DATA_BYTES(BT_DATA_SVC_DATA16,
BT_UUID_16_ENCODE(BT_UUID_ASCS_VAL),
BT_AUDIO_UNICAST_ANNOUNCEMENT_TARGETED,
BT_BYTES_LIST_LE16(SINK_CONTEXT),
BT_BYTES_LIST_LE16(SOURCE_CONTEXT),
0x00, /* Metadata length */),
))
};
static struct bt_le_ext_adv *adv;
static struct peer_config peer;
static K_SEM_DEFINE(sem_state_change, 0, 1);
static void connected_cb(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
(void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_INF("Connected: %s", addr);
peer.conn = bt_conn_ref(conn);
k_sem_give(&sem_state_change);
}
static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if (conn != peer.conn) {
return;
}
(void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_INF("Disconnected: %s (reason 0x%02x)", addr, reason);
bt_conn_unref(peer.conn);
peer.conn = NULL;
k_sem_give(&sem_state_change);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected_cb,
.disconnected = disconnected_cb,
};
static int advertise(void)
{
int err;
err = bt_le_ext_adv_create(BT_LE_EXT_ADV_CONN, NULL, &adv);
if (err) {
LOG_ERR("Failed to create advertising set: %d", err);
return err;
}
err = bt_le_ext_adv_set_data(adv, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
LOG_ERR("Failed to set advertising data: %d", err);
return err;
}
err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT);
if (err) {
LOG_ERR("Failed to start advertising set: %d", err);
return err;
}
LOG_INF("Advertising successfully started");
/* Wait for connection*/
err = k_sem_take(&sem_state_change, K_FOREVER);
if (err != 0) {
LOG_ERR("Failed to take sem_state_change: err %d", err);
return err;
}
return 0;
}
struct bt_cap_stream *stream_alloc(enum bt_audio_dir dir)
{
if (dir == BT_AUDIO_DIR_SINK && peer.sink_stream.bap_stream.ep == NULL) {
return &peer.sink_stream;
} else if (dir == BT_AUDIO_DIR_SOURCE && peer.source_stream.bap_stream.ep == NULL) {
return &peer.source_stream;
}
return NULL;
}
void stream_released(const struct bt_cap_stream *cap_stream)
{
if (cap_stream == &peer.source_stream) {
k_sem_give(&peer.source_stream_sem);
} else if (cap_stream == &peer.sink_stream) {
k_sem_give(&peer.sink_stream_sem);
}
}
static int reset_cap_acceptor(void)
{
int err;
LOG_INF("Resetting");
if (peer.conn != NULL) {
err = bt_conn_disconnect(peer.conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err != 0) {
return err;
}
err = k_sem_take(&sem_state_change, K_FOREVER);
if (err != 0) {
LOG_ERR("Timeout on disconnect: %d", err);
return err;
}
}
if (adv != NULL) {
err = bt_le_ext_adv_stop(adv);
if (err != 0) {
LOG_ERR("Failed to stop advertiser: %d", err);
return err;
}
err = bt_le_ext_adv_delete(adv);
if (err != 0) {
LOG_ERR("Failed to delete advertiser: %d", err);
return err;
}
adv = NULL;
}
if (peer.source_stream.bap_stream.ep != NULL) {
err = k_sem_take(&peer.source_stream_sem, SEM_TIMEOUT);
if (err != 0) {
LOG_ERR("Timeout on source_stream_sem: %d", err);
return err;
}
}
if (peer.sink_stream.bap_stream.ep != NULL) {
err = k_sem_take(&peer.sink_stream_sem, SEM_TIMEOUT);
if (err != 0) {
LOG_ERR("Timeout on sink_stream_sem: %d", err);
return err;
}
}
k_sem_reset(&sem_state_change);
return 0;
}
/** Register the PAC records for PACS */
static int register_pac(enum bt_audio_dir dir, enum bt_audio_context context,
struct bt_pacs_cap *cap)
{
int err;
err = bt_pacs_cap_register(dir, cap);
if (err != 0) {
LOG_ERR("Failed to register capabilities: %d", err);
return err;
}
err = bt_pacs_set_location(dir, BT_AUDIO_LOCATION_MONO_AUDIO);
if (err != 0) {
LOG_ERR("Failed to set location: %d", err);
return err;
}
err = bt_pacs_set_supported_contexts(dir, context);
if (err != 0 && err != -EALREADY) {
LOG_ERR("Failed to set supported contexts: %d", err);
return err;
}
err = bt_pacs_set_available_contexts(dir, context);
if (err != 0 && err != -EALREADY) {
LOG_ERR("Failed to set available contexts: %d", err);
return err;
}
return 0;
}
static int init_cap_acceptor(void)
{
static const struct bt_audio_codec_cap lc3_codec_cap = BT_AUDIO_CODEC_CAP_LC3(
SUPPORTED_FREQ, SUPPORTED_DURATION, MAX_CHAN_PER_STREAM, MIN_SDU, MAX_SDU,
FRAMES_PER_SDU, (SINK_CONTEXT | SOURCE_CONTEXT));
int err;
err = bt_enable(NULL);
if (err != 0) {
LOG_ERR("Bluetooth enable failed: %d", err);
return 0;
}
LOG_INF("Bluetooth initialized");
if (IS_ENABLED(CONFIG_BT_PAC_SNK)) {
static struct bt_pacs_cap sink_cap = {
.codec_cap = &lc3_codec_cap,
};
int err;
err = register_pac(BT_AUDIO_DIR_SINK, SINK_CONTEXT, &sink_cap);
if (err != 0) {
LOG_ERR("Failed to register sink capabilities: %d", err);
return -ENOEXEC;
}
}
if (IS_ENABLED(CONFIG_BT_PAC_SRC)) {
static struct bt_pacs_cap source_cap = {
.codec_cap = &lc3_codec_cap,
};
int err;
err = register_pac(BT_AUDIO_DIR_SOURCE, SOURCE_CONTEXT, &source_cap);
if (err != 0) {
LOG_ERR("Failed to register sink capabilities: %d", err);
return -ENOEXEC;
}
}
return 0;
}
int main(void)
{
int err;
err = init_cap_acceptor();
if (err != 0) {
return 0;
}
if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER)) {
err = init_cap_acceptor_unicast(&peer);
if (err != 0) {
return 0;
}
}
LOG_INF("CAP Acceptor initialized");
while (true) {
err = reset_cap_acceptor();
if (err != 0) {
LOG_ERR("Failed to reset");
break;
}
/* Start advertising as a CAP Acceptor, which includes setting the required
* advertising data based on the roles we support. The Common Audio Service data is
* always advertised, as CAP Initiators and CAP Commanders will use this to identify
* our device as a CAP Acceptor.
*/
err = advertise();
if (err != 0) {
continue;
}
/* After advertising we expect CAP Initiators to connect to us and setup streams,
* and eventually disconnect again. As a CAP Acceptor we just need to react to their
* requests and not do anything else.
*/
/* Reset if disconnected */
err = k_sem_take(&sem_state_change, K_FOREVER);
if (err != 0) {
LOG_ERR("Failed to take sem_state_change: err %d", err);
break;
}
}
/* TODO: Add CAP acceptor broadcast support */
return 0;
}

View 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})