Bluetooth: Audio: Add TMAP broadcast samples

Add two TMAP broadcast sample applications.

tmap_bms implements the Broadcast Media Sender role. It uses CAP
Initiator broadcast APIs to broadcast an audio stream.

tmap_bmr implements the Broadcast Media Receiver role. It scans and
syncs to the PA having filtered the EA by the presence of TMAP role
information. It also instantiates the VCP.

Signed-off-by: Silviu Petria <silviu.petria@nxp.com>
This commit is contained in:
Silviu Petria 2023-07-12 10:50:43 +03:00 committed by Fabio Baltieri
commit f3aaf33422
24 changed files with 1215 additions and 0 deletions

View file

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

View file

@ -0,0 +1,12 @@
#
# Copyright (c) 2022 Codecoup
# Copyright 2023 NXP
#
# SPDX-License-Identifier: Apache-2.0
#
mainmenu "Bluetooth: Earbuds"
menu "Zephyr"
source "Kconfig.zephyr"
endmenu

View file

@ -0,0 +1,21 @@
.. _bluetooth_tmap_bmr:
Bluetooth: TMAP BMR
###################
Overview
********
Application demonstrating the LE Audio TMAP Broadcast Media Receiver functionality.
Implements the BMR role.
Requirements
************
* A board with Bluetooth Low Energy 5.2 support
Building and Running
********************
This sample can be found under :zephyr_file:`samples/bluetooth/tmap_bmr` in the Zephyr tree.
See :ref:`bluetooth samples section <bluetooth-samples>` for details.

View file

@ -0,0 +1,10 @@
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_BT_TINYCRYPT_ECC=y
CONFIG_LIBLC3=y
CONFIG_FPU=y
# For LE-audio at 10ms intervals we need the tick counter to occur more frequently
# than every 10 ms as each PDU for some reason takes 2 ticks to process.
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000
CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME=y

View file

@ -0,0 +1,4 @@
CONFIG_BT_CTLR_PERIPHERAL_ISO=y
# Supports the highest SDU size required by any BAP LC3 presets (155)
CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE=155

View file

@ -0,0 +1,8 @@
# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3=y
# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence
# inctease stack size for that thread.
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
# LC3 lib requires floating point support in the c-lib NEWLIB is one way of getting that.
CONFIG_NEWLIB_LIBC=y

View file

@ -0,0 +1,8 @@
# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3=y
# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence
# inctease stack size for that thread.
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
# LC3 lib requires floating point support in the c-lib NEWLIB is one way of getting that.
CONFIG_NEWLIB_LIBC=y

View file

@ -0,0 +1,37 @@
CONFIG_BT=y
CONFIG_LOG=y
CONFIG_BT_PAC_SNK=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_PRIVACY=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
# TMAP support
CONFIG_BT_TMAP=y
# 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
# VCP support
CONFIG_BT_VCP_VOL_REND=y
# 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_GATT_DYNAMIC_DB=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_DEVICE_NAME="TMAP BMR"

View file

@ -0,0 +1,10 @@
sample:
description: Bluetooth Low Energy Audio TMAP BMR sample
name: Bluetooth Low Energy Audio TMAP BMR sample
tests:
sample.bluetooth.tmap_bmr:
harness: bluetooth
platform_allow: qemu_cortex_m3 qemu_x86
tags: bluetooth
integration_platforms:
- qemu_cortex_m3

View file

@ -0,0 +1,413 @@
/*
* Copyright (c) 2022-2023 Nordic Semiconductor ASA
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#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/tmap.h>
#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 tmap_bms_found;
static K_SEM_DEFINE(sem_pa_synced, 0U, 1U);
static K_SEM_DEFINE(sem_base_received, 0U, 1U);
static K_SEM_DEFINE(sem_sink_created, 0U, 1U);
static K_SEM_DEFINE(sem_syncable, 0U, 1U);
static K_SEM_DEFINE(sem_pa_sync_lost, 0U, 1U);
static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *ad);
static void broadcast_scan_timeout(void);
static void broadcast_pa_synced(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info);
static void broadcast_pa_recv(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_recv_info *info,
struct net_buf_simple *buf);
static struct bt_le_scan_cb broadcast_scan_cb = {
.recv = broadcast_scan_recv,
.timeout = broadcast_scan_timeout
};
static struct bt_le_per_adv_sync_cb broadcast_sync_cb = {
.synced = broadcast_pa_synced,
.recv = broadcast_pa_recv,
};
static struct bt_bap_broadcast_sink *broadcast_sink;
static 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 struct bt_audio_codec_cap codec = BT_AUDIO_CODEC_LC3(
BT_AUDIO_CODEC_LC3_FREQ_48KHZ,
BT_AUDIO_CODEC_LC3_DURATION_10, BT_AUDIO_CODEC_LC3_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u,
(BT_AUDIO_CONTEXT_TYPE_MEDIA));
/* Create a mask for the maximum BIS we can sync to using the number of streams
* we have. We add an additional 1 since the bis indexes start from 1 and not
* 0.
*/
static const uint32_t bis_index_mask = BIT_MASK(ARRAY_SIZE(streams) + 1U);
static uint32_t bis_index_bitfield;
static 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(&param.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(&param, &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;
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_TMAS)) {
struct net_buf_simple tmas_svc_data;
uint16_t uuid_val;
uint16_t peer_tmap_role = 0;
net_buf_simple_init_with_data(&tmas_svc_data,
(void *)data->data,
data->data_len);
uuid_val = net_buf_simple_pull_le16(&tmas_svc_data);
if (tmas_svc_data.len < sizeof(peer_tmap_role)) {
return false;
}
peer_tmap_role = net_buf_simple_pull_le16(&tmas_svc_data);
if ((peer_tmap_role & BT_TMAP_ROLE_BMS)) {
printk("Found TMAP BMS\n");
tmap_bms_found = true;
}
return true;
}
return true;
}
static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info,
struct net_buf_simple *ad)
{
uint32_t broadcast_id;
tmap_bms_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) && tmap_bms_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)
{
uint32_t base_bis_index_bitfield = 0U;
struct bt_bap_base base = { 0 };
if (data->type != BT_DATA_SVC_DATA16) {
return true;
}
if (data->data_len < BT_BAP_BASE_MIN_SIZE) {
return true;
}
if (bt_bap_decode_base(data, &base) != 0) {
return false;
}
for (size_t i = 0U; i < base.subgroup_count; i++) {
for (size_t j = 0U; j < base.subgroups[i].bis_count; j++) {
const uint8_t index = base.subgroups[i].bis_data[j].index;
base_bis_index_bitfield |= BIT(index);
}
}
bis_index_bitfield = base_bis_index_bitfield & bis_index_mask;
k_sem_give(&sem_base_received);
return false;
}
static void broadcast_pa_recv(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_recv_info *info,
struct net_buf_simple *buf)
{
bt_data_parse(buf, pa_decode_base, NULL);
}
static void broadcast_pa_synced(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info)
{
k_sem_give(&sem_pa_synced);
}
static void pa_synced_cb(struct bt_bap_broadcast_sink *sink,
struct bt_le_per_adv_sync *sync,
uint32_t broadcast_id)
{
if (broadcast_sink != NULL) {
printk("Unexpected PA sync\n");
return;
}
printk("PA synced for broadcast sink %p with broadcast ID 0x%06X\n",
sink, broadcast_id);
broadcast_sink = sink;
k_sem_give(&sem_sink_created);
}
static void syncable_cb(struct bt_bap_broadcast_sink *sink, bool encrypted)
{
k_sem_give(&sem_syncable);
}
static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base)
{
k_sem_give(&sem_base_received);
}
static void pa_sync_lost_cb(struct bt_bap_broadcast_sink *sink)
{
if (broadcast_sink == NULL) {
printk("Unexpected PA sync lost\n");
return;
}
printk("Sink %p disconnected\n", sink);
broadcast_sink = NULL;
k_sem_give(&sem_pa_sync_lost);
}
static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = {
.pa_synced = pa_synced_cb,
.syncable = syncable_cb,
.base_recv = base_recv_cb,
.pa_sync_lost = pa_sync_lost_cb
};
static int reset(void)
{
if (broadcast_sink != NULL) {
int err = bt_bap_broadcast_sink_delete(broadcast_sink);
if (err) {
printk("Deleting broadcast sink failed (err %d)\n", err);
return err;
}
broadcast_sink = NULL;
}
k_sem_reset(&sem_pa_synced);
k_sem_reset(&sem_base_received);
k_sem_reset(&sem_sink_created);
k_sem_reset(&sem_syncable);
k_sem_reset(&sem_pa_sync_lost);
return 0;
}
int bap_broadcast_sink_init(void)
{
int err;
bt_bap_broadcast_sink_register_cb(&broadcast_sink_cbs);
bt_le_per_adv_sync_cb_register(&broadcast_sync_cb);
err = bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap);
if (err) {
printk("Capability register failed (err %d)\n", err);
return err;
}
for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) {
streams[i].ops = &stream_ops;
}
for (size_t i = 0U; i < ARRAY_SIZE(streams_p); i++) {
streams_p[i] = &streams[i];
}
return 0;
}
int bap_broadcast_sink_run(void)
{
while (true) {
int err = reset();
if (err != 0) {
printk("Resetting failed: %d - Aborting\n", err);
return err;
}
bt_le_scan_cb_register(&broadcast_scan_cb);
/* Start scanning */
err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL);
if (err) {
printk("Scan start failed (err %d)\n", err);
return err;
}
/* Wait for PA sync */
err = k_sem_take(&sem_pa_synced, SEM_TIMEOUT);
if (err != 0) {
printk("sem_pa_synced timed out\n");
return err;
}
printk("Broadcast source PA synced, waiting for BASE\n");
/* Wait for BASE decode */
err = k_sem_take(&sem_base_received, SEM_TIMEOUT);
if (err != 0) {
printk("sem_base_received timed out\n");
return err;
}
/* Create broadcast sink */
printk("BASE received, creating broadcast sink\n");
err = bt_bap_broadcast_sink_create(bcast_pa_sync, bcast_id);
err = k_sem_take(&sem_sink_created, SEM_TIMEOUT);
if (err != 0) {
printk("sem_sink_created timed out\n");
return err;
}
k_sem_take(&sem_syncable, SEM_TIMEOUT);
if (err != 0) {
printk("sem_syncable timed out\n");
return err;
}
/* Sync to broadcast source */
printk("Syncing to broadcast\n");
err = bt_bap_broadcast_sink_sync(broadcast_sink, bis_index_bitfield,
streams_p, NULL);
if (err != 0) {
printk("Unable to sync to broadcast source: %d\n", err);
return err;
}
k_sem_take(&sem_pa_sync_lost, K_FOREVER);
}
return 0;
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/audio/tmap.h>
#include "tmap_bmr.h"
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 TMAP and setting role\n");
err = bt_tmap_register(BT_TMAP_ROLE_BMR);
if (err != 0) {
return err;
}
err = vcp_vol_renderer_init();
if (err != 0) {
return err;
}
printk("VCP initialized\n");
printk("Initializing BAP Broadcast Sink\n");
err = bap_broadcast_sink_init();
if (err != 0) {
return err;
}
printk("Starting BAP Broadcast Sink\n");
err = bap_broadcast_sink_run();
if (err != 0) {
return err;
}
return 0;
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
/**
* @brief Initialize the VCP Volume Renderer role
*
* @return 0 if success, errno on failure.
*/
int vcp_vol_renderer_init(void);
/**
* @brief Initialize BAP Broadcast Sink
*
* @return 0 if success, errno on failure.
*/
int bap_broadcast_sink_init(void);
/**
* @brief Run BAP Broadcast Sink
*
* @return 0 if success, errno on failure.
*/
int bap_broadcast_sink_run(void);

View file

@ -0,0 +1,68 @@
/** @file
* @brief Bluetooth Volume Control Profile (VCP) Volume Renderer role.
*
* Copyright (c) 2020 Bose Corporation
* Copyright (c) 2020-2022 Nordic Semiconductor ASA
* Copyright (c) 2022 Codecoup
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <stdlib.h>
#include <stdio.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/vcp.h>
static struct bt_vcp_included vcp_included;
static void vcs_state_cb(int err, uint8_t volume, uint8_t mute)
{
if (err) {
printk("VCS state get failed (%d)\n", err);
} else {
printk("VCS volume %u, mute %u\n", volume, mute);
}
}
static void vcs_flags_cb(int err, uint8_t flags)
{
if (err) {
printk("VCS flags get failed (%d)\n", err);
} else {
printk("VCS flags 0x%02X\n", flags);
}
}
static struct bt_vcp_vol_rend_cb vcp_cbs = {
.state = vcs_state_cb,
.flags = vcs_flags_cb,
};
int vcp_vol_renderer_init(void)
{
int err;
struct bt_vcp_vol_rend_register_param vcp_register_param;
memset(&vcp_register_param, 0, sizeof(vcp_register_param));
vcp_register_param.step = 1;
vcp_register_param.mute = BT_VCP_STATE_UNMUTED;
vcp_register_param.volume = 100;
vcp_register_param.cb = &vcp_cbs;
err = bt_vcp_vol_rend_register(&vcp_register_param);
if (err) {
return err;
}
err = bt_vcp_vol_rend_included_get(&vcp_included);
if (err != 0) {
return err;
}
return 0;
}

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(tmap_bms)
target_sources(app PRIVATE
src/main.c
src/cap_initiator.c
)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)

View file

@ -0,0 +1,22 @@
.. _bluetooth_tmap_bms:
Bluetooth: TMAP BMS
###################
Overview
********
Application demonstrating the LE Audio TMAP Broadcast Media Sender functionality.
Implements the BMS role.
Requirements
************
* A board with Bluetooth Low Energy 5.2 support
Building and Running
********************
This sample can be found under :zephyr_file:`samples/bluetooth/tmap_bms` in the Zephyr tree.
See :ref:`bluetooth samples section <bluetooth-samples>` for details.

View file

@ -0,0 +1,10 @@
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_BT_TINYCRYPT_ECC=y
CONFIG_LIBLC3=y
CONFIG_FPU=y
# For LE-audio at 10ms intervals we need the tick counter to occur more frequently
# than every 10 ms as each PDU for some reason takes 2 ticks to process.
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000
CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME=y

View file

@ -0,0 +1,4 @@
CONFIG_BT_CTLR_CENTRAL_ISO=y
# Supports the highest SDU size required by any BAP LC3 presets (155)
CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE=155

View file

@ -0,0 +1,8 @@
# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3=y
# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence
# inctease stack size for that thread.
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
# LC3 lib requires floating point support in the c-lib NEWLIB is one way of getting that.
CONFIG_NEWLIB_LIBC=y

View file

@ -0,0 +1,8 @@
# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3=y
# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence
# inctease stack size for that thread.
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
# LC3 lib requires floating point support in the c-lib NEWLIB is one way of getting that.
CONFIG_NEWLIB_LIBC=y

View file

@ -0,0 +1,26 @@
CONFIG_BT=y
CONFIG_LOG=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_AUDIO=y
CONFIG_BT_SMP=y
CONFIG_BT_KEYS_OVERWRITE_OLDEST=y
CONFIG_BT_L2CAP_TX_BUF_COUNT=20
# TMAP support
CONFIG_BT_TMAP=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_ISO_TX_BUF_COUNT=2
CONFIG_BT_ISO_MAX_CHAN=2
CONFIG_BT_GATT_DYNAMIC_DB=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_DEVICE_NAME="TMAP BMS"

View file

@ -0,0 +1,10 @@
sample:
description: Bluetooth Low Energy Audio TMAP BMS sample
name: Bluetooth Low Energy Audio TMAP BMS sample
tests:
sample.bluetooth.tmap_bms:
harness: bluetooth
platform_allow: qemu_cortex_m3 qemu_x86
tags: bluetooth
integration_platforms:
- qemu_cortex_m3

View file

@ -0,0 +1,346 @@
/*
* Copyright (c) 2022-2023 Nordic Semiconductor ASA
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <zephyr/kernel.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/tmap.h>
#define BROADCAST_ENQUEUE_COUNT 2U
#define MOCK_CCID 0x1234
NET_BUF_POOL_FIXED_DEFINE(tx_pool,
(BROADCAST_ENQUEUE_COUNT * CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT),
BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), 8, NULL);
static K_SEM_DEFINE(sem_broadcast_started, 0, 1);
static K_SEM_DEFINE(sem_broadcast_stopped, 0, 1);
static struct bt_cap_stream broadcast_source_stream;
static struct bt_cap_stream *broadcast_stream;
struct bt_audio_codec_data bis_codec_data = BT_AUDIO_CODEC_DATA(
BT_AUDIO_CODEC_CONFIG_LC3_FREQ, BT_AUDIO_CODEC_CONFIG_LC3_FREQ_48KHZ);
const struct bt_audio_codec_data new_metadata[] = {
BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT,
(BT_AUDIO_CONTEXT_TYPE_MEDIA & 0xFFU),
((BT_AUDIO_CONTEXT_TYPE_MEDIA >> 8) & 0xFFU)),
BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_CCID_LIST,
(MOCK_CCID & 0xFFU),
((MOCK_CCID >> 8) & 0xFFU))
};
static struct bt_bap_lc3_preset broadcast_preset_48_2_1 =
BT_BAP_LC3_UNICAST_PRESET_48_2_1(BT_AUDIO_LOCATION_FRONT_LEFT,
BT_AUDIO_CONTEXT_TYPE_MEDIA);
struct bt_cap_initiator_broadcast_stream_param stream_params;
struct bt_cap_initiator_broadcast_subgroup_param subgroup_param;
struct bt_cap_initiator_broadcast_create_param create_param;
struct bt_cap_broadcast_source *broadcast_source;
struct bt_le_ext_adv *adv;
static uint8_t tmap_addata[] = {
BT_UUID_16_ENCODE(BT_UUID_TMAS_VAL), /* TMAS UUID */
BT_BYTES_LIST_LE16(BT_TMAP_ROLE_BMS), /* TMAP Role */
};
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)
{
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);
struct bt_data ext_ad[2];
struct bt_data per_ad;
uint32_t broadcast_id;
int err;
err = bt_cap_initiator_broadcast_get_id(source, &broadcast_id);
if (err != 0) {
printk("Unable to get broadcast ID: %d\n", err);
return err;
}
/* Setup extended advertising data */
ext_ad[0].type = BT_DATA_SVC_DATA16;
ext_ad[0].data_len = ARRAY_SIZE(tmap_addata);
ext_ad[0].data = tmap_addata;
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);
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)
{
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_count = 1U;
stream_params.data = &bis_codec_data;
subgroup_param.stream_count = 1U;
subgroup_param.stream_params = &stream_params;
subgroup_param.codec_cfg = &broadcast_preset_48_2_1.codec_cfg;
create_param.subgroup_count = 1U;
create_param.subgroup_params = &subgroup_param;
create_param.qos = &broadcast_preset_48_2_1.qos;
create_param.packing = BT_ISO_PACKING_SEQUENTIAL;
create_param.encryption = false;
while (true) {
err = reset();
if (err != 0) {
printk("Resetting failed: %d - Aborting\n", err);
return;
}
printk("Creating broadcast source\n");
err = setup_extended_adv(&adv);
if (err != 0) {
printk("Unable to setup extended advertiser: %d\n", err);
return;
}
err = bt_cap_initiator_broadcast_audio_create(&create_param, &broadcast_source);
if (err != 0) {
printk("Unable to create broadcast source: %d\n", err);
return;
}
err = bt_cap_initiator_broadcast_audio_start(broadcast_source, adv);
if (err != 0) {
printk("Unable to start broadcast source: %d\n", err);
return;
}
err = setup_extended_adv_data(broadcast_source, adv);
if (err != 0) {
printk("Unable to setup extended advertising data: %d\n", err);
return;
}
err = start_extended_adv(adv);
if (err != 0) {
printk("Unable to start extended advertiser: %d\n", err);
return;
}
k_sem_take(&sem_broadcast_started, K_FOREVER);
/* Initialize sending */
for (unsigned int j = 0U; j < BROADCAST_ENQUEUE_COUNT; j++) {
broadcast_sent_cb(&broadcast_stream->bap_stream);
}
/* Run for a little while */
k_sleep(K_SECONDS(10));
err = bt_cap_initiator_broadcast_audio_update(broadcast_source,
new_metadata,
ARRAY_SIZE(new_metadata));
if (err != 0) {
printk("Failed to update broadcast source metadata: %d\n", err);
return;
}
/* Run for a little while */
k_sleep(K_SECONDS(10));
err = bt_cap_initiator_broadcast_audio_stop(broadcast_source);
if (err != 0) {
printk("Failed to stop broadcast source: %d\n", err);
return;
}
k_sem_take(&sem_broadcast_stopped, K_FOREVER);
err = bt_cap_initiator_broadcast_audio_delete(broadcast_source);
if (err != 0) {
printk("Failed to stop broadcast source: %d\n", err);
return;
}
broadcast_source = NULL;
err = stop_and_delete_extended_adv(adv);
if (err != 0) {
printk("Failed to stop and delete extended advertising: %d\n", err);
return;
}
}
}

View file

@ -0,0 +1,63 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/tmap.h>
#include "tmap_bms.h"
static int init(void)
{
int err;
err = bt_enable(NULL);
if (err != 0) {
printk("Bluetooth enable failed (err %d)\n", err);
return err;
}
printk("Bluetooth initialized\n");
return 0;
}
int main(void)
{
int err;
err = init();
if (err != 0) {
return err;
}
printk("Initializing TMAP and setting role\n");
/* Initialize TMAP */
err = bt_tmap_register(BT_TMAP_ROLE_BMS);
if (err != 0) {
return err;
}
/* Initialize CAP Initiator */
err = cap_initiator_init();
if (err != 0) {
return err;
}
printk("CAP initialized\n");
/* Configure and start broadcast stream */
err = cap_initiator_setup();
if (err != 0) {
return err;
}
return 0;
}

View file

@ -0,0 +1,20 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
/**
* @brief Initialize the CAP Initiator role
*
* @return 0 if success, errno on failure.
*/
int cap_initiator_init(void);
/**
* @brief Setup streams for CAP Initiator
*
* @return 0 if success, errno on failure.
*/
int cap_initiator_setup(void);