Initial commit for nrf5340_audio

This commit is contained in:
2025-06-30 09:28:01 +02:00
commit 62dbb94724
174 changed files with 27001 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# editors
*.swp
*~
# build
/build*/

79
CMakeLists.txt Normal file
View File

@@ -0,0 +1,79 @@
#
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
cmake_minimum_required(VERSION 3.20.0)
# Flag which defines whether application is compiled as gateway/dongle or headset
add_compile_definitions(HEADSET=1)
add_compile_definitions(GATEWAY=2)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
# Test a configuration file option has been given
if(NOT DEFINED EXTRA_CONF_FILE)
message(FATAL_ERROR "No configuration file specified, set -- -DEXTRA_CONF_FILE=<configuration file>")
endif()
project(nrf5340_audio)
string(TIMESTAMP NRF5340_AUDIO_CORE_APP_COMP_DATE "%a %b %d %H:%M:%S %Y")
# Generate fw_info_app.c
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/utils/fw_info_app.c.in"
"${CMAKE_BINARY_DIR}/fw_info_app.c"
@ONLY)
# Target sources below are specific to the nRF5340 Audio DK HW
target_sources(app PRIVATE
${CMAKE_BINARY_DIR}/fw_info_app.c
)
if (CONFIG_BT_BAP_BROADCAST_SINK)
add_subdirectory(broadcast_sink)
endif()
if (CONFIG_BT_BAP_BROADCAST_SOURCE)
add_subdirectory(broadcast_source)
endif()
if (CONFIG_BT_BAP_UNICAST_CLIENT)
add_subdirectory(unicast_client)
endif()
if (CONFIG_BT_BAP_UNICAST_SERVER)
add_subdirectory(unicast_server)
endif()
# Include application events and configuration headers
zephyr_library_include_directories(
include
src/audio
src/bluetooth
src/drivers
src/modules
src/utils
src/utils/macros
)
zephyr_library_include_directories(app PRIVATE
${ZEPHYR_NRF_MODULE_DIR}/boards/arm/nrf5340_audio_dk_nrf5340)
# Application sources
add_subdirectory(src/audio)
add_subdirectory(src/bluetooth)
add_subdirectory(src/drivers)
add_subdirectory(src/modules)
add_subdirectory(src/utils)
## Cirrus Logic
if (CONFIG_HW_CODEC_CIRRUS_LOGIC)
if (ZEPHYR_CIRRUS_LOGIC_MODULE_DIR)
add_subdirectory(${ZEPHYR_CIRRUS_LOGIC_MODULE_DIR} cirrus_logic_bin_dir)
else()
message(FATAL_ERROR "Cirrus Logic/sdk-mcu-drivers repository not found\n")
endif()
endif()

66
Kconfig Normal file
View File

@@ -0,0 +1,66 @@
#
# Copyright (c) 2018 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
menuconfig NRF5340_AUDIO
bool "nRF5340 Audio [EXPERIMENTAL]"
select EXPERIMENTAL
if NRF5340_AUDIO
config CUSTOM_BROADCASTER
bool "Allow custom broadcasters"
default n
help
Incomplete feature for now, but will introduce support for multiple BIGs and subgroups.
config AUDIO_DEV
int "Select which device type to compile for. 1=HEADSET or 2=GATEWAY"
range 1 2
default 1
help
Setting this variable to 1 selects that the project is compiled
as a HEADSET device.
Setting to 2 will compile as a GATEWAY.
choice NRF5340_AUDIO_TRANSPORT_MODE
prompt "Choose BIS or CIS for ISO transport"
default TRANSPORT_CIS if WALKIE_TALKIE_DEMO
default TRANSPORT_CIS
config TRANSPORT_BIS
bool "Use BIS (Broadcast Isochronous Stream)"
config TRANSPORT_CIS
bool "Use CIS (Connected Isochronous Stream)"
endchoice
#----------------------------------------------------------------------------#
rsource "Kconfig.defaults"
rsource "src/audio/Kconfig"
rsource "src/bluetooth/Kconfig"
rsource "src/drivers/Kconfig"
rsource "src/modules/Kconfig"
rsource "src/utils/Kconfig"
#----------------------------------------------------------------------------#
menu "Logging"
module = MAIN
module-str = main
source "subsys/logging/Kconfig.template.log_config"
config PRINT_STACK_USAGE_MS
depends on THREAD_ANALYZER && INIT_STACKS
int "Print stack usage every x milliseconds"
default 5000
endmenu # Log levels
#----------------------------------------------------------------------------#
endif # NRF5340_AUDIO
source "Kconfig.zephyr"

128
Kconfig.defaults Normal file
View File

@@ -0,0 +1,128 @@
#
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
config REBOOT
default y
config MAIN_THREAD_PRIORITY
default 10
config MAIN_STACK_SIZE
default 1800 if SD_CARD_PLAYBACK
default 1600
config SYSTEM_WORKQUEUE_STACK_SIZE
default 1200
# As long as thread names are used, config must be set to "y"
config THREAD_NAME
default y
# Workaround to not use fatal_error.c in NCS. Note that the system may still
# reset on error depending on the build configuraion
config RESET_ON_FATAL_ERROR
default n
# Default Config for Debug and Release build
config BT
default y
config ZBUS
default y
config ZBUS_RUNTIME_OBSERVERS
default y
config ZBUS_MSG_SUBSCRIBER
default y
config SENSOR
default y
config REGULATOR
default y
config CONTIN_ARRAY
default y
config NRFX_I2S0
default y
config PCM_MIX
default y
config PSCM
default y
config DATA_FIFO
default y
# Enable NRFX_CLOCK for ACLK control
config NRFX_CLOCK
default y
config I2C
default y
choice LIBC_IMPLEMENTATION
# NOTE: Since we are not using minimal libc, error codes from
# minimal libc are not used
default NEWLIB_LIBC
endchoice
# Audio codec LC3 related defines
# FPU_SHARING enables preservation of the hardware floating point registers
# across context switches to allow multiple threads to perform concurrent
# floating point operations.
config FPU
default y
config FPU_SHARING
default y
# Enable SDHC interface
config DISK_DRIVERS
default y
config DISK_DRIVER_SDMMC
default y
# Enable SPI interface
config SPI
default y
# Enable ADC for board version readback
config ADC
default y
# Allocate buffer on RAM for transferring chunck of data
# from Flash to SPI
config SPI_NRFX_RAM_BUFFER_SIZE
default 8
# Config the file system
config FILE_SYSTEM
default y
config FAT_FILESYSTEM_ELM
default y
config FS_FATFS_LFN
default y
choice FS_FATFS_LFN_MODE
# Using stack for LFN work queue
default FS_FATFS_LFN_MODE_STACK
endchoice
# exFAT enabled to support longer file names and higher transfer speed
config FS_FATFS_EXFAT
default y
config WATCHDOG
default y
config TASK_WDT
default y

10
Kconfig.sysbuild Normal file
View File

@@ -0,0 +1,10 @@
#
# Copyright (c) 2024 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
config NRF_DEFAULT_IPC_RADIO
default y
source "share/sysbuild/Kconfig"

36
LICENSE Normal file
View File

@@ -0,0 +1,36 @@
LicenseID: LicenseRef-PCFT
ExtractedText: <text>
Redistribution and use of the Audio subsystem for nRF5340 Software, in binary
and source code forms, with or without modification, are permitted provided that
the following conditions are met:
1. Redistributions of source code form must retain the above copyright notice,
this list of conditions, and the following disclaimer.
2. Redistributions in binary code form, except as embedded into a Nordic Semiconductor ASA
nRF53 chip or a software update for such product, must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
3. Neither the name of Packetcraft, Inc. nor Nordic Semiconductor ASA nor the names of its
contributors may be used to endorse or promote products derived from this software without
specific prior written permission.
4. This software, with or without modification, must only be used with a Nordic Semiconductor ASA
nRF53 chip.
5. Any software provided in binary or source code form under this license must not be reverse
engineered, decompiled, modified and/or disassembled.
THIS SOFTWARE IS PROVIDED BY PACKETCRAFT, INC. AND NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL
PACKETCRAFT, INC., NORDIC SEMICONDUCTOR ASA, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
</text>

View File

@@ -0,0 +1,9 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_NRF5340_AUDIO_CS47L63_DRIVER=y
CONFIG_NRF5340_AUDIO_POWER_MEASUREMENT=y
CONFIG_NRF5340_AUDIO_SD_CARD_MODULE=y

View File

@@ -0,0 +1,39 @@
/ {
chosen {
nordic,pm-ext-flash = &mx25r64;
};
};
&qspi {
status = "disabled";
};
&spi4 {
cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>, <&gpio0 11 GPIO_ACTIVE_LOW>, <&gpio0 17 GPIO_ACTIVE_LOW>;
status = "okay";
mx25r64: mx25r6435f@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <8000000>;
jedec-id = [c2 28 17];
sfdp-bfp = [
e5 20 f1 ff ff ff ff 03 44 eb 08 6b 08 3b 04 bb
ee ff ff ff ff ff 00 ff ff ff 00 ff 0c 20 0f 52
10 d8 00 ff 23 72 f5 00 82 ed 04 cc 44 83 68 44
30 b0 30 b0 f7 c4 d5 5c 00 be 29 ff f0 d0 ff ff
];
size = <67108864>;
has-dpd;
t-enter-dpd = <10000>;
t-exit-dpd = <5000>;
dpd-wakeup-sequence = <30000 20 45000>;
};
};
&gpio_fwd {
uart {
gpios = < &gpio1 0x9 0x0 >, < &gpio1 0x8 0x0 >, < &gpio1 0xb 0x0 >;
};
};

View File

@@ -0,0 +1,8 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_NRF5340_AUDIO_CS47L63_DRIVER=y
CONFIG_NRF5340_AUDIO_SD_CARD_MODULE=y

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
&usbd {
hs_0: hs_0 {
compatible = "usb-audio-hs";
mic-feature-mute;
mic-channel-l;
mic-channel-r;
hp-feature-mute;
hp-channel-l;
hp-channel-r;
};
};

View File

@@ -0,0 +1,8 @@
#
# Copyright (c) 2023 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/main.c)

107
broadcast_sink/README.rst Normal file
View File

@@ -0,0 +1,107 @@
.. _nrf53_audio_broadcast_sink_app:
nRF5340 Audio: Broadcast sink
#############################
.. contents::
:local:
:depth: 2
The nRF5340 Audio broadcast sink application implements the :ref:`BIS headset mode <nrf53_audio_app_overview>`.
In this mode, receiving broadcast audio happens using Broadcast Isochronous Stream (BIS) and Broadcast Isochronous Group (BIG).
The following limitations apply to this application:
* One BIG, one of the two BIS streams (selectable).
* Audio output: I2S/Analog headset output.
* Configuration: 16 bit, several bit rates ranging from 32 kbps to 124 kbps.
.. _nrf53_audio_broadcast_sink_app_requirements:
Requirements
************
The application shares the :ref:`requirements common to all nRF5340 Audio application <nrf53_audio_app_requirements>`.
.. _nrf53_audio_broadcast_sink_app_ui:
User interface
**************
Most of the user interface mappings are common across all nRF5340 Audio applications.
See the :ref:`nrf53_audio_app_ui` page for detailed overview.
This application uses specific mapping for the following user interface elements:
* Long-pressed on the broadcast sink device during startup:
* **VOL-** - Changes the headset to the left channel one.
* **VOL+** - Changes the headset to the right channel one.
* Pressed on the broadcast sink device during playback:
* **PLAY/PAUSE** - Starts or pauses listening to the stream.
* **VOL-** - Turns the playback volume down.
* **VOL+** - Turns the playback volume up.
* **BTN 4** - Changes audio stream (different BIS), if more than one is available.
* **BTN 5** - Changes the gateway, if more than one is available.
* **LED1**:
* Solid blue - Devices have synchronized with a broadcasted stream.
* Blinking blue - Devices have started streaming audio (BIS mode).
* **LED2** - Solid green - Sync achieved (both drift and presentation compensation are in the ``LOCKED`` state).
* **RGB**:
* Solid blue - The device is programmed as the left headset.
* Solid magenta - The device is programmed as the right headset.
.. _nrf53_audio_broadcast_sink_app_configuration:
Configuration
*************
The application requires the ``CONFIG_TRANSPORT_BIS`` Kconfig option to be set to ``y`` in the :file:`applications/nrf5340_audio/prj.conf` file for `Building and running`_ to succeed.
For other configuration options, see :ref:`nrf53_audio_app_configuration` and :ref:`nrf53_audio_app_fota`.
For information about how to configure applications in the |NCS|, see :ref:`configure_application`.
.. _nrf53_audio_broadcast_sink_app_building:
Building and running
********************
This application can be found under :file:`applications/nrf5340_audio/broadcast_sink` in the nRF Connect SDK folder structure, but it uses :file:`.conf` files at :file:`applications/nrf5340_audio/`.
The nRF5340 Audio DK comes preprogrammed with basic firmware that indicates if the kit is functional.
See :ref:`nrf53_audio_app_dk_testing_out_of_the_box` for more information.
To build the application, complete the following steps:
1. Select the BIS mode by setting the ``CONFIG_TRANSPORT_BIS`` Kconfig option to ``y`` in the :file:`applications/nrf5340_audio/prj.conf` file for the debug version and in the :file:`applications/nrf5340_audio/prj_release.conf` file for the release version.
#. Complete the steps for building and programming common to all audio applications using one of the following methods:
* :ref:`nrf53_audio_app_building_script`
* :ref:`nrf53_audio_app_building_standard`
.. _nrf53_audio_broadcast_sink_app_testing:
Testing
*******
.. note::
|nrf5340_audio_external_devices_note|
To test the broadcast sink application, complete the following steps:
1. Make sure you have another nRF5340 Audio DK for testing purposes.
#. Program the other DK with the :ref:`broadcast source <nrf53_audio_broadcast_source_app>` application.
The broadcast sink device automatically synchronizes with the broadcast source after programming.
#. Proceed to testing the devices using the :ref:`nrf53_audio_broadcast_sink_app_ui` buttons and LEDs.
Dependencies
************
For the list of dependencies, check the application's source files under :file:`applications/nrf5340_audio/broadcast_sink`.

614
broadcast_sink/main.c Normal file
View File

@@ -0,0 +1,614 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "streamctrl.h"
#include <zephyr/kernel.h>
#include <zephyr/zbus/zbus.h>
#include "broadcast_sink.h"
#include "zbus_common.h"
#include "nrf5340_audio_dk.h"
#include "led.h"
#include "button_assignments.h"
#include "macros_common.h"
#include "audio_system.h"
#include "bt_mgmt.h"
#include "bt_rendering_and_capture.h"
#include "audio_datapath.h"
#include "le_audio_rx.h"
#include "fw_info_app.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, CONFIG_MAIN_LOG_LEVEL);
struct ble_iso_data {
uint8_t data[CONFIG_BT_ISO_RX_MTU];
size_t data_size;
bool bad_frame;
uint32_t sdu_ref;
uint32_t recv_frame_ts;
} __packed;
static uint32_t last_broadcast_id = BRDCAST_ID_NOT_USED;
ZBUS_SUBSCRIBER_DEFINE(button_evt_sub, CONFIG_BUTTON_MSG_SUB_QUEUE_SIZE);
ZBUS_MSG_SUBSCRIBER_DEFINE(le_audio_evt_sub);
ZBUS_MSG_SUBSCRIBER_DEFINE(bt_mgmt_evt_sub);
ZBUS_CHAN_DECLARE(button_chan);
ZBUS_CHAN_DECLARE(le_audio_chan);
ZBUS_CHAN_DECLARE(bt_mgmt_chan);
ZBUS_CHAN_DECLARE(volume_chan);
ZBUS_OBS_DECLARE(volume_evt_sub);
static struct k_thread button_msg_sub_thread_data;
static struct k_thread le_audio_msg_sub_thread_data;
static struct k_thread bt_mgmt_msg_sub_thread_data;
static k_tid_t button_msg_sub_thread_id;
static k_tid_t le_audio_msg_sub_thread_id;
static k_tid_t bt_mgmt_msg_sub_thread_id;
K_THREAD_STACK_DEFINE(button_msg_sub_thread_stack, CONFIG_BUTTON_MSG_SUB_STACK_SIZE);
K_THREAD_STACK_DEFINE(le_audio_msg_sub_thread_stack, CONFIG_LE_AUDIO_MSG_SUB_STACK_SIZE);
K_THREAD_STACK_DEFINE(bt_mgmt_msg_sub_thread_stack, CONFIG_BT_MGMT_MSG_SUB_STACK_SIZE);
static enum stream_state strm_state = STATE_PAUSED;
/* Function for handling all stream state changes */
static void stream_state_set(enum stream_state stream_state_new)
{
strm_state = stream_state_new;
}
/**
* @brief Handle button activity.
*/
static void button_msg_sub_thread(void)
{
int ret;
const struct zbus_channel *chan;
bool broadcast_alt = true;
while (1) {
ret = zbus_sub_wait(&button_evt_sub, &chan, K_FOREVER);
ERR_CHK(ret);
struct button_msg msg;
ret = zbus_chan_read(chan, &msg, ZBUS_READ_TIMEOUT_MS);
ERR_CHK(ret);
LOG_DBG("Got btn evt from queue - id = %d, action = %d", msg.button_pin,
msg.button_action);
if (msg.button_action != BUTTON_PRESS) {
LOG_WRN("Unhandled button action");
continue;
}
switch (msg.button_pin) {
case BUTTON_PLAY_PAUSE:
if (strm_state == STATE_STREAMING) {
ret = broadcast_sink_stop();
if (ret) {
LOG_WRN("Failed to stop broadcast sink: %d", ret);
}
} else if (strm_state == STATE_PAUSED) {
ret = broadcast_sink_start();
if (ret) {
LOG_WRN("Failed to start broadcast sink: %d", ret);
}
} else {
LOG_WRN("In invalid state: %d", strm_state);
}
break;
case BUTTON_VOLUME_UP:
ret = bt_r_and_c_volume_up();
if (ret) {
LOG_WRN("Failed to increase volume: %d", ret);
}
break;
case BUTTON_VOLUME_DOWN:
ret = bt_r_and_c_volume_down();
if (ret) {
LOG_WRN("Failed to decrease volume: %d", ret);
}
break;
case BUTTON_4:
ret = broadcast_sink_change_active_audio_stream();
if (ret) {
LOG_WRN("Failed to change active audio stream: %d", ret);
}
break;
case BUTTON_5:
if (IS_ENABLED(CONFIG_AUDIO_MUTE)) {
ret = bt_r_and_c_volume_mute(false);
if (ret) {
LOG_WRN("Failed to mute, ret: %d", ret);
}
break;
}
ret = broadcast_sink_disable();
if (ret) {
LOG_ERR("Failed to disable the broadcast sink: %d", ret);
break;
}
if (broadcast_alt) {
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_BROADCAST,
CONFIG_BT_AUDIO_BROADCAST_NAME_ALT,
BRDCAST_ID_NOT_USED);
broadcast_alt = false;
} else {
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_BROADCAST,
CONFIG_BT_AUDIO_BROADCAST_NAME,
BRDCAST_ID_NOT_USED);
broadcast_alt = true;
}
if (ret) {
LOG_WRN("Failed to start scanning for broadcaster: %d", ret);
}
break;
default:
LOG_WRN("Unexpected/unhandled button id: %d", msg.button_pin);
}
STACK_USAGE_PRINT("button_msg_thread", &button_msg_sub_thread_data);
}
}
/**
* @brief Handle Bluetooth LE audio events.
*/
static void le_audio_msg_sub_thread(void)
{
int ret;
uint32_t pres_delay_us;
uint32_t bitrate_bps;
uint32_t sampling_rate_hz;
const struct zbus_channel *chan;
while (1) {
struct le_audio_msg msg;
ret = zbus_sub_wait_msg(&le_audio_evt_sub, &chan, &msg, K_FOREVER);
ERR_CHK(ret);
LOG_DBG("Received event = %d, current state = %d", msg.event, strm_state);
switch (msg.event) {
case LE_AUDIO_EVT_STREAMING:
LOG_DBG("LE audio evt streaming");
if (strm_state == STATE_STREAMING) {
LOG_DBG("Got streaming event in streaming state");
break;
}
audio_system_start();
stream_state_set(STATE_STREAMING);
ret = led_blink(LED_APP_1_BLUE);
ERR_CHK(ret);
break;
case LE_AUDIO_EVT_NOT_STREAMING:
LOG_DBG("LE audio evt not_streaming");
if (strm_state == STATE_PAUSED) {
LOG_DBG("Got not_streaming event in paused state");
break;
}
stream_state_set(STATE_PAUSED);
audio_system_stop();
ret = led_on(LED_APP_1_BLUE);
ERR_CHK(ret);
break;
case LE_AUDIO_EVT_CONFIG_RECEIVED:
LOG_DBG("LE audio config received");
ret = broadcast_sink_config_get(&bitrate_bps, &sampling_rate_hz,
&pres_delay_us);
if (ret) {
LOG_WRN("Failed to get config: %d", ret);
break;
}
LOG_DBG("\tSampling rate: %d Hz", sampling_rate_hz);
LOG_DBG("\tBitrate (compressed): %d bps", bitrate_bps);
ret = audio_system_config_set(VALUE_NOT_SET, VALUE_NOT_SET,
sampling_rate_hz);
ERR_CHK(ret);
ret = audio_datapath_pres_delay_us_set(pres_delay_us);
if (ret) {
break;
}
LOG_INF("Presentation delay %d us is set", pres_delay_us);
break;
case LE_AUDIO_EVT_SYNC_LOST:
LOG_INF("Sync lost");
ret = bt_mgmt_pa_sync_delete(msg.pa_sync);
if (ret) {
LOG_WRN("Failed to delete PA sync");
}
if (strm_state == STATE_STREAMING) {
stream_state_set(STATE_PAUSED);
audio_system_stop();
ret = led_on(LED_APP_1_BLUE);
ERR_CHK(ret);
}
if (IS_ENABLED(CONFIG_BT_OBSERVER)) {
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_BROADCAST, NULL,
last_broadcast_id);
if (ret) {
if (ret == -EALREADY) {
break;
}
LOG_ERR("Failed to restart scanning: %d", ret);
break;
}
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Restarted scanning for broadcaster");
}
break;
case LE_AUDIO_EVT_NO_VALID_CFG:
LOG_WRN("No valid configurations found, disabling the broadcast sink");
ret = broadcast_sink_disable();
if (ret) {
LOG_ERR("Failed to disable the broadcast sink: %d", ret);
break;
}
break;
case LE_AUDIO_EVT_STREAM_SENT:
/* Nothing to do. */
break;
default:
LOG_WRN("Unexpected/unhandled le_audio event: %d", msg.event);
break;
}
STACK_USAGE_PRINT("le_audio_msg_thread", &le_audio_msg_sub_thread_data);
}
}
/**
* @brief Handle bt_mgmt events.
*/
static void bt_mgmt_msg_sub_thread(void)
{
int ret;
static uint8_t *broadcast_code;
const struct zbus_channel *chan;
while (1) {
struct bt_mgmt_msg msg;
ret = zbus_sub_wait_msg(&bt_mgmt_evt_sub, &chan, &msg, K_FOREVER);
ERR_CHK(ret);
switch (msg.event) {
case BT_MGMT_CONNECTED:
LOG_DBG("Connected");
break;
case BT_MGMT_DISCONNECTED:
LOG_DBG("Disconnected");
break;
case BT_MGMT_SECURITY_CHANGED:
LOG_DBG("Security changed");
break;
case BT_MGMT_PA_SYNCED:
LOG_DBG("PA synced");
ret = broadcast_sink_pa_sync_set(msg.pa_sync, msg.broadcast_id);
if (ret) {
last_broadcast_id = BRDCAST_ID_NOT_USED;
LOG_WRN("Failed to set PA sync");
} else {
last_broadcast_id = msg.broadcast_id;
}
break;
case BT_MGMT_PA_SYNC_LOST:
LOG_INF("PA sync lost, reason: %d", msg.pa_sync_term_reason);
if (IS_ENABLED(CONFIG_BT_OBSERVER) &&
msg.pa_sync_term_reason != BT_HCI_ERR_LOCALHOST_TERM_CONN) {
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_BROADCAST, NULL,
BRDCAST_ID_NOT_USED);
if (ret) {
if (ret == -EALREADY) {
LOG_ERR("Failed to restart scanning: %d", ret);
}
break;
}
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Restarted scanning for broadcaster");
}
break;
case BT_MGMT_BROADCAST_SINK_DISABLE:
LOG_DBG("Broadcast sink disabled");
ret = broadcast_sink_disable();
if (ret) {
LOG_ERR("Failed to disable the broadcast sink: %d", ret);
}
break;
case BT_MGMT_BROADCAST_CODE_RECEIVED:
LOG_DBG("Broadcast code received");
bt_mgmt_broadcast_code_ptr_get(&broadcast_code);
ret = broadcast_sink_broadcast_code_set(broadcast_code);
if (ret) {
LOG_ERR("Failed to set broadcast code: %d", ret);
}
break;
default:
LOG_WRN("Unexpected/unhandled bt_mgmt event: %d", msg.event);
break;
}
}
}
/**
* @brief Create zbus subscriber threads.
*
* @return 0 for success, error otherwise.
*/
static int zbus_subscribers_create(void)
{
int ret;
button_msg_sub_thread_id = k_thread_create(
&button_msg_sub_thread_data, button_msg_sub_thread_stack,
CONFIG_BUTTON_MSG_SUB_STACK_SIZE, (k_thread_entry_t)button_msg_sub_thread, NULL,
NULL, NULL, K_PRIO_PREEMPT(CONFIG_BUTTON_MSG_SUB_THREAD_PRIO), 0, K_NO_WAIT);
ret = k_thread_name_set(button_msg_sub_thread_id, "BUTTON_MSG_SUB");
if (ret) {
LOG_ERR("Failed to create button_msg thread");
return ret;
}
le_audio_msg_sub_thread_id = k_thread_create(
&le_audio_msg_sub_thread_data, le_audio_msg_sub_thread_stack,
CONFIG_LE_AUDIO_MSG_SUB_STACK_SIZE, (k_thread_entry_t)le_audio_msg_sub_thread, NULL,
NULL, NULL, K_PRIO_PREEMPT(CONFIG_LE_AUDIO_MSG_SUB_THREAD_PRIO), 0, K_NO_WAIT);
ret = k_thread_name_set(le_audio_msg_sub_thread_id, "LE_AUDIO_MSG_SUB");
if (ret) {
LOG_ERR("Failed to create le_audio_msg thread");
return ret;
}
bt_mgmt_msg_sub_thread_id = k_thread_create(
&bt_mgmt_msg_sub_thread_data, bt_mgmt_msg_sub_thread_stack,
CONFIG_BT_MGMT_MSG_SUB_STACK_SIZE, (k_thread_entry_t)bt_mgmt_msg_sub_thread, NULL,
NULL, NULL, K_PRIO_PREEMPT(CONFIG_BT_MGMT_MSG_SUB_THREAD_PRIO), 0, K_NO_WAIT);
ret = k_thread_name_set(bt_mgmt_msg_sub_thread_id, "BT_MGMT_MSG_SUB");
if (ret) {
LOG_ERR("Failed to create le_audio_msg thread");
return ret;
}
return 0;
}
/**
* @brief Link zbus producers and observers.
*
* @return 0 for success, error otherwise.
*/
static int zbus_link_producers_observers(void)
{
int ret;
if (!IS_ENABLED(CONFIG_ZBUS)) {
return -ENOTSUP;
}
ret = zbus_chan_add_obs(&button_chan, &button_evt_sub, ZBUS_ADD_OBS_TIMEOUT_MS);
if (ret) {
LOG_ERR("Failed to add button sub");
return ret;
}
ret = zbus_chan_add_obs(&le_audio_chan, &le_audio_evt_sub, ZBUS_ADD_OBS_TIMEOUT_MS);
if (ret) {
LOG_ERR("Failed to add le_audio sub");
return ret;
}
ret = zbus_chan_add_obs(&volume_chan, &volume_evt_sub, ZBUS_ADD_OBS_TIMEOUT_MS);
if (ret) {
LOG_ERR("Failed to add add volume sub");
return ret;
}
ret = zbus_chan_add_obs(&bt_mgmt_chan, &bt_mgmt_evt_sub, ZBUS_ADD_OBS_TIMEOUT_MS);
if (ret) {
LOG_ERR("Failed to add bt_mgmt sub");
return ret;
}
return 0;
}
/*
* @brief The following configures the data for the extended advertising.
*
* @param ext_adv_buf Pointer to the bt_data used for extended advertising.
* @param ext_adv_buf_size Size of @p ext_adv_buf.
* @param ext_adv_count Pointer to the number of elements added to @p adv_buf.
*
* @return 0 for success, error otherwise.
*/
static int ext_adv_populate(struct bt_data *ext_adv_buf, size_t ext_adv_buf_size,
size_t *ext_adv_count)
{
int ret;
size_t ext_adv_buf_cnt = 0;
NET_BUF_SIMPLE_DEFINE_STATIC(uuid_buf, CONFIG_EXT_ADV_UUID_BUF_MAX);
ext_adv_buf[ext_adv_buf_cnt].type = BT_DATA_UUID16_ALL;
ext_adv_buf[ext_adv_buf_cnt].data = uuid_buf.data;
ext_adv_buf_cnt++;
ret = bt_mgmt_manufacturer_uuid_populate(&uuid_buf, CONFIG_BT_DEVICE_MANUFACTURER_ID);
if (ret) {
LOG_ERR("Failed to add adv data with manufacturer ID: %d", ret);
return ret;
}
ret = broadcast_sink_uuid_populate(&uuid_buf);
if (ret < 0) {
LOG_ERR("Failed to add UUID with broadcast sink: %d", ret);
return ret;
}
ret = bt_r_and_c_uuid_populate(&uuid_buf);
if (ret) {
LOG_ERR("Failed to add adv data from renderer: %d", ret);
return ret;
}
ret = broadcast_sink_adv_populate(&ext_adv_buf[ext_adv_buf_cnt],
ext_adv_buf_size - ext_adv_buf_cnt);
if (ret < 0) {
LOG_ERR("Failed to add adv data from broadcast sink: %d", ret);
return ret;
}
ext_adv_buf_cnt += ret;
/* Add the number of UUIDs */
ext_adv_buf[0].data_len = uuid_buf.len;
LOG_DBG("Size of adv data: %d, num_elements: %d", sizeof(struct bt_data) * ext_adv_buf_cnt,
ext_adv_buf_cnt);
*ext_adv_count = ext_adv_buf_cnt;
return 0;
}
uint8_t stream_state_get(void)
{
return strm_state;
}
void streamctrl_send(void const *const data, size_t size, uint8_t num_ch)
{
ARG_UNUSED(data);
ARG_UNUSED(size);
ARG_UNUSED(num_ch);
LOG_WRN("Sending is not possible for broadcast sink");
}
int main(void)
{
int ret;
LOG_DBG("Main started");
ret = nrf5340_audio_dk_init();
ERR_CHK(ret);
ret = fw_info_app_print();
ERR_CHK(ret);
ret = bt_mgmt_init();
ERR_CHK(ret);
ret = audio_system_init();
ERR_CHK(ret);
ret = zbus_subscribers_create();
ERR_CHK_MSG(ret, "Failed to create zbus subscriber threads");
ret = zbus_link_producers_observers();
ERR_CHK_MSG(ret, "Failed to link zbus producers and observers");
ret = le_audio_rx_init();
ERR_CHK_MSG(ret, "Failed to initialize rx path");
ret = broadcast_sink_enable(le_audio_rx_data_handler);
ERR_CHK_MSG(ret, "Failed to enable broadcast sink");
if (IS_ENABLED(CONFIG_BT_AUDIO_SCAN_DELEGATOR)) {
static struct bt_data ext_adv_buf[CONFIG_EXT_ADV_BUF_MAX];
size_t ext_adv_buf_cnt = 0;
bt_mgmt_scan_delegator_init();
ret = bt_r_and_c_init();
ERR_CHK(ret);
ret = ext_adv_populate(ext_adv_buf, ARRAY_SIZE(ext_adv_buf), &ext_adv_buf_cnt);
ERR_CHK(ret);
ret = bt_mgmt_adv_start(0, ext_adv_buf, ext_adv_buf_cnt, NULL, 0, true);
ERR_CHK(ret);
} else {
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_BROADCAST,
CONFIG_BT_AUDIO_BROADCAST_NAME, BRDCAST_ID_NOT_USED);
ERR_CHK_MSG(ret, "Failed to start scanning");
}
return 0;
}

View File

@@ -0,0 +1,60 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_TRANSPORT_BIS=y
CONFIG_AUDIO_DEV=1
CONFIG_BT_DEVICE_NAME="NRF5340_BIS_HEADSET"
## ACL related configs ##
CONFIG_BT_OBSERVER=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_SMP=y
CONFIG_BT_AUDIO=y
CONFIG_BT_GATT_DYNAMIC_DB=y
CONFIG_BT_GATT_CACHING=n
CONFIG_SETTINGS=y
CONFIG_BT_SETTINGS=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
CONFIG_MBEDTLS_ENABLE_HEAP=y
CONFIG_MBEDTLS_HEAP_SIZE=2048
CONFIG_BT_BUF_ACL_TX_COUNT=18
CONFIG_BT_PERIPHERAL_PREF_MIN_INT=64
CONFIG_BT_PERIPHERAL_PREF_MAX_INT=69
CONFIG_BT_PERIPHERAL_PREF_LATENCY=0
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=200
# Generic Audio Sink - 0x0840
CONFIG_BT_DEVICE_APPEARANCE=2112
CONFIG_BT_PER_ADV_SYNC_MAX=2
## ISO related configs ##
CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT=2
CONFIG_BT_BAP_BROADCAST_SNK_COUNT=2
CONFIG_BT_ISO_MAX_CHAN=2
CONFIG_BT_ISO_MAX_BIG=2
## PACS related configs ##
CONFIG_BT_PAC_SNK_NOTIFIABLE=y
CONFIG_BT_PAC_SNK=y
CONFIG_BT_PAC_SRC_NOTIFIABLE=y
CONFIG_BT_PAC_SRC=y
## Audio related configs ##
CONFIG_AUDIO_MUTE=n
CONFIG_AUDIO_TEST_TONE=n
CONFIG_BT_ISO_SYNC_RECEIVER=y
CONFIG_BT_BAP_SCAN_DELEGATOR=y
CONFIG_BT_BAP_BROADCAST_SINK=y
## LC3 related configs ##
CONFIG_LC3_DEC_CHAN_MAX=1

View File

@@ -0,0 +1,8 @@
#
# Copyright (c) 2023 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/main.c)

View File

@@ -0,0 +1,45 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
## ISO related configs ##
config BT_CAP_INITIATOR
default y
config BT_MAX_CONN
default 1
# Broadcasting Device - 0x0885
config BT_DEVICE_APPEARANCE
default 2181
config BT_ISO_BROADCASTER
default y
config BT_BAP_BROADCAST_SOURCE
default y
config BT_ISO_TX_BUF_COUNT
default 2
config BT_BAP_BROADCAST_SRC_STREAM_COUNT
default 2
config BT_ISO_MAX_CHAN
default 2
config BT_ISO_MAX_BIG
default 2
config BT_AUDIO_TX
default y
## LC3 related configs ##
config LC3_BITRATE
default BT_AUDIO_BITRATE_BROADCAST_SRC
config LC3_ENC_CHAN_MAX
default 2

View File

@@ -0,0 +1,96 @@
.. _nrf53_audio_broadcast_source_app:
nRF5340 Audio: Broadcast source
###############################
.. contents::
:local:
:depth: 2
The nRF5340 Audio broadcast source application implements the :ref:`BIS gateway mode <nrf53_audio_app_overview>`.
In this mode, transmitting broadcast audio happens using Broadcast Isochronous Stream (BIS) and Broadcast Isochronous Group (BIG).
Play and pause are emulated by enabling and disabling stream, respectively.
The following limitations apply to this application:
* One BIG with two BIS streams.
* Audio input: USB or I2S (Line in or using Pulse Density Modulation).
* Configuration: 16 bit, several bit rates ranging from 32 kbps to 124 kbps.
.. _nrf53_audio_broadcast_source_app_requirements:
Requirements
************
The application shares the :ref:`requirements common to all nRF5340 Audio application <nrf53_audio_app_requirements>`.
.. _nrf53_audio_broadcast_source_app_ui:
User interface
**************
Most of the user interface mappings are common across all nRF5340 Audio applications.
See the :ref:`nrf53_audio_app_ui` page for detailed overview.
This application uses specific mapping for the following user interface elements:
* Pressed on the broadcast source device during playback:
* **PLAY/PAUSE** - Starts or pauses the playback of the stream.
* **BTN 4** - Toggles between the normal audio stream and different test tones generated on the device.
Use this tone to check the synchronization of headsets.
* **LED1** - Blinking blue - Device has started broadcasting audio.
* **RGB** - Solid green - The device is programmed as the gateway.
.. _nrf53_audio_broadcast_source_app_configuration:
Configuration
*************
The application requires the ``CONFIG_TRANSPORT_BIS`` Kconfig option to be set to ``y`` in the :file:`applications/nrf5340_audio/prj.conf` file for `Building and running`_ to succeed.
For other configuration options, see :ref:`nrf53_audio_app_configuration` and :ref:`nrf53_audio_app_fota`.
For information about how to configure applications in the |NCS|, see :ref:`configure_application`.
.. _nrf53_audio_broadcast_source_app_building:
Building and running
********************
This application can be found under :file:`applications/nrf5340_audio/broadcast_source` in the nRF Connect SDK folder structure, but it uses :file:`.conf` files at :file:`applications/nrf5340_audio/`.
The nRF5340 Audio DK comes preprogrammed with basic firmware that indicates if the kit is functional.
See :ref:`nrf53_audio_app_dk_testing_out_of_the_box` for more information.
To build the application, complete the following steps:
1. Select the BIS mode by setting the ``CONFIG_TRANSPORT_BIS`` Kconfig option to ``y`` in the :file:`applications/nrf5340_audio/prj.conf` file for the debug version and in the :file:`applications/nrf5340_audio/prj_release.conf` file for the release version.
#. Complete the steps for building and programming common to all audio applications using one of the following methods:
* :ref:`nrf53_audio_app_building_script`
* :ref:`nrf53_audio_app_building_standard`
After programming, the broadcast source automatically starts broadcasting the default 48-kHz audio stream.
.. _nrf53_audio_broadcast_source_app_testing:
Testing
*******
.. note::
|nrf5340_audio_external_devices_note|
To test the broadcast source application, complete the following steps:
1. Make sure you have another nRF5340 Audio DK for testing purposes.
#. Program the other DK with the :ref:`broadcast sink <nrf53_audio_broadcast_sink_app>` application.
The broadcast sink device automatically synchronizes with the broadcast source after programming.
#. Proceed to testing the broadcast source using the :ref:`nrf53_audio_broadcast_source_app_ui` buttons and LEDs.
Dependencies
************
For the list of dependencies, check the application's source files.

606
broadcast_source/main.c Normal file
View File

@@ -0,0 +1,606 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "streamctrl.h"
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/kernel.h>
#include <zephyr/zbus/zbus.h>
#include <zephyr/sys/byteorder.h>
#include "broadcast_source.h"
#include "zbus_common.h"
#include "nrf5340_audio_dk.h"
#include "led.h"
#include "button_assignments.h"
#include "macros_common.h"
#include "audio_system.h"
#include "bt_mgmt.h"
#include "fw_info_app.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, CONFIG_MAIN_LOG_LEVEL);
ZBUS_SUBSCRIBER_DEFINE(button_evt_sub, CONFIG_BUTTON_MSG_SUB_QUEUE_SIZE);
ZBUS_MSG_SUBSCRIBER_DEFINE(le_audio_evt_sub);
ZBUS_CHAN_DECLARE(button_chan);
ZBUS_CHAN_DECLARE(le_audio_chan);
ZBUS_CHAN_DECLARE(bt_mgmt_chan);
ZBUS_CHAN_DECLARE(sdu_ref_chan);
ZBUS_OBS_DECLARE(sdu_ref_msg_listen);
static struct k_thread button_msg_sub_thread_data;
static struct k_thread le_audio_msg_sub_thread_data;
static k_tid_t button_msg_sub_thread_id;
static k_tid_t le_audio_msg_sub_thread_id;
struct bt_le_ext_adv *ext_adv;
K_THREAD_STACK_DEFINE(button_msg_sub_thread_stack, CONFIG_BUTTON_MSG_SUB_STACK_SIZE);
K_THREAD_STACK_DEFINE(le_audio_msg_sub_thread_stack, CONFIG_LE_AUDIO_MSG_SUB_STACK_SIZE);
static enum stream_state strm_state = STATE_PAUSED;
/* Buffer for the UUIDs. */
#define EXT_ADV_UUID_BUF_SIZE (128)
NET_BUF_SIMPLE_DEFINE_STATIC(uuid_data, EXT_ADV_UUID_BUF_SIZE);
NET_BUF_SIMPLE_DEFINE_STATIC(uuid_data2, EXT_ADV_UUID_BUF_SIZE);
/* Buffer for periodic advertising BASE data. */
NET_BUF_SIMPLE_DEFINE_STATIC(base_data, 128);
NET_BUF_SIMPLE_DEFINE_STATIC(base_data2, 128);
/* Extended advertising buffer. */
static struct bt_data ext_adv_buf[CONFIG_BT_ISO_MAX_BIG][CONFIG_EXT_ADV_BUF_MAX];
/* Periodic advertising buffer. */
static struct bt_data per_adv_buf[CONFIG_BT_ISO_MAX_BIG];
#if (CONFIG_AURACAST)
/* Total size of the PBA buffer includes the 16-bit UUID, 8-bit features and the
* meta data size.
*/
#define BROADCAST_SRC_PBA_BUF_SIZE \
(BROADCAST_SOURCE_PBA_HEADER_SIZE + CONFIG_BT_AUDIO_BROADCAST_PBA_METADATA_SIZE)
/* Number of metadata items that can be assigned. */
#define BROADCAST_SOURCE_PBA_METADATA_VACANT \
(CONFIG_BT_AUDIO_BROADCAST_PBA_METADATA_SIZE / (sizeof(struct bt_data)))
/* Make sure pba_buf is large enough for a 16bit UUID and meta data
* (any addition to pba_buf requires an increase of this value)
*/
uint8_t pba_data[CONFIG_BT_ISO_MAX_BIG][BROADCAST_SRC_PBA_BUF_SIZE];
/**
* @brief Broadcast source static extended advertising data.
*/
static struct broadcast_source_ext_adv_data ext_adv_data[] = {
{.uuid_buf = &uuid_data,
.pba_metadata_vacant_cnt = BROADCAST_SOURCE_PBA_METADATA_VACANT,
.pba_buf = pba_data[0]},
{.uuid_buf = &uuid_data2,
.pba_metadata_vacant_cnt = BROADCAST_SOURCE_PBA_METADATA_VACANT,
.pba_buf = pba_data[1]}};
#else
/**
* @brief Broadcast source static extended advertising data.
*/
static struct broadcast_source_ext_adv_data ext_adv_data[] = {{.uuid_buf = &uuid_data},
{.uuid_buf = &uuid_data2}};
#endif /* (CONFIG_AURACAST) */
/**
* @brief Broadcast source static periodic advertising data.
*/
static struct broadcast_source_per_adv_data per_adv_data[] = {{.base_buf = &base_data},
{.base_buf = &base_data2}};
/* Function for handling all stream state changes */
static void stream_state_set(enum stream_state stream_state_new)
{
strm_state = stream_state_new;
}
/**
* @brief Handle button activity.
*/
static void button_msg_sub_thread(void)
{
int ret;
const struct zbus_channel *chan;
while (1) {
ret = zbus_sub_wait(&button_evt_sub, &chan, K_FOREVER);
ERR_CHK(ret);
struct button_msg msg;
ret = zbus_chan_read(chan, &msg, ZBUS_READ_TIMEOUT_MS);
ERR_CHK(ret);
LOG_DBG("Got btn evt from queue - id = %d, action = %d", msg.button_pin,
msg.button_action);
if (msg.button_action != BUTTON_PRESS) {
LOG_WRN("Unhandled button action");
return;
}
switch (msg.button_pin) {
case BUTTON_PLAY_PAUSE:
if (strm_state == STATE_STREAMING) {
ret = broadcast_source_stop(0);
if (ret) {
LOG_WRN("Failed to stop broadcaster: %d", ret);
}
} else if (strm_state == STATE_PAUSED) {
ret = broadcast_source_start(0, ext_adv);
if (ret) {
LOG_WRN("Failed to start broadcaster: %d", ret);
}
} else {
LOG_WRN("In invalid state: %d", strm_state);
}
break;
case BUTTON_4:
if (IS_ENABLED(CONFIG_AUDIO_TEST_TONE)) {
if (strm_state != STATE_STREAMING) {
LOG_WRN("Not in streaming state");
break;
}
ret = audio_system_encode_test_tone_step();
if (ret) {
LOG_WRN("Failed to play test tone, ret: %d", ret);
}
break;
}
break;
default:
LOG_WRN("Unexpected/unhandled button id: %d", msg.button_pin);
}
STACK_USAGE_PRINT("button_msg_thread", &button_msg_sub_thread_data);
}
}
/**
* @brief Handle Bluetooth LE audio events.
*/
static void le_audio_msg_sub_thread(void)
{
int ret;
const struct zbus_channel *chan;
while (1) {
struct le_audio_msg msg;
ret = zbus_sub_wait_msg(&le_audio_evt_sub, &chan, &msg, K_FOREVER);
ERR_CHK(ret);
LOG_DBG("Received event = %d, current state = %d", msg.event, strm_state);
switch (msg.event) {
case LE_AUDIO_EVT_STREAMING:
LOG_DBG("LE audio evt streaming");
audio_system_encoder_start();
if (strm_state == STATE_STREAMING) {
LOG_DBG("Got streaming event in streaming state");
break;
}
audio_system_start();
stream_state_set(STATE_STREAMING);
ret = led_blink(LED_APP_1_BLUE);
ERR_CHK(ret);
break;
case LE_AUDIO_EVT_NOT_STREAMING:
LOG_DBG("LE audio evt not_streaming");
audio_system_encoder_stop();
if (strm_state == STATE_PAUSED) {
LOG_DBG("Got not_streaming event in paused state");
break;
}
stream_state_set(STATE_PAUSED);
audio_system_stop();
ret = led_on(LED_APP_1_BLUE);
ERR_CHK(ret);
break;
case LE_AUDIO_EVT_STREAM_SENT:
/* Nothing to do. */
break;
default:
LOG_WRN("Unexpected/unhandled le_audio event: %d", msg.event);
break;
}
STACK_USAGE_PRINT("le_audio_msg_thread", &le_audio_msg_sub_thread_data);
}
}
/**
* @brief Create zbus subscriber threads.
*
* @return 0 for success, error otherwise.
*/
static int zbus_subscribers_create(void)
{
int ret;
button_msg_sub_thread_id = k_thread_create(
&button_msg_sub_thread_data, button_msg_sub_thread_stack,
CONFIG_BUTTON_MSG_SUB_STACK_SIZE, (k_thread_entry_t)button_msg_sub_thread, NULL,
NULL, NULL, K_PRIO_PREEMPT(CONFIG_BUTTON_MSG_SUB_THREAD_PRIO), 0, K_NO_WAIT);
ret = k_thread_name_set(button_msg_sub_thread_id, "BUTTON_MSG_SUB");
if (ret) {
LOG_ERR("Failed to create button_msg thread");
return ret;
}
le_audio_msg_sub_thread_id = k_thread_create(
&le_audio_msg_sub_thread_data, le_audio_msg_sub_thread_stack,
CONFIG_LE_AUDIO_MSG_SUB_STACK_SIZE, (k_thread_entry_t)le_audio_msg_sub_thread, NULL,
NULL, NULL, K_PRIO_PREEMPT(CONFIG_LE_AUDIO_MSG_SUB_THREAD_PRIO), 0, K_NO_WAIT);
ret = k_thread_name_set(le_audio_msg_sub_thread_id, "LE_AUDIO_MSG_SUB");
if (ret) {
LOG_ERR("Failed to create le_audio_msg thread");
return ret;
}
ret = zbus_chan_add_obs(&sdu_ref_chan, &sdu_ref_msg_listen, ZBUS_ADD_OBS_TIMEOUT_MS);
if (ret) {
LOG_ERR("Failed to add timestamp listener");
return ret;
}
return 0;
}
/**
* @brief Zbus listener to receive events from bt_mgmt.
*
* @param[in] chan Zbus channel.
*
* @note Will in most cases be called from BT_RX context,
* so there should not be too much processing done here.
*/
static void bt_mgmt_evt_handler(const struct zbus_channel *chan)
{
int ret;
const struct bt_mgmt_msg *msg;
msg = zbus_chan_const_msg(chan);
switch (msg->event) {
case BT_MGMT_EXT_ADV_WITH_PA_READY:
LOG_INF("Ext adv ready");
ext_adv = msg->ext_adv;
ret = broadcast_source_start(msg->index, ext_adv);
if (ret) {
LOG_ERR("Failed to start broadcaster: %d", ret);
}
break;
default:
LOG_WRN("Unexpected/unhandled bt_mgmt event: %d", msg->event);
break;
}
}
ZBUS_LISTENER_DEFINE(bt_mgmt_evt_listen, bt_mgmt_evt_handler);
/**
* @brief Link zbus producers and observers.
*
* @return 0 for success, error otherwise.
*/
static int zbus_link_producers_observers(void)
{
int ret;
if (!IS_ENABLED(CONFIG_ZBUS)) {
return -ENOTSUP;
}
ret = zbus_chan_add_obs(&button_chan, &button_evt_sub, ZBUS_ADD_OBS_TIMEOUT_MS);
if (ret) {
LOG_ERR("Failed to add button sub");
return ret;
}
ret = zbus_chan_add_obs(&le_audio_chan, &le_audio_evt_sub, ZBUS_ADD_OBS_TIMEOUT_MS);
if (ret) {
LOG_ERR("Failed to add le_audio sub");
return ret;
}
ret = zbus_chan_add_obs(&bt_mgmt_chan, &bt_mgmt_evt_listen, ZBUS_ADD_OBS_TIMEOUT_MS);
if (ret) {
LOG_ERR("Failed to add bt_mgmt listener");
return ret;
}
return 0;
}
/*
* @brief The following configures the data for the extended advertising.
* This includes the Broadcast Audio Announcements [BAP 3.7.2.1] and Broadcast_ID
* [BAP 3.7.2.1.1] in the AUX_ADV_IND Extended Announcements.
*
* @param big_index Index of the Broadcast Isochronous Group (BIG) to get
* advertising data for.
* @param ext_adv_data Pointer to the extended advertising buffers.
* @param ext_adv_buf Pointer to the bt_data used for extended advertising.
* @param ext_adv_buf_size Size of @p ext_adv_buf.
* @param ext_adv_count Pointer to the number of elements added to @p adv_buf.
*
* @return 0 for success, error otherwise.
*/
static int ext_adv_populate(uint8_t big_index, struct broadcast_source_ext_adv_data *ext_adv_data,
struct bt_data *ext_adv_buf, size_t ext_adv_buf_size,
size_t *ext_adv_count)
{
int ret;
size_t ext_adv_buf_cnt = 0;
if (IS_ENABLED(CONFIG_BT_AUDIO_USE_BROADCAST_NAME_ALT)) {
if (sizeof(CONFIG_BT_AUDIO_BROADCAST_NAME_ALT) >
ARRAY_SIZE(ext_adv_data->brdcst_name_buf)) {
LOG_ERR("CONFIG_BT_AUDIO_BROADCAST_NAME_ALT is too long");
return -EINVAL;
}
size_t brdcst_name_size = sizeof(CONFIG_BT_AUDIO_BROADCAST_NAME_ALT) - 1;
memcpy(ext_adv_data->brdcst_name_buf, CONFIG_BT_AUDIO_BROADCAST_NAME_ALT,
brdcst_name_size);
} else {
if (sizeof(CONFIG_BT_AUDIO_BROADCAST_NAME) >
ARRAY_SIZE(ext_adv_data->brdcst_name_buf)) {
LOG_ERR("CONFIG_BT_AUDIO_BROADCAST_NAME is too long");
return -EINVAL;
}
size_t brdcst_name_size = sizeof(CONFIG_BT_AUDIO_BROADCAST_NAME) - 1;
memcpy(ext_adv_data->brdcst_name_buf, CONFIG_BT_AUDIO_BROADCAST_NAME,
brdcst_name_size);
}
ext_adv_buf[ext_adv_buf_cnt].type = BT_DATA_UUID16_ALL;
ext_adv_buf[ext_adv_buf_cnt].data = ext_adv_data->uuid_buf->data;
ext_adv_buf_cnt++;
ret = bt_mgmt_manufacturer_uuid_populate(ext_adv_data->uuid_buf,
CONFIG_BT_DEVICE_MANUFACTURER_ID);
if (ret) {
LOG_ERR("Failed to add adv data with manufacturer ID: %d", ret);
return ret;
}
bool fixed_id = !IS_ENABLED(CONFIG_BT_AUDIO_USE_BROADCAST_ID_RANDOM);
uint32_t broadcast_id = CONFIG_BT_AUDIO_BROADCAST_ID_FIXED;
ret = broadcast_source_ext_adv_populate(big_index, fixed_id, broadcast_id, ext_adv_data,
&ext_adv_buf[ext_adv_buf_cnt],
ext_adv_buf_size - ext_adv_buf_cnt);
if (ret < 0) {
LOG_ERR("Failed to add ext adv data for broadcast source: %d", ret);
return ret;
}
ext_adv_buf_cnt += ret;
/* Add the number of UUIDs */
ext_adv_buf[0].data_len = ext_adv_data->uuid_buf->len;
LOG_DBG("Size of adv data: %d, num_elements: %d", sizeof(struct bt_data) * ext_adv_buf_cnt,
ext_adv_buf_cnt);
*ext_adv_count = ext_adv_buf_cnt;
return 0;
}
/*
* @brief The following configures the data for the periodic advertising.
* This includes the Basic Audio Announcement, including the
* BASE [BAP 3.7.2.2] and BIGInfo.
*
* @param big_index Index of the Broadcast Isochronous Group (BIG) to get
* advertising data for.
* @param pre_adv_data Pointer to the periodic advertising buffers.
* @param per_adv_buf Pointer to the bt_data used for periodic advertising.
* @param per_adv_buf_size Size of @p ext_adv_buf.
* @param per_adv_count Pointer to the number of elements added to @p adv_buf.
*
* @return 0 for success, error otherwise.
*/
static int per_adv_populate(uint8_t big_index, struct broadcast_source_per_adv_data *pre_adv_data,
struct bt_data *per_adv_buf, size_t per_adv_buf_size,
size_t *per_adv_count)
{
int ret;
size_t per_adv_buf_cnt = 0;
ret = broadcast_source_per_adv_populate(big_index, pre_adv_data, per_adv_buf,
per_adv_buf_size - per_adv_buf_cnt);
if (ret < 0) {
LOG_ERR("Failed to add per adv data for broadcast source: %d", ret);
return ret;
}
per_adv_buf_cnt += ret;
LOG_DBG("Size of per adv data: %d, num_elements: %d",
sizeof(struct bt_data) * per_adv_buf_cnt, per_adv_buf_cnt);
*per_adv_count = per_adv_buf_cnt;
return 0;
}
uint8_t stream_state_get(void)
{
return strm_state;
}
void streamctrl_send(void const *const data, size_t size, uint8_t num_ch)
{
int ret;
static int prev_ret;
struct le_audio_encoded_audio enc_audio = {.data = data, .size = size, .num_ch = num_ch};
if (strm_state == STATE_STREAMING) {
ret = broadcast_source_send(0, 0, enc_audio);
if (ret != 0 && ret != prev_ret) {
if (ret == -ECANCELED) {
LOG_WRN("Sending operation cancelled");
} else {
LOG_WRN("Problem with sending LE audio data, ret: %d", ret);
}
}
prev_ret = ret;
}
}
#if CONFIG_CUSTOM_BROADCASTER
/* Example of how to create a custom broadcaster */
/**
* Remember to increase:
* CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT
* CONFIG_BT_CTLR_ADV_ISO_STREAM_COUNT (set in hci_ipc.conf)
* CONFIG_BT_ISO_TX_BUF_COUNT
* CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT
* CONFIG_BT_ISO_MAX_CHAN
*/
#error Feature is incomplete and should only be used as a guideline for now
static struct bt_bap_lc3_preset lc3_preset_48 = BT_BAP_LC3_BROADCAST_PRESET_48_4_1(
BT_AUDIO_LOCATION_FRONT_LEFT | BT_AUDIO_LOCATION_FRONT_RIGHT, BT_AUDIO_CONTEXT_TYPE_MEDIA);
static void broadcast_create(struct broadcast_source_big *broadcast_param)
{
static enum bt_audio_location location[2] = {BT_AUDIO_LOCATION_FRONT_LEFT,
BT_AUDIO_LOCATION_FRONT_RIGHT};
static struct subgroup_config subgroups[2];
subgroups[0].group_lc3_preset = lc3_preset_48;
subgroups[0].num_bises = 2;
subgroups[0].context = BT_AUDIO_CONTEXT_TYPE_MEDIA;
subgroups[0].location = location;
subgroups[1].group_lc3_preset = lc3_preset_48;
subgroups[1].num_bises = 2;
subgroups[1].context = BT_AUDIO_CONTEXT_TYPE_MEDIA;
subgroups[1].location = location;
broadcast_param->packing = BT_ISO_PACKING_INTERLEAVED;
broadcast_param->encryption = false;
bt_audio_codec_cfg_meta_set_bcast_audio_immediate_rend_flag(
&subgroups[0].group_lc3_preset.codec_cfg);
bt_audio_codec_cfg_meta_set_bcast_audio_immediate_rend_flag(
&subgroups[1].group_lc3_preset.codec_cfg);
uint8_t spanish_src[3] = "spa";
uint8_t english_src[3] = "eng";
bt_audio_codec_cfg_meta_set_stream_lang(&subgroups[0].group_lc3_preset.codec_cfg,
(uint32_t)sys_get_le24(english_src));
bt_audio_codec_cfg_meta_set_stream_lang(&subgroups[1].group_lc3_preset.codec_cfg,
(uint32_t)sys_get_le24(spanish_src));
broadcast_param->subgroups = subgroups;
broadcast_param->num_subgroups = 2;
}
#endif /* CONFIG_CUSTOM_BROADCASTER */
int main(void)
{
int ret;
static struct broadcast_source_big broadcast_param;
LOG_DBG("Main started");
size_t ext_adv_buf_cnt = 0;
size_t per_adv_buf_cnt = 0;
ret = nrf5340_audio_dk_init();
ERR_CHK(ret);
ret = fw_info_app_print();
ERR_CHK(ret);
ret = bt_mgmt_init();
ERR_CHK(ret);
ret = audio_system_init();
ERR_CHK(ret);
ret = zbus_subscribers_create();
ERR_CHK_MSG(ret, "Failed to create zbus subscriber threads");
ret = zbus_link_producers_observers();
ERR_CHK_MSG(ret, "Failed to link zbus producers and observers");
broadcast_source_default_create(&broadcast_param);
/* Only one BIG supported at the moment */
ret = broadcast_source_enable(&broadcast_param, 0);
ERR_CHK_MSG(ret, "Failed to enable broadcaster(s)");
ret = audio_system_config_set(
bt_audio_codec_cfg_freq_to_freq_hz(CONFIG_BT_AUDIO_PREF_SAMPLE_RATE_VALUE),
CONFIG_BT_AUDIO_BITRATE_BROADCAST_SRC, VALUE_NOT_SET);
ERR_CHK_MSG(ret, "Failed to set sample- and bitrate");
/* Get advertising set for BIG0 */
ret = ext_adv_populate(0, &ext_adv_data[0], ext_adv_buf[0], ARRAY_SIZE(ext_adv_buf[0]),
&ext_adv_buf_cnt);
ERR_CHK(ret);
ret = per_adv_populate(0, &per_adv_data[0], &per_adv_buf[0], 1, &per_adv_buf_cnt);
ERR_CHK(ret);
/* Start broadcaster */
ret = bt_mgmt_adv_start(0, ext_adv_buf[0], ext_adv_buf_cnt, &per_adv_buf[0],
per_adv_buf_cnt, false);
ERR_CHK_MSG(ret, "Failed to start first advertiser");
LOG_INF("Broadcast source: %s started", CONFIG_BT_AUDIO_BROADCAST_NAME);
return 0;
}

View File

@@ -0,0 +1,28 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_TRANSPORT_BIS=y
CONFIG_AUDIO_DEV=2
CONFIG_BT_CAP_INITIATOR=y
CONFIG_BT_AUDIO=y
CONFIG_BT_DEVICE_APPEARANCE=2181
CONFIG_BT_ISO_BROADCASTER=y
CONFIG_BT_BAP_BROADCAST_SOURCE=y
CONFIG_BT_ISO_TX_BUF_COUNT=2
CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT=2
CONFIG_BT_ISO_MAX_CHAN=2
CONFIG_BT_ISO_MAX_BIG=2
CONFIG_LC3_ENC_CHAN_MAX=2
CONFIG_ENTROPY_GENERATOR=y

View File

@@ -0,0 +1,82 @@
.. _nrf53_audio_app_adapting:
Adapting nRF5340 Audio applications for end products
####################################################
.. contents::
:local:
:depth: 2
This page describes the relevant configuration sources and lists the steps required for adapting the :ref:`nrf53_audio_app` to end products.
Board configuration sources
***************************
The nRF5340 Audio applications use the following files as board configuration sources:
* Devicetree Specification (DTS) files - These reflect the hardware configuration.
See :ref:`zephyr:dt-guide` for more information about the DTS data structure.
* Kconfig files - These reflect the hardware-related software configuration.
See :ref:`kconfig_tips_and_tricks` for information about how to configure them.
* Memory layout configuration files - These define the memory layout of the application.
You can see the :file:`zephyr/boards/nordic/nrf5340_audio_dk` directory as an example of how these files are structured.
For information about differences between DTS and Kconfig, see :ref:`zephyr:dt_vs_kconfig`.
For detailed instructions for adding Zephyr support to a custom board, see Zephyr's :ref:`zephyr:board_porting_guide`.
.. _nrf53_audio_app_porting_guide_app_configuration:
Application configuration sources
*********************************
The application configuration source file defines a set of options used by the given nRF5340 Audio application.
This is a :file:`.conf` file that modifies the default Kconfig values defined in the Kconfig files.
Only one :file:`.conf` file is included at a time.
The :file:`prj.conf` file is the default configuration file and it implements the debug application version.
For the release application version, you need to include the :file:`prj_release.conf` configuration file.
In the release application version no debug features should be enabled.
Each nRF5340 Audio application also uses its own :file:`Kconfig.default` file to change configuration defaults automatically.
You need to edit :file:`prj.conf` and :file:`prj_release.conf` if you want to add new functionalities to your application, but editing these files when adding a new board is not required.
.. _nrf53_audio_app_porting_guide_adding_board:
Adding a new board
******************
.. note::
The first three steps of the configuration procedure are identical to the steps described in Zephyr's :ref:`zephyr:board_porting_guide`.
To use the nRF5340 Audio application with your custom board:
1. Define the board files for your custom board:
a. Create a new directory in the :file:`nrf/boards/<vendor>/` directory with the name of the new board.
#. Copy the nRF5340 Audio board files from the :file:`nrf5340_audio_dk` directory located in the :file:`zephyr/boards/nordic/` folder to the newly created directory.
#. Edit the DTS files to make sure they match the hardware configuration.
Pay attention to the following elements:
* Pins that are used.
* Interrupt priority that might be different.
#. Edit the board's Kconfig files to make sure they match the required system configuration.
For example, disable the drivers that will not be used by your device.
#. Build the application by selecting the name of the new board (for example, ``new_audio_board_name``) in your build system.
For example, when building from the command line, add ``-b new_audio_board_name`` to your build command.
FOTA for end products
*********************
Do not use the default MCUboot key for end products.
See :ref:`ug_fw_update` and :ref:`west-sign` for more information.
To create your own app that supports DFU, you can use the `nRF Connect Device Manager`_ libraries for Android and iOS.
Changing default values
***********************
Given the requirements for the Coordinated Set Identification Service (CSIS), make sure to change the Set Identity Resolving Key (SIRK) value when adapting the application.

327
doc/building.rst Normal file
View File

@@ -0,0 +1,327 @@
.. _nrf53_audio_app_building:
Building and running nRF5340 Audio applications
###############################################
.. contents::
:local:
:depth: 2
This nRF5340 Audio application source files can be found in their respective folders under :file:`applications/nrf5340_audio` in the nRF Connect SDK folder structure.
You can build and program the applications in one of the following ways:
* :ref:`nrf53_audio_app_building_script` - This is the suggested method.
Using this method allows you to build and program multiple development kits at the same time.
* :ref:`nrf53_audio_app_building_standard` - Using this method requires building and programming each development kit separately.
.. important::
Building and programming using the |nRFVSC| is currently not supported.
.. note::
You might want to check the :ref:`nRF5340 Audio application known issues <known_issues_nrf5340audio>` before building and programming the applications.
.. _nrf53_audio_app_dk_testing_out_of_the_box:
Testing out of the box
**********************
Each development kit comes preprogrammed with basic firmware that indicates if the kit is functional.
Before building the application, you can verify if the kit is working by completing the following steps:
1. Plug the device into the USB port.
#. Turn on the development kit using the On/Off switch.
#. Observe **RGB** (bottom side LEDs around the center opening that illuminate the Nordic Semiconductor logo) turn solid yellow, **OB/EXT** turn solid green, and **LED3** start blinking green.
You can now program the development kit.
.. _nrf53_audio_app_building_script:
Building and programming using script
*************************************
The suggested method for building each of the applications and programming it to the development kit is running the :file:`buildprog.py` Python script.
The script automates the process of selecting :ref:`configuration files <nrf53_audio_app_configuration_files>` and building different applications.
This eases the process of building and programming images for multiple development kits.
The script is located in the :file:`applications/nrf5340_audio/tools/buildprog` directory.
.. note::
The :file:`buildprog.py` script is an app-specific script for building and programming multiple kits and cores with various audio application configurations. The script will be deprecated in a future release. The audio applications will gradually shift only to using standard tools for building and programming development kits.
Preparing the JSON file
=======================
The script depends on the settings defined in the :file:`nrf5340_audio_dk_devices.json` file.
Before using the script, make sure to update this file with the following information for each development kit you want to use.
This is how the file looks by default:
.. literalinclude:: ../tools/buildprog/nrf5340_audio_dk_devices.json
:language: json
When preparing the JSON file, update the following fields:
* ``nrf5340_audio_dk_snr`` - This field lists the SEGGER serial number.
You can check this ten-digit number on the sticker on the nRF5340 Audio development kit.
Alternatively, connect the development kit to your PC and run ``nrfutil device list`` in a command window to print the SEGGER serial number of all connected kits.
* ``nrf5340_audio_dk_dev`` - This field assigns the specific nRF5340 Audio development kit to be ``headset`` or ``gateway``.
* ``channel`` - This field is valid only for headsets.
It sets the channels on which the headset is meant to work.
When no channel is set, the headset is programmed as a left channel one.
.. _nrf53_audio_app_building_script_running:
Running the script
==================
The script handles building and parallel programming of multiple kits.
The following sections explain these two steps separately.
Script parameters for building
------------------------------
After editing the :file:`nrf5340_audio_dk_devices.json` file, run :file:`buildprog.py` to build the firmware for the development kits.
The building command for running the script requires providing the following parameters:
.. list-table:: Parameters for the script
:header-rows: 1
* - Parameter
- Description
- Options
- More information
* - Core type (``-c``)
- Specifies the core type.
- ``app``, ``net``, ``both``
- :ref:`nrf53_audio_app_overview_architecture`
* - Application version (``-b``)
- Specifies the application version.
- ``release``, ``debug``
- | :ref:`nrf53_audio_app_configuration_files`
| **Note:** For FOTA DFU, you must use :ref:`nrf53_audio_app_building_standard`.
* - Transport type (``-t``)
- Specifies the transport type.
- ``broadcast``, ``unicast``
- :ref:`nrf53_audio_app_overview_architecture`
* - Device type (``-d``)
- Specifies the device type.
- ``headset``, ``gateway``, ``both``
- :ref:`nrf53_audio_app_overview_gateway_headsets`
For example, the following command builds headset and gateway applications using the script for the application core with the ``debug`` application version:
.. code-block:: console
python buildprog.py -c app -b debug -d both -t unicast
The command can be run from any location, as long as the correct path to :file:`buildprog.py` is given.
The build files are saved in separate subdirectories in the :file:`applications/nrf5340_audio/tools/build` directory.
The script creates a directory for each transport, device type, core, and version combination.
For example, when running the command above, the script creates the :file:`unicast/gateway/app/debug`, :file:`unicast/gateway/net/debug`, :file:`unicast/headset/app/debug`, :file:`unicast/headset/net/debug` files and directories.
Script parameters for programming
---------------------------------
The script can program the build files as part of the same `python buildprog.py` command used for building.
Use one of the following programming parameters:
* Programming (``-p`` parameter) - If you run the ``buildprog`` script with this parameter, you can program one or both of the cores after building the files.
* Sequential programming (``-s`` parameter) - If you encounter problems while programming, include this parameter alongside other parameters to program sequentially.
.. note::
The development kits are programmed according to the serial numbers set in the JSON file.
Make sure to connect the development kits to your PC using USB and turn them on using the **POWER** switch before you run the script with the programming parameter.
The command for programming can look as follows:
.. code-block:: console
python buildprog.py -c both -b debug -d both -t unicast -p
This command builds the unicast headset and the gateway applications with ``debug`` version of both the application core binary and the network core binary - and programs each to its respective core.
If you want to rebuild from scratch, you can add the ``--pristine`` parameter to the command (west's ``-p`` for cannot be used for a pristine build with the script).
.. note::
If the programming command fails because of a :ref:`readback protection error <readback_protection_error>`, run :file:`buildprog.py` with the ``--recover_on_fail`` or ``-f`` parameter to recover and re-program automatically when programming fails.
For example, using the programming command example above:
.. code-block:: console
python buildprog.py -c both -b debug -d both -t unicast -p --recover_on_fail
Getting help
------------
Run ``python buildprog.py -h`` for information about all available script parameters.
Configuration table overview
----------------------------
When running the script command, a table similar to the following one is displayed to provide an overview of the selected options and parameter values:
.. code-block:: console
+------------+----------+---------+--------------+---------------------+---------------------+
| snr | snr conn | device | only reboot | core app programmed | core net programmed |
+------------+----------+---------+--------------+---------------------+---------------------+
| 1010101010 | True | headset | Not selected | Selected TBD | Not selected |
| 2020202020 | True | gateway | Not selected | Selected TBD | Not selected |
| 3030303030 | True | headset | Not selected | Selected TBD | Not selected |
+------------+----------+---------+--------------+---------------------+---------------------+
See the following table for the meaning of each column and the list of possible values:
+-----------------------+-----------------------------------------------------------------------------------------------------+-------------------------------------------------+
| Column | Indication | Possible values |
+=======================+=====================================================================================================+=================================================+
| ``snr`` | Serial number of the device, as provided in the :file:`nrf5340_audio_dk_devices.json` file. | Serial number. |
+-----------------------+-----------------------------------------------------------------------------------------------------+-------------------------------------------------+
| ``snr conn`` | Whether the device with the provided serial number is connected to the PC with a serial connection. | ``True`` - Connected. |
| | +-------------------------------------------------+
| | | ``False`` - Not connected. |
+-----------------------+-----------------------------------------------------------------------------------------------------+-------------------------------------------------+
| ``device`` | Device type, as provided in the :file:`nrf5340_audio_dk_devices.json` file. | ``headset`` - Headset. |
| | +-------------------------------------------------+
| | | ``gateway`` - Gateway. |
+-----------------------+-----------------------------------------------------------------------------------------------------+-------------------------------------------------+
| ``only reboot`` | Whether the device is to be only reset and not programmed. | ``Not selected`` - No reset. |
| | This depends on the ``-r`` parameter in the command, which overrides other parameters. +-------------------------------------------------+
| | | ``Selected TBD`` - Only reset requested. |
| | +-------------------------------------------------+
| | | ``Done`` - Reset done. |
| | +-------------------------------------------------+
| | | ``Failed`` - Reset failed. |
+-----------------------+-----------------------------------------------------------------------------------------------------+-------------------------------------------------+
|``core app programmed``| Whether the application core is to be programmed. | ``Not selected`` - Core will not be programmed. |
| | This depends on the value provided to the ``-c`` parameter (see above). +-------------------------------------------------+
| | | ``Selected TBD`` - Programming requested. |
| | +-------------------------------------------------+
| | | ``Done`` - Programming done. |
| | +-------------------------------------------------+
| | | ``Failed`` - Programming failed. |
+-----------------------+-----------------------------------------------------------------------------------------------------+-------------------------------------------------+
|``core net programmed``| Whether the network core is to be programmed. | ``Not selected`` - Core will not be programmed. |
| | This depends on the value provided to the ``-c`` parameter (see above). +-------------------------------------------------+
| | | ``Selected TBD`` - Programming requested. |
| | +-------------------------------------------------+
| | | ``Done`` - Programming done. |
| | +-------------------------------------------------+
| | | ``Failed`` - Programming failed. |
+-----------------------+-----------------------------------------------------------------------------------------------------+-------------------------------------------------+
.. _nrf53_audio_app_building_standard:
Building and programming using command line
*******************************************
You can also build the nRF5340 Audio applications using the standard |NCS| :ref:`build steps <programming_cmd>` for the command line.
.. _nrf53_audio_app_building_config_files:
Application configuration files
===============================
The application uses a :file:`prj.conf` configuration file located in the sample root directory for the default configuration.
It also provides additional files for different custom configurations.
When you build the sample, you can select one of these configurations using the :makevar:`FILE_SUFFIX` variable.
See :ref:`app_build_file_suffixes` and :ref:`cmake_options` for more information.
The application supports the following custom configurations:
.. list-table:: Application custom configurations
:widths: auto
:header-rows: 1
* - Configuration
- File name
- FILE_SUFFIX
- Description
* - Debug (default)
- :file:`prj.conf`
- No suffix
- Debug version of the application. Provides full logging capabilities and debug optimizations to ease development.
* - Release
- :file:`prj_release.conf`
- ``release``
- Release version of the application. Disables logging capabilities and disables development features to create a smaller application binary.
* - FOTA DFU
- :file:`prj_fota.conf`
- ``fota``
- | Builds the debug version of the application with the features needed to perform DFU over Bluetooth LE, and includes bootloaders so that the applications on both the application core and network core can be updated.
| See :ref:`nrf53_audio_app_fota` for more information.
.. _nrf53_audio_app_configuration_select_build:
Building the application
========================
Complete the following steps to build the application:
1. Choose the combination of build flags:
a. Choose the device type by using one of the following :ref:`CMake options for extra Kconfig fragments <cmake_options>`:
* For unicast headset: ``-DEXTRA_CONF_FILE=".\unicast_server\overlay-unicast_server.conf"``
* For unicast gateway: ``-DEXTRA_CONF_FILE=".\unicast_client\overlay-unicast_client.conf"``
* For broadcast headset: ``-DEXTRA_CONF_FILE=".\broadcast_sink\overlay-broadcast_sink.conf"``
* For broadcast gateway: ``-DEXTRA_CONF_FILE=".\broadcast_source\overlay-broadcast_source.conf"``
#. Choose the application version (:ref:`nrf53_audio_app_building_config_files`) by using one of the following options:
* For the debug version: No build flag needed.
* For the release version: ``-DFILE_SUFFIX=release``
#. Build the application using the standard :ref:`build steps <building>` for the command line.
For example, if you want to build the firmware for the application core as a headset using the ``release`` application version, you can run the following command from the :file:`applications/nrf5340_audio/` directory:
.. code-block:: console
west build -b nrf5340_audio_dk/nrf5340/cpuapp --pristine -- -DEXTRA_CONF_FILE=".\unicast_server\overlay-unicast_server.conf" -DFILE_SUFFIX=release
This command creates the build files for headset device directly in the :file:`build` directory.
What this means is that you cannot create build files for all devices you want to program, because the subsequent commands will overwrite the files in the :file:`build` directory.
To work around this standard west behavior, you can add the ``-d`` parameter to the ``west`` command to specify a custom build folder for each device.
This way, you can build firmware for headset and gateway to separate directories before programming the development kits.
Alternatively, you can use the :ref:`nrf53_audio_app_building_script`, which handles this automatically.
Building the application for FOTA
---------------------------------
The following command example builds the application for :ref:`nrf53_audio_app_fota`:
.. code-block:: console
west build -b nrf5340_audio_dk/nrf5340/cpuapp --pristine -- -DEXTRA_CONF_FILE=".\unicast_server\overlay-unicast_server.conf" -DFILE_SUFFIX=fota
The command uses ``-DFILE_SUFFIX=fota`` to pick :file:`prj_fota.conf` instead of the default :file:`prj.conf`.
It also uses the ``--pristine`` to clean the existing directory before starting the build process.
Programming the application
===========================
After building the files for the development kit you want to program, follow the :ref:`standard procedure for programming applications <building>` in the |NCS|.
When using the default CIS configuration, if you want to use two headset devices, you must also populate the UICR with the desired channel for each headset.
Use the following commands, depending on which headset you want to populate:
* Left headset (``--value 0``):
.. code-block:: console
nrfutil device x-write --address 0x00FF80F4 --value 0
* Right headset (``--value 1``):
.. code-block:: console
nrfutil device x-write --address 0x00FF80F4 --value 1
Select the correct board when prompted with the popup.
Alternatively, you can add the ``--serial-number`` parameter followed by the SEGGER serial number of the correct board at the end of the ``nrfutil device`` command.
You can check the serial numbers of the connected devices with the ``nrfutil device list`` command.
.. note::
|usb_known_issues|

87
doc/configuration.rst Normal file
View File

@@ -0,0 +1,87 @@
.. _nrf53_audio_app_configuration:
Configuring the nRF5340 Audio applications
##########################################
.. contents::
:local:
:depth: 2
|config|
.. _nrf53_audio_app_configuration_select_bidirectional:
Selecting the CIS bidirectional communication
*********************************************
To switch to the bidirectional mode, set the ``CONFIG_STREAM_BIDIRECTIONAL`` Kconfig option to ``y`` in the :file:`applications/nrf5340_audio/prj.conf` file (for the debug version) or in the :file:`applications/nrf5340_audio/prj_release.conf` file (for the release version).
.. _nrf53_audio_app_configuration_enable_walkie_talkie:
Enabling the walkie-talkie demo
===============================
The walkie-talkie demo uses one or two bidirectional streams from the gateway to one or two headsets.
The PDM microphone is used as input on both the gateway and headset device.
To switch to using the walkie-talkie, set the ``CONFIG_WALKIE_TALKIE_DEMO`` Kconfig option to ``y`` in the :file:`applications/nrf5340_audio/prj.conf` file (for the debug version) or in the :file:`applications/nrf5340_audio/prj_release.conf` file (for the release version).
Enabling the Auracast™ (broadcast) mode
=======================================
If you want to work with `Auracast™`_ (broadcast) sources and sinks, set the :kconfig:option:`CONFIG_TRANSPORT_BIS` Kconfig option to ``y`` in the :file:`applications/nrf5340_audio/prj.conf` file.
.. _nrf53_audio_app_configuration_select_bis_two_gateways:
Enabling the BIS mode with two gateways
***************************************
In addition to the standard BIS mode with one gateway, you can also add a second gateway device.
The BIS headsets can then switch between the two gateways and receive audio stream from one of the two gateways.
To configure the second gateway, add both the ``CONFIG_TRANSPORT_BIS`` and the ``CONFIG_BT_AUDIO_USE_BROADCAST_NAME_ALT`` Kconfig options set to ``y`` to the :file:`applications/nrf5340_audio/prj.conf` file for the debug version and to the :file:`applications/nrf5340_audio/prj_release.conf` file for the release version.
You can provide an alternative name to the second gateway using the ``CONFIG_BT_AUDIO_BROADCAST_NAME_ALT`` or use the default alternative name.
You build each BIS gateway separately using the normal procedures from :ref:`nrf53_audio_app_building`.
After building the first gateway, configure the required Kconfig options for the second gateway and build the second gateway firmware.
Remember to program the two firmware versions to two separate gateway devices.
.. _nrf53_audio_app_configuration_select_i2s:
Selecting the analog jack input using I2S
*****************************************
In the default configuration, the gateway application uses USB as the audio source.
The :ref:`nrf53_audio_app_building` and the testing steps also refer to using the USB serial connection.
To switch to using the 3.5 mm jack analog input, set the ``CONFIG_AUDIO_SOURCE_I2S`` Kconfig option to ``y`` in the :file:`applications/nrf5340_audio/prj.conf` file for the debug version and in the :file:`applications/nrf5340_audio/prj_release.conf` file for the release version.
When testing the application, an additional audio jack cable is required to use I2S.
Use this cable to connect the audio source (PC) to the analog **LINE IN** on the development kit.
.. _nrf53_audio_app_adding_FEM_support:
Adding FEM support
******************
You can add support for the nRF21540 front-end module (FEM) to the following nRF5340 Audio applications:
* :ref:`Broadcast source <nrf53_audio_broadcast_source_app>`
* :ref:`Unicast client <nrf53_audio_unicast_client_app>`
* :ref:`Unicast server <nrf53_audio_unicast_server_app>`
The :ref:`broadcast sink application <nrf53_audio_broadcast_sink_app>` does not need FEM support as it only receives data.
Adding FEM support happens when :ref:`nrf53_audio_app_building`.
You can use one of the following options, depending on how you decide to build the application:
* If you opt for :ref:`nrf53_audio_app_building_script`, add the ``--nrf21540`` to the script's building command.
* If you opt for :ref:`nrf53_audio_app_building_standard`, add the ``-Dnrf5340_audio_SHIELD=nrf21540ek -Dipc_radio_SHIELD=nrf21540ek`` to the ``west build`` command.
For example:
.. code-block:: console
west build -b nrf5340_audio_dk/nrf5340/cpuapp --pristine -- -DEXTRA_CONF_FILE=".\unicast_server\overlay-unicast_server.conf" -Dnrf5340_audio_SHIELD=nrf21540ek -Dipc_radio_SHIELD=nrf21540ek
To set the TX power output, use the ``CONFIG_BT_CTLR_TX_PWR_ANTENNA`` and ``CONFIG_MPSL_FEM_NRF21540_TX_GAIN_DB`` Kconfig options in :file:`applications/nrf5340_audio/sysbuild/ipc_radio/prj.conf`.
See :ref:`ug_radio_fem` for more information about FEM in the |NCS|.

44
doc/feature_support.rst Normal file
View File

@@ -0,0 +1,44 @@
.. _nrf53_audio_app_dk_legal:
.. _nrf53_audio_feature_support:
nRF5340 Audio feature support and QDIDs
#######################################
.. contents::
:local:
:depth: 2
The following table lists features of the nRF5340 Audio application and their respective limitations and maturity level.
For an explanation of the maturity levels, see :ref:`Software maturity levels <software_maturity>`.
.. note::
Features not listed are not supported.
.. include:: /releases_and_maturity/software_maturity.rst
:start-after: software_maturity_application_nrf5340audio_table:
:end-before: software_maturity_protocol
.. _nrf5340_audio_dns_and_qdids:
nRF5340 Audio DNs and QDIDs
***************************
The following DNs (Design Numbers) and QDIDs (Qualified Design IDs) are related to the nRF5340 LE Audio applications:
nRF5340 DK Bluetooth DNs/QDIDs
See `nRF5340 DK Bluetooth DNs and QDIDs Compatibility Matrix`_ for the DNs/QDIDs for nRF5340 LE Audio applications.
A full Audio product DN will typically require DNs/QDIDs for Controller component, Host component, Profiles and Services component and LC3 codec component.
The exact DN/QDID numbers depend on the project configuration and the features used in the application.
.. note::
* The DNs/QDIDs listed in the Compatibility Matrix might not cover all use cases or combinations.
The full details of what is supported by a DN/QDID can be found in the associated ICS (Implementation Conformance Statement).
* The Audio applications do not demonstrate the full capabilities of the underlying DNs/QDIDs.
At the same time, the Audio applications may demonstrate features not available in the underlying DNs/QDID.
.. ncs-include:: lc3/README.rst
:docset: nrfxlib
:start-after: lc3_qdid_start
:end-before: lc3_qdid_end

View File

@@ -0,0 +1,290 @@
.. _nrf53_audio_app_overview:
nRF5340 Audio overview and firmware architecture
################################################
.. contents::
:local:
:depth: 2
Each nRF5340 Audio application corresponds to one specific LE Audio role: unicast client (gateway), unicast server (headset), broadcast source (gateway), or broadcast sink (headset).
Likewise, each nRF5340 Audio application is configured for one specific LE Audio mode: the *connected isochronous stream* (CIS, unicast) mode or in the *broadcast isochronous stream* (BIS) mode.
See :ref:`nrf53_audio_app_overview_modes` for more information.
The applications use the same code base, but use different :file:`main.c` files and include different modules and libraries depending on the configuration.
You might need to configure and program two applications for testing the interoperability, depending on your use case.
See the testing steps for each of the application for more information.
.. _nrf53_audio_app_overview_gateway_headsets:
Gateway and headset roles
*************************
The gateway is a common term for a base device, such as the unicast client or an `Auracast™`_ (broadcast) source, often used with USB or analog jack input.
Often, but not always, the gateway is the largest or most stationary device, and is commonly the Bluetooth Central (if applicable).
The headset is a common term for a receiver device that plays back the audio it gets from the gateway.
Headset devices include earbuds, headphones, speakers, hearing aids, or similar.
They act as a unicast server or a broadcast sink.
With reference to the gateway, the headset is often the smallest and most portable device, and is commonly the Bluetooth Peripheral (if applicable).
You can :ref:`select gateway or headset build <nrf53_audio_app_configuration_select_build>` when :ref:`nrf53_audio_app_configuration`.
.. _nrf53_audio_app_overview_modes:
Application modes
*****************
Each application works either in the *connected isochronous stream* (CIS) mode or in the *broadcast isochronous stream* (BIS) mode.
.. figure:: /images/nrf5340_audio_application_topologies.png
:alt: CIS and BIS mode overview
CIS and BIS mode overview
Connected Isochronous Stream (CIS)
CIS is a bidirectional communication protocol that allows for sending separate connected audio streams from a source device to one or more receivers.
The gateway can send the audio data using both the left and the right ISO channels at the same time, allowing for stereophonic sound reproduction with synchronized playback.
This is the mode available for the unicast applications (:ref:`unicast client<nrf53_audio_unicast_client_app>` and :ref:`unicast server<nrf53_audio_unicast_server_app>`).
In this mode, you can use the nRF5340 Audio development kit in the role of the gateway, the left headset, or the right headset.
In the current version of the nRF5340 Audio unicast client, the application offers both unidirectional and bidirectional communication.
In the bidirectional communication, the headset device will send audio from the on-board PDM microphone.
See :ref:`nrf53_audio_app_configuration_select_bidirectional` in the application description for more information.
You can also enable a walkie-talkie demonstration.
In this demonstration, the gateway device will send audio from the on-board PDM microphone instead of using USB or the line-in.
See :ref:`nrf53_audio_app_configuration_enable_walkie_talkie` in the application description for more information.
Broadcast Isochronous Stream (BIS)
BIS is a unidirectional communication protocol that allows for broadcasting one or more audio streams from a source device to an unlimited number of receivers that are not connected to the source.
This is the mode available for the broadcast applications (:ref:`broadcast source<nrf53_audio_broadcast_source_app>` for gateway and :ref:`broadcast sink<nrf53_audio_broadcast_sink_app>` for headset).
In this mode, you can use the nRF5340 Audio development kit in the role of the gateway or as one of the headsets.
Use multiple nRF5340 Audio development kits to test BIS having multiple receiving headsets.
.. note::
In the BIS mode, you can use any number of nRF5340 Audio development kits as receivers.
The audio quality for both modes does not change, although the processing time for stereo can be longer.
.. _nrf53_audio_app_overview_architecture:
Firmware architecture
*********************
The following figure illustrates the high-level software layout for the nRF5340 Audio application:
.. figure:: /images/nrf5340_audio_structure_generic.svg
:alt: nRF5340 Audio high-level design (overview)
nRF5340 Audio high-level design (overview)
The network core of the nRF5340 SoC runs the SoftDevice Controller, which is responsible for receiving the audio stream data from hardware layers and forwarding the data to the Bluetooth LE host on the application core.
The controller implements the lower layers of the Bluetooth Low Energy software stack.
See :ref:`ug_ble_controller_softdevice` for more information about the controller, and :ref:`SoftDevice Controller for LE Isochronous Channels <nrfxlib:softdevice_controller_iso>` for information on how it implements ISO channels used by the nRF5340 Audio applications.
The application core runs both the Bluetooth LE Host from Zephyr and the application layer.
The application layer is composed of a series of modules from different sources.
These modules include the following major ones:
* Peripheral modules from the |NCS|:
* I2S
* USB
* SPI
* TWI/I2C
* UART (debug)
* Timer
* LC3 encoder/decoder
* Application-specific Bluetooth modules for handling the Bluetooth connection:
* Management - This module handles scanning and advertising, in addition to general initialization, controller configuration, and transfer of DFU images.
* Stream - This module handles the setup and transfer of audio in the Bluetooth LE Audio context.
It includes submodules for CIS (unicast) and BIS (broadcast).
* Renderer - This module handles rendering, such as volume up and down.
* Content Control - This module handles content control, such as play and pause.
* Application-specific custom modules, including the synchronization module (part of `I2S-based firmware for gateway and headsets`_) - See `Synchronization module overview`_ for more information.
Since the application architecture is the same for all applications and the code before compilation is shared to a significant degree, the set of modules in use depends on the chosen audio inputs and outputs (USB or analog jack).
.. note::
In the current versions of the applications, the bootloader is disabled by default.
Device Firmware Update (DFU) can only be enabled when :ref:`nrf53_audio_app_building_script`.
See :ref:`nrf53_audio_app_configuration_configure_fota` for details.
.. _nrf53_audio_app_overview_files:
Source file architecture
========================
The following figure illustrates the software layout for the nRF5340 Audio application on the file-by-file level, regardless of the application chosen:
.. figure:: /images/nrf5340audio_all_packages.svg
:alt: nRF5340 Audio application file-level breakdown
nRF5340 Audio application file-level breakdown
Communication between modules is primarily done through Zephyr's :ref:`zephyr:zbus` to make sure that there are as few dependencies as possible. Each of the buses used by the applications has their message structures described in :file:`zbus_common.h`.
.. _nrf53_audio_app_overview_architecture_usb:
USB-based firmware for gateway
==============================
The following figures show an overview of the modules currently included in the firmware of applications that use USB.
In this firmware design, no synchronization module is used after decoding the incoming frames or before encoding the outgoing ones.
The Bluetooth LE RX FIFO is mainly used to make decoding run in a separate thread.
Broadcast source USB-based firmware
-----------------------------------
.. figure:: /images/nrf5340_audio_broadcast_source_USB_structure.svg
:alt: nRF5340 Audio modules for the broadcast source using USB
nRF5340 Audio modules for the broadcast source using USB
Unicast client USB-based firmware
---------------------------------
.. figure:: /images/nrf5340_audio_unicast_client_USB_structure.svg
:alt: nRF5340 Audio modules for the unicast client using USB
nRF5340 Audio modules for the unicast client using USB
.. _nrf53_audio_app_overview_architecture_i2s:
I2S-based firmware for gateway and headsets
===========================================
The following figure shows an overview of the modules currently included in the firmware of applications that use I2S.
The Bluetooth LE RX FIFO is mainly used to make :file:`audio_datapath.c` (synchronization module) run in a separate thread.
Broadcast source I2S-based firmware
-----------------------------------
.. figure:: /images/nrf5340_audio_broadcast_source_I2S_structure.svg
:alt: nRF5340 Audio modules for the broadcast source using I2S
nRF5340 Audio modules for the broadcast source using I2S
Broadcast sink I2S-based firmware
---------------------------------
.. figure:: /images/nrf5340_audio_broadcast_sink_I2S_structure.svg
:alt: nRF5340 Audio modules for the broadcast sink using I2S
nRF5340 Audio modules for the broadcast sink using I2S
Unicast client I2S-based firmware
---------------------------------
.. figure:: /images/nrf5340_audio_unicast_client_I2S_structure.svg
:alt: nRF5340 Audio modules for the unicast client using I2S
nRF5340 Audio modules for the unicast client using I2S
Unicast server I2S-based firmware
---------------------------------
.. figure:: /images/nrf5340_audio_unicast_server_I2S_structure.svg
:alt: nRF5340 Audio modules for the unicast server using I2S
nRF5340 Audio modules for the unicast server using I2S
.. _nrf53_audio_app_overview_architecture_sync_module:
Synchronization module overview
===============================
The synchronization module (:file:`audio_datapath.c`) handles audio synchronization.
To synchronize the audio, it executes the following types of adjustments:
* Presentation compensation
* Drift compensation
The presentation compensation makes all the headsets play audio at the same time, even if the packets containing the audio frames are not received at the same time on the different headsets.
In practice, it moves the audio data blocks in the FIFO forward or backward a few blocks, adding blocks of *silence* when needed.
The drift compensation adjusts the frequency of the audio clock to adjust the speed at which the audio is played.
This is required in the CIS mode, where the gateway and headsets must keep the audio playback synchronized to provide True Wireless Stereo (TWS) audio playback.
As such, it provides both larger adjustments at the start and then continuous small adjustments to the audio synchronization.
This compensation method counters any drift caused by the differences in the frequencies of the quartz crystal oscillators used in the development kits.
Development kits use quartz crystal oscillators to generate a stable clock frequency.
However, the frequency of these crystals always slightly differs.
The drift compensation makes the inter-IC sound (I2S) interface on the headsets run as fast as the Bluetooth packets reception.
This prevents I2S overruns or underruns, both in the CIS mode and the BIS mode.
See the following figure for an overview of the synchronization module.
.. figure:: /images/nrf5340_audio_structure_sync_module.svg
:alt: nRF5340 Audio synchronization module overview
nRF5340 Audio synchronization module overview
Both synchronization methods use the SDU reference timestamps (:c:type:`sdu_ref`) as the reference variable.
If the device is a gateway that is :ref:`using I2S as audio source <nrf53_audio_app_overview_architecture_i2s>` and the stream is unidirectional (gateway to headsets), :c:type:`sdu_ref` is continuously being extracted from the LE Audio Controller Subsystem for nRF53 on the gateway.
The extraction happens inside the :file:`unicast_client.c` and :file:`broadcast_source.c` files' send function.
The :c:type:`sdu_ref` values are then sent to the gateway's synchronization module, and used to do drift compensation.
.. note::
Inside the synchronization module (:file:`audio_datapath.c`), all time-related variables end with ``_us`` (for microseconds).
This means that :c:type:`sdu_ref` becomes :c:type:`sdu_ref_us` inside the module.
As the nRF5340 is a dual-core SoC, and both cores need the same concept of time, each core runs a free-running timer in an infinite loop.
These two timers are reset at the same time, and they run from the same clock source.
This means that they should always show the same values for the same points in time.
The network core of the nRF5340 running the LE controller for nRF53 uses its timer to generate the :c:type:`sdu_ref` timestamp for every audio packet received.
The application core running the nRF5340 Audio application uses its timer to generate :c:type:`cur_time` and :c:type:`frame_start_ts`.
After the decoding takes place, the audio data is divided into smaller blocks and added to a FIFO.
These blocks are then continuously being fed to I2S, block by block.
See the following figure for the details of the compensation methods of the synchronization module.
.. figure:: /images/nrf5340_audio_sync_module_states.svg
:alt: nRF5340 Audio's state machine for compensation mechanisms
nRF5340 Audio's state machine for compensation mechanisms
The following external factors can affect the presentation compensation:
* The drift compensation must be synchronized to the locked state (:c:enumerator:`DRIFT_STATE_LOCKED`) before the presentation compensation can start.
This drift compensation adjusts the frequency of the audio clock, indicating that the audio is being played at the right speed.
When the drift compensation is not in the locked state, the presentation compensation does not leave the init state (:c:enumerator:`PRES_STATE_INIT`).
Also, if the drift compensation loses synchronization, moving out of :c:enumerator:`DRIFT_STATE_LOCKED`, the presentation compensation moves back to :c:enumerator:`PRES_STATE_INIT`.
* When audio is being played, it is expected that a new audio frame is received in each ISO connection interval.
If this does not occur, the headset might have lost its connection with the gateway.
When the connection is restored, the application receives a :c:type:`sdu_ref` not consecutive with the previously received :c:type:`sdu_ref`.
Then the presentation compensation is put into :c:enumerator:`PRES_STATE_WAIT` to ensure that the audio is still in sync.
.. note::
When both the drift and presentation compensation are in state *locked* (:c:enumerator:`DRIFT_STATE_LOCKED` and :c:enumerator:`PRES_STATE_LOCKED`), **LED2** lights up.
Synchronization module flow
---------------------------
The received audio data in the I2S-based firmware devices follows the following path:
1. The SoftDevice Controller running on the network core receives the compressed audio data.
#. The controller, running in the :zephyr:code-sample:`bluetooth_hci_ipc` sample on the nRF5340 SoC network core, sends the audio data to the Zephyr Bluetooth LE host running on the nRF5340 SoC application core.
#. The host sends the data to the stream control module.
#. The data is sent to a FIFO buffer.
#. The data is sent from the FIFO buffer to the :file:`audio_datapath.c` synchronization module.
The :file:`audio_datapath.c` module performs the audio synchronization based on the SDU reference timestamps.
Each package sent from the gateway gets a unique SDU reference timestamp.
These timestamps are generated on the headset Bluetooth LE controller (in the network core).
This enables the creation of True Wireless Stereo (TWS) earbuds where the audio is synchronized in the CIS mode.
It does also keep the speed of the inter-IC sound (I2S) interface synchronized with the sending and receiving speed of Bluetooth packets.
#. The :file:`audio_datapath.c` module sends the compressed audio data to the LC3 audio decoder for decoding.
#. The audio decoder decodes the data and sends the uncompressed audio data (PCM) back to the :file:`audio_datapath.c` module.
#. The :file:`audio_datapath.c` module continuously feeds the uncompressed audio data to the hardware codec.
#. The hardware codec receives the uncompressed audio data over the inter-IC sound (I2S) interface and performs the digital-to-analog (DAC) conversion to an analog audio signal.

84
doc/fota.rst Normal file
View File

@@ -0,0 +1,84 @@
.. _nrf53_audio_app_fota:
Configuring and testing FOTA upgrades for nRF5340 Audio applications
####################################################################
.. contents::
:local:
:depth: 2
The nRF5340 Audio applications all support FOTA upgrades, and the application implementation is based on the procedure described in :ref:`ug_nrf53_developing_ble_fota`.
Requirements for FOTA
*********************
If the application is running on the nRF5340 Audio DK, you need an external flash shield to upgrade both the application and network core at the same time.
See `Requirements for external flash memory DFU`_ in the nRF5340 Audio DK Hardware documentation for more information.
.. _nrf53_audio_app_configuration_configure_fota:
Configuring FOTA upgrades
*************************
The nRF5340 Audio applications can be built with a :ref:`FOTA configuration <nrf53_audio_app_building_config_files>` that includes the required features and applications to perform firmware upgrades over Bluetooth LE.
The FOTA configuration requires that an external flash be available and that the required DTS overlay files use the external flash shield specified in the `Requirements for FOTA`_ above.
With the external flash connected, it is possible to upgrade both the application core and the network core at the same time.
See :ref:`multi-image DFU <ug_nrf5340_multi_image_dfu>` for more information about the FOTA process on the nRF5340 SoC.
.. caution::
Using the single-image upgrade strategy carries risk of the device being in a state where the application core firmware and the network core firmware are no longer compatible, which can result in a bricked device.
For devices where FOTA is the only DFU method available, multi-image upgrades are recommended to ensure compatibility between the cores.
Make sure to evaluate the risks for your device when selecting the FOTA method.
.. caution::
The application is provided with a memory partition configuration in :file:`pm_static_fota.yml`, which is required to perform FOTA using an external flash.
The partition configuration is an example that can be changed between versions, and can be incompatible with existing devices.
For this reason, always create a partition configuration that suits your application.
See :ref:`partition_manager` for more information on how to create a partition configuration.
Updating the SoftDevice
=======================
Both FOTA upgrade methods support updating the SoftDevice on the network core.
However, the current default build options for the SoftDevice create a binary that is too large to run on the network core together with a bootloader.
To reduce the size of the SoftDevice binary, you can disable unused features in the SoftDevice.
See :ref:`softdevice_controller` documentation for more information.
Entering the DFU mode
=====================
The |NCS| uses :ref:`SMP server and mcumgr <zephyr:device_mgmt>` as the DFU backend.
The SMP server service is separated from CIS and BIS services, and is only advertised when the application is in the DFU mode.
To enter the DFU mode, press **BTN 4** on the nRF5340 Audio DK during startup.
To identify the devices before the DFU takes place, the DFU mode advertising names mention the device type directly.
The names follow the pattern in which the device role is inserted between the device name and the ``_DFU`` suffix.
For example:
* Gateway: ``NRF5340_AUDIO_GW_DFU``
* Left Headset: ``NRF5340_AUDIO_HL_DFU``
* Right Headset: ``NRF5340_AUDIO_HR_DFU``
The first part of these names is based on :kconfig:option:`CONFIG_BT_DEVICE_NAME`.
.. note::
When performing DFU for the nRF5340 Audio applications, there will be one or more error prints related to opening flash area ID 1.
This is due to restrictions in the DFU system, and the error print is expected.
The DFU process should still complete successfully.
Building the FOTA configuration
*******************************
Use the :ref:`nrf53_audio_app_building_standard` procedure to build the nRF5340 Audio applications with the FOTA configuration.
Make sure to provide the :ref:`correct configuration file <nrf53_audio_app_building_config_files>` :file:`prj_fota.conf` when running the build command.
The :ref:`script-based method <nrf53_audio_app_building_script_running>` does not support building and programming the FOTA upgrades.
.. _nrf53_audio_unicast_client_app_testing_steps_fota:
Testing FOTA upgrades
*********************
To test FOTA for the nRF5340 Audio application, ensure the application is in the DFU mode, and then follow the testing steps in the FOTA over Bluetooth Low Energy section of :ref:`ug_nrf53_developing_ble_fota` (you can skip the configuration steps).

67
doc/requirements.rst Normal file
View File

@@ -0,0 +1,67 @@
.. _nrf53_audio_app_requirements:
nRF5340 Audio application requirements
######################################
.. contents::
:local:
:depth: 2
The nRF5340 Audio applications are designed to be used only with the following hardware:
.. table-from-rows:: /includes/sample_board_rows.txt
:header: heading
:rows: nrf5340_audio_dk_nrf5340
.. note::
The applications supports PCA10121 revisions 1.0.0 or above.
The applications are also compatible with the following pre-launch revisions:
* Revisions 0.8.0 and above.
You need at least two nRF5340 Audio development kits (one with the gateway firmware and one with headset firmware) to test each of the applications.
For CIS with TWS in mind, three kits are required.
If you want to test with other hardware (for example, a mobile phone or PC), it is highly recommended to test with Audio DKs on both the gateway and headset side first to verify basic functionality before moving on to testing with other vendors.
.. _nrf53_audio_app_requirements_codec:
Software codec requirements
***************************
The nRF5340 Audio applications only support the :ref:`LC3 software codec <nrfxlib:lc3>`, developed specifically for use with LE Audio.
The applications can be configured for other alternative codecs, but this integration is beyond the scope of this documentation.
.. _nrf53_audio_app_dk:
.. _nrf53_audio_app_dk_features:
nRF5340 Audio development kit
*****************************
The nRF5340 Audio development kit is a hardware development platform that demonstrates the nRF5340 Audio applications.
Read the `nRF5340 Audio DK Hardware`_ documentation for more information about this development kit.
You can :ref:`test the DK out of the box <nrf53_audio_app_dk_testing_out_of_the_box>` before you program it.
.. _nrf53_audio_app_configuration_files:
nRF5340 Audio configuration files
*********************************
All applications use the :file:`Kconfig.defaults` located in the :file:`nrf5340_audio` directory.
Additionally, each nRF5340 Audio application uses its own, application-specific :file:`Kconfig.defaults` file from the application directory, which includes configuration specific to the given application.
These files change the configuration defaults automatically, based on the different application versions and device types.
For each application, only one of the following :file:`.conf` files is included when building:
* :file:`prj.conf` is the default configuration file and it implements the debug application version.
* :file:`prj_release.conf` is the optional configuration file and it implements the release application version.
No debug features are enabled in the release application version.
When building using the command line, you must explicitly specify if :file:`prj_release.conf` is going to be included instead of :file:`prj.conf`.
See :ref:`nrf53_audio_app_building` for details.
* :file:`prj_fota.conf` is the optional configuration file used for FOTA DFU.
When used, the build system builds the debug version of the application (:file:`prj.conf`), but with the features needed to perform DFU over Bluetooth LE.
It also includes bootloaders so that the applications on both the application core and network core can be updated.
When building using the command line, you must explicitly specify if :file:`prj_fota.conf` is going to be included instead of :file:`prj.conf`.
See :ref:`nrf53_audio_app_fota` for more information.

160
doc/user_interface.rst Normal file
View File

@@ -0,0 +1,160 @@
.. _nrf53_audio_app_ui:
User interface
##############
.. contents::
:local:
:depth: 2
All nRF5340 Audio applications implement the same, simple user interface based on the available PCB elements of the nRF5340 Audio development kit.
You can control the application using predefined switches and buttons while the LEDs display information.
Some user interface options are only valid for some nRF5340 Audio applications.
.. _nrf53_audio_app_ui_switches:
Switches
********
The application uses the following switches on the supported development kit:
+-------------------+-------------------------------------------------------------------------------------+---------------------------------------+
| Switch | Function | Applications |
+===================+=====================================================================================+=======================================+
| **POWER** | Turns the development kit on or off. | All |
+-------------------+-------------------------------------------------------------------------------------+---------------------------------------+
| **DEBUG ENABLE** | Turns on or off power for debug features. | All |
| | This switch is used for accurate power and current measurements. | |
+-------------------+-------------------------------------------------------------------------------------+---------------------------------------+
.. _nrf53_audio_app_ui_buttons:
Buttons
*******
The application uses the following buttons on the supported development kit:
+---------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| Button | Function | Applications |
+===============+===========================================================================================================+=============================================+
| **VOL-** | Long-pressed during startup: Changes the headset to the left channel one. | * :ref:`nrf53_audio_broadcast_sink_app` |
| | | * :ref:`nrf53_audio_unicast_server_app` |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Pressed on the headset or the CIS gateway during playback: Turns the playback volume down. | * :ref:`nrf53_audio_broadcast_sink_app` |
| | | * :ref:`nrf53_audio_unicast_server_app` |
| | | * :ref:`nrf53_audio_unicast_client_app` |
+---------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **VOL+** | Long-pressed during startup: Changes the headset to the right channel one. | * :ref:`nrf53_audio_broadcast_sink_app` |
| | | * :ref:`nrf53_audio_unicast_server_app` |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Pressed on the headset or the CIS gateway during playback: Turns the playback volume up. | * :ref:`nrf53_audio_broadcast_sink_app` |
| | | * :ref:`nrf53_audio_unicast_server_app` |
| | | * :ref:`nrf53_audio_unicast_client_app` |
+---------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **PLAY/PAUSE**| Starts or pauses the playback of the stream or listening to the stream. | All |
+---------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **BTN 4** | Long-pressed during startup: Turns on the DFU mode, if | All |
| | the device is :ref:`configured for it<nrf53_audio_app_configuration_configure_fota>`. | |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Pressed on the gateway during playback: Toggles between the normal audio stream and different test | * :ref:`nrf53_audio_broadcast_source_app` |
| | tones generated on the device. Use this tone to check the synchronization of headsets. | * :ref:`nrf53_audio_unicast_client_app` |
| +-----------------------------------------------------------------------------------------------------------+ |
| | Pressed on the gateway during playback multiple times: Changes the test tone frequency. | |
| | The available values are 1000 Hz, 2000 Hz, and 4000 Hz. | |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Pressed on a BIS headset during playback: Change stream (different BIS), if more than one is available. | :ref:`nrf53_audio_broadcast_sink_app` |
+---------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **BTN 5** | Long-pressed during startup: Clears the previously stored bonding information. | * :ref:`nrf53_audio_unicast_server_app` |
| | | * :ref:`nrf53_audio_unicast_client_app` |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Pressed during playback: Mutes the playback volume. | * :ref:`nrf53_audio_unicast_server_app` |
| | | * :ref:`nrf53_audio_unicast_client_app` |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Pressed on a BIS headset during playback: Change the gateway, if more than one is available. | :ref:`nrf53_audio_broadcast_sink_app` |
+---------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **RESET** | Resets the device to the originally programmed settings. | All |
| | This reverts any changes made during testing, for example the channel switches with **VOL** buttons. | |
+---------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
.. _nrf53_audio_app_ui_leds:
LEDs
****
To indicate the tasks performed, the application uses the LED behavior described in the following table:
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| LED |Indication | Applications |
+==========================+===========================================================================================================+=============================================+
| **LED1** | Off - No Bluetooth connection. | All |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Solid blue on the CIS gateway and headset: Kits have connected. | * :ref:`nrf53_audio_unicast_server_app` |
| | | * :ref:`nrf53_audio_unicast_client_app` |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Solid blue on the BIS headset: Kits have found a broadcasting stream. | :ref:`nrf53_audio_broadcast_sink_app` |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Blinking blue on headset: Kits have started streaming audio (BIS and CIS modes). | * :ref:`nrf53_audio_broadcast_sink_app` |
| | | * :ref:`nrf53_audio_unicast_server_app` |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Blinking blue on the CIS gateway: Kit is streaming to a headset. | :ref:`nrf53_audio_unicast_client_app` |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Blinking blue on the BIS gateway: Kit has started broadcasting audio. | :ref:`nrf53_audio_broadcast_source_app` |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **LED2** | Off - Sync not achieved. | All |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Solid green - Sync achieved (both drift and presentation compensation are in the ``LOCKED`` state). | * :ref:`nrf53_audio_broadcast_sink_app` |
| | | * :ref:`nrf53_audio_unicast_server_app` |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **LED3** | Blinking green - The nRF5340 Audio DK application core is running. | All |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **CODEC** | Off - No configuration loaded to the onboard hardware codec. | All |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Solid green - Hardware codec configuration loaded. | All |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **RGB** | Solid green - The device is programmed as the gateway. | * :ref:`nrf53_audio_broadcast_source_app` |
| | | * :ref:`nrf53_audio_unicast_client_app` |
| (bottom side LEDs around +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| the center opening) | Solid blue - The device is programmed as the left headset. | * :ref:`nrf53_audio_broadcast_sink_app` |
| | | * :ref:`nrf53_audio_unicast_server_app` |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Solid magenta - The device is programmed as the right headset. | * :ref:`nrf53_audio_broadcast_sink_app` |
| | | * :ref:`nrf53_audio_unicast_server_app` |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Solid yellow - The device is programmed with factory firmware. | All |
| | It must be re-programmed as gateway or headset. | |
| +-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| | Solid red (debug mode) - Fault in the application core has occurred. | All |
| | See UART log for details and use the **RESET** button to reset the device. | |
| | In the release mode, the device resets automatically with no indication on LED or UART. | |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **ERR** | PMIC error or a charging error (or both). | All |
| | Also turns on when charging the battery exceeds seven hours, since the PMIC has a protection timeout, | |
| | which stops the charging. | |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **CHG** | Off - Charge completed or no battery connected. | All |
| +-----------------------------------------------------------------------------------------------------------+ |
| | Solid yellow - Charging in progress. | |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **OB/EXT** | Off - No 3.3 V power available. | All |
| +-----------------------------------------------------------------------------------------------------------+ |
| | Solid green - On-board hardware codec selected. | |
| +-----------------------------------------------------------------------------------------------------------+ |
| | Solid yellow - External hardware codec selected. | |
| | This LED turns solid yellow also when the devices are reset, for the time then pins are floating. | |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **FTDI SPI** | Off - No data is written to the hardware codec using SPI. | All |
| +-----------------------------------------------------------------------------------------------------------+ |
| | Yellow - The same SPI is used for both the hardware codec and the SD card. | |
| | When this LED is yellow, the shared SPI is used by the FTDI to write data to the hardware codec. | |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **IFMCU** | Off - No PC connection available. | All |
| (bottom side) +-----------------------------------------------------------------------------------------------------------+ |
| | Solid green - Connected to PC. | |
| +-----------------------------------------------------------------------------------------------------------+ |
| | Rapid green flash - USB enumeration failed. | |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+
| **HUB** | Off - No PC connection available. | All |
| (bottom side) +-----------------------------------------------------------------------------------------------------------+ |
| | Green - Standard USB hub operation. | |
+--------------------------+-----------------------------------------------------------------------------------------------------------+---------------------------------------------+

102
include/zbus_common.h Normal file
View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _ZBUS_COMMON_H_
#define _ZBUS_COMMON_H_
#include <zephyr/bluetooth/audio/audio.h>
#include "le_audio.h"
#define ZBUS_READ_TIMEOUT_MS K_MSEC(100)
#define ZBUS_ADD_OBS_TIMEOUT_MS K_MSEC(200)
/***** Messages for zbus ******/
enum button_action {
BUTTON_PRESS = 1,
};
struct button_msg {
uint32_t button_pin;
enum button_action button_action;
};
enum le_audio_evt_type {
LE_AUDIO_EVT_CONFIG_RECEIVED = 1,
LE_AUDIO_EVT_PRES_DELAY_SET,
LE_AUDIO_EVT_STREAMING,
LE_AUDIO_EVT_NOT_STREAMING,
LE_AUDIO_EVT_STREAM_SENT,
LE_AUDIO_EVT_SYNC_LOST,
LE_AUDIO_EVT_NO_VALID_CFG,
LE_AUDIO_EVT_COORD_SET_DISCOVERED,
};
struct le_audio_msg {
enum le_audio_evt_type event;
struct bt_conn *conn;
struct bt_le_per_adv_sync *pa_sync;
enum bt_audio_dir dir;
uint8_t set_size;
uint8_t const *sirk;
struct stream_index idx;
};
/**
* tx_sync_ts_us The timestamp from get_tx_sync.
* curr_ts_us The current time. This must be in the controller frame of reference.
*/
struct sdu_ref_msg {
uint32_t tx_sync_ts_us;
uint32_t curr_ts_us;
bool adjust;
};
enum bt_mgmt_evt_type {
BT_MGMT_EXT_ADV_WITH_PA_READY = 1,
BT_MGMT_CONNECTED,
BT_MGMT_SECURITY_CHANGED,
BT_MGMT_PA_SYNCED,
BT_MGMT_PA_SYNC_LOST,
BT_MGMT_DISCONNECTED,
BT_MGMT_BROADCAST_SINK_DISABLE,
BT_MGMT_BROADCAST_CODE_RECEIVED,
};
struct bt_mgmt_msg {
enum bt_mgmt_evt_type event;
struct bt_conn *conn;
uint8_t index;
struct bt_le_ext_adv *ext_adv;
struct bt_le_per_adv_sync *pa_sync;
uint32_t broadcast_id;
uint8_t pa_sync_term_reason;
};
enum volume_evt_type {
VOLUME_UP = 1,
VOLUME_DOWN,
VOLUME_SET,
VOLUME_MUTE,
VOLUME_UNMUTE,
};
struct volume_msg {
enum volume_evt_type event;
uint8_t volume;
};
enum content_control_evt_type {
MEDIA_START = 1,
MEDIA_STOP,
};
struct content_control_msg {
enum content_control_evt_type event;
};
#endif /* _ZBUS_COMMON_H_ */

56
index.rst Normal file
View File

@@ -0,0 +1,56 @@
.. _nrf53_audio_app:
nRF5340 Audio applications
##########################
The nRF5340 Audio applications demonstrate audio playback over isochronous channels (ISO) using LC3 codec compression and decompression, as per `Bluetooth® LE Audio specifications`_.
.. note::
nRF5340 Audio applications and their DFU/FOTA functionality are marked as :ref:`experimental <software_maturity>`.
The following table summarizes the differences between the available nRF5340 Audio applications.
.. list-table:: Differences between nRF5340 Audio applications
:header-rows: 1
* - :ref:`Application name (LE Audio role) <nrf53_audio_app_overview>`
- :ref:`Application mode <nrf53_audio_app_overview_modes>`
- Minimum amount of nRF5340 Audio DKs recommended for testing
- :ref:`FEM support <nrf53_audio_app_adding_FEM_support>`
* - :ref:`Broadcast sink<nrf53_audio_broadcast_sink_app>`
- BIS (headset)
- 2
-
* - :ref:`Broadcast source<nrf53_audio_broadcast_source_app>`
- BIS (gateway)
- 2
- ✔
* - :ref:`Unicast client<nrf53_audio_unicast_client_app>`
- CIS (gateway)
- 3
- ✔
* - :ref:`Unicast server<nrf53_audio_unicast_server_app>`
- CIS (headset)
- 3
- ✔
See the subpages for detailed documentation of each of the nRF5340 applications and their internal modules:
.. _nrf53_audio_app_subpages:
.. toctree::
:maxdepth: 1
:caption: Subpages:
doc/firmware_architecture
doc/feature_support
doc/requirements
doc/user_interface
doc/configuration
doc/building
broadcast_sink/README
broadcast_source/README
unicast_client/README
unicast_server/README
doc/fota
doc/adapting_application

55
pm_static_fota.yml Normal file
View File

@@ -0,0 +1,55 @@
app:
address: 0x10200
region: flash_primary
size: 0xdfe00
mcuboot:
address: 0x0
region: flash_primary
size: 0x10000
mcuboot_pad:
address: 0x10000
region: flash_primary
size: 0x200
mcuboot_primary:
address: 0x10000
orig_span: &id001
- mcuboot_pad
- app
region: flash_primary
size: 0xe0000
span: *id001
mcuboot_primary_app:
address: 0x10200
orig_span: &id002
- app
region: flash_primary
size: 0xdfe00
span: *id002
settings_storage:
address: 0xf0000
region: flash_primary
size: 0x10000
mcuboot_primary_1:
address: 0x0
size: 0x40000
device: flash_ctrl
region: ram_flash
mcuboot_secondary:
address: 0x00000
size: 0xe0000
device: MX25R64
region: external_flash
mcuboot_secondary_1:
address: 0xe0000
size: 0x40000
device: MX25R64
region: external_flash
external_flash:
address: 0x120000
size: 0x6e0000
device: MX25R64
region: external_flash
pcd_sram:
address: 0x20000000
size: 0x2000
region: sram_primary

72
prj.conf Normal file
View File

@@ -0,0 +1,72 @@
#
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
# nRF5340 Audio
CONFIG_NRF5340_AUDIO=y
CONFIG_SAMPLE_RATE_CONVERTER=y
CONFIG_SAMPLE_RATE_CONVERTER_FILTER_SIMPLE=y
# General
CONFIG_DEBUG=y
CONFIG_DEBUG_INFO=y
CONFIG_ASSERT=y
CONFIG_STACK_USAGE=y
CONFIG_THREAD_RUNTIME_STATS=y
CONFIG_STACK_SENTINEL=y
CONFIG_INIT_STACKS=y
CONFIG_BT=y
# Uart driver
CONFIG_SERIAL=y
# Logging
CONFIG_LOG=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_LOG_TAG_MAX_LEN=2
CONFIG_LOG_TAG_DEFAULT="--"
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_BUFFER_SIZE=4096
# Use this for debugging thread usage
#CONFIG_LOG_THREAD_ID_PREFIX=y
# Console related defines
CONFIG_CONSOLE=y
CONFIG_RTT_CONSOLE=y
CONFIG_UART_CONSOLE=y
# Shell related defines
CONFIG_SHELL=y
CONFIG_KERNEL_SHELL=y
CONFIG_USE_SEGGER_RTT=y
## Disable logs on RTT
CONFIG_SHELL_RTT_INIT_LOG_LEVEL_NONE=y
CONFIG_SHELL_BACKEND_RTT=y
CONFIG_SHELL_BACKEND_SERIAL=n
CONFIG_SHELL_VT100_COMMANDS=y
CONFIG_SHELL_VT100_COLORS=y
CONFIG_SHELL_STACK_SIZE=4096
CONFIG_SHELL_CMD_BUFF_SIZE=128
## Reduce shell memory usage
CONFIG_SHELL_WILDCARD=n
CONFIG_SHELL_HELP_ON_WRONG_ARGUMENT_COUNT=n
CONFIG_SHELL_STATS=n
CONFIG_SHELL_CMDS=n
CONFIG_SHELL_HISTORY=y
# Turn off default shell commands
CONFIG_I2C_SHELL=n
CONFIG_HWINFO_SHELL=n
CONFIG_CLOCK_CONTROL_NRF_SHELL=n
CONFIG_FLASH_SHELL=n
CONFIG_DEVICE_SHELL=n
# Suppress LOG_ERR messages from sd_check_card_type. Because SPI_SDHC has no card presence method,
# assume card is in slot. Thus error message is always shown if card is not inserted
CONFIG_SD_LOG_LEVEL_OFF=y
# Suppress LOG_INF messages from hci_core
CONFIG_BT_HCI_CORE_LOG_LEVEL_WRN=y

84
prj_fota.conf Normal file
View File

@@ -0,0 +1,84 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
# nRF5340 Audio
CONFIG_NRF5340_AUDIO=y
CONFIG_SAMPLE_RATE_CONVERTER=y
CONFIG_SAMPLE_RATE_CONVERTER_FILTER_SIMPLE=y
# General
CONFIG_DEBUG=y
CONFIG_DEBUG_INFO=y
CONFIG_ASSERT=y
CONFIG_STACK_USAGE=y
CONFIG_THREAD_RUNTIME_STATS=y
CONFIG_STACK_SENTINEL=y
CONFIG_INIT_STACKS=y
# Uart driver
CONFIG_SERIAL=y
# Logging
CONFIG_LOG=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_LOG_TAG_MAX_LEN=2
CONFIG_LOG_TAG_DEFAULT="--"
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_BUFFER_SIZE=4096
# Use this for debugging thread usage
#CONFIG_LOG_THREAD_ID_PREFIX=y
# Console related defines
CONFIG_CONSOLE=y
CONFIG_RTT_CONSOLE=y
CONFIG_UART_CONSOLE=y
# Shell related defines
CONFIG_SHELL=y
CONFIG_KERNEL_SHELL=y
CONFIG_USE_SEGGER_RTT=y
## Disable logs on RTT
CONFIG_SHELL_RTT_INIT_LOG_LEVEL_NONE=y
CONFIG_SHELL_BACKEND_RTT=y
CONFIG_SHELL_BACKEND_SERIAL=n
CONFIG_SHELL_VT100_COMMANDS=y
CONFIG_SHELL_VT100_COLORS=y
CONFIG_SHELL_STACK_SIZE=4096
CONFIG_SHELL_CMD_BUFF_SIZE=128
## Reduce shell memory usage
CONFIG_SHELL_WILDCARD=n
CONFIG_SHELL_HELP_ON_WRONG_ARGUMENT_COUNT=n
CONFIG_SHELL_STATS=n
CONFIG_SHELL_CMDS=n
CONFIG_SHELL_HISTORY=y
# Turn off default shell commands
CONFIG_I2C_SHELL=n
CONFIG_HWINFO_SHELL=n
CONFIG_CLOCK_CONTROL_NRF_SHELL=n
CONFIG_FLASH_SHELL=n
CONFIG_DEVICE_SHELL=n
# Suppress LOG_ERR messages from sd_check_card_type. Because SPI_SDHC has no card presence method,
# assume card is in slot. Thus error message is always shown if card is not inserted
CONFIG_SD_LOG_LEVEL_OFF=y
# Suppress LOG_INF messages from hci_core
CONFIG_BT_HCI_CORE_LOG_LEVEL_WRN=y
# DFU
CONFIG_AUDIO_BT_MGMT_DFU=y
CONFIG_MCUMGR_TRANSPORT_BT_PERM_RW=y
CONFIG_BT_L2CAP_TX_MTU=498
CONFIG_BT_BUF_ACL_TX_SIZE=251
# External Flash
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_SPI_NOR=y
CONFIG_SPI_NOR_SFDP_DEVICETREE=y

34
prj_release.conf Normal file
View File

@@ -0,0 +1,34 @@
#
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
# nRF5340 Audio
CONFIG_NRF5340_AUDIO=y
CONFIG_SAMPLE_RATE_CONVERTER=y
CONFIG_SAMPLE_RATE_CONVERTER_FILTER_SIMPLE=y
# General
CONFIG_DEBUG=n
CONFIG_ASSERT=n
CONFIG_STACK_USAGE=n
CONFIG_THREAD_MONITOR=n
CONFIG_SERIAL=n
CONFIG_CONSOLE=n
CONFIG_PRINTK=n
CONFIG_UART_CONSOLE=n
CONFIG_BOOT_BANNER=n
# Bluetooth
# Default value from src/bluetooth/Kconfig.defaults.
# BT_PRIVACY is default turned off to ease the development.
# Should be turned on before production.
# CONFIG_BT_PRIVACY=y
# USB
# Default values from src/modules/Kconfig.defaults.
# USB VID and PID must be changed before production.
# CONFIG_USB_DEVICE_VID=0x1915
# CONFIG_USB_DEVICE_PID=0x530A

47
sample.yaml Normal file
View File

@@ -0,0 +1,47 @@
sample:
name: nRF5340 Audio application
description: LE Audio and Auracast implementation example
common:
integration_platforms:
- nrf5340_audio_dk/nrf5340/cpuapp
platform_allow: nrf5340_audio_dk/nrf5340/cpuapp
sysbuild: true
build_only: true
tags:
- ci_build
- sysbuild
tests:
applications.nrf5340_audio.headset_unicast:
extra_args:
- FILE_SUFFIX=release
- CONFIG_AUDIO_DEV=1
- EXTRA_CONF_FILE=unicast_server/overlay-unicast_server.conf
applications.nrf5340_audio.gateway_unicast:
extra_args:
- FILE_SUFFIX=release
- CONFIG_AUDIO_DEV=2
- EXTRA_CONF_FILE=unicast_client/overlay-unicast_client.conf
applications.nrf5340_audio.headset_broadcast:
extra_args:
- FILE_SUFFIX=release
- CONFIG_AUDIO_DEV=1
- CONFIG_TRANSPORT_BIS=y
- EXTRA_CONF_FILE=broadcast_sink/overlay-broadcast_sink.conf
applications.nrf5340_audio.gateway_broadcast:
extra_args:
- FILE_SUFFIX=release
- CONFIG_AUDIO_DEV=2
- CONFIG_TRANSPORT_BIS=y
- EXTRA_CONF_FILE=broadcast_source/overlay-broadcast_source.conf
applications.nrf5340_audio.headset_unicast_sd_card:
extra_args:
- FILE_SUFFIX=release
- CONFIG_AUDIO_DEV=1
- CONFIG_SD_CARD_PLAYBACK=y
- EXTRA_CONF_FILE=unicast_server/overlay-unicast_server.conf
applications.nrf5340_audio.headset_dfu:
extra_args:
- FILE_SUFFIX=fota
- ipc_radio_FILE_SUFFIX=release
- CONFIG_AUDIO_DEV=1
- EXTRA_CONF_FILE=unicast_server/overlay-unicast_server.conf

12
src/audio/CMakeLists.txt Normal file
View File

@@ -0,0 +1,12 @@
#
# Copyright (c) 2022 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/audio_system.c
${CMAKE_CURRENT_SOURCE_DIR}/audio_datapath.c
${CMAKE_CURRENT_SOURCE_DIR}/sw_codec_select.c
${CMAKE_CURRENT_SOURCE_DIR}/le_audio_rx.c
)

388
src/audio/Kconfig Normal file
View File

@@ -0,0 +1,388 @@
#
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
rsource "Kconfig.defaults"
menu "Audio"
choice AUDIO_FRAME_DURATION
prompt "Select frame duration - 7.5 ms frame duration is not tested"
default AUDIO_FRAME_DURATION_10_MS
help
LC3 supports frame duration of 7.5 and 10 ms.
If USB is selected as audio source, we should
have a frame duration of 10 ms since USB sends 1ms at a time.
config AUDIO_FRAME_DURATION_7_5_MS
bool "7.5 ms"
config AUDIO_FRAME_DURATION_10_MS
bool "10 ms"
endchoice
config AUDIO_FRAME_DURATION_US
int
default 7500 if AUDIO_FRAME_DURATION_7_5_MS
default 10000 if AUDIO_FRAME_DURATION_10_MS
help
Audio frame duration in µs.
config AUDIO_MIN_PRES_DLY_US
int "The minimum presentation delay"
default 5000 if STREAM_BIDIRECTIONAL
default 3000
help
The minimum allowable presentation delay in microseconds.
This needs to allow time for decoding and internal routing.
config AUDIO_MAX_PRES_DLY_US
int "The maximum presentation delay"
default 60000
help
The maximum allowable presentation delay in microseconds.
Increasing this will also increase the FIFO buffers to allow buffering.
choice AUDIO_SYSTEM_SAMPLE_RATE
prompt "System audio sample rate"
default AUDIO_SAMPLE_RATE_16000_HZ if BT_BAP_BROADCAST_16_2_1
default AUDIO_SAMPLE_RATE_16000_HZ if BT_BAP_BROADCAST_16_2_2
default AUDIO_SAMPLE_RATE_16000_HZ if BT_BAP_UNICAST_16_2_1
default AUDIO_SAMPLE_RATE_24000_HZ if BT_BAP_BROADCAST_24_2_1
default AUDIO_SAMPLE_RATE_24000_HZ if BT_BAP_BROADCAST_24_2_2
default AUDIO_SAMPLE_RATE_24000_HZ if BT_BAP_UNICAST_24_2_1
default AUDIO_SAMPLE_RATE_48000_HZ
help
This configuration reflects the system sample rate, but the audio data may be resampled to
another sample rate before encoding, and after decoding.
config AUDIO_SAMPLE_RATE_16000_HZ
bool "16 kHz"
help
Sample rate of 16kHz is currently only valid for I2S/line-in.
config AUDIO_SAMPLE_RATE_24000_HZ
bool "24 kHz"
help
Sample rate of 24kHz is currently only valid for I2S/line-in.
config AUDIO_SAMPLE_RATE_48000_HZ
bool "48 kHz"
help
Sample rate of 48kHz is valid for both I2S/line-in and USB.
endchoice
config AUDIO_SAMPLE_RATE_HZ
int
default 16000 if AUDIO_SAMPLE_RATE_16000_HZ
default 24000 if AUDIO_SAMPLE_RATE_24000_HZ
default 48000 if AUDIO_SAMPLE_RATE_48000_HZ
help
I2S supports 16, 24, and 48 kHz sample rates for both input and output.
USB supports only 48 kHz for input.
choice AUDIO_BIT_DEPTH
prompt "Audio bit depth"
default AUDIO_BIT_DEPTH_16
help
Select the bit depth for audio.
config AUDIO_BIT_DEPTH_16
bool "16 bit audio"
config AUDIO_BIT_DEPTH_32
bool "32 bit audio"
endchoice
config AUDIO_BIT_DEPTH_BITS
int
default 16 if AUDIO_BIT_DEPTH_16
default 32 if AUDIO_BIT_DEPTH_32
help
Bit depth of one sample in storage.
config AUDIO_BIT_DEPTH_OCTETS
int
default 2 if AUDIO_BIT_DEPTH_16
default 4 if AUDIO_BIT_DEPTH_32
help
Bit depth of one sample in storage given in octets.
choice AUDIO_SOURCE_GATEWAY
prompt "Audio source for gateway"
default AUDIO_SOURCE_I2S if WALKIE_TALKIE_DEMO
default AUDIO_SOURCE_USB
help
Select audio source for the gateway.
config AUDIO_SOURCE_USB
bool "Use USB as audio source"
help
Set USB as audio source. Note that this forces the
stream to be unidirectional because of CPU load.
config AUDIO_SOURCE_I2S
bool "Use I2S as audio source"
endchoice
choice AUDIO_HEADSET_CHANNEL
prompt "Headset audio channel assignment"
default AUDIO_HEADSET_CHANNEL_RUNTIME
help
Set whether audio channel assignment for the headset
should happen at runtime or compile-time.
config AUDIO_HEADSET_CHANNEL_RUNTIME
bool "Select at runtime"
help
Make channel selection at runtime. Selected value is stored in persistent memory.
Left channel: Hold volume-down button on headset while resetting headset.
Right channel: Hold volume-up button on headset while resetting headset.
config AUDIO_HEADSET_CHANNEL_COMPILE_TIME
bool "Set at compile-time"
help
Set channel selection at compile-time.
endchoice
config AUDIO_TEST_TONE
bool "Test tone instead of doing user defined action"
select TONE
default y
help
Use button 4 to set a test tone
instead of doing a user defined action.
The test tone is generated on the device itself.
config AUDIO_MUTE
bool "Mute instead of doing user defined action"
default y
help
Use button 5 to mute audio instead of
doing a user defined action.
if AUDIO_HEADSET_CHANNEL_COMPILE_TIME
config AUDIO_HEADSET_CHANNEL
int "Audio channel used by headset"
range 0 1
default 0
help
Audio channel compile-time selection.
Left = 0.
Right = 1.
endif # AUDIO_HEADSET_CHANNEL_COMPILE_TIME
#----------------------------------------------------------------------------#
menu "SW Codec"
choice SW_CODEC_DEFAULT
prompt "Starting SW codec"
default SW_CODEC_LC3
help
Select the default codec to use on start up.
config SW_CODEC_LC3
bool "LC3"
select SW_CODEC_LC3_T2_SOFTWARE
help
LC3 is the mandatory codec for LE Audio.
config SW_CODEC_NONE
bool "None"
help
Choose this if no software (SW) codec is needed.
# Leave room for other codecs
endchoice
config SW_CODEC_PLC_DISABLED
bool "Skip PLC on a bad frame and fill the output buffer(s) with zeros instead"
default n
select LC3_PLC_DISABLED
#----------------------------------------------------------------------------#
menu "LC3"
visible if SW_CODEC_LC3
config LC3_BITRATE_MAX
int "Max bitrate for LC3"
default 124000
config LC3_BITRATE_MIN
int "Min bitrate for LC3"
default 32000
config LC3_BITRATE
int
range LC3_BITRATE_MIN LC3_BITRATE_MAX
default 96000
osource "../nrfxlib/lc3/Kconfig"
endmenu # LC3
endmenu # SW Codec
#----------------------------------------------------------------------------#
menu "Stream"
config BUF_BLE_RX_PACKET_NUM
int
default 5
range 2 5
help
Value can be adjusted to affect the overall latency.
This adjusts the number packets in the BLE FIFO RX buffer,
which is where the main latency resides. A low value will decrease
latency and reduce stability, and vice-versa.
Two is recommended minimum to reduce the likelyhood of audio
gaps due to BLE retransmits.
config STREAM_BIDIRECTIONAL
depends on TRANSPORT_CIS
bool "Bidirectional stream"
default n
help
Bidirectional stream enables encoder and decoder on both sides,
and one device can both send and receive audio.
config WALKIE_TALKIE_DEMO
select STREAM_BIDIRECTIONAL
bool "Walkie talkie demo"
default n
help
The walkie talkie demo will set up a bidirectional stream using PDM
microphones on each side.
config MONO_TO_ALL_RECEIVERS
bool "Send mono (first/left channel) to all receivers"
default y if BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT = 1
default y if BT_BAP_BROADCAST_SRC_STREAM_COUNT = 1
default n
help
With this flag set, the gateway will encode and send the same (first/left)
channel on all ISO channels.
endmenu # Stream
#----------------------------------------------------------------------------#
menu "Log levels"
module = AUDIO_SYSTEM
module-str = audio-system
source "subsys/logging/Kconfig.template.log_config"
module = SW_CODEC_SELECT
module-str = sw-codec-select
source "subsys/logging/Kconfig.template.log_config"
module = STREAMCTRL
module-str = streamctrl
source "subsys/logging/Kconfig.template.log_config"
module = AUDIO_DATAPATH
module-str = audio-datapath
source "subsys/logging/Kconfig.template.log_config"
module = AUDIO_SYNC_TIMER
module-str = audio-sync-timer
source "subsys/logging/Kconfig.template.log_config"
module = LE_AUDIO_RX
module-str = le-audio-rx
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log levels
#----------------------------------------------------------------------------#
menu "Thread priorities"
config ENCODER_THREAD_PRIO
int "Priority for encoder thread"
default 3
help
This is a preemptible thread.
config AUDIO_DATAPATH_THREAD_PRIO
int "Priority for audio datapath thread"
default 4
help
This is a preemptible thread.
config BUTTON_MSG_SUB_THREAD_PRIO
int "Thread priority for button subscriber"
default 5
help
This is a preemptible thread.
This thread will subscribe to button events from zbus.
config LE_AUDIO_MSG_SUB_THREAD_PRIO
int "Thread priority for LE Audio subscriber"
default 5
help
This is a preemptible thread.
This thread will subscribe to LE Audio events from zbus.
config BT_MGMT_MSG_SUB_THREAD_PRIO
int "Thread priority for bt_mgmt subscriber"
default 5
help
This is a preemptible thread.
This thread will subscribe to BT management events from zbus.
config CONTENT_CONTROL_MSG_SUB_THREAD_PRIO
int "Thread priority for content control subscriber"
default 5
help
This is a preemptible thread.
This thread will subscribe to content control events from zbus.
endmenu # Thread priorities
#----------------------------------------------------------------------------#
menu "Stack sizes"
config ENCODER_STACK_SIZE
int "Stack size for encoder thread"
default 11000 if AUDIO_BIT_DEPTH_16
default 21400 if AUDIO_BIT_DEPTH_32
config AUDIO_DATAPATH_STACK_SIZE
int "Stack size for audio datapath thread"
default 7600 if AUDIO_BIT_DEPTH_16
default 14700 if AUDIO_BIT_DEPTH_32
config BUTTON_MSG_SUB_STACK_SIZE
int "Stack size for button subscriber"
default 2048
config LE_AUDIO_MSG_SUB_STACK_SIZE
int "Stack size for LE Audio subscriber"
default 2048
config BT_MGMT_MSG_SUB_STACK_SIZE
int "Stack size for bt_mgmt subscriber"
default 2048
config CONTENT_CONTROL_MSG_SUB_STACK_SIZE
int "Stack size for content control subscriber"
default 1024
endmenu # Stack sizes
#----------------------------------------------------------------------------#
menu "Zbus"
config BUTTON_MSG_SUB_QUEUE_SIZE
int "Queue size for button subscriber"
default 4
config CONTENT_CONTROL_MSG_SUB_QUEUE_SIZE
int "Queue size for content control subscriber"
default 4
endmenu # Zbus
endmenu # Audio

View File

@@ -0,0 +1,13 @@
#
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
# Audio sync timer
config NRFX_TIMER1
default y
# Audio sync timer
config NRFX_DPPI
default y

1218
src/audio/audio_datapath.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2021, PACKETCRAFT, INC.
*
* SPDX-License-Identifier: LicenseRef-PCFT
*/
#ifndef _AUDIO_DATAPATH_H_
#define _AUDIO_DATAPATH_H_
#include <zephyr/kernel.h>
#include <stdint.h>
#include <stdbool.h>
#include <data_fifo.h>
#include "sw_codec_select.h"
/**
* @brief Mixes a tone into the I2S TX stream
*
* @param freq Tone frequency [Hz]
* @param dur_ms Tone duration [ms]. 0 = forever
* @param amplitude Tone amplitude [0, 1]
*
* @return 0 if successful, error otherwise
*/
int audio_datapath_tone_play(uint16_t freq, uint16_t dur_ms, float amplitude);
/**
* @brief Stops tone playback
*/
void audio_datapath_tone_stop(void);
/**
* @brief Set the presentation delay
*
* @param delay_us The presentation delay in µs
*
* @return 0 if successful, error otherwise
*/
int audio_datapath_pres_delay_us_set(uint32_t delay_us);
/**
* @brief Get the current presentation delay
*
* @param delay_us The presentation delay in µs
*/
void audio_datapath_pres_delay_us_get(uint32_t *delay_us);
/**
* @brief Input an audio data frame which is processed and outputted over I2S
*
* @note A frame of raw encoded audio data is inputted, and this data then is decoded
* and processed before being outputted over I2S. The audio is synchronized
* using sdu_ref_us
*
* @param buf Pointer to audio data frame
* @param size Size of audio data frame in bytes
* @param sdu_ref_us ISO timestamp reference from BLE controller
* @param bad_frame Indicating if the audio frame is bad or not
* @param recv_frame_ts_us Timestamp of when audio frame was received
*/
void audio_datapath_stream_out(const uint8_t *buf, size_t size, uint32_t sdu_ref_us, bool bad_frame,
uint32_t recv_frame_ts_us);
/**
* @brief Start the audio datapath module
*
* @note The continuously running I2S is started
*
* @param fifo_rx Pointer to FIFO structure where I2S RX data is put
*
* @return 0 if successful, error otherwise
*/
int audio_datapath_start(struct data_fifo *fifo_rx);
/**
* @brief Stop the audio datapath module
*
* @return 0 if successful, error otherwise
*/
int audio_datapath_stop(void);
/**
* @brief Initialize the audio datapath module
*
* @return 0 if successful, error otherwise
*/
int audio_datapath_init(void);
#endif /* _AUDIO_DATAPATH_H_ */

530
src/audio/audio_system.c Normal file
View File

@@ -0,0 +1,530 @@
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "audio_system.h"
#include <zephyr/kernel.h>
#include <zephyr/shell/shell.h>
#include <data_fifo.h>
#include <contin_array.h>
#include <pcm_stream_channel_modifier.h>
#include <tone.h>
#include "macros_common.h"
#include "sw_codec_select.h"
#include "audio_datapath.h"
#include "audio_i2s.h"
#include "hw_codec.h"
#include "audio_usb.h"
#include "streamctrl.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(audio_system, CONFIG_AUDIO_SYSTEM_LOG_LEVEL);
#define FIFO_TX_BLOCK_COUNT (CONFIG_FIFO_FRAME_SPLIT_NUM * CONFIG_FIFO_TX_FRAME_COUNT)
#define FIFO_RX_BLOCK_COUNT (CONFIG_FIFO_FRAME_SPLIT_NUM * CONFIG_FIFO_RX_FRAME_COUNT)
#define DEBUG_INTERVAL_NUM 1000
#define TEST_TONE_BASE_FREQ_HZ 1000
K_THREAD_STACK_DEFINE(encoder_thread_stack, CONFIG_ENCODER_STACK_SIZE);
DATA_FIFO_DEFINE(fifo_tx, FIFO_TX_BLOCK_COUNT, WB_UP(BLOCK_SIZE_BYTES));
DATA_FIFO_DEFINE(fifo_rx, FIFO_RX_BLOCK_COUNT, WB_UP(BLOCK_SIZE_BYTES));
static K_SEM_DEFINE(sem_encoder_start, 0, 1);
static struct k_thread encoder_thread_data;
static k_tid_t encoder_thread_id;
static struct k_poll_signal encoder_sig;
static struct k_poll_event encoder_evt =
K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &encoder_sig);
static struct sw_codec_config sw_codec_cfg;
/* Buffer which can hold max 1 period test tone at 1000 Hz */
static int16_t test_tone_buf[CONFIG_AUDIO_SAMPLE_RATE_HZ / 1000];
static size_t test_tone_size;
static bool sample_rate_valid(uint32_t sample_rate_hz)
{
if (sample_rate_hz == 16000 || sample_rate_hz == 24000 || sample_rate_hz == 48000) {
return true;
}
return false;
}
static void audio_gateway_configure(void)
{
if (IS_ENABLED(CONFIG_SW_CODEC_LC3)) {
sw_codec_cfg.sw_codec = SW_CODEC_LC3;
} else {
ERR_CHK_MSG(-EINVAL, "No codec selected");
}
#if (CONFIG_STREAM_BIDIRECTIONAL)
sw_codec_cfg.decoder.audio_ch = AUDIO_CHANNEL_DEFAULT;
sw_codec_cfg.decoder.num_ch = 1;
sw_codec_cfg.decoder.channel_mode = SW_CODEC_MONO;
#endif /* (CONFIG_STREAM_BIDIRECTIONAL) */
if (IS_ENABLED(CONFIG_MONO_TO_ALL_RECEIVERS)) {
sw_codec_cfg.encoder.num_ch = 1;
} else {
sw_codec_cfg.encoder.num_ch = 2;
}
sw_codec_cfg.encoder.channel_mode =
(sw_codec_cfg.encoder.num_ch == 1) ? SW_CODEC_MONO : SW_CODEC_STEREO;
}
static void audio_headset_configure(void)
{
if (IS_ENABLED(CONFIG_SW_CODEC_LC3)) {
sw_codec_cfg.sw_codec = SW_CODEC_LC3;
} else {
ERR_CHK_MSG(-EINVAL, "No codec selected");
}
#if (CONFIG_STREAM_BIDIRECTIONAL)
sw_codec_cfg.decoder.audio_ch = AUDIO_CHANNEL_DEFAULT;
sw_codec_cfg.encoder.num_ch = 1;
sw_codec_cfg.encoder.channel_mode = SW_CODEC_MONO;
#endif /* (CONFIG_STREAM_BIDIRECTIONAL) */
channel_assignment_get(&sw_codec_cfg.decoder.audio_ch);
sw_codec_cfg.decoder.num_ch = 1;
sw_codec_cfg.decoder.channel_mode = SW_CODEC_MONO;
if (IS_ENABLED(CONFIG_SD_CARD_PLAYBACK)) {
/* Need an extra decoder channel to decode data from SD card */
sw_codec_cfg.decoder.num_ch++;
}
}
static void encoder_thread(void *arg1, void *arg2, void *arg3)
{
int ret;
uint32_t blocks_alloced_num;
uint32_t blocks_locked_num;
int debug_trans_count = 0;
size_t encoded_data_size = 0;
void *tmp_pcm_raw_data[CONFIG_FIFO_FRAME_SPLIT_NUM];
char pcm_raw_data[FRAME_SIZE_BYTES];
static uint8_t *encoded_data;
static size_t pcm_block_size;
static uint32_t test_tone_finite_pos;
while (1) {
/* Don't start encoding until the stream needing it has started */
ret = k_poll(&encoder_evt, 1, K_FOREVER);
/* Get PCM data from I2S */
/* Since one audio frame is divided into a number of
* blocks, we need to fetch the pointers to all of these
* blocks before copying it to a continuous area of memory
* before sending it to the encoder
*/
for (int i = 0; i < CONFIG_FIFO_FRAME_SPLIT_NUM; i++) {
ret = data_fifo_pointer_last_filled_get(&fifo_rx, &tmp_pcm_raw_data[i],
&pcm_block_size, K_FOREVER);
ERR_CHK(ret);
memcpy(pcm_raw_data + (i * BLOCK_SIZE_BYTES), tmp_pcm_raw_data[i],
pcm_block_size);
data_fifo_block_free(&fifo_rx, tmp_pcm_raw_data[i]);
}
if (sw_codec_cfg.encoder.enabled) {
if (test_tone_size) {
/* Test tone takes over audio stream */
uint32_t num_bytes;
char tmp[FRAME_SIZE_BYTES / 2];
ret = contin_array_create(tmp, FRAME_SIZE_BYTES / 2, test_tone_buf,
test_tone_size, &test_tone_finite_pos);
ERR_CHK(ret);
ret = pscm_copy_pad(tmp, FRAME_SIZE_BYTES / 2,
CONFIG_AUDIO_BIT_DEPTH_BITS, pcm_raw_data,
&num_bytes);
ERR_CHK(ret);
}
ret = sw_codec_encode(pcm_raw_data, FRAME_SIZE_BYTES, &encoded_data,
&encoded_data_size);
ERR_CHK_MSG(ret, "Encode failed");
}
/* Print block usage */
if (debug_trans_count == DEBUG_INTERVAL_NUM) {
ret = data_fifo_num_used_get(&fifo_rx, &blocks_alloced_num,
&blocks_locked_num);
ERR_CHK(ret);
LOG_DBG(COLOR_CYAN "RX alloced: %d, locked: %d" COLOR_RESET,
blocks_alloced_num, blocks_locked_num);
debug_trans_count = 0;
} else {
debug_trans_count++;
}
if (sw_codec_cfg.encoder.enabled) {
streamctrl_send(encoded_data, encoded_data_size,
sw_codec_cfg.encoder.num_ch);
}
STACK_USAGE_PRINT("encoder_thread", &encoder_thread_data);
}
}
void audio_system_encoder_start(void)
{
LOG_DBG("Encoder started");
k_poll_signal_raise(&encoder_sig, 0);
}
void audio_system_encoder_stop(void)
{
k_poll_signal_reset(&encoder_sig);
}
int audio_system_encode_test_tone_set(uint32_t freq)
{
int ret;
if (freq == 0) {
test_tone_size = 0;
return 0;
}
if (IS_ENABLED(CONFIG_AUDIO_TEST_TONE)) {
ret = tone_gen(test_tone_buf, &test_tone_size, freq, CONFIG_AUDIO_SAMPLE_RATE_HZ,
1);
ERR_CHK(ret);
} else {
LOG_ERR("Test tone is not enabled");
return -ENXIO;
}
if (test_tone_size > sizeof(test_tone_buf)) {
return -ENOMEM;
}
return 0;
}
int audio_system_encode_test_tone_step(void)
{
int ret;
static uint32_t test_tone_hz;
if (CONFIG_AUDIO_BIT_DEPTH_BITS != 16) {
LOG_WRN("Tone gen only supports 16 bits");
return -ECANCELED;
}
if (test_tone_hz == 0) {
test_tone_hz = TEST_TONE_BASE_FREQ_HZ;
} else if (test_tone_hz >= TEST_TONE_BASE_FREQ_HZ * 4) {
test_tone_hz = 0;
} else {
test_tone_hz = test_tone_hz * 2;
}
if (test_tone_hz != 0) {
LOG_INF("Test tone set at %d Hz", test_tone_hz);
} else {
LOG_INF("Test tone off");
}
ret = audio_system_encode_test_tone_set(test_tone_hz);
if (ret) {
LOG_ERR("Failed to generate test tone");
return ret;
}
return 0;
}
int audio_system_config_set(uint32_t encoder_sample_rate_hz, uint32_t encoder_bitrate,
uint32_t decoder_sample_rate_hz)
{
if (sample_rate_valid(encoder_sample_rate_hz)) {
sw_codec_cfg.encoder.sample_rate_hz = encoder_sample_rate_hz;
} else if (encoder_sample_rate_hz) {
LOG_ERR("%d is not a valid sample rate", encoder_sample_rate_hz);
return -EINVAL;
}
if (sample_rate_valid(decoder_sample_rate_hz)) {
sw_codec_cfg.decoder.enabled = true;
sw_codec_cfg.decoder.sample_rate_hz = decoder_sample_rate_hz;
} else if (decoder_sample_rate_hz) {
LOG_ERR("%d is not a valid sample rate", decoder_sample_rate_hz);
return -EINVAL;
}
if (encoder_bitrate) {
sw_codec_cfg.encoder.enabled = true;
sw_codec_cfg.encoder.bitrate = encoder_bitrate;
}
return 0;
}
/* This function is only used on gateway using USB as audio source and bidirectional stream */
int audio_system_decode(void const *const encoded_data, size_t encoded_data_size, bool bad_frame)
{
int ret;
uint32_t blocks_alloced_num;
uint32_t blocks_locked_num;
static int debug_trans_count;
static void *tmp_pcm_raw_data[CONFIG_FIFO_FRAME_SPLIT_NUM];
static void *pcm_raw_data;
size_t pcm_block_size;
if (!sw_codec_cfg.initialized) {
/* Throw away data */
/* This can happen when using play/pause since there might be
* some packages left in the buffers
*/
LOG_DBG("Trying to decode while codec is not initialized");
return -EPERM;
}
ret = data_fifo_num_used_get(&fifo_tx, &blocks_alloced_num, &blocks_locked_num);
if (ret) {
return ret;
}
uint8_t free_blocks_num = FIFO_TX_BLOCK_COUNT - blocks_locked_num;
/* If not enough space for a full frame, remove oldest samples to make room */
if (free_blocks_num < CONFIG_FIFO_FRAME_SPLIT_NUM) {
void *old_data;
size_t size;
for (int i = 0; i < (CONFIG_FIFO_FRAME_SPLIT_NUM - free_blocks_num); i++) {
ret = data_fifo_pointer_last_filled_get(&fifo_tx, &old_data, &size,
K_NO_WAIT);
if (ret == -ENOMSG) {
/* If there are no more blocks in FIFO, break */
break;
}
data_fifo_block_free(&fifo_tx, old_data);
}
}
for (int i = 0; i < CONFIG_FIFO_FRAME_SPLIT_NUM; i++) {
ret = data_fifo_pointer_first_vacant_get(&fifo_tx, &tmp_pcm_raw_data[i], K_FOREVER);
if (ret) {
return ret;
}
}
ret = sw_codec_decode(encoded_data, encoded_data_size, bad_frame, &pcm_raw_data,
&pcm_block_size);
if (ret) {
LOG_ERR("Failed to decode");
return ret;
}
/* Split decoded frame into CONFIG_FIFO_FRAME_SPLIT_NUM blocks */
for (int i = 0; i < CONFIG_FIFO_FRAME_SPLIT_NUM; i++) {
memcpy(tmp_pcm_raw_data[i], (char *)pcm_raw_data + (i * (BLOCK_SIZE_BYTES)),
BLOCK_SIZE_BYTES);
ret = data_fifo_block_lock(&fifo_tx, &tmp_pcm_raw_data[i], BLOCK_SIZE_BYTES);
if (ret) {
LOG_ERR("Failed to lock block");
return ret;
}
}
if (debug_trans_count == DEBUG_INTERVAL_NUM) {
ret = data_fifo_num_used_get(&fifo_tx, &blocks_alloced_num, &blocks_locked_num);
if (ret) {
return ret;
}
LOG_DBG(COLOR_MAGENTA "TX alloced: %d, locked: %d" COLOR_RESET, blocks_alloced_num,
blocks_locked_num);
debug_trans_count = 0;
} else {
debug_trans_count++;
}
return 0;
}
/**@brief Initializes the FIFOs, the codec, and starts the I2S
*/
void audio_system_start(void)
{
int ret;
if (CONFIG_AUDIO_DEV == HEADSET) {
audio_headset_configure();
} else if (CONFIG_AUDIO_DEV == GATEWAY) {
audio_gateway_configure();
} else {
LOG_ERR("Invalid CONFIG_AUDIO_DEV: %d", CONFIG_AUDIO_DEV);
ERR_CHK(-EINVAL);
}
if (!fifo_tx.initialized) {
ret = data_fifo_init(&fifo_tx);
ERR_CHK_MSG(ret, "Failed to set up tx FIFO");
}
if (!fifo_rx.initialized) {
ret = data_fifo_init(&fifo_rx);
ERR_CHK_MSG(ret, "Failed to set up rx FIFO");
}
ret = sw_codec_init(sw_codec_cfg);
ERR_CHK_MSG(ret, "Failed to set up codec");
sw_codec_cfg.initialized = true;
if (sw_codec_cfg.encoder.enabled && encoder_thread_id == NULL) {
encoder_thread_id = k_thread_create(
&encoder_thread_data, encoder_thread_stack, CONFIG_ENCODER_STACK_SIZE,
(k_thread_entry_t)encoder_thread, NULL, NULL, NULL,
K_PRIO_PREEMPT(CONFIG_ENCODER_THREAD_PRIO), 0, K_NO_WAIT);
ret = k_thread_name_set(encoder_thread_id, "ENCODER");
ERR_CHK(ret);
}
#if ((CONFIG_AUDIO_SOURCE_USB) && (CONFIG_AUDIO_DEV == GATEWAY))
ret = audio_usb_start(&fifo_tx, &fifo_rx);
ERR_CHK(ret);
#else
ret = hw_codec_default_conf_enable();
ERR_CHK(ret);
ret = audio_datapath_start(&fifo_rx);
ERR_CHK(ret);
#endif /* ((CONFIG_AUDIO_SOURCE_USB) && (CONFIG_AUDIO_DEV == GATEWAY))) */
}
void audio_system_stop(void)
{
int ret;
if (!sw_codec_cfg.initialized) {
LOG_WRN("Codec already unitialized");
return;
}
LOG_DBG("Stopping codec");
#if ((CONFIG_AUDIO_DEV == GATEWAY) && CONFIG_AUDIO_SOURCE_USB)
audio_usb_stop();
#else
ret = hw_codec_soft_reset();
ERR_CHK(ret);
ret = audio_datapath_stop();
ERR_CHK(ret);
#endif /* ((CONFIG_AUDIO_DEV == GATEWAY) && CONFIG_AUDIO_SOURCE_USB) */
ret = sw_codec_uninit(sw_codec_cfg);
ERR_CHK_MSG(ret, "Failed to uninit codec");
sw_codec_cfg.initialized = false;
data_fifo_empty(&fifo_rx);
data_fifo_empty(&fifo_tx);
}
int audio_system_fifo_rx_block_drop(void)
{
int ret;
void *temp;
size_t temp_size;
ret = data_fifo_pointer_last_filled_get(&fifo_rx, &temp, &temp_size, K_NO_WAIT);
if (ret) {
LOG_WRN("Failed to get last filled block");
return -ECANCELED;
}
data_fifo_block_free(&fifo_rx, temp);
LOG_DBG("Block dropped");
return 0;
}
int audio_system_decoder_num_ch_get(void)
{
return sw_codec_cfg.decoder.num_ch;
}
int audio_system_init(void)
{
int ret;
#if ((CONFIG_AUDIO_DEV == GATEWAY) && (CONFIG_AUDIO_SOURCE_USB))
ret = audio_usb_init();
if (ret) {
LOG_ERR("Failed to initialize USB: %d", ret);
return ret;
}
#else
ret = audio_datapath_init();
if (ret) {
LOG_ERR("Failed to initialize audio datapath: %d", ret);
return ret;
}
ret = hw_codec_init();
if (ret) {
LOG_ERR("Failed to initialize HW codec: %d", ret);
return ret;
}
#endif
k_poll_signal_init(&encoder_sig);
return 0;
}
static int cmd_audio_system_start(const struct shell *shell, size_t argc, const char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
audio_system_start();
shell_print(shell, "Audio system started");
return 0;
}
static int cmd_audio_system_stop(const struct shell *shell, size_t argc, const char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
audio_system_stop();
shell_print(shell, "Audio system stopped");
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(audio_system_cmd,
SHELL_COND_CMD(CONFIG_SHELL, start, NULL, "Start the audio system",
cmd_audio_system_start),
SHELL_COND_CMD(CONFIG_SHELL, stop, NULL, "Stop the audio system",
cmd_audio_system_stop),
SHELL_SUBCMD_SET_END);
SHELL_CMD_REGISTER(audio_system, &audio_system_cmd, "Audio system commands", NULL);

111
src/audio/audio_system.h Normal file
View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _AUDIO_SYSTEM_H_
#define _AUDIO_SYSTEM_H_
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#define VALUE_NOT_SET 0
/**
* @brief Start the execution of the encoder thread.
*/
void audio_system_encoder_start(void);
/**
* @brief Stop the encoder thread from executing.
*
* @note Using this allows the encode thread to always be enabled,
* but disables the execution when not needed, saving power.
*/
void audio_system_encoder_stop(void);
/**
* @brief Toggle a test tone on and off.
*
* @note A stream must already be running to use this feature.
*
* @param[in] freq Desired frequency of tone. Off if set to 0.
*
* @retval -ENOMEM The frequency is too low (buffer overflow).
* @retval 0 Success.
*/
int audio_system_encode_test_tone_set(uint32_t freq);
/**
* @brief Step through different test tones.
*
* @note A stream must already be running to use this feature.
* Will step through test tones: 1 kHz, 2 kHz, 4 kHz and off.
*
* @return 0 on success, error otherwise.
*/
int audio_system_encode_test_tone_step(void);
/**
* @brief Set the sample rates for the encoder and the decoder, and the bit rate for encoder.
*
* @note If any of the values are 0, the corresponding configuration will not be set.
*
* @param[in] encoder_sample_rate_hz Sample rate to be used by the encoder; can be 0.
* @param[in] encoder_bitrate Bit rate to be used by the encoder (bps); can be 0.
* @param[in] decoder_sample_rate_hz Sample rate to be used by the decoder; can be 0.
*
* @retval -EINVAL Invalid sample rate given.
* @retval 0 On success.
*/
int audio_system_config_set(uint32_t encoder_sample_rate_hz, uint32_t encoder_bitrate,
uint32_t decoder_sample_rate_hz);
/**
* @brief Decode data and then add it to TX FIFO buffer.
*
* @param[in] encoded_data Pointer to encoded data.
* @param[in] encoded_data_size Size of encoded data.
* @param[in] bad_frame Indication on missed or incomplete frame.
*
* @return 0 on success, error otherwise.
*/
int audio_system_decode(void const *const encoded_data, size_t encoded_data_size, bool bad_frame);
/**
* @brief Initialize and start both HW and SW audio codec.
*/
void audio_system_start(void);
/**
* @brief Stop all activities related to audio.
*/
void audio_system_stop(void);
/**
* @brief Drop oldest block from the fifo_rx buffer.
*
* @note This can be used to reduce latency by adjusting the timing of the completed frame
* that was sampled in relation to the connection interval in Bluetooth LE.
*
* @return 0 on success, -ECANCELED otherwise.
*/
int audio_system_fifo_rx_block_drop(void);
/**
* @brief Get number of decoder channels.
*
* @return Number of decoder channels.
*/
int audio_system_decoder_num_ch_get(void);
/**
* @brief Initialize the audio_system.
*
* @return 0 on success, error otherwise.
*/
int audio_system_init(void);
#endif /* _AUDIO_SYSTEM_H_ */

204
src/audio/le_audio_rx.c Normal file
View File

@@ -0,0 +1,204 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include <zephyr/kernel.h>
#include <nrfx_clock.h>
#include "streamctrl.h"
#include "audio_datapath.h"
#include "macros_common.h"
#include "audio_system.h"
#include "audio_sync_timer.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(le_audio_rx, CONFIG_LE_AUDIO_RX_LOG_LEVEL);
struct ble_iso_data {
uint8_t data[CONFIG_BT_ISO_RX_MTU];
size_t data_size;
bool bad_frame;
uint32_t sdu_ref;
uint32_t recv_frame_ts;
} __packed;
struct rx_stats {
uint32_t recv_cnt;
uint32_t bad_frame_cnt;
uint32_t data_size_mismatch_cnt;
};
static bool initialized;
static struct k_thread audio_datapath_thread_data;
static k_tid_t audio_datapath_thread_id;
K_THREAD_STACK_DEFINE(audio_datapath_thread_stack, CONFIG_AUDIO_DATAPATH_STACK_SIZE);
DATA_FIFO_DEFINE(ble_fifo_rx, CONFIG_BUF_BLE_RX_PACKET_NUM, WB_UP(sizeof(struct ble_iso_data)));
/* Callback for handling ISO RX */
void le_audio_rx_data_handler(uint8_t const *const p_data, size_t data_size, bool bad_frame,
uint32_t sdu_ref, enum audio_channel channel_index,
size_t desired_data_size)
{
int ret;
uint32_t blocks_alloced_num, blocks_locked_num;
struct ble_iso_data *iso_received = NULL;
static struct rx_stats rx_stats[AUDIO_CH_NUM];
static uint32_t num_overruns;
static uint32_t num_thrown;
if (!initialized) {
ERR_CHK_MSG(-EPERM, "Data received but le_audio_rx is not initialized");
}
/* Capture timestamp of when audio frame is received */
uint32_t recv_frame_ts = audio_sync_timer_capture();
rx_stats[channel_index].recv_cnt++;
if (data_size != desired_data_size) {
/* A valid frame should always be equal to desired_data_size, set bad_frame
* if that is not the case
*/
bad_frame = true;
rx_stats[channel_index].data_size_mismatch_cnt++;
}
if (bad_frame) {
rx_stats[channel_index].bad_frame_cnt++;
}
if ((rx_stats[channel_index].recv_cnt % 100) == 0 && rx_stats[channel_index].recv_cnt) {
/* NOTE: The string below is used by the Nordic CI system */
LOG_DBG("ISO RX SDUs: Ch: %d Total: %d Bad: %d Size mismatch %d", channel_index,
rx_stats[channel_index].recv_cnt, rx_stats[channel_index].bad_frame_cnt,
rx_stats[channel_index].data_size_mismatch_cnt);
}
if (stream_state_get() != STATE_STREAMING) {
/* Throw away data */
num_thrown++;
if ((num_thrown % 100) == 1) {
LOG_WRN("Not in streaming state (%d), thrown %d packet(s)",
stream_state_get(), num_thrown);
}
return;
}
if (channel_index != AUDIO_CH_L && (CONFIG_AUDIO_DEV == GATEWAY)) {
/* Only left channel RX data in use on gateway */
return;
}
ret = data_fifo_num_used_get(&ble_fifo_rx, &blocks_alloced_num, &blocks_locked_num);
ERR_CHK(ret);
if (blocks_alloced_num >= CONFIG_BUF_BLE_RX_PACKET_NUM) {
/* FIFO buffer is full, swap out oldest frame for a new one */
void *stale_data;
size_t stale_size;
num_overruns++;
if ((num_overruns % 100) == 1) {
LOG_WRN("BLE ISO RX overrun: Num: %d", num_overruns);
}
ret = data_fifo_pointer_last_filled_get(&ble_fifo_rx, &stale_data, &stale_size,
K_NO_WAIT);
ERR_CHK(ret);
data_fifo_block_free(&ble_fifo_rx, stale_data);
}
ret = data_fifo_pointer_first_vacant_get(&ble_fifo_rx, (void *)&iso_received, K_NO_WAIT);
ERR_CHK_MSG(ret, "Unable to get FIFO pointer");
if (data_size > ARRAY_SIZE(iso_received->data)) {
ERR_CHK_MSG(-ENOMEM, "Data size too large for buffer");
return;
}
memcpy(iso_received->data, p_data, data_size);
iso_received->bad_frame = bad_frame;
iso_received->data_size = data_size;
iso_received->sdu_ref = sdu_ref;
iso_received->recv_frame_ts = recv_frame_ts;
ret = data_fifo_block_lock(&ble_fifo_rx, (void *)&iso_received,
sizeof(struct ble_iso_data));
ERR_CHK_MSG(ret, "Failed to lock block");
}
/**
* @brief Receive data from BLE through a k_fifo and send to USB or audio datapath.
*/
static void audio_datapath_thread(void *dummy1, void *dummy2, void *dummy3)
{
int ret;
struct ble_iso_data *iso_received = NULL;
size_t iso_received_size;
while (1) {
ret = data_fifo_pointer_last_filled_get(&ble_fifo_rx, (void *)&iso_received,
&iso_received_size, K_FOREVER);
ERR_CHK(ret);
if (IS_ENABLED(CONFIG_AUDIO_SOURCE_USB) && (CONFIG_AUDIO_DEV == GATEWAY)) {
ret = audio_system_decode(iso_received->data, iso_received->data_size,
iso_received->bad_frame);
ERR_CHK(ret);
} else {
audio_datapath_stream_out(iso_received->data, iso_received->data_size,
iso_received->sdu_ref, iso_received->bad_frame,
iso_received->recv_frame_ts);
}
data_fifo_block_free(&ble_fifo_rx, (void *)iso_received);
STACK_USAGE_PRINT("audio_datapath_thread", &audio_datapath_thread_data);
}
}
static int audio_datapath_thread_create(void)
{
int ret;
audio_datapath_thread_id = k_thread_create(
&audio_datapath_thread_data, audio_datapath_thread_stack,
CONFIG_AUDIO_DATAPATH_STACK_SIZE, (k_thread_entry_t)audio_datapath_thread, NULL,
NULL, NULL, K_PRIO_PREEMPT(CONFIG_AUDIO_DATAPATH_THREAD_PRIO), 0, K_NO_WAIT);
ret = k_thread_name_set(audio_datapath_thread_id, "AUDIO_DATAPATH");
if (ret) {
LOG_ERR("Failed to create audio_datapath thread");
return ret;
}
return 0;
}
int le_audio_rx_init(void)
{
int ret;
if (initialized) {
return -EALREADY;
}
ret = data_fifo_init(&ble_fifo_rx);
if (ret) {
LOG_ERR("Failed to set up ble_rx FIFO");
return ret;
}
ret = audio_datapath_thread_create();
if (ret) {
return ret;
}
initialized = true;
return 0;
}

31
src/audio/le_audio_rx.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _LE_AUDIO_RX_H_
#define _LE_AUDIO_RX_H_
/**
* @brief Data handler when ISO data has been received.
*
* @param[in] p_data Pointer to the received data.
* @param[in] data_size Size of the received data.
* @param[in] bad_frame Bad frame flag. (I.e. set for missed ISO data).
* @param[in] sdu_ref SDU reference timestamp.
* @param[in] channel_index Which channel is received.
* @param[in] desired_data_size The expected data size.
*/
void le_audio_rx_data_handler(uint8_t const *const p_data, size_t data_size, bool bad_frame,
uint32_t sdu_ref, enum audio_channel channel_index,
size_t desired_data_size);
/**
* @brief Initialize the receive audio path.
*
* @return 0 if successful, error otherwise.
*/
int le_audio_rx_init(void);
#endif /* _LE_AUDIO_RX_H_ */

35
src/audio/streamctrl.h Normal file
View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _STREAMCTRL_H_
#define _STREAMCTRL_H_
#include <stddef.h>
#include <zephyr/kernel.h>
/* State machine states for peer or stream. */
enum stream_state {
STATE_STREAMING,
STATE_PAUSED,
};
/**
* @brief Get the current streaming state.
*
* @return strm_state enum value.
*/
uint8_t stream_state_get(void);
/**
* @brief Send audio data over the stream.
*
* @param data Data to send.
* @param size Size of data.
* @param num_ch Number of audio channels.
*/
void streamctrl_send(void const *const data, size_t size, uint8_t num_ch);
#endif /* _STREAMCTRL_H_ */

464
src/audio/sw_codec_select.c Normal file
View File

@@ -0,0 +1,464 @@
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "sw_codec_select.h"
#include <zephyr/kernel.h>
#include <errno.h>
#include <pcm_stream_channel_modifier.h>
#include <sample_rate_converter.h>
#if (CONFIG_SW_CODEC_LC3)
#include "sw_codec_lc3.h"
#endif /* (CONFIG_SW_CODEC_LC3) */
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sw_codec_select, CONFIG_SW_CODEC_SELECT_LOG_LEVEL);
static struct sw_codec_config m_config;
static struct sample_rate_converter_ctx encoder_converters[AUDIO_CH_NUM];
static struct sample_rate_converter_ctx decoder_converters[AUDIO_CH_NUM];
/**
* @brief Converts the sample rate of the uncompressed audio stream if needed.
*
* @details Two buffers must be made available for the function: the input_data buffer that
* contains the samples for the audio stream, and the conversion buffer that will be
* used to store the converted audio stream. data_ptr will point to conversion_buffer
* if a conversion took place; otherwise, it will point to input_data.
*
* @param[in] ctx Sample rate converter context.
* @param[in] input_sample_rate Input sample rate.
* @param[in] output_sample_rate Output sample rate.
* @param[in] input_data Data coming in. Buffer is assumed to be of size
* PCM_NUM_BYTES_MONO.
* @param[in] input_data_size Size of input data.
* @param[in] conversion_buffer Buffer to perform sample rate conversion. Must be of size
* PCM_NUM_BYTES_MONO.
* @param[out] data_ptr Pointer to the data to be used from this point on.
* Will point to either @p input_data or @p conversion_buffer.
* @param[out] output_size Number of bytes out.
*
* @retval -ENOTSUP Sample rates are not equal, and the sample rate conversion has not
*been enabled in the application.
* @retval 0 Success.
*/
static int sw_codec_sample_rate_convert(struct sample_rate_converter_ctx *ctx,
uint32_t input_sample_rate, uint32_t output_sample_rate,
char *input_data, size_t input_data_size,
char *conversion_buffer, char **data_ptr,
size_t *output_size)
{
int ret;
if (input_sample_rate == output_sample_rate) {
*data_ptr = input_data;
*output_size = input_data_size;
} else if (IS_ENABLED(CONFIG_SAMPLE_RATE_CONVERTER)) {
ret = sample_rate_converter_process(ctx, SAMPLE_RATE_FILTER_SIMPLE, input_data,
input_data_size, input_sample_rate,
conversion_buffer, PCM_NUM_BYTES_MONO,
output_size, output_sample_rate);
if (ret) {
LOG_ERR("Failed to convert sample rate: %d", ret);
return ret;
}
*data_ptr = conversion_buffer;
} else {
LOG_ERR("Sample rates are not equal, and sample rate conversion has not been "
"enabled in the application.");
return -ENOTSUP;
}
return 0;
}
bool sw_codec_is_initialized(void)
{
return m_config.initialized;
}
int sw_codec_encode(void *pcm_data, size_t pcm_size, uint8_t **encoded_data, size_t *encoded_size)
{
int ret;
/* Temp storage for split stereo PCM signal */
char pcm_data_mono_system_sample_rate[AUDIO_CH_NUM][PCM_NUM_BYTES_MONO] = {0};
/* Make sure we have enough space for two frames (stereo) */
static uint8_t m_encoded_data[ENC_MAX_FRAME_SIZE * AUDIO_CH_NUM];
char pcm_data_mono_converted_buf[AUDIO_CH_NUM][PCM_NUM_BYTES_MONO] = {0};
size_t pcm_block_size_mono_system_sample_rate;
size_t pcm_block_size_mono;
if (!m_config.encoder.enabled) {
LOG_ERR("Encoder has not been initialized");
return -ENXIO;
}
switch (m_config.sw_codec) {
case SW_CODEC_LC3: {
#if (CONFIG_SW_CODEC_LC3)
uint16_t encoded_bytes_written;
char *pcm_data_mono_ptrs[m_config.encoder.channel_mode];
/* Since LC3 is a single channel codec, we must split the
* stereo PCM stream
*/
ret = pscm_two_channel_split(pcm_data, pcm_size, CONFIG_AUDIO_BIT_DEPTH_BITS,
pcm_data_mono_system_sample_rate[AUDIO_CH_L],
pcm_data_mono_system_sample_rate[AUDIO_CH_R],
&pcm_block_size_mono_system_sample_rate);
if (ret) {
return ret;
}
for (int i = 0; i < m_config.encoder.channel_mode; ++i) {
ret = sw_codec_sample_rate_convert(
&encoder_converters[i], CONFIG_AUDIO_SAMPLE_RATE_HZ,
m_config.encoder.sample_rate_hz,
pcm_data_mono_system_sample_rate[i],
pcm_block_size_mono_system_sample_rate,
pcm_data_mono_converted_buf[i], &pcm_data_mono_ptrs[i],
&pcm_block_size_mono);
if (ret) {
LOG_ERR("Sample rate conversion failed for channel %d: %d", i, ret);
return ret;
}
}
switch (m_config.encoder.channel_mode) {
case SW_CODEC_MONO: {
ret = sw_codec_lc3_enc_run(pcm_data_mono_ptrs[AUDIO_CH_L],
pcm_block_size_mono, LC3_USE_BITRATE_FROM_INIT,
0, sizeof(m_encoded_data), m_encoded_data,
&encoded_bytes_written);
if (ret) {
return ret;
}
break;
}
case SW_CODEC_STEREO: {
ret = sw_codec_lc3_enc_run(pcm_data_mono_ptrs[AUDIO_CH_L],
pcm_block_size_mono, LC3_USE_BITRATE_FROM_INIT,
AUDIO_CH_L, sizeof(m_encoded_data),
m_encoded_data, &encoded_bytes_written);
if (ret) {
return ret;
}
ret = sw_codec_lc3_enc_run(
pcm_data_mono_ptrs[AUDIO_CH_R], pcm_block_size_mono,
LC3_USE_BITRATE_FROM_INIT, AUDIO_CH_R,
sizeof(m_encoded_data) - encoded_bytes_written,
m_encoded_data + encoded_bytes_written, &encoded_bytes_written);
if (ret) {
return ret;
}
encoded_bytes_written += encoded_bytes_written;
break;
}
default:
LOG_ERR("Unsupported channel mode for encoder: %d",
m_config.encoder.channel_mode);
return -ENODEV;
}
*encoded_data = m_encoded_data;
*encoded_size = encoded_bytes_written;
#endif /* (CONFIG_SW_CODEC_LC3) */
break;
}
default:
LOG_ERR("Unsupported codec: %d", m_config.sw_codec);
return -ENODEV;
}
return 0;
}
int sw_codec_decode(uint8_t const *const encoded_data, size_t encoded_size, bool bad_frame,
void **decoded_data, size_t *decoded_size)
{
if (!m_config.decoder.enabled) {
LOG_ERR("Decoder has not been initialized");
return -ENXIO;
}
int ret;
static char pcm_data_stereo[PCM_NUM_BYTES_STEREO];
char decoded_data_mono[AUDIO_CH_NUM][PCM_NUM_BYTES_MONO] = {0};
char decoded_data_mono_system_sample_rate[AUDIO_CH_NUM][PCM_NUM_BYTES_MONO] = {0};
size_t pcm_size_stereo = 0;
size_t pcm_size_mono = 0;
size_t decoded_data_size = 0;
switch (m_config.sw_codec) {
case SW_CODEC_LC3: {
#if (CONFIG_SW_CODEC_LC3)
char *pcm_in_data_ptrs[m_config.decoder.channel_mode];
switch (m_config.decoder.channel_mode) {
case SW_CODEC_MONO: {
if (bad_frame && IS_ENABLED(CONFIG_SW_CODEC_OVERRIDE_PLC)) {
memset(decoded_data_mono[AUDIO_CH_L], 0, PCM_NUM_BYTES_MONO);
decoded_data_size = PCM_NUM_BYTES_MONO;
} else {
ret = sw_codec_lc3_dec_run(
encoded_data, encoded_size, LC3_PCM_NUM_BYTES_MONO, 0,
decoded_data_mono[AUDIO_CH_L],
(uint16_t *)&decoded_data_size, bad_frame);
if (ret) {
return ret;
}
ret = sw_codec_sample_rate_convert(
&decoder_converters[AUDIO_CH_L],
m_config.decoder.sample_rate_hz,
CONFIG_AUDIO_SAMPLE_RATE_HZ, decoded_data_mono[AUDIO_CH_L],
decoded_data_size,
decoded_data_mono_system_sample_rate[AUDIO_CH_L],
&pcm_in_data_ptrs[AUDIO_CH_L], &pcm_size_mono);
if (ret) {
LOG_ERR("Sample rate conversion failed for mono: %d", ret);
return ret;
}
}
/* For now, i2s is only stereo, so in order to send
* just one channel, we need to insert 0 for the
* other channel
*/
ret = pscm_zero_pad(pcm_in_data_ptrs[AUDIO_CH_L], pcm_size_mono,
m_config.decoder.audio_ch, CONFIG_AUDIO_BIT_DEPTH_BITS,
pcm_data_stereo, &pcm_size_stereo);
if (ret) {
return ret;
}
break;
}
case SW_CODEC_STEREO: {
if (bad_frame && IS_ENABLED(CONFIG_SW_CODEC_OVERRIDE_PLC)) {
memset(decoded_data_mono[AUDIO_CH_L], 0, PCM_NUM_BYTES_MONO);
memset(decoded_data_mono[AUDIO_CH_R], 0, PCM_NUM_BYTES_MONO);
decoded_data_size = PCM_NUM_BYTES_MONO;
} else {
/* Decode left channel */
ret = sw_codec_lc3_dec_run(
encoded_data, encoded_size / 2, LC3_PCM_NUM_BYTES_MONO,
AUDIO_CH_L, decoded_data_mono[AUDIO_CH_L],
(uint16_t *)&decoded_data_size, bad_frame);
if (ret) {
return ret;
}
/* Decode right channel */
ret = sw_codec_lc3_dec_run(
(encoded_data + (encoded_size / 2)), encoded_size / 2,
LC3_PCM_NUM_BYTES_MONO, AUDIO_CH_R,
decoded_data_mono[AUDIO_CH_R],
(uint16_t *)&decoded_data_size, bad_frame);
if (ret) {
return ret;
}
for (int i = 0; i < m_config.decoder.channel_mode; ++i) {
ret = sw_codec_sample_rate_convert(
&decoder_converters[i],
m_config.decoder.sample_rate_hz,
CONFIG_AUDIO_SAMPLE_RATE_HZ, decoded_data_mono[i],
decoded_data_size,
decoded_data_mono_system_sample_rate[i],
&pcm_in_data_ptrs[i], &pcm_size_mono);
if (ret) {
LOG_ERR("Sample rate conversion failed for channel "
"%d : %d",
i, ret);
return ret;
}
}
}
ret = pscm_combine(pcm_in_data_ptrs[AUDIO_CH_L],
pcm_in_data_ptrs[AUDIO_CH_R], pcm_size_mono,
CONFIG_AUDIO_BIT_DEPTH_BITS, pcm_data_stereo,
&pcm_size_stereo);
if (ret) {
return ret;
}
break;
}
default:
LOG_ERR("Unsupported channel mode for decoder: %d",
m_config.decoder.channel_mode);
return -ENODEV;
}
*decoded_size = pcm_size_stereo;
*decoded_data = pcm_data_stereo;
#endif /* (CONFIG_SW_CODEC_LC3) */
break;
}
default:
LOG_ERR("Unsupported codec: %d", m_config.sw_codec);
return -ENODEV;
}
return 0;
}
int sw_codec_uninit(struct sw_codec_config sw_codec_cfg)
{
int ret;
if (m_config.sw_codec != sw_codec_cfg.sw_codec) {
LOG_ERR("Trying to uninit a codec that is not first initialized");
return -ENODEV;
}
switch (m_config.sw_codec) {
case SW_CODEC_LC3:
#if (CONFIG_SW_CODEC_LC3)
if (sw_codec_cfg.encoder.enabled) {
if (!m_config.encoder.enabled) {
LOG_ERR("Trying to uninit encoder, it has not been "
"initialized");
return -EALREADY;
}
ret = sw_codec_lc3_enc_uninit_all();
if (ret) {
return ret;
}
m_config.encoder.enabled = false;
}
if (sw_codec_cfg.decoder.enabled) {
if (!m_config.decoder.enabled) {
LOG_WRN("Trying to uninit decoder, it has not been "
"initialized");
return -EALREADY;
}
ret = sw_codec_lc3_dec_uninit_all();
if (ret) {
return ret;
}
m_config.decoder.enabled = false;
}
#endif /* (CONFIG_SW_CODEC_LC3) */
break;
default:
LOG_ERR("Unsupported codec: %d", m_config.sw_codec);
return false;
}
m_config.initialized = false;
return 0;
}
int sw_codec_init(struct sw_codec_config sw_codec_cfg)
{
int ret;
switch (sw_codec_cfg.sw_codec) {
case SW_CODEC_LC3: {
#if (CONFIG_SW_CODEC_LC3)
if (m_config.sw_codec != SW_CODEC_LC3) {
/* Check if LC3 is already initialized */
ret = sw_codec_lc3_init(NULL, NULL, CONFIG_AUDIO_FRAME_DURATION_US);
if (ret) {
return ret;
}
}
if (sw_codec_cfg.encoder.enabled) {
if (m_config.encoder.enabled) {
LOG_WRN("The LC3 encoder is already initialized");
return -EALREADY;
}
uint16_t pcm_bytes_req_enc;
LOG_DBG("Encode: %dHz %dbits %dus %dbps %d channel(s)",
sw_codec_cfg.encoder.sample_rate_hz, CONFIG_AUDIO_BIT_DEPTH_BITS,
CONFIG_AUDIO_FRAME_DURATION_US, sw_codec_cfg.encoder.bitrate,
sw_codec_cfg.encoder.num_ch);
ret = sw_codec_lc3_enc_init(
sw_codec_cfg.encoder.sample_rate_hz, CONFIG_AUDIO_BIT_DEPTH_BITS,
CONFIG_AUDIO_FRAME_DURATION_US, sw_codec_cfg.encoder.bitrate,
sw_codec_cfg.encoder.num_ch, &pcm_bytes_req_enc);
if (ret) {
return ret;
}
}
if (sw_codec_cfg.decoder.enabled) {
if (m_config.decoder.enabled) {
LOG_WRN("The LC3 decoder is already initialized");
return -EALREADY;
}
LOG_DBG("Decode: %dHz %dbits %dus %d channel(s)",
sw_codec_cfg.decoder.sample_rate_hz, CONFIG_AUDIO_BIT_DEPTH_BITS,
CONFIG_AUDIO_FRAME_DURATION_US, sw_codec_cfg.decoder.num_ch);
ret = sw_codec_lc3_dec_init(
sw_codec_cfg.decoder.sample_rate_hz, CONFIG_AUDIO_BIT_DEPTH_BITS,
CONFIG_AUDIO_FRAME_DURATION_US, sw_codec_cfg.decoder.num_ch);
if (ret) {
return ret;
}
}
break;
#else
LOG_ERR("LC3 is not compiled in, please open menuconfig and select "
"LC3");
return -ENODEV;
#endif /* (CONFIG_SW_CODEC_LC3) */
}
default:
LOG_ERR("Unsupported codec: %d", sw_codec_cfg.sw_codec);
return false;
}
if (sw_codec_cfg.encoder.enabled && IS_ENABLED(SAMPLE_RATE_CONVERTER)) {
for (int i = 0; i < sw_codec_cfg.encoder.channel_mode; i++) {
ret = sample_rate_converter_open(&encoder_converters[i]);
if (ret) {
LOG_ERR("Failed to initialize the sample rate converter for "
"encoding channel %d: %d",
i, ret);
return ret;
}
}
}
if (sw_codec_cfg.decoder.enabled && IS_ENABLED(SAMPLE_RATE_CONVERTER)) {
for (int i = 0; i < sw_codec_cfg.decoder.channel_mode; i++) {
ret = sample_rate_converter_open(&decoder_converters[i]);
if (ret) {
LOG_ERR("Failed to initialize the sample rate converter for "
"decoding channel %d: %d",
i, ret);
return ret;
}
}
}
m_config = sw_codec_cfg;
m_config.initialized = true;
return 0;
}

131
src/audio/sw_codec_select.h Normal file
View File

@@ -0,0 +1,131 @@
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _SW_CODEC_SELECT_H_
#define _SW_CODEC_SELECT_H_
#include <zephyr/kernel.h>
#include "channel_assignment.h"
#if (CONFIG_SW_CODEC_LC3)
#define LC3_MAX_FRAME_SIZE_MS 10
#define LC3_ENC_MONO_FRAME_SIZE (CONFIG_LC3_BITRATE_MAX * LC3_MAX_FRAME_SIZE_MS / (8 * 1000))
#define LC3_PCM_NUM_BYTES_MONO \
(CONFIG_AUDIO_SAMPLE_RATE_HZ * CONFIG_AUDIO_BIT_DEPTH_OCTETS * LC3_MAX_FRAME_SIZE_MS / 1000)
#define LC3_ENC_TIME_US 3000
#define LC3_DEC_TIME_US 1500
#else
#define LC3_ENC_MONO_FRAME_SIZE 0
#define LC3_PCM_NUM_BYTES_MONO 0
#define LC3_ENC_TIME_US 0
#define LC3_DEC_TIME_US 0
#endif /* CONFIG_SW_CODEC_LC3 */
/* Max will be used when multiple codecs are supported */
#define ENC_MAX_FRAME_SIZE MAX(LC3_ENC_MONO_FRAME_SIZE, 0)
#define ENC_TIME_US MAX(LC3_ENC_TIME_US, 0)
#define DEC_TIME_US MAX(LC3_DEC_TIME_US, 0)
#define PCM_NUM_BYTES_MONO MAX(LC3_PCM_NUM_BYTES_MONO, 0)
#define PCM_NUM_BYTES_STEREO (PCM_NUM_BYTES_MONO * 2)
enum sw_codec_select {
SW_CODEC_NONE,
SW_CODEC_LC3, /* Low Complexity Communication Codec */
};
enum sw_codec_channel_mode {
SW_CODEC_MONO = 1,
SW_CODEC_STEREO,
};
struct sw_codec_encoder {
bool enabled;
int bitrate;
enum sw_codec_channel_mode channel_mode;
uint8_t num_ch;
enum audio_channel audio_ch;
uint32_t sample_rate_hz;
};
struct sw_codec_decoder {
bool enabled;
enum sw_codec_channel_mode channel_mode; /* Mono or stereo. */
uint8_t num_ch; /* Number of decoder channels. */
enum audio_channel audio_ch; /* Used to choose which channel to use. */
uint32_t sample_rate_hz;
};
/**
* @brief Sw_codec configuration structure.
*/
struct sw_codec_config {
enum sw_codec_select sw_codec; /* sw_codec to be used, e.g. LC3, etc. */
struct sw_codec_decoder decoder; /* Struct containing settings for decoder. */
struct sw_codec_encoder encoder; /* Struct containing settings for encoder. */
bool initialized; /* Status of codec. */
};
/**
* @brief Check if the software codec is initialized.
*
* @retval true SW codec is initialized.
* @retval false SW codec is not initialized.
*/
bool sw_codec_is_initialized(void);
/**
* @brief Encode PCM data and output encoded data.
*
* @note Takes in stereo PCM stream, will encode either one or two
* channels, based on channel_mode set during init.
*
* @param[in] pcm_data Pointer to PCM data.
* @param[in] pcm_size Size of PCM data.
* @param[out] encoded_data Pointer to buffer to store encoded data.
* @param[out] encoded_size Size of encoded data.
*
* @return 0 if success, error codes depends on sw_codec selected.
*/
int sw_codec_encode(void *pcm_data, size_t pcm_size, uint8_t **encoded_data, size_t *encoded_size);
/**
* @brief Decode encoded data and output PCM data.
*
* @param[in] encoded_data Pointer to encoded data.
* @param[in] encoded_size Size of encoded data.
* @param[in] bad_frame Flag to indicate a missing/bad frame (only LC3).
* @param[out] pcm_data Pointer to buffer to store decoded PCM data.
* @param[out] pcm_size Size of decoded data.
*
* @return 0 if success, error codes depends on sw_codec selected.
*/
int sw_codec_decode(uint8_t const *const encoded_data, size_t encoded_size, bool bad_frame,
void **pcm_data, size_t *pcm_size);
/**
* @brief Uninitialize the software codec and free the allocated space.
*
* @note Must be called before calling init for another sw_codec.
*
* @param[in] sw_codec_cfg Struct to tear down sw_codec.
*
* @return 0 if success, error codes depends on sw_codec selected.
*/
int sw_codec_uninit(struct sw_codec_config sw_codec_cfg);
/**
* @brief Initialize the software codec and statically or dynamically
* allocate memory to be used, depending on the selected codec
* and its configuration.
*
* @param[in] sw_codec_cfg Struct to set up sw_codec.
*
* @return 0 if success, error codes depends on sw_codec selected.
*/
int sw_codec_init(struct sw_codec_config sw_codec_cfg);
#endif /* _SW_CODEC_SELECT_H_ */

View File

@@ -0,0 +1,17 @@
#
# Copyright (c) 2022 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
add_subdirectory(bt_management)
add_subdirectory(bt_rendering_and_capture)
add_subdirectory(bt_content_control)
add_subdirectory(bt_stream)
zephyr_library_include_directories(
bt_management
bt_rendering_and_capture
bt_content_control
bt_stream
)

129
src/bluetooth/Kconfig Normal file
View File

@@ -0,0 +1,129 @@
#
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
rsource "Kconfig.defaults"
menu "Bluetooth"
rsource "bt_management/Kconfig"
#----------------------------------------------------------------------------#
menu "Bluetooth audio"
if TRANSPORT_BIS
rsource "bt_stream/broadcast/Kconfig"
endif # TRANSPORT_BIS
if TRANSPORT_CIS
rsource "bt_stream/unicast/Kconfig"
endif # TRANSPORT_CIS
config BT_AUDIO_PACKING_INTERLEAVED
bool "Interleaved packing"
default n
help
ISO channels can either be interleaved or sequentially packed; sequential is the default one.
config BT_AUDIO_PREF_SAMPLE_RATE_VALUE
hex
default 0x03 if BT_AUDIO_PREF_SAMPLE_RATE_16KHZ
default 0x05 if BT_AUDIO_PREF_SAMPLE_RATE_24KHZ
default 0x08 if BT_AUDIO_PREF_SAMPLE_RATE_48KHZ
choice BT_AUDIO_PREF_SAMPLE_RATE
prompt "Preferred BT audio sample rate"
default BT_AUDIO_PREF_SAMPLE_RATE_16KHZ if BT_BAP_BROADCAST_16_2_1
default BT_AUDIO_PREF_SAMPLE_RATE_16KHZ if BT_BAP_BROADCAST_16_2_2
default BT_AUDIO_PREF_SAMPLE_RATE_16KHZ if BT_BAP_UNICAST_16_2_1
default BT_AUDIO_PREF_SAMPLE_RATE_24KHZ if STREAM_BIDIRECTIONAL
default BT_AUDIO_PREF_SAMPLE_RATE_24KHZ if BT_BAP_BROADCAST_24_2_1
default BT_AUDIO_PREF_SAMPLE_RATE_24KHZ if BT_BAP_BROADCAST_24_2_2
default BT_AUDIO_PREF_SAMPLE_RATE_24KHZ if BT_BAP_UNICAST_24_2_1
default BT_AUDIO_PREF_SAMPLE_RATE_48KHZ
help
Select the preferred sample rate to stream if there are more than one to choose from.
Only valid when used by unicast_client if CONFIG_SAMPLE_RATE_CONVERTER=y and
CONFIG_AUDIO_SAMPLE_RATE_48000_HZ=y, meaning 16, 24, and 48kHz are supported.
config BT_AUDIO_PREF_SAMPLE_RATE_48KHZ
bool "48 kHz"
help
Select 48000 Hz as the preferred sample rate.
config BT_AUDIO_PREF_SAMPLE_RATE_24KHZ
bool "24 kHz"
help
Select 24000 Hz as the preferred sample rate.
config BT_AUDIO_PREF_SAMPLE_RATE_16KHZ
bool "16 kHz"
help
Select 16000 Hz as the preferred sample rate.
endchoice
#----------------------------------------------------------------------------#
menu "QoS"
config BT_AUDIO_PRESENTATION_DELAY_US
int "Presentation delay"
range AUDIO_MIN_PRES_DLY_US AUDIO_MAX_PRES_DLY_US
default AUDIO_MIN_PRES_DLY_US
help
The audio source/client defined presentation delay if within
AUDIO_MIN_PRES_DLY_US and AUDIO_MAX_PRES_DLY_US range. This will
override the audio receivers presentation delay as long as it
is in range of the max and min supported by the audio receivers.
If it is outside this range, then it will revert to the closest
supported value.
config BT_AUDIO_MAX_TRANSPORT_LATENCY_MS
int "Max transport latency"
range 5 4000
default 10
help
Max transport latency for the ISO link.
config BT_AUDIO_RETRANSMITS
int "Number of re-transmits"
range 0 30
default 2
help
Number of re-transmits for the ISO link. 2 re-transmits means a total
of 3 packets sent per stream.
endmenu # QoS
endmenu # Bluetooth audio
rsource "bt_rendering_and_capture/Kconfig"
rsource "bt_content_control/Kconfig"
#----------------------------------------------------------------------------#
menu "Log levels"
module = BLE
module-str = ble
source "subsys/logging/Kconfig.template.log_config"
module = BT_LE_AUDIO_TX
module-str = bt_le_audio_tx
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log levels
#----------------------------------------------------------------------------#
menu "Testing"
config TESTING_BLE_ADDRESS_RANDOM
bool "Random address and bonding clear on every restart [EXPERIMENTAL]"
default n
select EXPERIMENTAL
help
If enabled the system will generate a new address on every
restart (i.e. reset, re-flash). Any bonding information will
be cleared. This is only for testing purposes.
endmenu # Testing
endmenu # Bluetooth

View File

@@ -0,0 +1,25 @@
#
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
config BT_AUDIO
default y
config BT_DEVICE_NAME
default BT_AUDIO_BROADCAST_NAME if TRANSPORT_BIS
default "NRF5340_AUDIO"
config BT_DEVICE_NAME_DYNAMIC
default y
config BT_ECC
default y if BT
config BT_EXT_ADV
default y
# Mandatory to support at least 1 for ASCS
config BT_ATT_PREPARE_COUNT
default 1

View File

@@ -0,0 +1,17 @@
#
# Copyright (c) 2023 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
zephyr_library_include_directories(
media
)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/bt_content_ctrl.c)
if (CONFIG_BT_MCC OR CONFIG_BT_MCS)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/media/bt_content_ctrl_media.c)
endif()

View File

@@ -0,0 +1,19 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
menu "Content control"
rsource "media/Kconfig"
#----------------------------------------------------------------------------#
menu "Log level"
module = BT_CONTENT_CTRL
module-str = bt_content_ctrl
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log level
endmenu # Content control

View File

@@ -0,0 +1,149 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_content_ctrl.h"
#include <zephyr/zbus/zbus.h>
#include <zephyr/bluetooth/uuid.h>
#include "bt_content_ctrl_media_internal.h"
#include "zbus_common.h"
#include "macros_common.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_content_ctrl, CONFIG_BT_CONTENT_CTRL_LOG_LEVEL);
ZBUS_CHAN_DEFINE(cont_media_chan, struct content_control_msg, NULL, NULL, ZBUS_OBSERVERS_EMPTY,
ZBUS_MSG_INIT(0));
static void media_control_cb(bool play)
{
int ret;
struct content_control_msg msg;
if (play) {
msg.event = MEDIA_START;
} else {
msg.event = MEDIA_STOP;
}
ret = zbus_chan_pub(&cont_media_chan, &msg, K_NO_WAIT);
ERR_CHK_MSG(ret, "zbus publication failed");
}
int bt_content_ctrl_start(struct bt_conn *conn)
{
int ret;
struct content_control_msg msg;
if (IS_ENABLED(CONFIG_BT_MCC) || IS_ENABLED(CONFIG_BT_MCS)) {
ret = bt_content_ctrl_media_play(conn);
if (ret) {
LOG_WRN("Failed to change the streaming state");
return ret;
}
return 0;
}
msg.event = MEDIA_START;
ret = zbus_chan_pub(&cont_media_chan, &msg, K_NO_WAIT);
ERR_CHK_MSG(ret, "zbus publication failed");
return 0;
}
int bt_content_ctrl_stop(struct bt_conn *conn)
{
int ret;
struct content_control_msg msg;
if (IS_ENABLED(CONFIG_BT_MCC) || IS_ENABLED(CONFIG_BT_MCS)) {
ret = bt_content_ctrl_media_pause(conn);
if (ret) {
LOG_WRN("Failed to change the streaming state");
return ret;
}
return 0;
}
msg.event = MEDIA_STOP;
ret = zbus_chan_pub(&cont_media_chan, &msg, K_NO_WAIT);
ERR_CHK_MSG(ret, "zbus publication failed");
return 0;
}
int bt_content_ctrl_conn_disconnected(struct bt_conn *conn)
{
int ret;
if (IS_ENABLED(CONFIG_BT_MCC)) {
ret = bt_content_ctrl_media_conn_disconnected(conn);
/* Try to reset MCS state. -ESRCH is returned if MCS hasn't been discovered
* yet, and shouldn't cause an error print
*/
if (ret && ret != -ESRCH) {
LOG_ERR("bt_content_ctrl_media_conn_disconnected failed with %d", ret);
}
}
return 0;
}
int bt_content_ctrl_discover(struct bt_conn *conn)
{
int ret;
if (IS_ENABLED(CONFIG_BT_MCC)) {
ret = bt_content_ctrl_media_discover(conn);
if (ret) {
LOG_ERR("Failed to discover the media control client");
return ret;
}
}
return 0;
}
int bt_content_ctrl_uuid_populate(struct net_buf_simple *uuid_buf)
{
if (IS_ENABLED(CONFIG_BT_MCC)) {
if (net_buf_simple_tailroom(uuid_buf) >= BT_UUID_SIZE_16) {
net_buf_simple_add_le16(uuid_buf, BT_UUID_MCS_VAL);
} else {
return -ENOMEM;
}
}
return 0;
}
int bt_content_ctrl_init(void)
{
int ret;
if (IS_ENABLED(CONFIG_BT_MCS)) {
ret = bt_content_ctrl_media_server_init(media_control_cb);
if (ret) {
LOG_ERR("MCS server init failed");
return ret;
}
}
if (IS_ENABLED(CONFIG_BT_MCC)) {
ret = bt_content_ctrl_media_client_init();
if (ret) {
LOG_ERR("MCS client init failed");
return ret;
}
}
return 0;
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_CONTENT_CTRL_H_
#define _BT_CONTENT_CTRL_H_
#include <zephyr/bluetooth/conn.h>
/**
* @brief Send the start request for content transmission.
*
* @param[in] conn Pointer to the connection to control; can be NULL.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_start(struct bt_conn *conn);
/**
* @brief Send the stop request for content transmission.
*
* @param[in] conn Pointer to the connection to control; can be NULL.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_stop(struct bt_conn *conn);
/**
* @brief Handle disconnected connection for the content control services.
*
* @param[in] conn Pointer to the disconnected connection.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_conn_disconnected(struct bt_conn *conn);
/**
* @brief Discover the content control services for the given connection pointer.
*
* @param[in] conn Pointer to the connection on which to discover the services.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_discover(struct bt_conn *conn);
/**
* @brief Put the UUIDs from this module into the buffer.
*
* @note This partial data is used to build a complete extended advertising packet.
*
* @param[out] uuid_buf Buffer being populated with UUIDs.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_uuid_populate(struct net_buf_simple *uuid_buf);
/**
* @brief Check if the media player is playing.
*
* @retval true Media player is in a playing state.
* @retval false Media player is not in a playing state.
*/
bool bt_content_ctlr_media_state_playing(void);
/**
* @brief Initialize the content control module.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_init(void);
#endif /* _BT_CONTENT_CTRL_H_ */

View File

@@ -0,0 +1,17 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
menu "Media"
#----------------------------------------------------------------------------#
menu "Log level"
module = BT_CONTENT_CTRL_MEDIA
module-str = bt_content_ctrl_media
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log level
endmenu # Media

View File

@@ -0,0 +1,527 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_content_ctrl_media_internal.h"
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/bluetooth/att.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/media_proxy.h>
#include <zephyr/bluetooth/audio/mcs.h>
#include <zephyr/bluetooth/audio/mcc.h>
#include "macros_common.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_content_ctrl_media, CONFIG_BT_CONTENT_CTRL_MEDIA_LOG_LEVEL);
static uint8_t media_player_state = BT_MCS_MEDIA_STATE_PLAYING;
static struct media_player *local_player;
static bt_content_ctrl_media_play_pause_cb play_pause_cb;
enum mcs_disc_status {
IDLE,
IN_PROGRESS,
FINISHED,
};
struct media_ctlr {
enum mcs_disc_status mcp_mcs_disc_status;
struct bt_conn *conn;
};
static struct media_ctlr mcc_peer[CONFIG_BT_MAX_CONN];
/**
* @brief Get the index of the first available mcc_peer
*
* @return Index if success, -ENOMEM if no available indexes
*/
static int mcc_peer_index_free_get(void)
{
for (int i = 0; i < ARRAY_SIZE(mcc_peer); i++) {
if (mcc_peer[i].conn == NULL) {
return i;
}
}
LOG_WRN("No more indexes for MCC peer");
return -ENOMEM;
}
/**
* @brief Get index of a given conn pointer
*
* @param conn Pointer to check against
*
* @return index if found, -ESRCH if not found, -EINVAL if invalid conn pointer
*/
static int mcc_peer_index_get(struct bt_conn *conn)
{
if (conn == NULL) {
LOG_WRN("Invalid conn pointer");
return -EINVAL;
}
for (uint8_t i = 0; i < ARRAY_SIZE(mcc_peer); i++) {
if (mcc_peer[i].conn == conn) {
return i;
}
}
LOG_DBG("No matching conn pointer for this mcc_peer");
return -ESRCH;
}
/**
* @brief Callback handler for MCS discover finished.
*
* @note This callback handler will be triggered when MCS
* discovery is finished. Used by the client.
*/
static void mcc_discover_mcs_cb(struct bt_conn *conn, int err)
{
int ret;
int idx = mcc_peer_index_get(conn);
if (idx < 0) {
LOG_WRN("Unable to look up conn pointer: %d", idx);
return;
}
if (err) {
if (err == BT_ATT_ERR_UNLIKELY) {
/* BT_ATT_ERR_UNLIKELY may occur in normal operating conditions if there is
* a disconnect while discovering, hence it will be treated as a warning.
*/
LOG_WRN("Discovery of MCS failed (%d)", err);
} else {
LOG_ERR("Discovery of MCS failed (%d)", err);
}
mcc_peer[idx].mcp_mcs_disc_status = IDLE;
return;
}
if (mcc_peer[idx].mcp_mcs_disc_status != IN_PROGRESS) {
/* Due to the design of MCC, there will be several
* invocations of this callback. We are only interested
* in what we have explicitly requested.
*/
LOG_DBG("Filtered out callback");
return;
}
mcc_peer[idx].mcp_mcs_disc_status = FINISHED;
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Discovery of MCS finished");
ret = bt_content_ctrl_media_state_update(conn);
if (ret < 0 && ret != -EBUSY) {
LOG_WRN("Failed to update media state: %d", ret);
}
}
#if defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT)
/**
* @brief Callback handler for sent MCS commands.
*
* @note This callback will be triggered when MCS commands have been sent.
* Used by the client.
*/
static void mcc_send_command_cb(struct bt_conn *conn, int err, const struct mpl_cmd *cmd)
{
int ret;
LOG_DBG("mcc_send_command_cb");
if (err) {
struct bt_conn_info info;
/* Check that we are actually in a connected state before printing an error */
ret = bt_conn_get_info(conn, &info);
if (!ret && info.state == (BT_CONN_STATE_CONNECTED)) {
LOG_ERR("MCC: cmd send failed (%d) - opcode: %u, param: %d", err,
cmd->opcode, cmd->param);
}
}
}
#endif /* defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) */
/**
* @brief Callback handler for received notifications.
*
* @note This callback will be triggered when a notification has been received.
* Used by the client.
*/
static void mcc_cmd_notification_cb(struct bt_conn *conn, int err, const struct mpl_cmd_ntf *ntf)
{
LOG_DBG("mcc_cmd_ntf_cb");
if (err) {
LOG_ERR("MCC: cmd ntf error (%d) - opcode: %u, result: %u", err,
ntf->requested_opcode, ntf->result_code);
}
}
#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE)
/**
* @brief Callback handler for reading media state.
*
* @note This callback will be triggered when the client has asked to read
* the current state of the media player.
*/
static void mcc_read_media_state_cb(struct bt_conn *conn, int err, uint8_t state)
{
LOG_DBG("mcc_read_media_cb, state: %d", state);
if (err) {
LOG_ERR("MCC: Media State read failed (%d)", err);
return;
}
media_player_state = state;
}
#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */
/**
* @brief Callback handler for received MCS commands.
*
* @note This callback will be triggered when the server has received a
* command from the client or the commander.
*/
static void mcs_command_recv_cb(struct media_player *plr, int err,
const struct mpl_cmd_ntf *cmd_ntf)
{
if (err) {
LOG_ERR("Command failed (%d)", err);
return;
}
LOG_DBG("Received opcode: %d", cmd_ntf->requested_opcode);
if (cmd_ntf->requested_opcode == BT_MCS_OPC_PLAY) {
play_pause_cb(true);
} else if (cmd_ntf->requested_opcode == BT_MCS_OPC_PAUSE) {
play_pause_cb(false);
} else {
LOG_WRN("Unsupported opcode: %d", cmd_ntf->requested_opcode);
}
}
/**
* @brief Callback handler for getting the current state of the media player.
*
* @note This callback will be triggered when the server has asked for the
* current state of its local media player.
*/
static void mcs_media_state_cb(struct media_player *plr, int err, uint8_t state)
{
if (err) {
LOG_ERR("Media state failed (%d)", err);
return;
}
media_player_state = state;
}
/**
* @brief Callback handler for getting a pointer to the local media player.
*
* @note This callback will be triggered during initialization when the
* local media player is ready.
*/
static void mcs_local_player_instance_cb(struct media_player *player, int err)
{
int ret;
struct mpl_cmd cmd;
if (err) {
LOG_ERR("Local player instance failed (%d)", err);
return;
}
LOG_DBG("Received local player");
local_player = player;
cmd.opcode = BT_MCS_OPC_PLAY;
/* Since the media player is default paused when initialized, we
* send a play command when the first stream is enabled
*/
ret = media_proxy_ctrl_send_command(local_player, &cmd);
if (ret) {
LOG_WRN("Failed to set media proxy state to play: %d", ret);
}
}
/**
* @brief Send command to either local media player or peer
*
* @param conn Pointer to the conn to send the command to
* @param cmd Command to send
*
* @return 0 for success, error otherwise.
*/
static int mpl_cmd_send(struct bt_conn *conn, struct mpl_cmd *cmd)
{
int ret;
int any_failures = 0;
if (IS_ENABLED(CONFIG_BT_MCS)) {
ret = media_proxy_ctrl_send_command(local_player, cmd);
if (ret) {
LOG_WRN("Failed to send command: %d", ret);
return ret;
}
}
if (IS_ENABLED(CONFIG_BT_MCC)) {
if (conn != NULL) {
int idx = mcc_peer_index_get(conn);
if (idx < 0) {
LOG_ERR("Unable to find mcc_peer");
return idx;
}
if (mcc_peer[idx].mcp_mcs_disc_status == FINISHED) {
ret = bt_mcc_send_cmd(mcc_peer[idx].conn, cmd);
if (ret) {
LOG_WRN("Failed to send command: %d", ret);
return ret;
}
} else {
LOG_WRN("MCS discovery has not finished: %d",
mcc_peer[idx].mcp_mcs_disc_status);
return -EBUSY;
}
return 0;
}
/* Send cmd to all peers connected and has finished discovery */
for (uint8_t i = 0; i < ARRAY_SIZE(mcc_peer); i++) {
if (mcc_peer[i].conn != NULL) {
if (mcc_peer[i].mcp_mcs_disc_status == FINISHED) {
ret = bt_mcc_send_cmd(mcc_peer[i].conn, cmd);
if (ret) {
LOG_WRN("Failed to send command: %d", ret);
any_failures = ret;
}
} else {
LOG_WRN("MCS discovery has not finished: %d",
mcc_peer[i].mcp_mcs_disc_status);
any_failures = -EBUSY;
}
}
}
}
if (any_failures) {
return any_failures;
}
return 0;
}
int bt_content_ctrl_media_discover(struct bt_conn *conn)
{
int ret;
if (!IS_ENABLED(CONFIG_BT_MCC)) {
LOG_ERR("MCC not enabled");
return -ECANCELED;
}
if (conn == NULL) {
LOG_ERR("Invalid conn pointer");
return -EINVAL;
}
int idx = mcc_peer_index_get(conn);
if (idx == -ESRCH) {
idx = mcc_peer_index_free_get();
if (idx < 0) {
LOG_WRN("Error getting free index: %d", idx);
return idx;
}
mcc_peer[idx].conn = conn;
}
if (mcc_peer[idx].mcp_mcs_disc_status == FINISHED ||
mcc_peer[idx].mcp_mcs_disc_status == IN_PROGRESS) {
return -EALREADY;
}
mcc_peer[idx].mcp_mcs_disc_status = IN_PROGRESS;
ret = bt_mcc_discover_mcs(conn, true);
if (ret) {
mcc_peer[idx].mcp_mcs_disc_status = IDLE;
return ret;
}
return 0;
}
int bt_content_ctrl_media_state_update(struct bt_conn *conn)
{
if (!IS_ENABLED(CONFIG_BT_MCC)) {
LOG_ERR("MCC not enabled");
return -ECANCELED;
}
int idx = mcc_peer_index_get(conn);
if (idx < 0) {
LOG_WRN("Unable to look up conn pointer: %d", idx);
return idx;
}
if (mcc_peer[idx].mcp_mcs_disc_status != FINISHED) {
LOG_WRN("MCS discovery has not finished");
return -EBUSY;
}
return bt_mcc_read_media_state(conn);
}
int bt_content_ctrl_media_play(struct bt_conn *conn)
{
int ret;
struct mpl_cmd cmd;
if (media_player_state != BT_MCS_MEDIA_STATE_PLAYING &&
media_player_state != BT_MCS_MEDIA_STATE_PAUSED) {
LOG_ERR("Invalid state: %d", media_player_state);
return -ECANCELED;
}
if (media_player_state == BT_MCS_MEDIA_STATE_PLAYING) {
LOG_WRN("Already in a playing state");
return -EAGAIN;
}
cmd.opcode = BT_MCS_OPC_PLAY;
cmd.use_param = false;
ret = mpl_cmd_send(conn, &cmd);
if (ret) {
return ret;
}
return 0;
}
int bt_content_ctrl_media_pause(struct bt_conn *conn)
{
int ret;
struct mpl_cmd cmd;
if (media_player_state != BT_MCS_MEDIA_STATE_PLAYING &&
media_player_state != BT_MCS_MEDIA_STATE_PAUSED) {
LOG_ERR("Invalid state: %d", media_player_state);
return -ECANCELED;
}
if (media_player_state == BT_MCS_MEDIA_STATE_PAUSED) {
LOG_WRN("Already in a paused state");
return -EAGAIN;
}
cmd.opcode = BT_MCS_OPC_PAUSE;
cmd.use_param = false;
ret = mpl_cmd_send(conn, &cmd);
if (ret) {
return ret;
}
return 0;
}
bool bt_content_ctlr_media_state_playing(void)
{
if (media_player_state == BT_MCS_MEDIA_STATE_PLAYING) {
return true;
}
return false;
}
int bt_content_ctrl_media_conn_disconnected(struct bt_conn *conn)
{
int idx = mcc_peer_index_get(conn);
if (idx < 0) {
LOG_WRN("Unable to look up conn pointer: %d", idx);
return idx;
}
LOG_DBG("MCS discover state reset due to disconnection");
mcc_peer[idx].mcp_mcs_disc_status = IDLE;
mcc_peer[idx].conn = NULL;
return 0;
}
int bt_content_ctrl_media_client_init(void)
{
if (!IS_ENABLED(CONFIG_BT_MCC)) {
LOG_ERR("MCC not enabled");
return -ECANCELED;
}
static struct bt_mcc_cb mcc_cb;
mcc_cb.discover_mcs = mcc_discover_mcs_cb;
#if defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT)
mcc_cb.send_cmd = mcc_send_command_cb;
#endif /* defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) */
mcc_cb.cmd_ntf = mcc_cmd_notification_cb;
#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE)
mcc_cb.read_media_state = mcc_read_media_state_cb;
#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */
return bt_mcc_init(&mcc_cb);
}
int bt_content_ctrl_media_server_init(bt_content_ctrl_media_play_pause_cb play_pause)
{
int ret;
if (!IS_ENABLED(CONFIG_BT_MCS)) {
LOG_ERR("MCS not enabled");
return -ECANCELED;
}
static struct media_proxy_ctrl_cbs mcs_cb;
play_pause_cb = play_pause;
ret = media_proxy_pl_init();
if (ret) {
LOG_ERR("Failed to init media proxy: %d", ret);
return ret;
}
mcs_cb.command_recv = mcs_command_recv_cb;
mcs_cb.media_state_recv = mcs_media_state_cb;
mcs_cb.local_player_instance = mcs_local_player_instance_cb;
ret = media_proxy_ctrl_register(&mcs_cb);
if (ret) {
LOG_ERR("Could not init mpl: %d", ret);
return ret;
}
return 0;
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_CONTENT_CTRL_MEDIA_INTERNAL_H_
#define _BT_CONTENT_CTRL_MEDIA_INTERNAL_H_
#include <zephyr/bluetooth/conn.h>
/**
* @brief Callback for changing the stream state.
*
* @param[in] play Differentiate between the play command and the pause command.
*/
typedef void (*bt_content_ctrl_media_play_pause_cb)(bool play);
/**
* @brief Discover Media Control Service and the included services.
*
* @note Only valid for client.
*
* @param[in] conn Pointer to the active connection.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_media_discover(struct bt_conn *conn);
/**
* @brief Get the current state of the media player.
*
* @note Only valid for client.
*
* @param[in] conn Pointer to the active connection.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_media_state_update(struct bt_conn *conn);
/**
* @brief Send a play command to the media player,
* depending on the current state.
*
* @param[in] conn Pointer to the connection to control; can be NULL.
*
* @note If @p conn is NULL, play will be sent to all mcc_peers discovered.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_media_play(struct bt_conn *conn);
/**
* @brief Send a pause command to the media player,
* depending on the current state.
*
* @param[in] conn Pointer to the connection to control; can be NULL.
*
* @note If @p conn is NULL, pause will be sent to all mcc_peers discovered.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_media_pause(struct bt_conn *conn);
/**
* @brief Reset the media control peer's discovered state
*
* @note Only valid for client.
*
* @param[in] conn Pointer to the active connection.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_media_conn_disconnected(struct bt_conn *conn);
/**
* @brief Initialize the Media Control Client.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_media_client_init(void);
/**
* @brief Initialize the Media Control Server.
*
* @param[in] play_pause_cb Callback for received play/pause commands.
*
* @return 0 for success, error otherwise.
*/
int bt_content_ctrl_media_server_init(bt_content_ctrl_media_play_pause_cb play_pause_cb);
#endif /* _BT_CONTENT_CTRL_MEDIA_INTERNAL_H_ */

View File

@@ -0,0 +1,44 @@
#
# Copyright (c) 2023 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
zephyr_library_include_directories(
advertising
controller_config
dfu
scanning
${ZEPHYR_BASE}/subsys/bluetooth/host/
)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/bt_mgmt.c
${CMAKE_CURRENT_SOURCE_DIR}/controller_config/bt_mgmt_ctlr_cfg.c
)
if (CONFIG_BT_CENTRAL)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/scanning/bt_mgmt_scan_for_conn.c)
endif()
if (CONFIG_BT_BAP_BROADCAST_SINK)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/scanning/bt_mgmt_scan_for_broadcast.c)
endif()
if (CONFIG_BT_OBSERVER)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/scanning/bt_mgmt_scan.c)
endif()
if (CONFIG_BT_PERIPHERAL OR CONFIG_BT_BROADCASTER)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/advertising/bt_mgmt_adv.c)
endif()
if (CONFIG_AUDIO_BT_MGMT_DFU)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/dfu/bt_mgmt_dfu.c
)
endif()

View File

@@ -0,0 +1,43 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
menu "BT management"
rsource "controller_config/Kconfig"
#----------------------------------------------------------------------------#
config WDT_CTLR
bool "Enable watchdog for controller"
default y
help
When true, the controller will be polled at regular intervals to check that it is alive.
Turn off to reduce overhead, or HCI traffic.
The watchdog will be deactivated automatically for DFU procedures.
menu "Thread priorities"
config CTLR_POLL_WORK_Q_PRIO
int "Work queue priority for controller poll"
default 2
help
This is a preemptible work queue.
This work queue will poll the controller to check it is alive.
endmenu # Thread priorities
rsource "dfu/Kconfig"
rsource "advertising/Kconfig"
rsource "scanning/Kconfig"
#----------------------------------------------------------------------------#
menu "Log level"
module = BT_MGMT
module-str = bt-mgmt
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log level
endmenu # BT management

View File

@@ -0,0 +1,78 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
menu "Advertising"
config BLE_ACL_PER_ADV_INT_MIN
hex "Minimum periodic advertising interval"
range 0x0018 0x03C0
default 0x0078
help
Minimum hexadecimal value for the interval of periodic advertisements.
For ms, multiply the provided value by 1.25.
config BLE_ACL_PER_ADV_INT_MAX
hex "Maximum periodic advertising interval"
range 0x0018 0x03C0
default 0x00A0
help
Maximum hexadecimal value for the interval of periodic advertisements.
For ms, multiply the provided value by 1.25.
config BLE_ACL_EXT_ADV_INT_MIN
hex "Minimum extended advertising interval"
range 0x0030 0x0780
default 0x30 if TRANSPORT_BIS
default 0x00A0
help
Minimum hexadecimal value for the interval of extended advertisements.
When the LE Audio Controller Subsystem for nRF53 is used, this interval
should be a multiple of the ISO interval and maximum 4x larger than the
lowest interval if using BIS.
For ms, multiply the provided value by 0.625.
config BLE_ACL_EXT_ADV_INT_MAX
hex "Maximum extended advertising interval"
range 0x0030 0x0780
default 0x40 if TRANSPORT_BIS
default 0x00F0
help
Maximum hexadecimal value for the interval of extended advertisements.
When the LE Audio Controller Subsystem for nRF53 is used, this interval
should be a multiple of the ISO interval and maximum 4x larger than the
lowest interval if using BIS.
For ms, multiply the provided value by 0.625.
config EXT_ADV_BUF_MAX
int "Maximum number of extended advertising data parameters"
default 20
config EXT_ADV_UUID_BUF_MAX
int "Maximum number of UUIDs to add to extended advertisements"
default 40
config BT_DEVICE_MANUFACTURER_ID
hex "Manufacturer ID"
default 0xFE58
help
Bluetooth manufacturer ID. For the list of possible values please
consult the following link:
https://www.bluetooth.com/specifications/assigned-numbers
config BLE_ACL_ADV_SID
hex "Advertising set ID"
range 0x00 0x0F
default 0x00
#----------------------------------------------------------------------------#
menu "Log level"
module = BT_MGMT_ADV
module-str = bt-mgmt-adv
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log level
endmenu # Advertising

View File

@@ -0,0 +1,8 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
config BT_EXT_ADV
default y

View File

@@ -0,0 +1,476 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_mgmt.h"
#include <zephyr/zbus/zbus.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include "macros_common.h"
#include "zbus_common.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mgmt_adv, CONFIG_BT_MGMT_ADV_LOG_LEVEL);
ZBUS_CHAN_DECLARE(bt_mgmt_chan);
#ifndef CONFIG_BT_MAX_PAIRED
#define BONDS_QUEUE_SIZE 0
#else
#define BONDS_QUEUE_SIZE CONFIG_BT_MAX_PAIRED
#endif
static struct k_work adv_work;
static bool dir_adv_timed_out;
static struct bt_le_ext_adv *ext_adv[CONFIG_BT_EXT_ADV_MAX_ADV_SET];
static const struct bt_data *adv_local[CONFIG_BT_EXT_ADV_MAX_ADV_SET];
static size_t adv_local_size[CONFIG_BT_EXT_ADV_MAX_ADV_SET];
static const struct bt_data *per_adv_local[CONFIG_BT_EXT_ADV_MAX_ADV_SET];
static size_t per_adv_local_size[CONFIG_BT_EXT_ADV_MAX_ADV_SET];
/* Bonded address queue */
K_MSGQ_DEFINE(bonds_queue, sizeof(bt_addr_le_t), BONDS_QUEUE_SIZE, 4);
K_MSGQ_DEFINE(adv_queue, sizeof(uint8_t), CONFIG_BT_EXT_ADV_MAX_ADV_SET, 4);
static struct bt_le_adv_param ext_adv_param = {
.id = BT_ID_DEFAULT,
.sid = CONFIG_BLE_ACL_ADV_SID,
.secondary_max_skip = 0,
.options = BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_USE_NAME,
.interval_min = CONFIG_BLE_ACL_EXT_ADV_INT_MIN,
.interval_max = CONFIG_BLE_ACL_EXT_ADV_INT_MAX,
.peer = NULL,
};
static void bond_find(const struct bt_bond_info *info, void *user_data)
{
int ret;
struct bt_conn *conn;
if (!IS_ENABLED(CONFIG_BT_BONDABLE)) {
return;
}
/* Filter already connected peers. */
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &info->addr);
if (conn) {
struct bt_conn_info conn_info;
ret = bt_conn_get_info(conn, &conn_info);
if (ret) {
LOG_WRN("Could not get conn info");
bt_conn_unref(conn);
return;
}
if (conn_info.state == BT_CONN_STATE_CONNECTED) {
LOG_WRN("Already connected");
bt_conn_unref(conn);
return;
}
bt_conn_unref(conn);
}
ret = k_msgq_put(&bonds_queue, (void *)&info->addr, K_NO_WAIT);
if (ret) {
LOG_WRN("No space in the queue for the bond");
}
}
static void filter_accept_list_add(const struct bt_bond_info *info, void *user_data)
{
int ret;
ret = bt_le_filter_accept_list_add(&info->addr);
if (ret) {
LOG_WRN("Could not add peer to Filter Accept List: %d", ret);
return;
}
}
/**
* @brief Prints the address of the local device and the remote device.
*
* @note The address of the remote device is only printed if directed advertisement is active.
*/
static int addr_print(bt_addr_le_t const *const local_addr, bt_addr_le_t const *const dir_adv_addr)
{
char local_addr_str[BT_ADDR_LE_STR_LEN] = {'\0'};
char directed_to_addr_str[BT_ADDR_LE_STR_LEN] = {'\0'};
if (local_addr == NULL) {
return -EINVAL;
}
(void)bt_addr_le_to_str(local_addr, local_addr_str, BT_ADDR_LE_STR_LEN);
LOG_INF("Local addr: %s", local_addr_str);
if (dir_adv_addr != NULL) {
(void)bt_addr_le_to_str(dir_adv_addr, directed_to_addr_str, BT_ADDR_LE_STR_LEN);
LOG_INF("Adv directed to: %s.", directed_to_addr_str);
}
return 0;
}
#if defined(CONFIG_BT_PRIVACY)
static bool adv_rpa_expired_cb(struct bt_le_ext_adv *adv)
{
int ret;
struct bt_le_ext_adv_info ext_adv_info;
LOG_INF("RPA (Resolvable Private Address) expired.");
ret = bt_le_ext_adv_get_info(adv, &ext_adv_info);
ERR_CHK_MSG(ret, "bt_le_ext_adv_get_info failed");
ret = addr_print(ext_adv_info.addr, NULL);
if (ret) {
LOG_ERR("addr_print failed");
}
return true;
}
#endif /* CONFIG_BT_PRIVACY */
static const struct bt_le_ext_adv_cb adv_cb = {
#if defined(CONFIG_BT_PRIVACY)
.rpa_expired = adv_rpa_expired_cb,
#endif /* CONFIG_BT_PRIVACY */
};
static int direct_adv_create(uint8_t ext_adv_index, bt_addr_le_t addr)
{
int ret;
struct bt_le_ext_adv_info ext_adv_info;
ext_adv_param = *BT_LE_ADV_CONN_DIR(&addr);
ext_adv_param.id = BT_ID_DEFAULT;
ext_adv_param.options |= BT_LE_ADV_OPT_DIR_ADDR_RPA;
/* Clear ADV data set before update to direct advertising */
ret = bt_le_ext_adv_set_data(ext_adv[ext_adv_index], NULL, 0, NULL, 0);
if (ret) {
LOG_ERR("Failed to clear advertising data for set %d. Err: %d", ext_adv_index, ret);
return ret;
}
ret = bt_le_ext_adv_update_param(ext_adv[ext_adv_index], &ext_adv_param);
if (ret) {
LOG_ERR("Failed to update ext_adv set %d to directed advertising. Err = %d",
ext_adv_index, ret);
return ret;
}
ret = bt_le_ext_adv_get_info(ext_adv[ext_adv_index], &ext_adv_info);
if (ret) {
return ret;
}
ret = addr_print(ext_adv_info.addr, &addr);
if (ret) {
return ret;
}
return 0;
}
static int extended_adv_create(uint8_t ext_adv_index)
{
int ret;
struct bt_le_ext_adv_info ext_adv_info;
if (adv_local[ext_adv_index] == NULL) {
LOG_ERR("Adv_local not set");
return -ENXIO;
}
ret = bt_le_ext_adv_set_data(ext_adv[ext_adv_index], adv_local[ext_adv_index],
adv_local_size[ext_adv_index], NULL, 0);
if (ret) {
LOG_ERR("Failed to set advertising data: %d", ret);
return ret;
}
if (per_adv_local[ext_adv_index] != NULL && IS_ENABLED(CONFIG_BT_PER_ADV)) {
/* Set periodic advertising parameters */
ret = bt_le_per_adv_set_param(ext_adv[ext_adv_index], LE_AUDIO_PERIODIC_ADV);
if (ret) {
LOG_ERR("Failed to set periodic advertising parameters: %d", ret);
return ret;
}
ret = bt_le_per_adv_set_data(ext_adv[ext_adv_index], per_adv_local[ext_adv_index],
per_adv_local_size[ext_adv_index]);
if (ret) {
LOG_ERR("Failed to set periodic advertising data: %d", ret);
return ret;
}
}
ret = bt_le_ext_adv_get_info(ext_adv[ext_adv_index], &ext_adv_info);
if (ret) {
return ret;
}
ret = addr_print(ext_adv_info.addr, NULL);
if (ret) {
return ret;
}
return 0;
}
static void advertising_process(struct k_work *work)
{
int ret;
struct bt_mgmt_msg msg;
uint8_t ext_adv_index;
ret = k_msgq_get(&adv_queue, &ext_adv_index, K_NO_WAIT);
if (ret) {
LOG_ERR("No ext_adv_index found");
return;
}
k_msgq_purge(&bonds_queue);
if (IS_ENABLED(CONFIG_BT_BONDABLE)) {
bt_foreach_bond(BT_ID_DEFAULT, bond_find, NULL);
/* Populate Filter Accept List */
if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST)) {
ret = bt_le_filter_accept_list_clear();
if (ret) {
LOG_ERR("Failed to clear filter accept list");
return;
}
bt_foreach_bond(BT_ID_DEFAULT, filter_accept_list_add, NULL);
}
}
bt_addr_le_t addr;
if (!k_msgq_get(&bonds_queue, &addr, K_NO_WAIT) && !dir_adv_timed_out) {
ret = direct_adv_create(ext_adv_index, addr);
if (ret) {
LOG_WRN("Failed to create direct advertisement: %d", ret);
return;
}
ret = bt_le_ext_adv_start(
ext_adv[ext_adv_index],
BT_LE_EXT_ADV_START_PARAM(BT_GAP_ADV_HIGH_DUTY_CYCLE_MAX_TIMEOUT, 0));
} else {
ret = extended_adv_create(ext_adv_index);
if (ret) {
LOG_WRN("Failed to create extended advertisement: %d", ret);
return;
}
dir_adv_timed_out = false;
ret = bt_le_ext_adv_start(ext_adv[ext_adv_index], BT_LE_EXT_ADV_START_DEFAULT);
}
if (ret) {
LOG_ERR("Failed to start advertising set. Err: %d", ret);
return;
}
if (per_adv_local[ext_adv_index] != NULL && IS_ENABLED(CONFIG_BT_PER_ADV)) {
/* Enable Periodic Advertising */
ret = bt_le_per_adv_start(ext_adv[ext_adv_index]);
if (ret) {
LOG_ERR("Failed to enable periodic advertising: %d", ret);
return;
}
msg.event = BT_MGMT_EXT_ADV_WITH_PA_READY;
msg.ext_adv = ext_adv[ext_adv_index];
msg.index = ext_adv_index;
ret = zbus_chan_pub(&bt_mgmt_chan, &msg, K_NO_WAIT);
ERR_CHK(ret);
}
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Advertising successfully started");
}
void bt_mgmt_dir_adv_timed_out(uint8_t ext_adv_index)
{
int ret;
dir_adv_timed_out = true;
LOG_DBG("Clearing ext_adv");
ret = bt_le_ext_adv_delete(ext_adv[ext_adv_index]);
if (ret) {
LOG_ERR("Failed to clear ext_adv");
}
if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST)) {
ret = bt_le_ext_adv_create(LE_AUDIO_EXTENDED_ADV_CONN_NAME_FILTER, &adv_cb,
&ext_adv[ext_adv_index]);
} else {
ret = bt_le_ext_adv_create(LE_AUDIO_EXTENDED_ADV_CONN_NAME, &adv_cb,
&ext_adv[ext_adv_index]);
}
if (ret) {
LOG_ERR("Unable to create a connectable extended advertising set: %d", ret);
return;
}
/* Restart normal advertising */
ret = bt_mgmt_adv_start(ext_adv_index, NULL, 0, NULL, 0, true);
if (ret) {
LOG_ERR("Unable start advertising: %d", ret);
return;
}
}
int bt_mgmt_manufacturer_uuid_populate(struct net_buf_simple *uuid_buf, uint16_t company_id)
{
if (net_buf_simple_tailroom(uuid_buf) >= BT_UUID_SIZE_16) {
net_buf_simple_add_le16(uuid_buf, company_id);
} else {
return -ENOMEM;
}
return 0;
}
int bt_mgmt_adv_buffer_put(struct bt_data *const adv_buf, uint32_t *index, size_t adv_buf_vacant,
size_t data_len, uint8_t type, void *data)
{
if (adv_buf == NULL || index == NULL || data_len == 0) {
return -EINVAL;
}
/* Check that we have space for data */
if (adv_buf_vacant <= *index) {
return -ENOMEM;
}
adv_buf[*index].type = type;
adv_buf[*index].data_len = data_len;
adv_buf[*index].data = data;
(*index)++;
return 0;
}
int bt_mgmt_per_adv_stop(uint8_t ext_adv_index)
{
int ret;
ret = bt_le_per_adv_stop(ext_adv[ext_adv_index]);
if (ret) {
LOG_ERR("Failed to top periodic advertising: %d", ret);
return ret;
}
return 0;
}
int bt_mgmt_ext_adv_stop(uint8_t ext_adv_index)
{
int ret;
ret = bt_le_ext_adv_stop(ext_adv[ext_adv_index]);
if (ret) {
LOG_ERR("Failed to stop advertising set: %d", ret);
return ret;
}
ret = bt_le_ext_adv_delete(ext_adv[ext_adv_index]);
if (ret) {
LOG_ERR("Failed to delete advertising set: %d", ret);
return ret;
}
return 0;
}
int bt_mgmt_adv_start(uint8_t ext_adv_index, const struct bt_data *adv, size_t adv_size,
const struct bt_data *per_adv, size_t per_adv_size, bool connectable)
{
int ret;
/* Special case for restarting advertising */
if (adv == NULL && adv_size == 0 && per_adv == NULL && per_adv_size == 0) {
if (adv_local[ext_adv_index] == NULL) {
LOG_ERR("No valid advertising data stored");
return -ENOENT;
}
ret = k_msgq_put(&adv_queue, &ext_adv_index, K_NO_WAIT);
if (ret) {
LOG_ERR("No space in the queue for adv_index");
return -ENOMEM;
}
k_work_submit(&adv_work);
return 0;
}
if (adv == NULL) {
LOG_ERR("No adv struct received");
return -EINVAL;
}
if (adv_size == 0) {
LOG_ERR("Invalid size of adv struct");
return -EINVAL;
}
adv_local[ext_adv_index] = adv;
adv_local_size[ext_adv_index] = adv_size;
per_adv_local[ext_adv_index] = per_adv;
per_adv_local_size[ext_adv_index] = per_adv_size;
/* Only use fixed address if no privacy and it is the first ext adv set */
if (!IS_ENABLED(CONFIG_BT_PRIVACY) && ext_adv_index == 0) {
ext_adv_param.options |= BT_LE_ADV_OPT_USE_IDENTITY;
} else {
/* If privacy is enabled, use RPA */
ext_adv_param.options &= ~BT_LE_ADV_OPT_USE_IDENTITY;
}
if (connectable) {
ret = bt_le_ext_adv_create(LE_AUDIO_EXTENDED_ADV_CONN_NAME, &adv_cb,
&ext_adv[ext_adv_index]);
if (ret) {
LOG_ERR("Unable to create a connectable extended advertising set: %d", ret);
return ret;
}
} else {
ret = bt_le_ext_adv_create(&ext_adv_param, &adv_cb, &ext_adv[ext_adv_index]);
if (ret) {
LOG_ERR("Unable to create extended advertising set: %d", ret);
return ret;
}
}
ret = k_msgq_put(&adv_queue, &ext_adv_index, K_NO_WAIT);
if (ret) {
LOG_ERR("No space in the queue for adv_index");
return -ENOMEM;
}
k_work_submit(&adv_work);
return 0;
}
void bt_mgmt_adv_init(void)
{
k_work_init(&adv_work, advertising_process);
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_MGMT_ADV_INTERNAL_H_
#define _BT_MGMT_ADV_INTERNAL_H_
#include <zephyr/bluetooth/bluetooth.h>
/**
* @brief Initialize the advertising part of the Bluetooth management module.
*/
void bt_mgmt_adv_init(void);
/**
* @brief Handle timed-out directed advertisement.
*
* This function deletes the old ext_adv and creates a new one.
* It also sets the dir_adv_timed_out flag and restarts advertisement.
*
* @param[in] ext_adv_index Index of the ext_adv to restart.
*/
void bt_mgmt_dir_adv_timed_out(uint8_t ext_adv_index);
#endif /* _BT_MGMT_ADV_INTERNAL_H_ */

View File

@@ -0,0 +1,415 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_mgmt.h"
#include <zephyr/zbus/zbus.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/byteorder.h>
#include <nrfx.h>
#include "macros_common.h"
#include "zbus_common.h"
#include "button_handler.h"
#include "bt_mgmt_ctlr_cfg_internal.h"
#include "bt_mgmt_adv_internal.h"
#include "bt_mgmt_dfu_internal.h"
#if CONFIG_BOARD_NRF5340_AUDIO_DK_NRF5340_CPUAPP
#include "button_assignments.h"
#endif
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mgmt, CONFIG_BT_MGMT_LOG_LEVEL);
ZBUS_CHAN_DEFINE(bt_mgmt_chan, struct bt_mgmt_msg, NULL, NULL, ZBUS_OBSERVERS_EMPTY,
ZBUS_MSG_INIT(0));
/* The bt_enable should take less than 15 ms.
* Buffer added as this will not add to bootup time
*/
#define BT_ENABLE_TIMEOUT_MS 100
K_SEM_DEFINE(sem_bt_enabled, 0, 1);
/**
* @brief Iterative function used to find connected conns
*
* @param[in] conn The connection to check
* @param[out] data Pointer to store number of valid conns
*/
static void conn_state_connected_check(struct bt_conn *conn, void *data)
{
int ret;
uint8_t *num_conn = (uint8_t *)data;
struct bt_conn_info info;
ret = bt_conn_get_info(conn, &info);
if (ret) {
LOG_ERR("Failed to get conn info for %p: %d", (void *)conn, ret);
return;
}
if (info.state != BT_CONN_STATE_CONNECTED) {
return;
}
(*num_conn)++;
}
static void connected_cb(struct bt_conn *conn, uint8_t err)
{
int ret;
char addr[BT_ADDR_LE_STR_LEN] = {0};
struct bt_mgmt_msg msg;
if (err == BT_HCI_ERR_ADV_TIMEOUT && IS_ENABLED(CONFIG_BT_PERIPHERAL)) {
LOG_INF("Directed adv timed out with no connection, reverting to normal adv");
bt_mgmt_dir_adv_timed_out(0);
return;
}
(void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err) {
if (err == BT_HCI_ERR_UNKNOWN_CONN_ID) {
LOG_WRN("ACL connection to addr: %s timed out, will try again", addr);
} else {
LOG_ERR("ACL connection to addr: %s, conn: %p, failed, error %d %s", addr,
(void *)conn, err, bt_hci_err_to_str(err));
}
bt_conn_unref(conn);
if (IS_ENABLED(CONFIG_BT_CENTRAL)) {
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_CONN, NULL,
BRDCAST_ID_NOT_USED);
if (ret && ret != -EALREADY) {
LOG_ERR("Failed to restart scanning: %d", ret);
}
}
if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) {
ret = bt_mgmt_adv_start(0, NULL, 0, NULL, 0, true);
if (ret) {
LOG_ERR("Failed to restart advertising: %d", ret);
}
}
return;
}
/* ACL connection established */
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Connected: %s", addr);
msg.event = BT_MGMT_CONNECTED;
msg.conn = conn;
ret = zbus_chan_pub(&bt_mgmt_chan, &msg, K_NO_WAIT);
ERR_CHK(ret);
if (IS_ENABLED(CONFIG_BT_CENTRAL)) {
ret = bt_conn_set_security(conn, BT_SECURITY_L2);
if (ret) {
LOG_ERR("Failed to set security to L2: %d", ret);
}
}
}
K_MUTEX_DEFINE(mtx_duplicate_scan);
static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
{
int ret;
char addr[BT_ADDR_LE_STR_LEN];
struct bt_mgmt_msg msg;
(void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Disconnected: %s, reason 0x%02x %s", addr, reason, bt_hci_err_to_str(reason));
if (IS_ENABLED(CONFIG_BT_CENTRAL)) {
bt_conn_unref(conn);
}
/* Publish disconnected */
msg.event = BT_MGMT_DISCONNECTED;
msg.conn = conn;
ret = zbus_chan_pub(&bt_mgmt_chan, &msg, K_NO_WAIT);
ERR_CHK(ret);
if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) {
ret = bt_mgmt_adv_start(0, NULL, 0, NULL, 0, true);
ERR_CHK(ret);
}
/* The mutex for preventing the racing condition if two headset disconnected too close,
* cause the disconnected_cb() triggered in short time leads to duplicate scanning
* operation.
*/
k_mutex_lock(&mtx_duplicate_scan, K_FOREVER);
if (IS_ENABLED(CONFIG_BT_CENTRAL)) {
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_CONN, NULL, BRDCAST_ID_NOT_USED);
if (ret && ret != -EALREADY) {
LOG_ERR("Failed to restart scanning: %d", ret);
}
}
k_mutex_unlock(&mtx_duplicate_scan);
}
#if defined(CONFIG_BT_SMP)
static void security_changed_cb(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
int ret;
struct bt_mgmt_msg msg;
if (err) {
LOG_WRN("Security failed: level %d err %d %s", level, err,
bt_security_err_to_str(err));
ret = bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
if (ret) {
LOG_WRN("Failed to disconnect %d", ret);
}
} else {
LOG_DBG("Security changed: level %d", level);
/* Publish connected */
msg.event = BT_MGMT_SECURITY_CHANGED;
msg.conn = conn;
ret = zbus_chan_pub(&bt_mgmt_chan, &msg, K_NO_WAIT);
ERR_CHK(ret);
}
}
#endif /* defined(CONFIG_BT_SMP) */
static struct bt_conn_cb conn_callbacks = {
.connected = connected_cb,
.disconnected = disconnected_cb,
#if defined(CONFIG_BT_SMP)
.security_changed = security_changed_cb,
#endif /* defined(CONFIG_BT_SMP) */
};
static void bt_enabled_cb(int err)
{
if (err) {
LOG_ERR("Bluetooth init failed (err code: %d)", err);
ERR_CHK(err);
}
k_sem_give(&sem_bt_enabled);
LOG_DBG("Bluetooth initialized");
}
static int bonding_clear_check(void)
{
#if CONFIG_BOARD_NRF5340_AUDIO_DK_NRF5340_CPUAPP
int ret;
bool pressed;
ret = button_pressed(BUTTON_5, &pressed);
if (ret) {
return ret;
}
if (pressed) {
ret = bt_mgmt_bonding_clear();
return ret;
}
#endif
return 0;
}
/* This function generates a random address for bonding testing */
static int random_static_addr_set(void)
{
int ret;
static bt_addr_le_t addr;
ret = bt_addr_le_create_static(&addr);
if (ret < 0) {
LOG_ERR("Failed to create address %d", ret);
return ret;
}
ret = bt_id_create(&addr, NULL);
if (ret < 0) {
LOG_ERR("Failed to create ID %d", ret);
return ret;
}
return 0;
}
static int local_identity_addr_print(void)
{
size_t num_ids = 0;
bt_addr_le_t addrs[CONFIG_BT_ID_MAX];
char addr_str[BT_ADDR_LE_STR_LEN];
bt_id_get(NULL, &num_ids);
if (num_ids != CONFIG_BT_ID_MAX) {
LOG_ERR("The default config supports %d ids, but %d was found", CONFIG_BT_ID_MAX,
num_ids);
return -ENOMEM;
}
bt_id_get(addrs, &num_ids);
for (int i = 0; i < num_ids; i++) {
(void)bt_addr_le_to_str(&(addrs[i]), addr_str, BT_ADDR_LE_STR_LEN);
LOG_INF("Local identity addr: %s", addr_str);
}
return 0;
}
void bt_mgmt_num_conn_get(uint8_t *num_conn)
{
bt_conn_foreach(BT_CONN_TYPE_LE, conn_state_connected_check, (void *)num_conn);
}
int bt_mgmt_bonding_clear(void)
{
int ret;
if (IS_ENABLED(CONFIG_SETTINGS)) {
LOG_INF("Clearing all bonds");
ret = bt_unpair(BT_ID_DEFAULT, NULL);
if (ret) {
LOG_ERR("Failed to clear bonding: %d", ret);
return ret;
}
}
return 0;
}
int bt_mgmt_pa_sync_delete(struct bt_le_per_adv_sync *pa_sync)
{
if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC)) {
int ret;
ret = bt_le_per_adv_sync_delete(pa_sync);
if (ret) {
LOG_ERR("Failed to delete PA sync");
return ret;
}
} else {
LOG_WRN("Periodic advertisement sync not enabled");
return -ENOTSUP;
}
return 0;
}
int bt_mgmt_conn_disconnect(struct bt_conn *conn, uint8_t reason)
{
if (IS_ENABLED(CONFIG_BT_CONN)) {
int ret;
ret = bt_conn_disconnect(conn, reason);
if (ret) {
LOG_ERR("Failed to disconnect connection %p (%d)", (void *)conn, ret);
return ret;
}
} else {
LOG_WRN("BT conn not enabled");
return -ENOTSUP;
}
return 0;
}
int bt_mgmt_init(void)
{
int ret;
ret = bt_enable(bt_enabled_cb);
if (ret) {
return ret;
}
ret = k_sem_take(&sem_bt_enabled, K_MSEC(BT_ENABLE_TIMEOUT_MS));
if (ret) {
LOG_ERR("bt_enable timed out");
return ret;
}
if (IS_ENABLED(CONFIG_TESTING_BLE_ADDRESS_RANDOM)) {
ret = random_static_addr_set();
if (ret) {
return ret;
}
}
if (IS_ENABLED(CONFIG_SETTINGS)) {
ret = settings_load();
if (ret) {
return ret;
}
ret = bonding_clear_check();
if (ret) {
return ret;
}
if (IS_ENABLED(CONFIG_TESTING_BLE_ADDRESS_RANDOM)) {
ret = bt_mgmt_bonding_clear();
if (ret) {
return ret;
}
}
}
#if defined(CONFIG_AUDIO_BT_MGMT_DFU)
bool pressed;
ret = button_pressed(BUTTON_4, &pressed);
if (ret) {
return ret;
}
if (pressed) {
ret = bt_mgmt_ctlr_cfg_init(false);
if (ret) {
return ret;
}
/* This call will not return */
bt_mgmt_dfu_start();
}
#endif /* CONFIG_AUDIO_BT_MGMT_DFU */
ret = bt_mgmt_ctlr_cfg_init(IS_ENABLED(CONFIG_WDT_CTLR));
if (ret) {
return ret;
}
ret = local_identity_addr_print();
if (ret) {
return ret;
}
if (IS_ENABLED(CONFIG_BT_CONN)) {
bt_conn_cb_register(&conn_callbacks);
}
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) || IS_ENABLED(CONFIG_BT_BROADCASTER)) {
bt_mgmt_adv_init();
}
return 0;
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_MGMT_H_
#define _BT_MGMT_H_
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/audio.h>
#define LE_AUDIO_EXTENDED_ADV_NAME \
BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_USE_NAME, \
CONFIG_BLE_ACL_EXT_ADV_INT_MIN, CONFIG_BLE_ACL_EXT_ADV_INT_MAX, NULL)
#define LE_AUDIO_EXTENDED_ADV_CONN_NAME \
BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_USE_NAME, \
CONFIG_BLE_ACL_EXT_ADV_INT_MIN, CONFIG_BLE_ACL_EXT_ADV_INT_MAX, NULL)
#define LE_AUDIO_EXTENDED_ADV_CONN_NAME_FILTER \
BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_USE_NAME | \
BT_LE_ADV_OPT_FILTER_CONN, \
CONFIG_BLE_ACL_EXT_ADV_INT_MIN, CONFIG_BLE_ACL_EXT_ADV_INT_MAX, NULL)
#define LE_AUDIO_PERIODIC_ADV \
BT_LE_PER_ADV_PARAM(CONFIG_BLE_ACL_PER_ADV_INT_MIN, CONFIG_BLE_ACL_PER_ADV_INT_MAX, \
BT_LE_PER_ADV_OPT_NONE)
#define BT_LE_ADV_FAST_CONN \
BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN, BT_GAP_ADV_FAST_INT_MIN_1, BT_GAP_ADV_FAST_INT_MAX_1, \
NULL)
/* Broadcast name can be max 32 bytes long, so this will be the limit for both.
* Add one for '\0' at the end.
*/
#define BLE_SEARCH_NAME_MAX_LEN 33
#if (CONFIG_SCAN_MODE_ACTIVE)
#define NRF5340_AUDIO_GATEWAY_SCAN_TYPE BT_LE_SCAN_TYPE_ACTIVE
#define NRF5340_AUDIO_GATEWAY_SCAN_PARAMS BT_LE_SCAN_ACTIVE
#elif (CONFIG_SCAN_MODE_PASSIVE)
#define NRF5340_AUDIO_GATEWAY_SCAN_TYPE BT_LE_SCAN_TYPE_PASSIVE
#define NRF5340_AUDIO_GATEWAY_SCAN_PARAMS BT_LE_SCAN_PASSIVE
#else
#error "Select either CONFIG_SCAN_MODE_ACTIVE or CONFIG_SCAN_MODE_PASSIVE"
#endif
enum bt_mgmt_scan_type {
BT_MGMT_SCAN_TYPE_CONN = 1,
BT_MGMT_SCAN_TYPE_BROADCAST = 2,
};
#define BRDCAST_ID_NOT_USED (BT_AUDIO_BROADCAST_ID_MAX + 1)
/**
* @brief Get the numbers of connected members of a given 'Set Identity Resolving Key' (SIRK).
* The SIRK shall be set through bt_mgmt_scan_sirk_set() before calling this function.
*
* @param[out] num_filled The number of connected set members.
*/
void bt_mgmt_set_size_filled_get(uint8_t *num_filled);
/**
* @brief Set 'Set Identity Resolving Key' (SIRK).
* Used for searching for other member of the same set.
*
* @param[in] sirk Pointer to the Set Identity Resolving Key to store.
*/
void bt_mgmt_scan_sirk_set(uint8_t const *const sirk);
/**
* @brief Load advertising data into an advertising buffer.
*
* @param[out] adv_buf Pointer to the advertising buffer to load.
* @param[in,out] index Next free index in the advertising buffer.
* @param[in] adv_buf_vacant Number of free advertising buffers.
* @param[in] data_len Length of the data.
* @param[in] type Type of the data.
* @param[in] data Data to store in the buffer, can be a pointer or value.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_adv_buffer_put(struct bt_data *const adv_buf, uint32_t *index, size_t adv_buf_vacant,
size_t data_len, uint8_t type, void *data);
/**
* @brief Start scanning for advertisements.
*
* @param[in] scan_intvl Scan interval in units of 0.625ms.
* Valid range: 0x4 - 0xFFFF; can be 0.
* @param[in] scan_win Scan window in units of 0.625ms.
* Valid range: 0x4 - 0xFFFF; can be 0.
* @param[in] type Type to scan for: ACL connection or broadcaster.
* @param[in] name Name to search for. Depending on @p type of search,
* device name or broadcast name. Can be max
* BLE_SEARCH_NAME_MAX_LEN long; everything beyond that value
* will be cropped. Can be NULL. Shall be '\0' terminated.
* @param[in] brdcast_id Broadcast ID to search for. Only valid if @p type is
* BT_MGMT_SCAN_TYPE_BROADCAST. If both @p name and @p brdcast_id are
* provided, then brdcast_id will be used.
* Set to BRDCAST_ID_NOT_USED if not in use.
*
* @note To restart scanning, call this function with all 0s and NULL, except for @p type.
* The same scanning parameters as when bt_mgmt_scan_start was last called will then
* be used.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_scan_start(uint16_t scan_intvl, uint16_t scan_win, enum bt_mgmt_scan_type type,
char const *const name, uint32_t brdcast_id);
/**
* @brief Add manufacturer ID UUID to the advertisement packet.
*
* @param[out] uuid_buf Buffer being populated with UUIDs.
* @param[in] company_id 16 bit UUID specific to the company.
*
* @return 0 for success, error otherwise.
*/
int bt_mgmt_manufacturer_uuid_populate(struct net_buf_simple *uuid_buf, uint16_t company_id);
/**
* @brief Stop periodic advertising.
*
* @note Must be called before bt_mgmt_ext_adv_stop.
*
* @param[in] ext_adv_index Index of the advertising set to stop.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_per_adv_stop(uint8_t ext_adv_index);
/**
* @brief Stop extended advertising.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_ext_adv_stop(uint8_t ext_adv_index);
/**
* @brief Create and start advertising for an ACL connection.
*
* @param[in] ext_adv_index Index of the advertising set to start.
* @param[in] ext_adv The data to be put in the extended advertisement.
* @param[in] ext_adv_size Size of @p ext_adv.
* @param[in] per_adv The data for the periodic advertisement; can be NULL.
* @param[in] per_adv_size Size of @p per_adv.
* @param[in] connectable Specify if advertisement should be connectable or not.
*
* @note To restart advertising, call this function with all 0s and NULL, except for
* connectable. The same advertising parameters as when bt_mgmt_adv_start was last
* called will then be used.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_adv_start(uint8_t ext_adv_index, const struct bt_data *ext_adv, size_t ext_adv_size,
const struct bt_data *per_adv, size_t per_adv_size, bool connectable);
/**
* @brief Get the number of active connections.
*
* @param[out] num_conn The number of active connections.
*/
void bt_mgmt_num_conn_get(uint8_t *num_conn);
/**
* @brief Clear all bonded devices.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_bonding_clear(void);
/**
* @brief Scan delegator feature initialization.
*/
void bt_mgmt_scan_delegator_init(void);
/**
* @brief Get the pointer to broadcast code.
*
* @param[out] broadcast_code_ptr Pointer to the broadcast code.
*/
void bt_mgmt_broadcast_code_ptr_get(uint8_t **broadcast_code_ptr);
/**
* @brief Delete a periodic advertisement sync.
*
* @param[in] pa_sync Pointer to the periodic advertisement sync to delete.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_pa_sync_delete(struct bt_le_per_adv_sync *pa_sync);
/**
* @brief Disconnect from a remote device or cancel the pending connection.
*
* @param[in] conn Connection to disconnect.
* @param[in] reason Reason code for the disconnection, as specified in
* HCI Error Codes, BT Core Spec [Vol 1, Part F].
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_conn_disconnect(struct bt_conn *conn, uint8_t reason);
/**
* @brief Initialize the Bluetooth management module.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_init(void);
#endif /* _BT_MGMT_H_ */

View File

@@ -0,0 +1,83 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
menu "Controller config"
#----------------------------------------------------------------------------#
menu "nRF21540"
config NRF_21540_ACTIVE
def_bool $(shields_list_contains,nrf21540ek)
select EXPERIMENTAL
help
The front end module can help boost the TX power as high as 20 dBm.
choice NRF_21540_MAIN_TX_POWER
prompt "TX power for the secondary channels"
default NRF_21540_MAIN_TX_POWER_10DBM
help
Set the TX power for the secondary Bluetooth LE channels (0-36).
Check your local regulations for max output power. If the
nRF21540 is used with the nRF5340 Audio DK the actual output power
will be about 25% lower due to the VDD being 1.8V instead of the
nominal 3.3V.
config NRF_21540_MAIN_TX_POWER_0DBM
bool "0dBm"
config NRF_21540_MAIN_TX_POWER_10DBM
bool "+10dBm"
config NRF_21540_MAIN_TX_POWER_20DBM
bool "+20dBm"
endchoice
config NRF_21540_MAIN_DBM
int
default 0 if NRF_21540_MAIN_TX_POWER_0DBM
default 10 if NRF_21540_MAIN_TX_POWER_10DBM
default 20 if NRF_21540_MAIN_TX_POWER_20DBM
choice NRF_21540_PRI_ADV_TX_POWER
prompt "TX power for the primary advertising channels"
default NRF_21540_PRI_ADV_TX_POWER_10DBM
help
Set the TX power for the primary Bluetooth LE advertising channels
(37, 38, 39).
Check your local regulations for max output power. If the
nRF21540 is used with the nRF5340 Audio DK the actual output power
will be about 25% lower due to the VDD being 1.8V instead of the
nominal 3.3V.
config NRF_21540_PRI_ADV_TX_POWER_0DBM
bool "0dBm"
config NRF_21540_PRI_ADV_TX_POWER_10DBM
bool "+10dBm"
config NRF_21540_PRI_ADV_TX_POWER_20DBM
bool "+20dBm"
endchoice
config NRF_21540_PRI_ADV_DBM
int
default 0 if NRF_21540_PRI_ADV_TX_POWER_0DBM
default 10 if NRF_21540_PRI_ADV_TX_POWER_10DBM
default 20 if NRF_21540_PRI_ADV_TX_POWER_20DBM
endmenu # nRF21540
#----------------------------------------------------------------------------#
menu "Log level"
module = BT_MGMT_CTLR_CFG
module-str = bt-mgmt-ctlr-cfg
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log level
endmenu # Controller config

View File

@@ -0,0 +1,135 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_mgmt_ctlr_cfg_internal.h"
#include <zephyr/bluetooth/hci.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/watchdog.h>
#include <zephyr/task_wdt/task_wdt.h>
#include <zephyr/logging/log_ctrl.h>
#include "macros_common.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mgmt_ctlr_cfg, CONFIG_BT_MGMT_CTLR_CFG_LOG_LEVEL);
#define COMPANY_ID_NORDIC 0x0059
#define WDT_TIMEOUT_MS 3000
#define CTLR_POLL_INTERVAL_MS (WDT_TIMEOUT_MS - 1000)
static struct k_work work_ctlr_poll;
#define CTLR_POLL_WORK_STACK_SIZE 1024
K_THREAD_STACK_DEFINE(ctlr_poll_stack_area, CTLR_POLL_WORK_STACK_SIZE);
struct k_work_q ctrl_poll_work_q;
struct k_work_queue_config ctrl_poll_work_q_config = {
.name = "ctlr_poll",
.no_yield = false,
};
static void ctlr_poll_timer_handler(struct k_timer *timer_id);
static int wdt_ch_id;
K_TIMER_DEFINE(ctlr_poll_timer, ctlr_poll_timer_handler, NULL);
static void work_ctlr_poll_handler(struct k_work *work)
{
int ret;
uint16_t manufacturer = 0;
ret = bt_mgmt_ctlr_cfg_manufacturer_get(false, &manufacturer);
ERR_CHK_MSG(ret, "Failed to contact net core");
ret = task_wdt_feed(wdt_ch_id);
ERR_CHK_MSG(ret, "Failed to feed watchdog");
}
static void ctlr_poll_timer_handler(struct k_timer *timer_id)
{
int ret;
ret = k_work_submit_to_queue(&ctrl_poll_work_q, &work_ctlr_poll);
if (ret < 0) {
LOG_ERR("Work q submit failed: %d", ret);
}
}
static void wdt_timeout_cb(int channel_id, void *user_data)
{
ERR_CHK_MSG(-ETIMEDOUT, "No response from IPC or controller");
}
int bt_mgmt_ctlr_cfg_manufacturer_get(bool print_version, uint16_t *manufacturer)
{
int ret;
struct net_buf *rsp;
ret = bt_hci_cmd_send_sync(BT_HCI_OP_READ_LOCAL_VERSION_INFO, NULL, &rsp);
if (ret) {
return ret;
}
struct bt_hci_rp_read_local_version_info *rp = (void *)rsp->data;
if (print_version) {
if (rp->manufacturer == COMPANY_ID_NORDIC) {
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Controller: SoftDevice: Version %s (0x%02x), Revision %d",
bt_hci_get_ver_str(rp->hci_version), rp->hci_version,
rp->hci_revision);
} else {
LOG_ERR("Unsupported controller");
return -EPERM;
}
}
*manufacturer = sys_le16_to_cpu(rp->manufacturer);
net_buf_unref(rsp);
return 0;
}
int bt_mgmt_ctlr_cfg_init(bool watchdog_enable)
{
int ret;
uint16_t manufacturer = 0;
ret = bt_mgmt_ctlr_cfg_manufacturer_get(true, &manufacturer);
if (ret) {
return ret;
}
if (watchdog_enable) {
ret = task_wdt_init(NULL);
if (ret != 0) {
LOG_ERR("task wdt init failure: %d\n", ret);
return ret;
}
wdt_ch_id = task_wdt_add(WDT_TIMEOUT_MS, wdt_timeout_cb, NULL);
if (wdt_ch_id < 0) {
return wdt_ch_id;
}
k_work_queue_init(&ctrl_poll_work_q);
k_work_queue_start(&ctrl_poll_work_q, ctlr_poll_stack_area,
K_THREAD_STACK_SIZEOF(ctlr_poll_stack_area),
K_PRIO_PREEMPT(CONFIG_CTLR_POLL_WORK_Q_PRIO),
&ctrl_poll_work_q_config);
k_work_init(&work_ctlr_poll, work_ctlr_poll_handler);
k_timer_start(&ctlr_poll_timer, K_MSEC(CTLR_POLL_INTERVAL_MS),
K_MSEC(CTLR_POLL_INTERVAL_MS));
}
return 0;
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_MGMT_CTRL_CFG_INTERNAL_H_
#define _BT_MGMT_CTRL_CFG_INTERNAL_H_
#include <stdbool.h>
#include <stdint.h>
/**
* @brief Get the Bluetooth controller manufacturer.
*
* @param[in] print_version Print the controller version.
* @param[out] manufacturer The controller manufacturer.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_ctlr_cfg_manufacturer_get(bool print_version, uint16_t *manufacturer);
/**
* @brief Configure the Bluetooth controller.
*
* @param[in] watchdog_enable If true, the function will, at given intervals, poll the controller
* to ensure it is still alive.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_ctlr_cfg_init(bool watchdog_enable);
#endif /* _BT_MGMT_CTRL_CFG_INTERNAL_H_ */

View File

@@ -0,0 +1,40 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
menuconfig AUDIO_BT_MGMT_DFU
bool "Enable BT MGMT DFU module"
default n
select EXPERIMENTAL
select BT_DEVICE_NAME_DYNAMIC
select MCUMGR
select NET_BUF
select ZCBOR
select CRC
select MCUMGR_TRANSPORT_BT
select IMG_ERASE_PROGRESSIVELY
imply MCUMGR_TRANSPORT_BT_CONN_PARAM_CONTROL
imply IMG_MANAGER
imply STREAM_FLASH
imply FLASH_MAP
imply FLASH
imply MCUMGR_GRP_IMG
imply MCUMGR_GRP_OS
imply MCUMGR_GRP_OS_MCUMGR_PARAMS
imply MCUMGR_GRP_OS_BOOTLOADER_INFO
imply MCUMGR_TRANSPORT_BT_REASSEMBLY
depends on BT_PERIPHERAL
depends on BOOTLOADER_MCUBOOT
help
Enable the BT MGMT module. This module adds the DFU mode
that can be entered from the main application.
if AUDIO_BT_MGMT_DFU
module = BT_MGMT_DFU
module-str = bt-mgmt-dfu
source "subsys/logging/Kconfig.template.log_config"
endif # AUDIO_BT_MGMT_DFU

View File

@@ -0,0 +1,132 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
/* Override compiler definition to use size-bounded string copying and concatenation function */
#define _BSD_SOURCE
#include "bt_mgmt_dfu_internal.h"
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include "string.h"
#include "macros_common.h"
#include "channel_assignment.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mgmt_dfu, CONFIG_BT_MGMT_DFU_LOG_LEVEL);
/* These defined name only used by DFU */
#define DEVICE_NAME_DFU CONFIG_BT_DFU_DEVICE_NAME
#define DEVICE_NAME_DFU_LEN (sizeof(DEVICE_NAME_DFU) - 1)
static const struct bt_data ad_peer[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL, 0x84, 0xaa, 0x60, 0x74, 0x52, 0x8a, 0x8b, 0x86, 0xd3,
0x4c, 0xb7, 0x1d, 0x1d, 0xdc, 0x53, 0x8d),
};
/* Set aside space for name to be in scan response */
static struct bt_data sd_peer[1];
K_SEM_DEFINE(adv_sem, 1, 1);
static void smp_adv(void)
{
int ret;
ret = bt_le_adv_start(BT_LE_ADV_CONN_FAST_2, ad_peer, ARRAY_SIZE(ad_peer), sd_peer,
ARRAY_SIZE(sd_peer));
if (ret == -EALREADY) {
return;
} else if (ret) {
LOG_ERR("SMP_SVR Advertising failed to start (ret %d)", ret);
return;
}
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Regular SMP_SVR advertising started");
}
/* These callbacks are to override callback registed in module le_audio_ */
static void dfu_connected_cb(struct bt_conn *conn, uint8_t err)
{
LOG_INF("SMP connected\n");
}
static void dfu_disconnected_cb(struct bt_conn *conn, uint8_t reason)
{
LOG_INF("SMP disconnected 0x%02x\n", reason);
}
static void dfu_recycled_cb(void)
{
k_sem_give(&adv_sem);
}
static struct bt_conn_cb dfu_conn_callbacks = {
.connected = dfu_connected_cb,
.disconnected = dfu_disconnected_cb,
.recycled = dfu_recycled_cb,
};
static void dfu_set_bt_name(void)
{
int ret;
static char name[CONFIG_BT_DEVICE_NAME_MAX];
strlcpy(name, CONFIG_BT_DEVICE_NAME, CONFIG_BT_DEVICE_NAME_MAX);
ret = strlcat(name, "_", CONFIG_BT_DEVICE_NAME_MAX);
if (ret >= CONFIG_BT_DEVICE_NAME_MAX) {
LOG_ERR("Failed to set full BT name, will truncate");
}
#if (CONFIG_AUDIO_DEV == GATEWAY)
ret = strlcat(name, GW_TAG, CONFIG_BT_DEVICE_NAME_MAX);
if (ret >= CONFIG_BT_DEVICE_NAME_MAX) {
LOG_ERR("Failed to set full BT name, will truncate");
}
#else
enum audio_channel channel;
channel_assignment_get(&channel);
if (channel == AUDIO_CH_L) {
ret = strlcat(name, CH_L_TAG, CONFIG_BT_DEVICE_NAME_MAX);
if (ret >= CONFIG_BT_DEVICE_NAME_MAX) {
LOG_ERR("Failed to set full BT name, will truncate");
}
} else {
ret = strlcat(name, CH_R_TAG, CONFIG_BT_DEVICE_NAME_MAX);
if (ret >= CONFIG_BT_DEVICE_NAME_MAX) {
LOG_ERR("Failed to set full BT name, will truncate");
}
}
#endif
ret = strlcat(name, "_DFU", CONFIG_BT_DEVICE_NAME_MAX);
if (ret >= CONFIG_BT_DEVICE_NAME_MAX) {
LOG_ERR("Failed to set full BT name, will truncate");
}
sd_peer[0].type = BT_DATA_NAME_COMPLETE;
sd_peer[0].data_len = strlen(name);
sd_peer[0].data = name;
}
void bt_mgmt_dfu_start(void)
{
LOG_INF("Entering SMP server mode");
bt_conn_cb_register(&dfu_conn_callbacks);
dfu_set_bt_name();
while (1) {
/* In DFU mode, the device should always advertise */
k_sem_take(&adv_sem, K_FOREVER);
smp_adv();
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_MGMT_DFU_INTERNAL_H_
#define _BT_MGMT_DFU_INTERNAL_H_
/**
* @brief Enter the DFU mode. Advertise the SMP_SVR service only.
*
* @note This call does not return.
*/
void bt_mgmt_dfu_start(void);
#endif /* _BT_MGMT_DFU_INTERNAL_H_ */

View File

@@ -0,0 +1,59 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
rsource "Kconfig.defaults"
menu "Scanning"
choice NRF5340_AUDIO_GATEWAY_SCAN_MODE
prompt "Select Scan Mode to find Unicast Headset"
default SCAN_MODE_PASSIVE
config SCAN_MODE_PASSIVE
bool "Passive Scan"
config SCAN_MODE_ACTIVE
bool "Active Scan"
endchoice
#----------------------------------------------------------------------------#
menu "Connection"
config BLE_ACL_CONN_INTERVAL
int "Bluetooth LE ACL Connection Interval (x*1.25ms)"
default 8
help
This interval should be a multiple of the ISO interval used. The recommendation is to
increase the interval to something like BLE_ACL_CONN_INTERVAL_SLOW after the discovery
process is done, to free up time on air.
config BLE_ACL_CONN_INTERVAL_SLOW
int "Bluetooth LE ACL Connection Interval (x*1.25ms)"
default 72
help
This interval should be a multiple of the ISO interval used. 72*1.25=90 which will
suit both 7.5ms and 10ms.
config BLE_ACL_SLAVE_LATENCY
int "Bluetooth LE Slave Latency"
default 0
config BLE_ACL_SUP_TIMEOUT
int "Bluetooth LE Supervision Timeout (x*10ms)"
default 100
endmenu # Connection
#----------------------------------------------------------------------------#
menu "Log level"
module = BT_MGMT_SCAN
module-str = bt-mgmt-scan
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log level
endmenu # Scanning

View File

@@ -0,0 +1,18 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
if BT_OBSERVER
config BT_BACKGROUND_SCAN_INTERVAL
default 32
config BT_BACKGROUND_SCAN_WINDOW
default 32
endif # BT_OBSERVER
config BT_SCAN_WITH_IDENTITY
default y

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_mgmt.h"
#include <zephyr/bluetooth/bluetooth.h>
#include <hci_core.h>
#include "bt_mgmt_scan_for_broadcast_internal.h"
#include "bt_mgmt_scan_for_conn_internal.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mgmt_scan, CONFIG_BT_MGMT_SCAN_LOG_LEVEL);
static char srch_name[BLE_SEARCH_NAME_MAX_LEN];
static void addr_print(void)
{
char addr_str[BT_ADDR_LE_STR_LEN];
static struct bt_le_oob _oob = {.addr = 0};
/* NOTE: We are using an internal struct here to get the address without forcing the
* RPA to time out, should be changed once k_forever bug (DRGN-21459) has been fixed
*/
bt_addr_le_copy(&_oob.addr, &bt_dev.random_addr);
(void)bt_addr_le_to_str(&_oob.addr, addr_str, BT_ADDR_LE_STR_LEN);
LOG_INF("Local addr: %s. May time out. Updates not printed", addr_str);
}
int bt_mgmt_scan_start(uint16_t scan_intvl, uint16_t scan_win, enum bt_mgmt_scan_type type,
char const *const name, uint32_t brdcast_id)
{
int ret;
static int scan_interval = CONFIG_BT_BACKGROUND_SCAN_INTERVAL;
static int scan_window = CONFIG_BT_BACKGROUND_SCAN_WINDOW;
/* Only change search name if a new name has been supplied */
if (name != NULL) {
size_t name_size = MIN(strlen(name), BLE_SEARCH_NAME_MAX_LEN - 1);
memcpy(srch_name, name, name_size);
srch_name[name_size] = '\0';
}
if (scan_intvl != 0) {
scan_interval = scan_intvl;
}
if (scan_win != 0) {
scan_window = scan_win;
}
struct bt_le_scan_param *scan_param =
BT_LE_SCAN_PARAM(NRF5340_AUDIO_GATEWAY_SCAN_TYPE, BT_LE_SCAN_OPT_FILTER_DUPLICATE,
scan_interval, scan_window);
if (type == BT_MGMT_SCAN_TYPE_CONN && IS_ENABLED(CONFIG_BT_CENTRAL)) {
ret = bt_mgmt_scan_for_conn_start(scan_param, srch_name);
} else if (type == BT_MGMT_SCAN_TYPE_BROADCAST &&
IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SINK)) {
ret = bt_mgmt_scan_for_broadcast_start(scan_param, srch_name, brdcast_id);
} else {
LOG_WRN("Invalid scan type: %d, scan not started", type);
return -EINVAL;
}
if (ret) {
return ret;
}
addr_print();
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Scanning successfully started");
return 0;
}

View File

@@ -0,0 +1,477 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_mgmt_scan_for_broadcast_internal.h"
#include <zephyr/zbus/zbus.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/sys/byteorder.h>
#include <hci_core.h>
#include "bt_mgmt.h"
#include "macros_common.h"
#include "zbus_common.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(bt_mgmt_scan);
/* Any value above 0xFFFFFF is invalid, so one can use 0xFFFFFFFF to denote
* an invalid broadcast ID.
*/
#define INVALID_BROADCAST_ID 0xFFFFFFFF
#define PA_SYNC_SKIP 2
/* Similar to retries for connections */
#define PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO 20
#define BIS_SYNC_STATE_NOT_SYNCED 0
ZBUS_CHAN_DECLARE(bt_mgmt_chan);
struct bt_le_scan_cb scan_callback;
static bool scan_cb_registered;
static bool scan_dlg_cb_registered;
static bool sync_cb_registered;
static char const *srch_name;
static uint32_t srch_brdcast_id = BRDCAST_ID_NOT_USED;
static struct bt_le_per_adv_sync *pa_sync;
static const struct bt_bap_scan_delegator_recv_state *req_recv_state;
static uint8_t bt_mgmt_broadcast_code[BT_ISO_BROADCAST_CODE_SIZE];
struct broadcast_source {
char name[BLE_SEARCH_NAME_MAX_LEN];
uint32_t id;
};
static struct broadcast_source brcast_src_info;
static void scan_restart_worker(struct k_work *work)
{
int ret;
ret = bt_le_scan_stop();
if (ret && ret != -EALREADY) {
LOG_WRN("Stop scan failed: %d", ret);
}
/* Delete pending PA sync before restarting scan */
ret = bt_mgmt_pa_sync_delete(pa_sync);
if (ret) {
LOG_WRN("Failed to delete pending PA sync: %d", ret);
}
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_BROADCAST, NULL, BRDCAST_ID_NOT_USED);
if (ret) {
LOG_WRN("Failed to restart scanning for broadcast: %d", ret);
}
}
K_WORK_DEFINE(scan_restart_work, scan_restart_worker);
static void pa_sync_timeout(struct k_timer *timer)
{
LOG_WRN("PA sync create timed out, restarting scanning");
k_work_submit(&scan_restart_work);
}
K_TIMER_DEFINE(pa_sync_timer, pa_sync_timeout, NULL);
static uint16_t interval_to_sync_timeout(uint16_t interval)
{
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(interval);
timeout = (interval_ms * PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO) / 10;
/* Enforce restraints */
timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, BT_GAP_PER_ADV_MAX_TIMEOUT);
return (uint16_t)timeout;
}
static void periodic_adv_sync(const struct bt_le_scan_recv_info *info,
struct broadcast_source source)
{
int ret;
struct bt_le_per_adv_sync_param param;
bt_le_scan_cb_unregister(&scan_callback);
scan_cb_registered = false;
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);
/* Set timeout to same value as PA sync timeout in ms */
k_timer_start(&pa_sync_timer, K_MSEC(param.timeout * 10), K_NO_WAIT);
ret = bt_le_per_adv_sync_create(&param, &pa_sync);
if (ret) {
LOG_ERR("Could not sync to PA: %d", ret);
ret = bt_mgmt_pa_sync_delete(pa_sync);
if (ret) {
LOG_ERR("Could not delete PA sync: %d", ret);
}
return;
}
brcast_src_info = source;
}
/**
* @brief Check and parse advertising data for broadcast name and ID.
*
* @param[in] data Advertising data to check and parse.
* @param[out] user_data Will contain pointer to broadcast_source struct to be populated.
*
* @retval true Continue to parse LTVs.
* @retval false Stop parsing LTVs.
*/
static bool scan_check_broadcast_source(struct bt_data *data, void *user_data)
{
struct broadcast_source *source = (struct broadcast_source *)user_data;
struct bt_uuid_16 adv_uuid;
if (data->type == BT_DATA_BROADCAST_NAME && data->data_len) {
/* Ensure that broadcast name is at least one character shorter than the value of
* BLE_SEARCH_NAME_MAX_LEN
*/
if (data->data_len < BLE_SEARCH_NAME_MAX_LEN) {
memcpy(source->name, data->data, data->data_len);
source->name[data->data_len] = '\0';
}
return true;
}
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 false;
}
if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO)) {
return true;
}
source->id = sys_get_le24(data->data + BT_UUID_SIZE_16);
return true;
}
/**
* @brief Callback handler for scan receive when scanning for broadcasters.
*
* @param[in] info Advertiser packet and scan response information.
* @param[in] ad Received advertising data.
*/
static void scan_recv_cb(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad)
{
struct broadcast_source source = {.id = INVALID_BROADCAST_ID};
static bool id_change_printed;
/* We are only interested in non-connectable periodic advertisers */
if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) || info->interval == 0) {
return;
}
bt_data_parse(ad, scan_check_broadcast_source, (void *)&source);
if (source.id == INVALID_BROADCAST_ID) {
return;
}
if (srch_brdcast_id < BRDCAST_ID_NOT_USED) {
/* Valid srch_brdcast_id supplied */
if (source.id != srch_brdcast_id) {
/* Broadcaster does not match src_brdcast_id */
if (!id_change_printed &&
strncmp(source.name, srch_name, BLE_SEARCH_NAME_MAX_LEN) == 0) {
LOG_INF("%s found with ID: 0x%06x\r\n"
"Looking for ID: 0x%06x. Broadcaster may have changed ID",
source.name, source.id, srch_brdcast_id);
id_change_printed = true;
}
return;
}
} else if (strncmp(source.name, srch_name, BLE_SEARCH_NAME_MAX_LEN) != 0) {
/* Broadcaster does not match src_name */
return;
}
LOG_DBG("Broadcast source %s found, id: 0x%06x", source.name, source.id);
id_change_printed = false;
periodic_adv_sync(info, source);
}
static void pa_synced_cb(struct bt_le_per_adv_sync *sync,
struct bt_le_per_adv_sync_synced_info *info)
{
int ret;
struct bt_mgmt_msg msg;
char addr_str[BT_ADDR_LE_STR_LEN];
(void)bt_addr_le_to_str(&sync->addr, addr_str, BT_ADDR_LE_STR_LEN);
LOG_INF("PA synced to name: %s, id: 0x%06x, addr: %s", brcast_src_info.name,
brcast_src_info.id, addr_str);
k_timer_stop(&pa_sync_timer);
ret = bt_le_scan_stop();
if (ret && ret != -EALREADY) {
LOG_WRN("Stop scan failed: %d", ret);
}
msg.event = BT_MGMT_PA_SYNCED;
msg.pa_sync = sync;
msg.broadcast_id = brcast_src_info.id;
ret = zbus_chan_pub(&bt_mgmt_chan, &msg, K_NO_WAIT);
ERR_CHK(ret);
}
static void pa_sync_terminated_cb(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_term_info *info)
{
int ret;
struct bt_mgmt_msg msg;
LOG_DBG("Periodic advertising sync lost");
msg.event = BT_MGMT_PA_SYNC_LOST;
msg.pa_sync = sync;
msg.pa_sync_term_reason = info->reason;
ret = zbus_chan_pub(&bt_mgmt_chan, &msg, K_NO_WAIT);
ERR_CHK(ret);
}
static struct bt_le_per_adv_sync_cb sync_callbacks = {
.synced = pa_synced_cb,
.term = pa_sync_terminated_cb,
};
static void pa_timer_handler(struct k_work *work)
{
int ret;
if (req_recv_state != NULL) {
enum bt_bap_pa_state pa_state;
if (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;
}
ret = bt_bap_scan_delegator_set_pa_state(req_recv_state->src_id, pa_state);
if (ret) {
LOG_ERR("set PA state to %d failed, err = %d", pa_state, ret);
}
}
}
static K_WORK_DELAYABLE_DEFINE(pa_timer, pa_timer_handler);
/**
* @brief Subscribe to periodic advertising sync transfer (PAST).
*
* @param[in] conn Pointer to the connection object.
* @param[in] pa_interval Periodic advertising interval.
* @return 0 if success, error otherwise.
*/
static int pa_sync_past(struct bt_conn *conn, uint16_t pa_interval)
{
int ret;
struct bt_le_per_adv_sync_transfer_param param = {0};
param.skip = PA_SYNC_SKIP;
param.timeout = interval_to_sync_timeout(pa_interval);
ret = bt_le_per_adv_sync_transfer_subscribe(conn, &param);
if (ret) {
LOG_WRN("Could not do PAST subscribe: %d", ret);
return ret;
}
LOG_DBG("Syncing with PAST: %d", ret);
/* param.timeout is scaled in 10ms, so we need to *10 when we put it into K_MSEC() */
(void)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)
{
int ret;
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) {
LOG_DBG("Already syncing");
/* TODO: Terminate existing sync and then sync to new?*/
return -EALREADY;
}
LOG_INF("broadcast ID received = %X", recv_state->broadcast_id);
brcast_src_info.id = recv_state->broadcast_id;
if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER) && past_avail) {
ret = pa_sync_past(conn, pa_interval);
if (ret) {
LOG_ERR("Subscribe to PA sync PAST failed, ret = %d", ret);
return ret;
}
ret = bt_bap_scan_delegator_set_pa_state(req_recv_state->src_id,
BT_BAP_PA_STATE_INFO_REQ);
if (ret) {
LOG_ERR("Set PA state to INFO_REQ failed, err = %d", ret);
return ret;
}
} else if (brcast_src_info.id != INVALID_BROADCAST_ID) {
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_BROADCAST, NULL,
brcast_src_info.id);
return ret;
}
return 0;
}
static int pa_sync_term_req_cb(struct bt_conn *conn,
const struct bt_bap_scan_delegator_recv_state *recv_state)
{
int ret;
struct bt_mgmt_msg msg;
msg.event = BT_MGMT_BROADCAST_SINK_DISABLE;
ret = zbus_chan_pub(&bt_mgmt_chan, &msg, K_NO_WAIT);
ERR_CHK(ret);
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_ISO_BROADCAST_CODE_SIZE])
{
int ret;
struct bt_mgmt_msg msg;
LOG_DBG("Broadcast code received for %p", (void *)recv_state);
memcpy(bt_mgmt_broadcast_code, broadcast_code, BT_ISO_BROADCAST_CODE_SIZE);
msg.event = BT_MGMT_BROADCAST_CODE_RECEIVED;
ret = zbus_chan_pub(&bt_mgmt_chan, &msg, K_NO_WAIT);
ERR_CHK(ret);
}
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])
{
int ret;
struct bt_mgmt_msg msg;
/* Only support one subgroup for now */
LOG_DBG("BIS sync request received for %p: 0x%08x", (void *)recv_state, bis_sync_req[0]);
if (bis_sync_req[0] == BIS_SYNC_STATE_NOT_SYNCED) {
msg.event = BT_MGMT_BROADCAST_SINK_DISABLE;
ret = zbus_chan_pub(&bt_mgmt_chan, &msg, K_NO_WAIT);
ERR_CHK(ret);
}
return 0;
}
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,
};
void bt_mgmt_broadcast_code_ptr_get(uint8_t **broadcast_code_ptr)
{
if (broadcast_code_ptr == NULL) {
LOG_ERR("Null pointer given");
return;
}
*broadcast_code_ptr = bt_mgmt_broadcast_code;
}
void bt_mgmt_scan_delegator_init(void)
{
if (!scan_dlg_cb_registered) {
bt_bap_scan_delegator_register(&scan_delegator_cbs);
scan_dlg_cb_registered = true;
}
if (!sync_cb_registered) {
bt_le_per_adv_sync_cb_register(&sync_callbacks);
sync_cb_registered = true;
}
}
int bt_mgmt_scan_for_broadcast_start(struct bt_le_scan_param *scan_param, char const *const name,
uint32_t brdcast_id)
{
int ret;
if (!sync_cb_registered) {
bt_le_per_adv_sync_cb_register(&sync_callbacks);
sync_cb_registered = true;
}
if (!scan_cb_registered) {
scan_callback.recv = scan_recv_cb;
bt_le_scan_cb_register(&scan_callback);
scan_cb_registered = true;
} else {
if (name == srch_name && brdcast_id == BRDCAST_ID_NOT_USED) {
return -EALREADY;
}
/* Might already be scanning, stop current scan to update param in case it has
* changed.
*/
ret = bt_le_scan_stop();
if (ret && ret != -EALREADY) {
LOG_ERR("Failed to stop scan: %d", ret);
return ret;
}
}
srch_name = name;
if (brdcast_id != BRDCAST_ID_NOT_USED) {
srch_brdcast_id = brdcast_id;
}
ret = bt_le_scan_start(scan_param, NULL);
if (ret) {
return ret;
}
return 0;
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_MGMT_SCAN_FOR_BROADCAST_INTERNAL_H_
#define _BT_MGMT_SCAN_FOR_BROADCAST_INTERNAL_H_
#include <zephyr/bluetooth/bluetooth.h>
/**
* @brief Scan for a broadcaster with the given @p name.
*
* @param[in] scan_param Pointer to the struct containing parameters to use.
* @param[in] name Broadcast name to search for.
* @param[in] brdcast_id Broadcast ID to search for.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_scan_for_broadcast_start(struct bt_le_scan_param *scan_param, char const *const name,
uint32_t brdcast_id);
#endif /* _BT_MGMT_SCAN_FOR_BROADCAST_INTERNAL_H_ */

View File

@@ -0,0 +1,363 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_mgmt_scan_for_conn_internal.h"
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/audio/csip.h>
/* Will become public when https://github.com/zephyrproject-rtos/zephyr/pull/73445 is merged */
#include <../subsys/bluetooth/audio/csip_internal.h>
#include "bt_mgmt.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(bt_mgmt_scan);
#define CONNECTION_PARAMETERS \
BT_LE_CONN_PARAM(CONFIG_BLE_ACL_CONN_INTERVAL, CONFIG_BLE_ACL_CONN_INTERVAL, \
CONFIG_BLE_ACL_SLAVE_LATENCY, CONFIG_BLE_ACL_SUP_TIMEOUT)
static uint8_t bonded_num;
static struct bt_le_scan_cb scan_callback;
static bool cb_registered;
static char const *srch_name;
static uint8_t const *server_sirk;
static void bond_check(const struct bt_bond_info *info, void *user_data)
{
char addr_buf[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(&info->addr, addr_buf, BT_ADDR_LE_STR_LEN);
LOG_DBG("Stored bonding found: %s", addr_buf);
bonded_num++;
}
static void bond_connect(const struct bt_bond_info *bond_info, void *user_data)
{
int ret;
const bt_addr_le_t *adv_addr = user_data;
struct bt_conn *conn = NULL;
char addr_string[BT_ADDR_LE_STR_LEN];
if (!bt_addr_le_cmp(&bond_info->addr, adv_addr)) {
LOG_DBG("Found bonded device");
/* Check if the device is still connected due to waiting for ACL timeout */
struct bt_conn *bonded_conn =
bt_conn_lookup_addr_le(BT_ID_DEFAULT, &bond_info->addr);
struct bt_conn_info conn_info;
if (bonded_conn != NULL) {
ret = bt_conn_get_info(bonded_conn, &conn_info);
if (ret == 0 && conn_info.state == BT_CONN_STATE_CONNECTED) {
LOG_DBG("Trying to connect to an already connected conn");
bt_conn_unref(bonded_conn);
return;
}
/* Unref is needed due to bt_conn_lookup */
bt_conn_unref(bonded_conn);
}
bt_le_scan_cb_unregister(&scan_callback);
cb_registered = false;
ret = bt_le_scan_stop();
if (ret) {
LOG_WRN("Stop scan failed: %d", ret);
}
bt_addr_le_to_str(adv_addr, addr_string, BT_ADDR_LE_STR_LEN);
LOG_INF("Creating connection to bonded device: %s", addr_string);
ret = bt_conn_le_create(adv_addr, BT_CONN_LE_CREATE_CONN, CONNECTION_PARAMETERS,
&conn);
if (ret) {
LOG_WRN("Create ACL connection failed: %d", ret);
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_CONN, NULL,
BRDCAST_ID_NOT_USED);
if (ret) {
LOG_ERR("Failed to restart scanning: %d", ret);
}
}
}
}
/**
* @brief Check if the address belongs to an already connected device.
*
* @param[in] addr Address to check.
*
* @retval false No device in connected state with that address.
* @retval true Device found.
*/
static bool conn_exist_check(bt_addr_le_t *addr)
{
int ret;
struct bt_conn_info info;
struct bt_conn *existing_conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr);
if (existing_conn != NULL) {
ret = bt_conn_get_info(existing_conn, &info);
if (ret == 0 && info.state == BT_CONN_STATE_CONNECTED) {
LOG_DBG("Trying to connect to an already connected conn");
bt_conn_unref(existing_conn);
return true;
} else if (ret) {
LOG_WRN("Failed to get info from conn: %d", ret);
}
/* Unref is needed due to bt_conn_lookup */
bt_conn_unref(existing_conn);
}
/* No existing connection found with that address */
return false;
}
/**
* @brief Check the advertising data for the matching device name.
*
* @param[in] data The advertising data to be checked.
* @param[in] user_data Pointer to the address.
*
* @retval false Stop going through adv data.
* @retval true Continue checking the data.
*/
static bool device_name_check(struct bt_data *data, void *user_data)
{
int ret;
bt_addr_le_t *addr = user_data;
struct bt_conn *conn = NULL;
char addr_string[BT_ADDR_LE_STR_LEN];
/* We only care about LTVs with name */
if (data->type == BT_DATA_NAME_COMPLETE || data->type == BT_DATA_NAME_SHORTENED) {
size_t srch_name_size = strlen(srch_name);
if ((data->data_len == srch_name_size) &&
(memcmp(srch_name, data->data, srch_name_size) == 0)) {
/* Check if the device is still connected due to waiting for ACL timeout */
if (conn_exist_check(addr)) {
/* Device is already connected, stop parsing the adv data */
return false;
}
LOG_DBG("Device found: %s", srch_name);
bt_le_scan_cb_unregister(&scan_callback);
cb_registered = false;
ret = bt_le_scan_stop();
if (ret) {
LOG_ERR("Stop scan failed: %d", ret);
}
bt_addr_le_to_str(addr, addr_string, BT_ADDR_LE_STR_LEN);
LOG_INF("Creating connection to device: %s", addr_string);
ret = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, CONNECTION_PARAMETERS,
&conn);
if (ret) {
LOG_ERR("Could not init connection: %d", ret);
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_CONN, NULL,
BRDCAST_ID_NOT_USED);
if (ret) {
LOG_ERR("Failed to restart scanning: %d", ret);
}
}
return false;
}
}
return true;
}
/**
* @brief Check the advertising data for the matching 'Set Identity Resolving Key' (SIRK).
*
* @param[in] data The advertising data to be checked.
* @param[in] user_data Pointer to the address.
*
* @retval false Stop going through adv data.
* @retval true Continue checking the data.
*/
static bool csip_found(struct bt_data *data, void *user_data)
{
int ret;
bt_addr_le_t *addr = user_data;
struct bt_conn *conn = NULL;
char addr_string[BT_ADDR_LE_STR_LEN];
if (!bt_csip_set_coordinator_is_set_member(server_sirk, data)) {
/* This part of the data doesn't contain matching SIRK, continue parsing */
return true;
}
/* Check if the device is still connected due to waiting for ACL timeout */
if (conn_exist_check(addr)) {
/* Device is already connected, stop parsing the adv data */
return false;
}
LOG_DBG("Coordinated set device found");
server_sirk = NULL;
bt_le_scan_cb_unregister(&scan_callback);
cb_registered = false;
ret = bt_le_scan_stop();
if (ret) {
LOG_ERR("Stop scan failed: %d", ret);
}
bt_addr_le_to_str(addr, addr_string, BT_ADDR_LE_STR_LEN);
LOG_INF("Creating connection to device in coordinated set: %s", addr_string);
ret = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, CONNECTION_PARAMETERS, &conn);
if (ret) {
LOG_ERR("Could not init connection: %d", ret);
ret = bt_mgmt_scan_start(0, 0, BT_MGMT_SCAN_TYPE_CONN, NULL, BRDCAST_ID_NOT_USED);
if (ret) {
LOG_ERR("Failed to restart scanning: %d", ret);
}
}
/* Set member found and connected, stop parsing */
return false;
}
/**
* @brief Callback handler for scan receive when scanning for connections.
*
* @param[in] info Advertiser packet and scan response information.
* @param[in] ad Received advertising data.
*/
static void scan_recv_cb(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad)
{
/* We only care about connectable advertisers */
if (!(info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE)) {
return;
}
switch (info->adv_type) {
case BT_GAP_ADV_TYPE_ADV_DIRECT_IND:
/* Direct advertising has no payload, so no need to parse */
bt_foreach_bond(BT_ID_DEFAULT, bond_connect, (void *)info->addr);
break;
case BT_GAP_ADV_TYPE_ADV_IND:
/* Fall through */
case BT_GAP_ADV_TYPE_EXT_ADV:
/* Fall through */
case BT_GAP_ADV_TYPE_SCAN_RSP:
/* Note: May lead to connection creation */
if (bonded_num < CONFIG_BT_MAX_PAIRED) {
if (server_sirk == NULL) {
bt_data_parse(ad, device_name_check, (void *)info->addr);
} else {
bt_data_parse(ad, csip_found, (void *)info->addr);
}
} else {
/* All bonded slots are taken, so we will only
* accept previously bonded devices
*/
bt_foreach_bond(BT_ID_DEFAULT, bond_connect, (void *)info->addr);
}
break;
default:
break;
}
}
static void conn_in_coord_set_check(struct bt_conn *conn, void *data)
{
int ret;
struct bt_conn_info info;
const struct bt_csip_set_coordinator_set_member *member;
if (data == NULL) {
LOG_ERR("Got NULL pointer");
return;
}
uint8_t *num_filled = (uint8_t *)data;
ret = bt_conn_get_info(conn, &info);
if (ret) {
LOG_ERR("Failed to get conn info for %p: %d", (void *)conn, ret);
return;
}
/* Don't care about connection not in a connected state */
if (info.state != BT_CONN_STATE_CONNECTED) {
return;
}
member = bt_csip_set_coordinator_set_member_by_conn(conn);
if (memcmp((void *)server_sirk, (void *)member->insts[0].info.sirk, BT_CSIP_SIRK_SIZE) ==
0) {
(*num_filled)++;
}
}
void bt_mgmt_set_size_filled_get(uint8_t *num_filled)
{
bt_conn_foreach(BT_CONN_TYPE_LE, conn_in_coord_set_check, (void *)num_filled);
}
void bt_mgmt_scan_sirk_set(uint8_t const *const sirk)
{
server_sirk = sirk;
}
int bt_mgmt_scan_for_conn_start(struct bt_le_scan_param *scan_param, char const *const name)
{
int ret;
srch_name = name;
if (!cb_registered) {
scan_callback.recv = scan_recv_cb;
bt_le_scan_cb_register(&scan_callback);
cb_registered = true;
} else {
/* Already scanning, stop current scan to update param in case it has changed */
ret = bt_le_scan_stop();
if (ret && ret != -EALREADY) {
LOG_ERR("Failed to stop scan: %d", ret);
return ret;
}
}
/* Reset number of bonds found */
bonded_num = 0;
bt_foreach_bond(BT_ID_DEFAULT, bond_check, NULL);
if (bonded_num >= CONFIG_BT_MAX_PAIRED) {
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("All bonded slots filled, will not accept new devices");
}
ret = bt_le_scan_start(scan_param, NULL);
if (ret && ret != -EALREADY) {
return ret;
}
return 0;
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_MGMT_SCAN_FOR_CONN_INTERNAL_H_
#define _BT_MGMT_SCAN_FOR_CONN_INTERNAL_H_
#include <zephyr/bluetooth/bluetooth.h>
/**
* @brief Scan for a connection with the given device @p name.
*
* @param[in] scan_param Scan parameters to use.
* @param[in] name Device name to search for.
*
* @return 0 if success, error otherwise.
*/
int bt_mgmt_scan_for_conn_start(struct bt_le_scan_param *scan_param, char const *const name);
#endif /* _BT_MGMT_SCAN_FOR_CONN_INTERNAL_H_ */

View File

@@ -0,0 +1,26 @@
#
# Copyright (c) 2023 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
zephyr_library_include_directories(
volume
)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/bt_rendering_and_capture.c)
if (CONFIG_BT_VCP_VOL_CTLR AND CONFIG_BT_VCP_VOL_REND)
message(FATAL_ERROR "No support for vol controller and renderer on same device")
endif()
if (CONFIG_BT_VCP_VOL_REND)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/volume/bt_vol_rend.c)
endif()
if (CONFIG_BT_VCP_VOL_CTLR)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/volume/bt_vol_ctlr.c)
endif()

View File

@@ -0,0 +1,19 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
menu "BT renderer"
rsource "volume/Kconfig"
#----------------------------------------------------------------------------#
menu "Log level"
module = BT_RENDERING_AND_CAPTURE
module-str = bt-rendering-and-capture
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log level
endmenu # BT renderer

View File

@@ -0,0 +1,184 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_rendering_and_capture.h"
#include <zephyr/zbus/zbus.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/uuid.h>
#include "bt_vol_rend_internal.h"
#include "bt_vol_ctlr_internal.h"
#include "zbus_common.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_r_c, CONFIG_BT_RENDERING_AND_CAPTURE_LOG_LEVEL);
ZBUS_CHAN_DEFINE(volume_chan, struct volume_msg, NULL, NULL, ZBUS_OBSERVERS_EMPTY,
ZBUS_MSG_INIT(0));
int bt_r_and_c_volume_up(void)
{
int ret;
struct volume_msg msg;
if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND)) {
ret = bt_vol_rend_up();
return ret;
}
if (IS_ENABLED(CONFIG_BT_VCP_VOL_CTLR)) {
ret = bt_vol_ctlr_up();
return ret;
}
msg.event = VOLUME_UP;
ret = zbus_chan_pub(&volume_chan, &msg, K_NO_WAIT);
return ret;
}
int bt_r_and_c_volume_down(void)
{
int ret;
struct volume_msg msg;
if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND)) {
ret = bt_vol_rend_down();
return ret;
}
if (IS_ENABLED(CONFIG_BT_VCP_VOL_CTLR)) {
ret = bt_vol_ctlr_down();
return ret;
}
msg.event = VOLUME_DOWN;
ret = zbus_chan_pub(&volume_chan, &msg, K_NO_WAIT);
return ret;
}
int bt_r_and_c_volume_set(uint8_t volume, bool from_vcp)
{
int ret;
struct volume_msg msg;
if ((IS_ENABLED(CONFIG_BT_VCP_VOL_REND)) && !from_vcp) {
ret = bt_vol_rend_set(volume);
return ret;
}
if ((IS_ENABLED(CONFIG_BT_VCP_VOL_CTLR)) && !from_vcp) {
ret = bt_vol_ctlr_set(volume);
return ret;
}
msg.event = VOLUME_SET;
msg.volume = volume;
ret = zbus_chan_pub(&volume_chan, &msg, K_NO_WAIT);
return ret;
}
int bt_r_and_c_volume_mute(bool from_vcp)
{
int ret;
struct volume_msg msg;
if ((IS_ENABLED(CONFIG_BT_VCP_VOL_REND)) && !from_vcp) {
ret = bt_vol_rend_mute();
return ret;
}
if ((IS_ENABLED(CONFIG_BT_VCP_VOL_CTLR)) && !from_vcp) {
ret = bt_vol_ctlr_mute();
return ret;
}
msg.event = VOLUME_MUTE;
ret = zbus_chan_pub(&volume_chan, &msg, K_NO_WAIT);
return ret;
}
int bt_r_and_c_volume_unmute(void)
{
int ret;
struct volume_msg msg;
if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND)) {
ret = bt_vol_rend_unmute();
return ret;
}
if (IS_ENABLED(CONFIG_BT_VCP_VOL_CTLR)) {
ret = bt_vol_ctlr_unmute();
return ret;
}
msg.event = VOLUME_UNMUTE;
ret = zbus_chan_pub(&volume_chan, &msg, K_NO_WAIT);
return ret;
}
int bt_r_and_c_discover(struct bt_conn *conn)
{
int ret;
if (IS_ENABLED(CONFIG_BT_VCP_VOL_CTLR)) {
ret = bt_vol_ctlr_discover(conn);
if (ret) {
LOG_WRN("Failed to discover VCS: %d", ret);
return ret;
}
} else {
return -ENOTSUP;
}
return 0;
}
int bt_r_and_c_uuid_populate(struct net_buf_simple *uuid_buf)
{
if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND)) {
if (net_buf_simple_tailroom(uuid_buf) >= BT_UUID_SIZE_16) {
net_buf_simple_add_le16(uuid_buf, BT_UUID_VCS_VAL);
} else {
return -ENOMEM;
}
} else {
return -ENOTSUP;
}
return 0;
}
int bt_r_and_c_init(void)
{
int ret;
if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND)) {
ret = bt_vol_rend_init();
if (ret) {
LOG_WRN("Failed to initialize VCS renderer: %d", ret);
return ret;
}
}
if (IS_ENABLED(CONFIG_BT_VCP_VOL_CTLR)) {
ret = bt_vol_ctlr_init();
if (ret) {
LOG_WRN("Failed to initialize VCS controller: %d", ret);
return ret;
}
}
return 0;
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_REND_H_
#define _BT_REND_H_
#include <zephyr/bluetooth/conn.h>
/**
* @brief Adjust volume up by one step.
*
* @return 0 if success, error otherwise.
*/
int bt_r_and_c_volume_up(void);
/**
* @brief Adjust volume down by one step.
*
* @return 0 if success, error otherwise.
*/
int bt_r_and_c_volume_down(void);
/**
* @brief Set the volume to the given @p volume value.
*
* @param[in] volume Value to set the volume to (0-255).
* @param[in] from_vcp Describe if the function was called from a service
* or from somewhere else (buttons, shell, etc).
*
* @return 0 if success, error otherwise.
*/
int bt_r_and_c_volume_set(uint8_t volume, bool from_vcp);
/**
* @brief Mute the volume.
*
* @param[in] from_vcp Describe if the function was called from a service
* or from somewhere else (buttons, shell, etc).
*
* @return 0 if success, error otherwise.
*/
int bt_r_and_c_volume_mute(bool from_vcp);
/**
* @brief Unmute the volume.
*
* @return 0 if success, error otherwise.
*/
int bt_r_and_c_volume_unmute(void);
/**
* @brief Discover the rendering services.
*
* @param[in] conn Pointer to the connection on which to do the discovery.
*
* @return 0 if success, error otherwise.
*/
int bt_r_and_c_discover(struct bt_conn *conn);
/**
* @brief Put the UUIDs from this module into the buffer.
*
* @note This partial data is used to build a complete extended advertising packet.
*
* @param[out] uuid_buf Buffer being populated with UUIDs.
*
* @return 0 for success, error otherwise.
*/
int bt_r_and_c_uuid_populate(struct net_buf_simple *uuid_buf);
/**
* @brief Initialize the rendering services or profiles, or both.
*
* @return 0 if success, error otherwise.
*/
int bt_r_and_c_init(void);
#endif /* _BT_REND_H_ */

View File

@@ -0,0 +1,29 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
menu "Volume"
config BT_AUDIO_VOL_DEFAULT
int "Default volume"
range 0 255
default 195
help
The default volume when starting a volume control renderer.
config BT_AUDIO_VOL_STEP_SIZE
int "Volume adjust step size"
range 6 32
default 16
#----------------------------------------------------------------------------#
menu "Log level"
module = BT_VOL
module-str = bt-vol
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log level
endmenu # Volume

View File

@@ -0,0 +1,254 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_vol_ctlr_internal.h"
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/vcp.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_vol_ctlr, CONFIG_BT_VOL_LOG_LEVEL);
static struct bt_vcp_vol_ctlr *vcs_client_peer[CONFIG_BT_MAX_CONN];
/**
* @brief Get the index of the first available vcs_client_peer.
*
* @retval Index if success.
* @retval -ENOMEM if no available indexes.
*/
static int vcs_client_peer_index_free_get(void)
{
for (int i = 0; i < ARRAY_SIZE(vcs_client_peer); i++) {
if (vcs_client_peer[i] == NULL) {
return i;
}
}
LOG_WRN("No more indexes for VCS peer clients");
return -ENOMEM;
}
/**
* @brief Check if the given @p conn has a vcs_client_peer pointer.
*
* @param[in] conn The connection pointer to be checked.
*
* @retval True if vcs_client_peer exists.
* @retval False otherwise.
*/
static bool vcs_client_peer_exists(struct bt_conn *conn)
{
int ret;
struct bt_conn *result_conn = NULL;
for (int i = 0; i < ARRAY_SIZE(vcs_client_peer); i++) {
ret = bt_vcp_vol_ctlr_conn_get(vcs_client_peer[i], &result_conn);
if (!ret && conn == result_conn) {
return true;
}
if (ret == -ENOTCONN) {
/* VCS client no longer connected, free the index */
vcs_client_peer[i] = NULL;
return false;
}
}
return false;
}
/**
* @brief Callback handler for the volume state.
*
* @note This callback handler will be triggered if volume state has changed,
* or the playback was muted or unmuted.
*/
static void vcs_state_ctlr_cb_handler(struct bt_vcp_vol_ctlr *vcs, int err, uint8_t volume,
uint8_t mute)
{
int ret;
if (err) {
LOG_ERR("VCS state callback error: %d", err);
return;
}
for (int i = 0; i < ARRAY_SIZE(vcs_client_peer); i++) {
if (vcs == vcs_client_peer[i]) {
LOG_DBG("VCS state from remote device %d:", i);
continue;
}
LOG_DBG("Sync with other devices %d", i);
if (vcs_client_peer[i] == NULL) {
/* Skip */
continue;
}
ret = bt_vcp_vol_ctlr_set_vol(vcs_client_peer[i], volume);
if (ret) {
LOG_DBG("Failed to sync volume to remote device %d, err = "
"%d",
i, ret);
}
}
}
/**
* @brief Callback handler for the VCS controller flags.
*
* @note This callback handler will be triggered if VCS flags changed.
*/
static void vcs_flags_ctlr_cb_handler(struct bt_vcp_vol_ctlr *vcs, int err, uint8_t flags)
{
if (err) {
LOG_ERR("VCS flag callback error: %d", err);
} else {
LOG_DBG("Volume flags = 0x%01X", flags);
}
}
/**
* @brief Callback handler for the finished VCS discovery.
*
* @note This callback handler will be triggered when the VCS discovery has finished.
*/
static void vcs_discover_cb_handler(struct bt_vcp_vol_ctlr *vcs, int err, uint8_t vocs_count,
uint8_t aics_count)
{
if (err) {
LOG_WRN("VCS discover finished callback error: %d", err);
} else {
LOG_INF("VCS discover finished");
}
}
int bt_vol_ctlr_set(uint8_t volume)
{
int ret;
for (int i = 0; i < ARRAY_SIZE(vcs_client_peer); i++) {
if (vcs_client_peer[i] != NULL) {
ret = bt_vcp_vol_ctlr_set_vol(vcs_client_peer[i], volume);
if (ret) {
LOG_WRN("Failed to set volume for remote channel %d, ret = "
"%d",
i, ret);
}
}
}
return 0;
}
int bt_vol_ctlr_up(void)
{
int ret;
for (int i = 0; i < ARRAY_SIZE(vcs_client_peer); i++) {
if (vcs_client_peer[i] != NULL) {
ret = bt_vcp_vol_ctlr_unmute_vol_up(vcs_client_peer[i]);
if (ret) {
LOG_WRN("Failed to volume up for remote channel %d, ret = "
"%d",
i, ret);
}
}
}
return 0;
}
int bt_vol_ctlr_down(void)
{
int ret;
for (int i = 0; i < ARRAY_SIZE(vcs_client_peer); i++) {
if (vcs_client_peer[i] != NULL) {
ret = bt_vcp_vol_ctlr_unmute_vol_down(vcs_client_peer[i]);
if (ret) {
LOG_WRN("Failed to volume down for remote channel %d, ret "
"= %d",
i, ret);
}
}
}
return 0;
}
int bt_vol_ctlr_mute(void)
{
int ret;
for (int i = 0; i < ARRAY_SIZE(vcs_client_peer); i++) {
if (vcs_client_peer[i] != NULL) {
ret = bt_vcp_vol_ctlr_mute(vcs_client_peer[i]);
if (ret) {
LOG_WRN("Failed to mute for remote channel %d, ret "
"= %d",
i, ret);
}
}
}
return 0;
}
int bt_vol_ctlr_unmute(void)
{
int ret;
for (int i = 0; i < ARRAY_SIZE(vcs_client_peer); i++) {
if (vcs_client_peer[i] != NULL) {
ret = bt_vcp_vol_ctlr_unmute(vcs_client_peer[i]);
if (ret) {
LOG_WRN("Failed to unmute for remote channel %d, "
"ret = %d",
i, ret);
}
}
}
return 0;
}
int bt_vol_ctlr_discover(struct bt_conn *conn)
{
int ret, index;
if (vcs_client_peer_exists(conn)) {
return -EAGAIN;
}
index = vcs_client_peer_index_free_get();
if (index < 0) {
return index;
}
ret = bt_vcp_vol_ctlr_discover(conn, &vcs_client_peer[index]);
return ret;
}
int bt_vol_ctlr_init(void)
{
static struct bt_vcp_vol_ctlr_cb vcs_client_callback;
vcs_client_callback.discover = vcs_discover_cb_handler;
vcs_client_callback.state = vcs_state_ctlr_cb_handler;
vcs_client_callback.flags = vcs_flags_ctlr_cb_handler;
return bt_vcp_vol_ctlr_cb_register(&vcs_client_callback);
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_VOL_CTLR_INTERNAL_H_
#define _BT_VOL_CTLR_INTERNAL_H_
#include <zephyr/bluetooth/conn.h>
/**
* @brief Set volume to a specific value.
*
* @param[in] volume The absolute volume to be set.
*
* @retval 0 Volume change success.
* @retval -ENXIO The feature is disabled.
* @retval other Errors from underlying drivers.
*/
int bt_vol_ctlr_set(uint8_t volume);
/**
* @brief Turn the volume up by one step.
*
* @retval 0 Volume change success.
* @retval -ENXIO The feature is disabled.
* @retval other Errors from underlying drivers.
*/
int bt_vol_ctlr_up(void);
/**
* @brief Turn the volume down by one step.
*
* @retval 0 Volume change success.
* @retval -ENXIO The feature is disabled.
* @retval other Errors from underlying drivers.
*/
int bt_vol_ctlr_down(void);
/**
* @brief Mute the output volume of the device.
*
* @retval 0 Volume change success.
* @retval -ENXIO The feature is disabled.
* @retval other Errors from underlying drivers.
*/
int bt_vol_ctlr_mute(void);
/**
* @brief Unmute the output volume of the device.
*
* @retval 0 Volume change success.
* @retval -ENXIO The feature is disabled.
* @retval other Errors from underlying drivers.
*/
int bt_vol_ctlr_unmute(void);
/**
* @brief Discover Volume Control Service and included services.
*
* @param[in] conn Pointer to the connection on which to discover the services.
*
* @note This function starts a GATT discovery and sets up handles and
* subscriptions for the VCS and included services.
* Call it once before any other actions related to the VCS.
*
* @return 0 for success, error otherwise.
*/
int bt_vol_ctlr_discover(struct bt_conn *conn);
/**
* @brief Initialize the Volume Control Service client.
*
* @return 0 for success, error otherwise.
*/
int bt_vol_ctlr_init(void);
#endif /* _BT_VOL_CTLR_INTERNAL_H_ */

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_vol_rend_internal.h"
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/vcp.h>
#include "bt_rendering_and_capture.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_vol_rend, CONFIG_BT_VOL_LOG_LEVEL);
/**
* @brief Callback handler for the volume state.
*
* @note This callback handler will be triggered if volume state has changed,
* or the playback was muted or unmuted from the volume_controller.
*/
static void vcs_state_rend_cb_handler(struct bt_conn *conn, int err, uint8_t volume, uint8_t mute)
{
int ret;
if (err) {
LOG_ERR("VCS state callback error: %d", err);
return;
}
LOG_INF("Volume = %d, mute state = %d", volume, mute);
/* Send to bt_rend */
ret = bt_r_and_c_volume_set(volume, true);
if (ret) {
LOG_WRN("Failed to set volume");
}
if (mute) {
ret = bt_r_and_c_volume_mute(true);
if (ret) {
LOG_WRN("Error muting volume");
}
}
}
/**
* @brief Callback handler for the changed VCS renderer flags.
*
* @note This callback handler will be triggered if the VCS flags has changed.
*/
static void vcs_flags_rend_cb_handler(struct bt_conn *conn, int err, uint8_t flags)
{
if (err) {
LOG_ERR("VCS flag callback error: %d", err);
} else {
LOG_DBG("Volume flags = 0x%01X", flags);
}
}
int bt_vol_rend_set(uint8_t volume)
{
return bt_vcp_vol_rend_set_vol(volume);
}
int bt_vol_rend_up(void)
{
return bt_vcp_vol_rend_unmute_vol_up();
}
int bt_vol_rend_down(void)
{
return bt_vcp_vol_rend_unmute_vol_down();
}
int bt_vol_rend_mute(void)
{
return bt_vcp_vol_rend_mute();
}
int bt_vol_rend_unmute(void)
{
return bt_vcp_vol_rend_unmute();
}
int bt_vol_rend_init(void)
{
int ret;
struct bt_vcp_vol_rend_register_param vcs_param;
static struct bt_vcp_vol_rend_cb vcs_server_callback;
vcs_server_callback.state = vcs_state_rend_cb_handler;
vcs_server_callback.flags = vcs_flags_rend_cb_handler;
vcs_param.cb = &vcs_server_callback;
vcs_param.mute = BT_VCP_STATE_UNMUTED;
vcs_param.step = CONFIG_BT_AUDIO_VOL_STEP_SIZE;
vcs_param.volume = CONFIG_BT_AUDIO_VOL_DEFAULT;
ret = bt_vcp_vol_rend_register(&vcs_param);
if (ret) {
return ret;
}
return 0;
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BT_VOL_REND_INTERNAL_H_
#define _BT_VOL_REND_INTERNAL_H_
#include <stdint.h>
/**
* @brief Set volume to a specific value.
*
* @param[in] volume The absolute volume to be set.
*
* @retval 0 Volume change success.
* @retval -ENXIO The feature is disabled.
* @retval other Errors from underlying drivers.
*/
int bt_vol_rend_set(uint8_t volume);
/**
* @brief Turn the volume up by one step.
*
* @retval 0 Volume change success.
* @retval -ENXIO The feature is disabled.
* @retval other Errors from underlying drivers.
*/
int bt_vol_rend_up(void);
/**
* @brief Turn the volume down by one step.
*
* @retval 0 Volume change success.
* @retval -ENXIO The feature is disabled.
* @retval other Errors from underlying drivers.
*/
int bt_vol_rend_down(void);
/**
* @brief Mute the output volume of the device.
*
* @retval 0 Volume change success.
* @retval -ENXIO The feature is disabled.
* @retval other Errors from underlying drivers.
*/
int bt_vol_rend_mute(void);
/**
* @brief Unmute the output volume of the device.
*
* @retval 0 Volume change success.
* @retval -ENXIO The feature is disabled.
* @retval other Errors from underlying drivers.
*/
int bt_vol_rend_unmute(void);
/**
* @brief Initialize the Volume renderer.
*
* @return 0 for success, error otherwise.
*/
int bt_vol_rend_init(void);
#endif /* _BT_VOL_REND_INTERNAL_H_ */

View File

@@ -0,0 +1,36 @@
#
# Copyright (c) 2023 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
zephyr_library_include_directories(
broadcast
unicast
bt_le_audio_tx
)
add_subdirectory(bt_le_audio_tx)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/le_audio.c)
if (CONFIG_BT_BAP_BROADCAST_SINK)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/broadcast/broadcast_sink.c)
endif()
if (CONFIG_BT_BAP_BROADCAST_SOURCE)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/broadcast/broadcast_source.c)
endif()
if (CONFIG_BT_BAP_UNICAST_CLIENT)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/unicast/unicast_client.c)
endif()
if (CONFIG_BT_BAP_UNICAST_SERVER)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/unicast/unicast_server.c)
endif()

View File

@@ -0,0 +1,259 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
rsource "Kconfig.defaults"
menu "Broadcast"
choice BT_AUDIO_BROADCAST_BAP_CONFIGURATION
prompt "Broadcast codec configuration"
depends on TRANSPORT_BIS
default BT_AUDIO_BROADCAST_CONFIGURABLE
help
Select the broadcast codec configuration as given
in Table 6.4 of the Bluetooth Audio Profile specification.
USB only supports 48-kHz sampling rate.
config BT_AUDIO_BROADCAST_CONFIGURABLE
bool "Configurable broadcast settings"
depends on TRANSPORT_BIS
help
Configurable option that doesn't follow any preset. Allows for more flexibility.
config BT_BAP_BROADCAST_16_2_1
bool "16_2_1"
depends on TRANSPORT_BIS
help
Broadcast mandatory codec capability 16_2_1.
16kHz, 32kbps, 2 retransmits, 10ms transport latency, and 40ms presentation delay.
config BT_BAP_BROADCAST_16_2_2
bool "16_2_2"
depends on TRANSPORT_BIS
help
Broadcast mandatory codec capability 16_2_2.
16kHz, 32kbps, 4 retransmits, 60ms transport latency, and 40ms presentation delay.
config BT_BAP_BROADCAST_24_2_1
bool "24_2_1"
depends on TRANSPORT_BIS
help
Broadcast codec capability 24_2_1.
24kHz, 48kbps, 2 retransmits, 10ms transport latency, and 40ms presentation delay.
config BT_BAP_BROADCAST_24_2_2
bool "24_2_2"
depends on TRANSPORT_BIS
help
Broadcast codec capability 24_2_2.
24kHz, 48kbps, 4 retransmits, 60ms transport latency, and 40ms presentation delay.
config BT_BAP_BROADCAST_48_2_1
bool "48_2_1"
depends on TRANSPORT_BIS
help
Broadcast codec capability 48_2_1.
48kHz, 80kbps, 4 retransmits, 20ms transport latency, and 40ms presentation delay.
config BT_BAP_BROADCAST_48_2_2
bool "48_2_2"
depends on TRANSPORT_BIS
help
Broadcast codec capability 48_2_2.
48kHz, 80kbps, 4 retransmits, 65ms transport latency, and 40ms presentation delay.
config BT_BAP_BROADCAST_48_4_1
bool "48_4_1"
depends on TRANSPORT_BIS
help
Broadcast codec capability 48_4_1.
48kHz, 96kbps, 4 retransmits, 20ms transport latency, and 40ms presentation delay.
config BT_BAP_BROADCAST_48_4_2
bool "48_4_2"
depends on TRANSPORT_BIS
help
Broadcast codec capability 48_4_2.
48kHz, 96kbps, 4 retransmits, 65ms transport latency, and 40ms presentation delay.
config BT_BAP_BROADCAST_48_6_1
bool "48_6_1"
depends on TRANSPORT_BIS
help
Broadcast codec capability 48_6_1.
48kHz, 124kbps, 4 retransmits, 20ms transport latency, and 40ms presentation delay.
config BT_BAP_BROADCAST_48_6_2
bool "48_6_2"
depends on TRANSPORT_BIS
help
Broadcast codec capability 48_6_2.
48kHz, 124kbps, 4 retransmits, 65ms transport latency, and 40ms presentation delay.
endchoice
config BT_AUDIO_BROADCAST_NAME
string "Broadcast name"
default "NRF5340_BROADCASTER"
# TODO: Add back 'depends on TRANSPORT_BIS' once applications are ready
help
Name of the broadcast; not the same as BT_DEVICE_NAME.
config BT_AUDIO_BROADCAST_NAME_ALT
string "Alternative broadcast name"
default "NRF5340_BROADCASTER_ALT"
# TODO: Add back 'depends on TRANSPORT_BIS' once applications are ready
help
Alternative name of the broadcast.
config BT_AUDIO_USE_BROADCAST_NAME_ALT
bool "Use the alternative broadcast name"
default n
# TODO: Add back 'depends on TRANSPORT_BIS' once applications are ready
help
Use the alternative broadcast name.
config BT_AUDIO_BROADCAST_ENCRYPTED
bool "Encrypted broadcast"
depends on TRANSPORT_BIS
default n
help
Encrypt the broadcast to limit the connection possibilities.
config BT_AUDIO_BROADCAST_ENCRYPTION_KEY
string "Broadcast encryption key"
depends on TRANSPORT_BIS
default "NRF5340_BIS_DEMO"
help
Key to use for encryption and decryption, with maximum BT_ISO_BROADCAST_CODE_SIZE
characters. Encryption keys larger than BT_ISO_BROADCAST_CODE_SIZE will be truncated to
BT_ISO_BROADCAST_CODE_SIZE.
config BT_AUDIO_USE_BROADCAST_ID_RANDOM
bool "Use a random broadcast ID"
depends on TRANSPORT_BIS
default y
help
Use a randomly generated broadcast ID.
config BT_AUDIO_BROADCAST_ID_FIXED
hex "Fixed broadcast ID"
depends on TRANSPORT_BIS
default 0x123456
help
Fixed broadcast ID; 3 octets. Will only be used if BT_AUDIO_USE_BROADCAST_ID_RANDOM=n.
Only use for debugging.
config BT_AUDIO_BROADCAST_PBA_METADATA_SIZE
int "Configure PBA meta data buffer size"
depends on TRANSPORT_BIS && AURACAST
default 16
help
Configure the maximum size of the Public Broadcast Announcement meata data buffer in octets.
This is the number of meta data LVT records, or the number of meta data items multiplied by
the size of the LTV (sizeof(bt_data)). Configurable option that doesn't follow any preset.
Allows for more flexibility.
config BT_AUDIO_BROADCAST_PARENTAL_RATING
hex "Parental rating"
depends on TRANSPORT_BIS
default 0x00
range 0x00 0x0F
help
Set the parental rating for the broadcast.
BT_AUDIO_PARENTAL_RATING_NO_RATING = 0x00,
BT_AUDIO_PARENTAL_RATING_AGE_ANY = 0x01,
BT_AUDIO_PARENTAL_RATING_AGE_5_OR_ABOVE = 0x02,
BT_AUDIO_PARENTAL_RATING_AGE_6_OR_ABOVE = 0x03,
BT_AUDIO_PARENTAL_RATING_AGE_7_OR_ABOVE = 0x04,
BT_AUDIO_PARENTAL_RATING_AGE_8_OR_ABOVE = 0x05,
BT_AUDIO_PARENTAL_RATING_AGE_9_OR_ABOVE = 0x06,
BT_AUDIO_PARENTAL_RATING_AGE_10_OR_ABOVE = 0x07,
BT_AUDIO_PARENTAL_RATING_AGE_11_OR_ABOVE = 0x08,
BT_AUDIO_PARENTAL_RATING_AGE_12_OR_ABOVE = 0x09,
BT_AUDIO_PARENTAL_RATING_AGE_13_OR_ABOVE = 0x0A,
BT_AUDIO_PARENTAL_RATING_AGE_14_OR_ABOVE = 0x0B,
BT_AUDIO_PARENTAL_RATING_AGE_15_OR_ABOVE = 0x0C,
BT_AUDIO_PARENTAL_RATING_AGE_16_OR_ABOVE = 0x0D,
BT_AUDIO_PARENTAL_RATING_AGE_17_OR_ABOVE = 0x0E,
BT_AUDIO_PARENTAL_RATING_AGE_18_OR_ABOVE = 0x0F
config BT_AUDIO_BROADCAST_IMMEDIATE_FLAG
bool "Immediate rendering flag"
depends on TRANSPORT_BIS
default n
help
Set the immediate rendering flag.
config AURACAST
bool "Enable Auracast"
depends on TRANSPORT_BIS
default y
help
When Auracast is enabled, a Public Broadcast Announcement will be included
when advertising.
config BT_AUDIO_BITRATE_BROADCAST_SRC
int "ISO stream bitrate"
depends on TRANSPORT_BIS
default 96000 if BT_AUDIO_BROADCAST_CONFIGURABLE
default 32000 if BT_BAP_BROADCAST_16_2_1 || BT_BAP_BROADCAST_16_2_2
default 48000 if BT_BAP_BROADCAST_24_2_1 || BT_BAP_BROADCAST_24_2_2
default 80000 if BT_BAP_BROADCAST_48_2_1 || BT_BAP_BROADCAST_48_2_2
default 96000 if BT_BAP_BROADCAST_48_4_1 || BT_BAP_BROADCAST_48_4_2
default 124000 if BT_BAP_BROADCAST_48_6_1 || BT_BAP_BROADCAST_48_6_2
help
Bitrate for the broadcast source ISO stream.
config BT_AUDIO_SCAN_DELEGATOR
bool "Enable scan delegator"
depends on TRANSPORT_BIS
select BT_CAP_ACCEPTOR
select BT_CSIP_SET_MEMBER
select BT_CAP_ACCEPTOR_SET_MEMBER
select BT_GAP_PERIPHERAL_PREF_PARAMS
select BT_VCP_VOL_REND
select BT_PER_ADV_SYNC_TRANSFER_RECEIVER
help
When scan delegator feature is enabled, the broadcast sink will not
search for a predefined broadcast source. Instead, it will wait for a
broadcast assistant to connect and control.
config BT_SET_IDENTITY_RESOLVING_KEY_DEFAULT
string
default "NRF5340_BIS_DEMO"
help
Default string to configure the Set Identify Resolving Key (SIRK), must
be changed before production uniquely for each coordinated set.
config BT_SET_IDENTITY_RESOLVING_KEY
string "String used to configure the SIRK"
default BT_SET_IDENTITY_RESOLVING_KEY_DEFAULT
help
Defines a string to configure the Set Identify Resolving Key (SIRK), must
be changed before production uniquely for each coordinated set. The SIRK
must be 16 characters (16 bytes).
config BT_AUDIO_BROADCAST_ZBUS_EVT_STREAM_SENT
bool "Enable Zephyr bus event for stream sent"
help
Enable ZBUS event signalling that a stream has been sent, and that the next frame can
be prepared. As this event will trigger once for each frame it will cause significant
overhead, even if the event is not used.
#----------------------------------------------------------------------------#
menu "Log levels"
module = BROADCAST_SOURCE
module-str = broadcast_source
source "subsys/logging/Kconfig.template.log_config"
module = BROADCAST_SINK
module-str = broadcast_sink
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log levels
endmenu # Broadcast

View File

@@ -0,0 +1,11 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
config BT_BUF_ACL_RX_SIZE
default 502 if (AUDIO_DFU > 0)
config BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE
default 80

View File

@@ -0,0 +1,849 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "broadcast_sink.h"
#include <zephyr/zbus/zbus.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/pacs.h>
#include <zephyr/bluetooth/audio/csip.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/sys/byteorder.h>
/* TODO: Remove when a get_info function is implemented in host */
#include <../subsys/bluetooth/audio/bap_endpoint.h>
#include "bt_mgmt.h"
#include "macros_common.h"
#include "zbus_common.h"
#include "channel_assignment.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(broadcast_sink, CONFIG_BROADCAST_SINK_LOG_LEVEL);
BUILD_ASSERT(CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT <= 2,
"A maximum of two broadcast streams are currently supported");
ZBUS_CHAN_DEFINE(le_audio_chan, struct le_audio_msg, NULL, NULL, ZBUS_OBSERVERS_EMPTY,
ZBUS_MSG_INIT(0));
static uint8_t bis_encryption_key[BT_ISO_BROADCAST_CODE_SIZE] = {0};
static bool broadcast_code_received;
struct audio_codec_info {
uint8_t id;
uint16_t cid;
uint16_t vid;
int frequency;
int frame_duration_us;
enum bt_audio_location chan_allocation;
int octets_per_sdu;
int bitrate;
int blocks_per_sdu;
};
struct active_audio_stream {
struct bt_bap_stream *stream;
struct audio_codec_info *codec;
uint32_t pd;
};
static struct bt_bap_broadcast_sink *broadcast_sink;
static struct bt_bap_stream audio_streams[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT];
static struct audio_codec_info audio_codec_info[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT];
static uint32_t bis_index_bitfields[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT];
static struct bt_le_per_adv_sync *pa_sync_stored;
static struct active_audio_stream active_stream;
/* The values of sync_stream_cnt and active_stream_index must never become larger
* than the sizes of the arrays above (audio_streams etc.)
*/
static uint8_t sync_stream_cnt;
static uint8_t active_stream_index;
static struct bt_audio_codec_cap codec_cap = BT_AUDIO_CODEC_CAP_LC3(
BT_AUDIO_CODEC_CAPABILIY_FREQ,
(BT_AUDIO_CODEC_CAP_DURATION_10 | BT_AUDIO_CODEC_CAP_DURATION_PREFER_10),
BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1), LE_AUDIO_SDU_SIZE_OCTETS(CONFIG_LC3_BITRATE_MIN),
LE_AUDIO_SDU_SIZE_OCTETS(CONFIG_LC3_BITRATE_MAX), 1u, BT_AUDIO_CONTEXT_TYPE_ANY);
static struct bt_pacs_cap capabilities = {
.codec_cap = &codec_cap,
};
#define AVAILABLE_SINK_CONTEXT (BT_AUDIO_CONTEXT_TYPE_ANY)
static le_audio_receive_cb receive_cb;
static bool init_routine_completed;
static bool paused;
static struct bt_csip_set_member_svc_inst *csip;
static uint8_t flags_adv_data;
static uint8_t bass_service_uuid[BT_UUID_SIZE_16];
static uint8_t gap_appear_adv_data[BT_UUID_SIZE_16];
static uint8_t csip_rsi_adv_data[BT_CSIP_RSI_SIZE];
#define CSIP_SET_SIZE 2
enum csip_set_rank {
CSIP_HL_RANK = 1,
CSIP_HR_RANK = 2
};
/* Callback for locking state change from server side */
static void csip_lock_changed_cb(struct bt_conn *conn, struct bt_csip_set_member_svc_inst *csip,
bool locked)
{
LOG_DBG("Client %p %s the lock", (void *)conn, locked ? "locked" : "released");
}
/* Callback for SIRK read request from peer side */
static uint8_t sirk_read_req_cb(struct bt_conn *conn, struct bt_csip_set_member_svc_inst *csip)
{
/* Accept the request to read the SIRK, but return encrypted SIRK instead of plaintext */
return BT_CSIP_READ_SIRK_REQ_RSP_ACCEPT_ENC;
}
static struct bt_csip_set_member_cb csip_callbacks = {
.lock_changed = csip_lock_changed_cb,
.sirk_read_req = sirk_read_req_cb,
};
struct bt_csip_set_member_register_param csip_param = {
.set_size = CSIP_SET_SIZE,
.lockable = true,
.cb = &csip_callbacks,
};
int broadcast_sink_uuid_populate(struct net_buf_simple *uuid_buf)
{
if (net_buf_simple_tailroom(uuid_buf) >= (BT_UUID_SIZE_16 * 3)) {
net_buf_simple_add_le16(uuid_buf, BT_UUID_BASS_VAL);
net_buf_simple_add_le16(uuid_buf, BT_UUID_PACS_VAL);
} else {
LOG_ERR("Not enough space for UUIDS");
return -ENOMEM;
}
return 0;
}
int broadcast_sink_adv_populate(struct bt_data *adv_buf, uint8_t adv_buf_vacant)
{
int ret;
uint32_t adv_buf_cnt = 0;
if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER)) {
ret = bt_mgmt_adv_buffer_put(adv_buf, &adv_buf_cnt, adv_buf_vacant,
sizeof(csip_rsi_adv_data), BT_DATA_CSIS_RSI,
(void *)csip_rsi_adv_data);
if (ret) {
return ret;
}
}
/*
* AD format required for broadcast sink with scan delegator.
* Details can be found in Basic Audio Profile Section 3.9.2.
*/
sys_put_le16(BT_UUID_BASS_VAL, &bass_service_uuid[0]);
ret = bt_mgmt_adv_buffer_put(adv_buf, &adv_buf_cnt, adv_buf_vacant,
sizeof(bass_service_uuid), BT_DATA_SVC_DATA16,
(void *)bass_service_uuid);
if (ret) {
return ret;
}
sys_put_le16(CONFIG_BT_DEVICE_APPEARANCE, &gap_appear_adv_data[0]);
ret = bt_mgmt_adv_buffer_put(adv_buf, &adv_buf_cnt, adv_buf_vacant,
sizeof(gap_appear_adv_data), BT_DATA_GAP_APPEARANCE,
(void *)gap_appear_adv_data);
if (ret) {
return ret;
}
flags_adv_data = BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR;
ret = bt_mgmt_adv_buffer_put(adv_buf, &adv_buf_cnt, adv_buf_vacant, sizeof(uint8_t),
BT_DATA_FLAGS, (void *)&flags_adv_data);
if (ret) {
return ret;
}
return adv_buf_cnt;
}
static int broadcast_sink_cleanup(void)
{
int ret;
init_routine_completed = false;
active_stream.pd = 0;
active_stream.stream = NULL;
active_stream.codec = NULL;
if (broadcast_sink != NULL) {
ret = bt_bap_broadcast_sink_delete(broadcast_sink);
if (ret && ret != -EALREADY) {
return ret;
}
broadcast_sink = NULL;
}
return 0;
}
static void bis_cleanup_worker(struct k_work *work)
{
int ret;
ret = broadcast_sink_cleanup();
if (ret) {
LOG_WRN("Failed to clean up BISes: %d", ret);
}
}
K_WORK_DEFINE(bis_cleanup_work, bis_cleanup_worker);
static void le_audio_event_publish(enum le_audio_evt_type event)
{
int ret;
struct le_audio_msg msg;
if (event == LE_AUDIO_EVT_SYNC_LOST) {
msg.pa_sync = pa_sync_stored;
pa_sync_stored = NULL;
}
msg.event = event;
ret = zbus_chan_pub(&le_audio_chan, &msg, LE_AUDIO_ZBUS_EVENT_WAIT_TIME);
ERR_CHK(ret);
}
static void print_codec(const struct audio_codec_info *codec)
{
LOG_INF("Codec config for LC3:");
LOG_INF("\tFrequency: %d Hz", codec->frequency);
LOG_INF("\tFrame Duration: %d us", codec->frame_duration_us);
LOG_INF("\tOctets per frame: %d (%d kbps)", codec->octets_per_sdu, codec->bitrate);
LOG_INF("\tFrames per SDU: %d", codec->blocks_per_sdu);
if (codec->chan_allocation >= 0) {
LOG_INF("\tChannel allocation: 0x%x", codec->chan_allocation);
}
}
static void get_codec_info(const struct bt_audio_codec_cfg *codec,
struct audio_codec_info *codec_info)
{
int ret;
ret = le_audio_freq_hz_get(codec, &codec_info->frequency);
if (ret) {
LOG_DBG("Failed retrieving sampling frequency: %d", ret);
}
ret = le_audio_duration_us_get(codec, &codec_info->frame_duration_us);
if (ret) {
LOG_DBG("Failed retrieving frame duration: %d", ret);
}
ret = bt_audio_codec_cfg_get_chan_allocation(codec, &codec_info->chan_allocation, false);
if (ret == -ENODATA) {
/* Codec channel allocation not set, defaulting to 0 */
codec_info->chan_allocation = 0;
} else if (ret) {
LOG_DBG("Failed retrieving channel allocation: %d", ret);
}
ret = le_audio_octets_per_frame_get(codec, &codec_info->octets_per_sdu);
if (ret) {
LOG_DBG("Failed retrieving octets per frame: %d", ret);
}
ret = le_audio_bitrate_get(codec, &codec_info->bitrate);
if (ret) {
LOG_DBG("Failed calculating bitrate: %d", ret);
}
ret = le_audio_frame_blocks_per_sdu_get(codec, &codec_info->blocks_per_sdu);
if (codec_info->octets_per_sdu < 0) {
LOG_DBG("Failed retrieving frame blocks per SDU: %d", codec_info->octets_per_sdu);
}
}
static void stream_started_cb(struct bt_bap_stream *stream)
{
le_audio_event_publish(LE_AUDIO_EVT_STREAMING);
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Stream index %d started", active_stream_index);
print_codec(&audio_codec_info[active_stream_index]);
}
static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason)
{
switch (reason) {
case BT_HCI_ERR_LOCALHOST_TERM_CONN:
LOG_INF("Stream stopped by user");
le_audio_event_publish(LE_AUDIO_EVT_NOT_STREAMING);
break;
case BT_HCI_ERR_CONN_FAIL_TO_ESTAB:
/* Fall-through */
case BT_HCI_ERR_CONN_TIMEOUT:
LOG_INF("Stream sync lost");
k_work_submit(&bis_cleanup_work);
le_audio_event_publish(LE_AUDIO_EVT_SYNC_LOST);
break;
case BT_HCI_ERR_REMOTE_USER_TERM_CONN:
LOG_INF("Broadcast source stopped streaming");
le_audio_event_publish(LE_AUDIO_EVT_NOT_STREAMING);
break;
case BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL:
LOG_INF("MIC fail. The encryption key may be wrong");
break;
default:
LOG_WRN("Unhandled reason: %d", reason);
break;
}
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Stream index %d stopped. Reason: %d", active_stream_index, reason);
}
static void stream_recv_cb(struct bt_bap_stream *stream, const struct bt_iso_recv_info *info,
struct net_buf *buf)
{
bool bad_frame = false;
if (receive_cb == NULL) {
LOG_ERR("The RX callback has not been set");
return;
}
if (!(info->flags & BT_ISO_FLAGS_VALID)) {
bad_frame = true;
}
receive_cb(buf->data, buf->len, bad_frame, info->ts, active_stream_index,
active_stream.codec->octets_per_sdu);
}
static struct bt_bap_stream_ops stream_ops = {
.started = stream_started_cb,
.stopped = stream_stopped_cb,
.recv = stream_recv_cb,
};
static bool base_subgroup_bis_cb(const struct bt_bap_base_subgroup_bis *bis, void *user_data)
{
int ret;
struct bt_audio_codec_cfg codec_cfg = {0};
LOG_DBG("BIS found, index %d", bis->index);
ret = bt_bap_base_subgroup_bis_codec_to_codec_cfg(bis, &codec_cfg);
if (ret != 0) {
LOG_WRN("Could not find codec configuration for BIS index %d, ret "
"= %d",
bis->index, ret);
return true;
}
get_codec_info(&codec_cfg, &audio_codec_info[bis->index - 1]);
LOG_DBG("Channel allocation: 0x%x for BIS index %d",
audio_codec_info[bis->index - 1].chan_allocation, bis->index);
uint32_t chan_bitfield = audio_codec_info[bis->index - 1].chan_allocation;
bool single_bit = (chan_bitfield & (chan_bitfield - 1)) == 0;
if (single_bit) {
bis_index_bitfields[bis->index - 1] = BIT(bis->index - 1);
} else {
LOG_WRN("More than one bit set in channel location, we only support 1 channel per "
"BIS");
}
return true;
}
static bool base_subgroup_cb(const struct bt_bap_base_subgroup *subgroup, void *user_data)
{
int ret;
int bis_num;
struct bt_audio_codec_cfg codec_cfg = {0};
struct bt_bap_base_codec_id codec_id;
bool *suitable_stream_found = user_data;
ret = bt_bap_base_subgroup_codec_to_codec_cfg(subgroup, &codec_cfg);
if (ret) {
LOG_WRN("Failed to convert codec to codec_cfg: %d", ret);
return true;
}
ret = bt_bap_base_get_subgroup_codec_id(subgroup, &codec_id);
if (ret && codec_id.cid != BT_HCI_CODING_FORMAT_LC3) {
LOG_WRN("Failed to get codec ID or codec ID is not supported: %d", ret);
return true;
}
ret = le_audio_bitrate_check(&codec_cfg);
if (!ret) {
LOG_WRN("Bitrate check failed");
return true;
}
ret = le_audio_freq_check(&codec_cfg);
if (!ret) {
LOG_WRN("Sample rate not supported");
return true;
}
bis_num = bt_bap_base_get_subgroup_bis_count(subgroup);
LOG_DBG("Subgroup %p has %d BISes", (void *)subgroup, bis_num);
if (bis_num > 0) {
*suitable_stream_found = true;
sync_stream_cnt = bis_num;
for (int i = 0; i < bis_num; i++) {
get_codec_info(&codec_cfg, &audio_codec_info[i]);
}
ret = bt_bap_base_subgroup_foreach_bis(subgroup, base_subgroup_bis_cb, NULL);
if (ret < 0) {
LOG_WRN("Could not get BIS for subgroup %p: %d", (void *)subgroup, ret);
}
return false;
}
return true;
}
static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base,
size_t base_size)
{
int ret;
bool suitable_stream_found = false;
if (init_routine_completed) {
return;
}
sync_stream_cnt = 0;
uint32_t subgroup_count = bt_bap_base_get_subgroup_count(base);
LOG_DBG("Received BASE with %d subgroup(s) from broadcast sink", subgroup_count);
ret = bt_bap_base_foreach_subgroup(base, base_subgroup_cb, &suitable_stream_found);
if (ret != 0 && ret != -ECANCELED) {
LOG_WRN("Failed to parse subgroups: %d", ret);
return;
}
if (suitable_stream_found) {
/* Set the initial active stream based on the defined channel of the device */
enum audio_channel audio_channel_temp;
channel_assignment_get(&audio_channel_temp);
if (audio_channel_temp > AUDIO_CH_NUM) {
LOG_ERR("Invalid channel assignment");
return;
}
active_stream_index = (uint8_t)audio_channel_temp;
/** If the stream matching channel is not present, revert back to first BIS, e.g.
* mono stream but channel assignment is RIGHT
*/
if ((active_stream_index + 1) > sync_stream_cnt) {
LOG_WRN("BIS index: %d not found, reverting to first BIS",
(active_stream_index + 1));
active_stream_index = 0;
}
active_stream.stream = &audio_streams[active_stream_index];
active_stream.codec = &audio_codec_info[active_stream_index];
ret = bt_bap_base_get_pres_delay(base);
if (ret == -EINVAL) {
LOG_WRN("Failed to get pres_delay: %d", ret);
active_stream.pd = 0;
} else {
active_stream.pd = ret;
}
le_audio_event_publish(LE_AUDIO_EVT_CONFIG_RECEIVED);
LOG_DBG("Channel %s active",
((active_stream_index == AUDIO_CH_L) ? CH_L_TAG : CH_R_TAG));
LOG_DBG("Waiting for syncable");
} else {
LOG_DBG("Found no suitable stream");
le_audio_event_publish(LE_AUDIO_EVT_NO_VALID_CFG);
}
}
static void syncable_cb(struct bt_bap_broadcast_sink *sink, const struct bt_iso_biginfo *biginfo)
{
int ret;
struct bt_bap_stream *audio_streams_p[] = {&audio_streams[active_stream_index]};
static uint32_t prev_broadcast_id;
LOG_DBG("Broadcast sink is syncable");
if (active_stream.stream != NULL && active_stream.stream->ep != NULL) {
if (active_stream.stream->ep->status.state == BT_BAP_EP_STATE_STREAMING) {
LOG_WRN("Syncable received, but already in a stream");
return;
}
}
if (paused) {
LOG_DBG("Syncable received, but in paused state");
return;
}
if (bis_index_bitfields[active_stream_index] == 0) {
LOG_ERR("No bits set in bitfield");
return;
} else if (!IS_POWER_OF_TWO(bis_index_bitfields[active_stream_index])) {
/* Check that only one bit is set */
LOG_ERR("Application syncs to only one stream");
return;
}
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Syncing to broadcast stream index %d", active_stream_index);
if (IS_ENABLED(CONFIG_BT_AUDIO_BROADCAST_ENCRYPTED)) {
memcpy(bis_encryption_key, CONFIG_BT_AUDIO_BROADCAST_ENCRYPTION_KEY,
MIN(strlen(CONFIG_BT_AUDIO_BROADCAST_ENCRYPTION_KEY),
ARRAY_SIZE(bis_encryption_key)));
} else {
/* If the biginfo shows the stream is encrypted, then wait until broadcast code is
* received then start to sync. If headset is out of sync but still looking for same
* broadcaster, then the same broadcast code can be used.
*/
if (!broadcast_code_received && biginfo->encryption == true &&
sink->broadcast_id != prev_broadcast_id) {
LOG_WRN("Stream is encrypted, but haven not received broadcast code");
return;
}
broadcast_code_received = false;
}
ret = bt_bap_broadcast_sink_sync(broadcast_sink, bis_index_bitfields[active_stream_index],
audio_streams_p, bis_encryption_key);
if (ret) {
LOG_WRN("Unable to sync to broadcast source, ret: %d", ret);
return;
}
prev_broadcast_id = sink->broadcast_id;
/* Only a single stream used for now */
active_stream.stream = &audio_streams[active_stream_index];
init_routine_completed = true;
}
static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = {
.base_recv = base_recv_cb,
.syncable = syncable_cb,
};
int broadcast_sink_change_active_audio_stream(void)
{
int ret;
if (broadcast_sink == NULL) {
LOG_WRN("No broadcast sink");
return -ECANCELED;
}
if (active_stream.stream != NULL && active_stream.stream->ep != NULL) {
if (active_stream.stream->ep->status.state == BT_BAP_EP_STATE_STREAMING) {
ret = bt_bap_broadcast_sink_stop(broadcast_sink);
if (ret) {
LOG_ERR("Failed to stop sink");
}
}
}
/* Wrap streams */
if (++active_stream_index >= sync_stream_cnt) {
active_stream_index = 0;
}
active_stream.stream = &audio_streams[active_stream_index];
active_stream.codec = &audio_codec_info[active_stream_index];
LOG_INF("Changed to stream %d", active_stream_index);
return 0;
}
int broadcast_sink_config_get(uint32_t *bitrate, uint32_t *sampling_rate, uint32_t *pres_delay)
{
if (active_stream.codec == NULL) {
LOG_WRN("No active stream to get config from");
return -ENXIO;
}
if (bitrate == NULL && sampling_rate == NULL && pres_delay == NULL) {
LOG_ERR("No valid pointers received");
return -ENXIO;
}
if (sampling_rate != NULL) {
*sampling_rate = active_stream.codec->frequency;
}
if (bitrate != NULL) {
*bitrate = active_stream.codec->bitrate;
}
if (pres_delay != NULL) {
if (active_stream.stream == NULL) {
LOG_WRN("No active stream");
return -ENXIO;
}
*pres_delay = active_stream.pd;
}
return 0;
}
int broadcast_sink_pa_sync_set(struct bt_le_per_adv_sync *pa_sync, uint32_t broadcast_id)
{
int ret;
if (pa_sync == NULL) {
LOG_ERR("Invalid PA sync received");
return -EINVAL;
}
LOG_DBG("Trying to set PA sync with ID: %d", broadcast_id);
if (active_stream.stream != NULL && active_stream.stream->ep != NULL) {
if (active_stream.stream->ep->status.state == BT_BAP_EP_STATE_STREAMING) {
ret = bt_bap_broadcast_sink_stop(broadcast_sink);
if (ret) {
LOG_ERR("Failed to stop broadcast sink: %d", ret);
return ret;
}
broadcast_sink_cleanup();
}
}
/* If broadcast_sink was not in an active stream we still need to clean it up */
if (broadcast_sink != NULL) {
broadcast_sink_cleanup();
}
ret = bt_bap_broadcast_sink_create(pa_sync, broadcast_id, &broadcast_sink);
if (ret) {
LOG_WRN("Failed to create sink: %d", ret);
return ret;
}
pa_sync_stored = pa_sync;
return 0;
}
int broadcast_sink_broadcast_code_set(uint8_t *broadcast_code)
{
if (broadcast_code == NULL) {
LOG_ERR("Invalid broadcast code received");
return -EINVAL;
}
memcpy(bis_encryption_key, broadcast_code, BT_ISO_BROADCAST_CODE_SIZE);
broadcast_code_received = true;
return 0;
}
int broadcast_sink_start(void)
{
if (!paused) {
LOG_WRN("Already playing");
return -EALREADY;
}
paused = false;
return 0;
}
int broadcast_sink_stop(void)
{
int ret;
if (paused) {
LOG_WRN("Already paused");
return -EALREADY;
}
if (active_stream.stream == NULL || active_stream.stream->ep == NULL) {
LOG_WRN("Stream or endpoint not set");
return -EPERM;
}
if (active_stream.stream->ep->status.state == BT_BAP_EP_STATE_STREAMING) {
paused = true;
ret = bt_bap_broadcast_sink_stop(broadcast_sink);
if (ret) {
LOG_ERR("Failed to stop broadcast sink: %d", ret);
return ret;
}
} else {
LOG_WRN("Current stream not in streaming state");
return -EALREADY;
}
return 0;
}
int broadcast_sink_disable(void)
{
int ret;
if (active_stream.stream != NULL && active_stream.stream->ep != NULL) {
if (active_stream.stream->ep->status.state == BT_BAP_EP_STATE_STREAMING) {
ret = bt_bap_broadcast_sink_stop(broadcast_sink);
if (ret) {
LOG_ERR("Failed to stop sink");
}
}
}
if (pa_sync_stored != NULL) {
ret = bt_le_per_adv_sync_delete(pa_sync_stored);
if (ret) {
LOG_ERR("Failed to delete pa_sync");
return ret;
}
}
ret = broadcast_sink_cleanup();
if (ret) {
LOG_ERR("Error cleaning up");
return ret;
}
LOG_DBG("Broadcast sink disabled");
return 0;
}
int broadcast_sink_enable(le_audio_receive_cb recv_cb)
{
int ret;
static bool initialized;
enum audio_channel channel;
if (initialized) {
LOG_WRN("Already initialized");
return -EALREADY;
}
if (recv_cb == NULL) {
LOG_ERR("Receive callback is NULL");
return -EINVAL;
}
receive_cb = recv_cb;
channel_assignment_get(&channel);
if (channel == AUDIO_CH_L) {
ret = bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_LEFT);
csip_param.rank = CSIP_HL_RANK;
} else {
ret = bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_RIGHT);
csip_param.rank = CSIP_HR_RANK;
}
if (ret) {
LOG_ERR("Location set failed");
return ret;
}
ret = bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK, AVAILABLE_SINK_CONTEXT);
if (ret) {
LOG_ERR("Supported context set failed. Err: %d", ret);
return ret;
}
ret = bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK, AVAILABLE_SINK_CONTEXT);
if (ret) {
LOG_ERR("Available context set failed. Err: %d", ret);
return ret;
}
ret = bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &capabilities);
if (ret) {
LOG_ERR("Capability register failed (ret %d)", ret);
return ret;
}
if (IS_ENABLED(CONFIG_BT_AUDIO_SCAN_DELEGATOR)) {
if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA)) {
LOG_WRN("CSIP test sample data is used, must be changed "
"before production");
} else {
if (strcmp(CONFIG_BT_SET_IDENTITY_RESOLVING_KEY_DEFAULT,
CONFIG_BT_SET_IDENTITY_RESOLVING_KEY) == 0) {
LOG_WRN("CSIP using the default SIRK, must be changed "
"before production");
}
memcpy(csip_param.sirk, CONFIG_BT_SET_IDENTITY_RESOLVING_KEY,
BT_CSIP_SIRK_SIZE);
}
ret = bt_cap_acceptor_register(&csip_param, &csip);
if (ret) {
LOG_ERR("Failed to register CAP acceptor. Err: %d", ret);
return ret;
}
ret = bt_csip_set_member_generate_rsi(csip, csip_rsi_adv_data);
if (ret) {
LOG_ERR("Failed to generate RSI. Err: %d", ret);
return ret;
}
}
bt_bap_broadcast_sink_register_cb(&broadcast_sink_cbs);
for (int i = 0; i < ARRAY_SIZE(audio_streams); i++) {
audio_streams[i].ops = &stream_ops;
}
initialized = true;
LOG_DBG("Broadcast sink enabled");
return 0;
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BROADCAST_SINK_H_
#define _BROADCAST_SINK_H_
#include "bt_le_audio_tx.h"
/**
* @brief Put the UUIDs from this module into the buffer.
*
* @note This partial data is used to build a complete extended advertising packet.
*
* @param[out] uuid_buf Buffer being populated with UUIDs.
*
* @return 0 for success, error otherwise.
*/
int broadcast_sink_uuid_populate(struct net_buf_simple *uuid_buf);
/**
* @brief Put the advertising data from this module into the buffer.
*
* @note This partial data is used to build a complete extended advertising packet.
*
* @param[out] adv_buf Buffer being populated with ext adv elements.
* @param[in] adv_buf_vacant Number of vacant elements in @p adv_buf.
*
* @return Negative values for errors or number of elements added to @p adv_buf.
*/
int broadcast_sink_adv_populate(struct bt_data *adv_buf, uint8_t adv_buf_vacant);
/**
* @brief Change the active audio stream if the broadcast isochronous group (BIG) contains
* more than one broadcast isochronous stream (BIS).
*
* @note Only streams within the same broadcast source are relevant, meaning
* that the broadcast source is not changed.
* The active stream will iterate every time this function is called.
*
* @return 0 for success, error otherwise.
*/
int broadcast_sink_change_active_audio_stream(void);
/**
* @brief Get configuration for the audio stream.
*
* @param[out] bitrate Pointer to the bitrate used; can be NULL.
* @param[out] sampling_rate Pointer to the sampling rate used; can be NULL.
* @param[out] pres_delay Pointer to the presentation delay used; can be NULL.
*
* @retval 0 Operation successful.
* @retval -ENXIO The feature is disabled.
*/
int broadcast_sink_config_get(uint32_t *bitrate, uint32_t *sampling_rate, uint32_t *pres_delay);
/**
* @brief Set periodic advertising sync.
*
* @param[in] pa_sync Pointer to the periodic advertising sync.
* @param[in] broadcast_id Broadcast ID of the periodic advertising.
*
* @return 0 for success, error otherwise.
*/
int broadcast_sink_pa_sync_set(struct bt_le_per_adv_sync *pa_sync, uint32_t broadcast_id);
/**
* @brief Set the broadcast code for the Bluetooth LE Audio broadcast sink.
* The broadcast code length is defined in BT_ISO_BROADCAST_CODE_SIZE,
* which is 16 bytes.
*
* @param[in] broadcast_code Pointer to the broadcast code.
*
* @return 0 for success, error otherwise.
*/
int broadcast_sink_broadcast_code_set(uint8_t *broadcast_code);
/**
* @brief Start the Bluetooth LE Audio broadcast sink.
*
* @return 0 for success, error otherwise.
*/
int broadcast_sink_start(void);
/**
* @brief Stop the Bluetooth LE Audio broadcast sink.
*
* @return 0 for success, error otherwise.
*/
int broadcast_sink_stop(void);
/**
* @brief Disable the LE Audio broadcast (BIS) sink.
*
* @return 0 for success, error otherwise.
*/
int broadcast_sink_disable(void);
/**
* @brief Enable the LE Audio broadcast (BIS) sink.
*
* @param[in] recv_cb Callback for receiving Bluetooth LE Audio data.
*
* @return 0 for success, error otherwise.
*/
int broadcast_sink_enable(le_audio_receive_cb recv_cb);
#endif /* _BROADCAST_SINK_H_ */

View File

@@ -0,0 +1,800 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "broadcast_source.h"
#include <zephyr/zbus/zbus.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/pbp.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include "bt_mgmt.h"
#include "macros_common.h"
#include "bt_le_audio_tx.h"
#include "le_audio.h"
#include "zbus_common.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(broadcast_source, CONFIG_BROADCAST_SOURCE_LOG_LEVEL);
/* Length-type-value size for channel allocation */
#define LTV_CHAN_ALLOC_SIZE 6
#if (CONFIG_AURACAST)
/* Index values into the PBA advertising data format.
*
* See Table 4.1 of the Public Broadcast Profile, Bluetooth® Profile Specification, v1.0.
*/
#define PBA_UUID_INDEX (0)
#define PBA_FEATURES_INDEX (2)
#define PBA_METADATA_SIZE_INDEX (3)
#define PBA_METADATA_START_INDEX (4)
#endif /* CONFIG_AURACAST */
ZBUS_CHAN_DEFINE(le_audio_chan, struct le_audio_msg, NULL, NULL, ZBUS_OBSERVERS_EMPTY,
ZBUS_MSG_INIT(0));
static struct bt_cap_broadcast_source *broadcast_sources[CONFIG_BT_ISO_MAX_BIG];
struct bt_cap_initiator_broadcast_create_param create_param[CONFIG_BT_ISO_MAX_BIG];
/* Make sure we have statically allocated streams for all potential BISes */
static struct bt_cap_stream cap_streams[CONFIG_BT_ISO_MAX_BIG]
[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT]
[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT];
static struct bt_bap_lc3_preset lc3_preset = BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO;
static bool initialized;
static bool delete_broadcast_src[CONFIG_BT_ISO_MAX_BIG];
static uint32_t stored_broadcast_id;
static int metadata_u8_add(uint8_t buffer[], uint8_t *index, uint8_t type, uint8_t value)
{
if (buffer == NULL || index == NULL) {
return -EINVAL;
}
/* Add length of type and value */
buffer[(*index)++] = (sizeof(type) + sizeof(uint8_t));
buffer[(*index)++] = type;
buffer[(*index)++] = value;
return 0;
}
static void le_audio_event_publish(enum le_audio_evt_type event, const struct stream_index *idx)
{
int ret;
struct le_audio_msg msg;
msg.event = event;
msg.idx = *idx;
ret = zbus_chan_pub(&le_audio_chan, &msg, LE_AUDIO_ZBUS_EVENT_WAIT_TIME);
ERR_CHK(ret);
}
static int stream_index_get(struct bt_bap_stream *stream, struct stream_index *idx)
{
for (int i = 0; i < CONFIG_BT_ISO_MAX_BIG; i++) {
for (int j = 0; j < CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT; j++) {
for (int k = 0; k < ARRAY_SIZE(cap_streams[i][j]); k++) {
if (&cap_streams[i][j][k].bap_stream == stream) {
idx->lvl1 = i;
idx->lvl2 = j;
idx->lvl3 = k;
return 0;
}
}
}
}
LOG_WRN("Stream %p not found", (void *)stream);
return -EINVAL;
}
static void stream_sent_cb(struct bt_bap_stream *stream)
{
int ret;
struct stream_index idx;
ret = stream_index_get(stream, &idx);
if (ret) {
return;
}
if (IS_ENABLED(CONFIG_BT_AUDIO_BROADCAST_ZBUS_EVT_STREAM_SENT)) {
le_audio_event_publish(LE_AUDIO_EVT_STREAM_SENT, &idx);
}
ERR_CHK(bt_le_audio_tx_stream_sent(idx));
}
static void stream_started_cb(struct bt_bap_stream *stream)
{
int ret;
struct stream_index idx;
ret = stream_index_get(stream, &idx);
if (ret) {
return;
}
ERR_CHK(bt_le_audio_tx_stream_started(idx));
le_audio_event_publish(LE_AUDIO_EVT_STREAMING, &idx);
/* NOTE: The string below is used by the Nordic CI system */
LOG_INF("Broadcast source %p started", (void *)stream);
le_audio_print_codec(stream->codec_cfg, BT_AUDIO_DIR_SOURCE);
}
/**
* @brief Check if there are any streaming streams in a broadcast source (BIG).
*
* @param big_index BIG index.
*
* @return true if there are streaming streams, false otherwise.
*/
static bool source_has_streaming_streams(uint8_t big_index)
{
for (int i = 0; i < CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT; i++) {
for (int j = 0; j < CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT; j++) {
if (le_audio_ep_state_check(cap_streams[big_index][i][j].bap_stream.ep,
BT_BAP_EP_STATE_STREAMING)) {
return true;
}
}
}
return false;
}
static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason)
{
int ret;
struct stream_index idx;
ret = stream_index_get(stream, &idx);
if (ret) {
return;
}
le_audio_event_publish(LE_AUDIO_EVT_NOT_STREAMING, &idx);
LOG_INF("Broadcast source %p stopped. Reason: %d", (void *)stream, reason);
if (delete_broadcast_src[idx.lvl1] && broadcast_sources[idx.lvl1] != NULL &&
!source_has_streaming_streams(idx.lvl1)) {
ret = bt_cap_initiator_broadcast_audio_delete(broadcast_sources[idx.lvl1]);
if (ret) {
LOG_ERR("Unable to delete broadcast source %p, ret: %d", (void *)stream,
ret);
delete_broadcast_src[idx.lvl1] = false;
return;
}
broadcast_sources[idx.lvl1] = NULL;
LOG_INF("Broadcast source %p deleted", (void *)stream);
delete_broadcast_src[idx.lvl1] = false;
}
}
static struct bt_bap_stream_ops stream_ops = {
.sent = stream_sent_cb,
.started = stream_started_cb,
.stopped = stream_stopped_cb,
};
#if (CONFIG_AURACAST)
static void public_broadcast_features_set(uint8_t *features, uint8_t big_index)
{
if (features == NULL) {
LOG_ERR("No pointer to features");
return;
}
if (big_index >= ARRAY_SIZE(create_param)) {
LOG_ERR("BIG index %d out of range", big_index);
return;
}
if (create_param[big_index].encryption) {
*features |= BT_PBP_ANNOUNCEMENT_FEATURE_ENCRYPTION;
}
for (uint8_t i = 0; i < create_param->subgroup_count; i++) {
int freq = bt_audio_codec_cfg_get_freq(
create_param[big_index].subgroup_params[i].codec_cfg);
if (freq < 0) {
LOG_ERR("Unable to get frequency");
continue;
}
if (freq == BT_AUDIO_CODEC_CFG_FREQ_16KHZ ||
freq == BT_AUDIO_CODEC_CFG_FREQ_24KHZ) {
*features |= BT_PBP_ANNOUNCEMENT_FEATURE_STANDARD_QUALITY;
} else if (freq == BT_AUDIO_CODEC_CFG_FREQ_48KHZ) {
*features |= BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY;
} else {
LOG_WRN("%dkHz is not compatible with Auracast, choose 16kHz, 24kHz or "
"48kHz",
freq);
}
}
}
#endif /* (CONFIG_AURACAST) */
int broadcast_source_ext_adv_populate(uint8_t big_index, bool fixed_id, uint32_t broadcast_id,
struct broadcast_source_ext_adv_data *ext_adv_data,
struct bt_data *ext_adv_buf, size_t ext_adv_buf_vacant)
{
int ret;
uint32_t ext_adv_buf_cnt = 0;
if (big_index >= CONFIG_BT_ISO_MAX_BIG) {
LOG_ERR("Trying to populate ext adv for BIG %d out of %d", big_index,
CONFIG_BT_ISO_MAX_BIG);
return -EINVAL;
}
if (ext_adv_data == NULL || ext_adv_buf == NULL || ext_adv_buf_vacant == 0) {
LOG_ERR("Advertising populate failed.");
return -EINVAL;
}
size_t brdcast_name_size = strlen(ext_adv_data->brdcst_name_buf);
ret = bt_mgmt_adv_buffer_put(ext_adv_buf, &ext_adv_buf_cnt, ext_adv_buf_vacant,
brdcast_name_size, BT_DATA_BROADCAST_NAME,
(void *)ext_adv_data->brdcst_name_buf);
if (ret) {
return ret;
}
if (!fixed_id) {
/* Use a random broadcast ID */
ret = bt_rand(&broadcast_id, BT_AUDIO_BROADCAST_ID_SIZE);
if (ret) {
LOG_WRN("Unable to generate broadcast ID: %d\n", ret);
return ret;
}
}
stored_broadcast_id = broadcast_id;
sys_put_le16(BT_UUID_BROADCAST_AUDIO_VAL, ext_adv_data->brdcst_id_buf);
sys_put_le24(broadcast_id, &ext_adv_data->brdcst_id_buf[BROADCAST_SOURCE_ADV_ID_START]);
ret = bt_mgmt_adv_buffer_put(ext_adv_buf, &ext_adv_buf_cnt, ext_adv_buf_vacant,
sizeof(ext_adv_data->brdcst_id_buf), BT_DATA_SVC_DATA16,
(void *)ext_adv_data->brdcst_id_buf);
if (ret) {
return ret;
}
sys_put_le16(CONFIG_BT_DEVICE_APPEARANCE, ext_adv_data->brdcst_appearance_buf);
ret = bt_mgmt_adv_buffer_put(ext_adv_buf, &ext_adv_buf_cnt, ext_adv_buf_vacant,
sizeof(ext_adv_data->brdcst_appearance_buf),
BT_DATA_GAP_APPEARANCE,
(void *)ext_adv_data->brdcst_appearance_buf);
if (ret) {
return ret;
}
#if (CONFIG_AURACAST)
uint8_t meta_data_buf_size = 0;
sys_put_le16(BT_UUID_PBA_VAL, &ext_adv_data->pba_buf[PBA_UUID_INDEX]);
public_broadcast_features_set(&ext_adv_data->pba_buf[PBA_FEATURES_INDEX], big_index);
/* Metadata */
/* Parental rating */
ret = metadata_u8_add(&ext_adv_data->pba_buf[PBA_METADATA_START_INDEX], &meta_data_buf_size,
BT_AUDIO_METADATA_TYPE_PARENTAL_RATING,
CONFIG_BT_AUDIO_BROADCAST_PARENTAL_RATING);
if (ret) {
return ret;
}
/* Active flag */
ret = metadata_u8_add(&ext_adv_data->pba_buf[PBA_METADATA_START_INDEX], &meta_data_buf_size,
BT_AUDIO_METADATA_TYPE_AUDIO_STATE, BT_AUDIO_ACTIVE_STATE_ENABLED);
if (ret) {
return ret;
}
/* Metadata size */
ext_adv_data->pba_buf[PBA_METADATA_SIZE_INDEX] = meta_data_buf_size;
/* Add PBA buffer to extended advertising data */
ret = bt_mgmt_adv_buffer_put(ext_adv_buf, &ext_adv_buf_cnt, ext_adv_buf_vacant,
BROADCAST_SOURCE_PBA_HEADER_SIZE +
ext_adv_data->pba_buf[PBA_METADATA_SIZE_INDEX],
BT_DATA_SVC_DATA16, (void *)ext_adv_data->pba_buf);
if (ret) {
return ret;
}
#endif /* (CONFIG_AURACAST) */
return ext_adv_buf_cnt;
}
int broadcast_source_per_adv_populate(uint8_t big_index,
struct broadcast_source_per_adv_data *per_adv_data,
struct bt_data *per_adv_buf, size_t per_adv_buf_vacant)
{
int ret;
size_t per_adv_buf_cnt = 0;
if (big_index >= CONFIG_BT_ISO_MAX_BIG) {
LOG_ERR("Trying to populate per adv for BIG %d out of %d", big_index,
CONFIG_BT_ISO_MAX_BIG);
return -EINVAL;
}
if (per_adv_data == NULL || per_adv_buf == NULL || per_adv_buf_vacant == 0) {
LOG_ERR("Periodic advertising populate failed.");
return -EINVAL;
}
/* Setup periodic advertising data */
ret = bt_cap_initiator_broadcast_get_base(broadcast_sources[big_index],
per_adv_data->base_buf);
if (ret) {
LOG_ERR("Failed to get encoded BASE: %d", ret);
return ret;
}
ret = bt_mgmt_adv_buffer_put(per_adv_buf, &per_adv_buf_cnt, per_adv_buf_vacant,
per_adv_data->base_buf->len, BT_DATA_SVC_DATA16,
(void *)per_adv_data->base_buf->data);
if (ret) {
return ret;
}
return per_adv_buf_cnt;
}
/**
* @brief Set the channel allocation to a preset codec configuration.
*
* @param data The preset codec configuration.
* @param data_len Length of @p data
* @param loc Location bitmask setting.
*/
static void bt_audio_codec_allocation_set(uint8_t *data, uint8_t data_len,
enum bt_audio_location loc)
{
data[0] = data_len - 1;
data[1] = BT_AUDIO_CODEC_CFG_CHAN_ALLOC;
sys_put_le32((const uint32_t)loc, &data[2]);
}
static int create_param_produce(uint8_t big_index,
struct broadcast_source_big const *const ext_create_param,
struct bt_cap_initiator_broadcast_create_param *create_param)
{
int ret;
if (big_index >= CONFIG_BT_ISO_MAX_BIG) {
LOG_ERR("Trying to create param for BIG %d out of %d", big_index,
CONFIG_BT_ISO_MAX_BIG);
return -EINVAL;
}
if (ext_create_param->num_subgroups > CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT) {
LOG_ERR("Trying to create %d subgroups, but only allocated memory for %d",
ext_create_param->num_subgroups,
CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT);
return -EINVAL;
}
uint8_t total_num_bis = 0;
for (size_t i = 0U; i < ext_create_param->num_subgroups; i++) {
for (size_t j = 0; j < ext_create_param->subgroups[i].num_bises; j++) {
total_num_bis++;
}
}
if (total_num_bis > CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT) {
LOG_ERR("Trying to set up %d BISes in total, but only allocated memory for %d",
total_num_bis, CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT);
return -EINVAL;
}
static struct bt_cap_initiator_broadcast_stream_param
stream_params[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT][2];
static uint8_t bis_codec_data[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT][2]
[LTV_CHAN_ALLOC_SIZE];
static struct bt_cap_initiator_broadcast_subgroup_param
subgroup_params[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT];
(void)memset(cap_streams[big_index], 0, sizeof(cap_streams[big_index]));
for (size_t i = 0U; i < ext_create_param->num_subgroups; i++) {
enum bt_audio_location subgroup_loc = 0;
for (size_t j = 0; j < ext_create_param->subgroups[i].num_bises; j++) {
stream_params[i][j].stream = &cap_streams[big_index][i][j];
stream_params[i][j].data_len = ARRAY_SIZE(bis_codec_data[i][j]);
stream_params[i][j].data = bis_codec_data[i][j];
enum bt_audio_location loc = ext_create_param->subgroups[i].location[j];
subgroup_loc |= loc;
bt_audio_codec_allocation_set(stream_params[i][j].data,
stream_params[i][j].data_len, loc);
}
subgroup_params[i].stream_count = ext_create_param->subgroups[i].num_bises;
subgroup_params[i].stream_params = stream_params[i];
subgroup_params[i].codec_cfg =
&ext_create_param->subgroups[i].group_lc3_preset.codec_cfg;
ret = bt_audio_codec_cfg_set_chan_allocation(subgroup_params[i].codec_cfg,
subgroup_loc);
if (ret < 0) {
LOG_WRN("Failed to set location: %d", ret);
return -EINVAL;
}
ret = bt_audio_codec_cfg_meta_set_stream_context(
subgroup_params[i].codec_cfg, ext_create_param->subgroups[i].context);
if (ret < 0) {
LOG_WRN("Failed to set context: %d", ret);
return -EINVAL;
}
}
/* Create broadcast_source */
create_param->subgroup_count = ext_create_param->num_subgroups;
create_param->subgroup_params = subgroup_params;
/* All QoS within the BIG will be the same, so we get the one from the first subgroup */
create_param->qos = &ext_create_param->subgroups[0].group_lc3_preset.qos;
create_param->packing = ext_create_param->packing;
create_param->encryption = ext_create_param->encryption;
if (ext_create_param->encryption) {
memset(create_param->broadcast_code, 0, sizeof(create_param->broadcast_code));
memcpy(create_param->broadcast_code, ext_create_param->broadcast_code,
sizeof(ext_create_param->broadcast_code));
}
return 0;
}
bool broadcast_source_is_streaming(uint8_t big_index)
{
if (big_index >= CONFIG_BT_ISO_MAX_BIG) {
LOG_ERR("Trying to check BIG %d out of %d", big_index, CONFIG_BT_ISO_MAX_BIG);
return false;
}
if (broadcast_sources[big_index] == NULL) {
return false;
}
/* All streams in a broadcast source is in the same state,
* so we can just check the first stream
*/
return le_audio_ep_state_check(cap_streams[big_index][0][0].bap_stream.ep,
BT_BAP_EP_STATE_STREAMING);
}
int broadcast_source_start(uint8_t big_index, struct bt_le_ext_adv *ext_adv)
{
int ret;
if (ext_adv == NULL) {
LOG_ERR("No advertising set available");
return -EINVAL;
}
if (big_index >= CONFIG_BT_ISO_MAX_BIG) {
LOG_ERR("Trying to start BIG %d out of %d", big_index, CONFIG_BT_ISO_MAX_BIG);
return -EINVAL;
}
LOG_DBG("Starting broadcast source");
/* All streams in a broadcast source is in the same state,
* so we can just check the first stream
*/
if (cap_streams[big_index][0][0].bap_stream.ep == NULL) {
LOG_ERR("stream->ep is NULL");
return -ECANCELED;
}
if (le_audio_ep_state_check(cap_streams[big_index][0][0].bap_stream.ep,
BT_BAP_EP_STATE_STREAMING)) {
LOG_WRN("Already streaming");
return -EALREADY;
}
ret = bt_cap_initiator_broadcast_audio_start(broadcast_sources[big_index], ext_adv);
if (ret) {
LOG_WRN("Failed to start broadcast, ret: %d", ret);
return ret;
}
return 0;
}
int broadcast_source_stop(uint8_t big_index)
{
int ret;
if (big_index >= CONFIG_BT_ISO_MAX_BIG) {
LOG_ERR("Trying to stop BIG %d out of %d", big_index, CONFIG_BT_ISO_MAX_BIG);
return -EINVAL;
}
/* All streams in a broadcast source is in the same state,
* so we can just check the first stream
*/
if (cap_streams[big_index][0][0].bap_stream.ep == NULL) {
LOG_ERR("stream->ep is NULL");
return -ECANCELED;
}
if (le_audio_ep_state_check(cap_streams[big_index][0][0].bap_stream.ep,
BT_BAP_EP_STATE_STREAMING)) {
ret = bt_cap_initiator_broadcast_audio_stop(broadcast_sources[big_index]);
if (ret) {
LOG_WRN("Failed to stop broadcast, ret: %d", ret);
return ret;
}
} else {
LOG_WRN("Not in a streaming state");
return -EINVAL;
}
return 0;
}
/* TODO: Use the function below once
* https://github.com/zephyrproject-rtos/zephyr/pull/72908 is merged
*/
#if CONFIG_CUSTOM_BROADCASTER
static uint8_t audio_map_location_get(struct bt_bap_stream *bap_stream)
{
int ret;
enum bt_audio_location loc;
ret = bt_audio_codec_cfg_get_chan_allocation(bap_stream->codec_cfg, &loc, false);
if (ret) {
LOG_WRN("Unable to find location, defaulting to left");
return AUDIO_CH_L;
}
/* For now, only front_left and front_right are supported,
* left is default for everything else.
*/
if (loc == BT_AUDIO_LOCATION_FRONT_RIGHT) {
LOG_WRN("Setting right");
return AUDIO_CH_R;
}
return AUDIO_CH_L;
}
#endif
int broadcast_source_id_get(uint8_t big_index, uint32_t *broadcast_id)
{
if (big_index >= CONFIG_BT_ISO_MAX_BIG) {
LOG_ERR("Failed to get broadcast ID for BIG %d out of %d", big_index,
CONFIG_BT_ISO_MAX_BIG);
return -EINVAL;
}
if (broadcast_sources[big_index] == NULL) {
LOG_ERR("No broadcast source");
return -EINVAL;
}
if (broadcast_id == NULL) {
LOG_ERR("NULL pointer given for broadcast_id");
return -EINVAL;
}
*broadcast_id = stored_broadcast_id;
return 0;
}
int broadcast_source_send(uint8_t big_index, uint8_t subgroup_index,
struct le_audio_encoded_audio enc_audio)
{
int ret;
uint8_t num_active_streams = 0;
if (big_index >= CONFIG_BT_ISO_MAX_BIG) {
LOG_ERR("Trying to send to BIG %d out of %d", big_index, CONFIG_BT_ISO_MAX_BIG);
return -EINVAL;
}
struct le_audio_tx_info
tx[CONFIG_BT_ISO_MAX_CHAN * CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT];
for (int i = 0; i < ARRAY_SIZE(cap_streams[big_index][subgroup_index]); i++) {
if (!le_audio_ep_state_check(
cap_streams[big_index][subgroup_index][i].bap_stream.ep,
BT_BAP_EP_STATE_STREAMING)) {
/* Skip streams not in a streaming state */
continue;
}
/* Set cap stream pointer */
tx[num_active_streams].cap_stream = &cap_streams[big_index][subgroup_index][i];
/* Set index */
tx[num_active_streams].idx.lvl1 = big_index;
tx[num_active_streams].idx.lvl2 = subgroup_index;
tx[num_active_streams].idx.lvl3 = i;
/* Set channel location */
/* TODO: Use the function below once
* https://github.com/zephyrproject-rtos/zephyr/pull/72908 is merged
*/
/* tx[num_active_streams].audio_channel = audio_map_location_get(stream);*/
tx[num_active_streams].audio_channel = i;
num_active_streams++;
}
if (num_active_streams == 0) {
LOG_WRN("No active streams");
return -ECANCELED;
}
ret = bt_le_audio_tx_send(tx, num_active_streams, enc_audio);
if (ret) {
return ret;
}
return 0;
}
int broadcast_source_disable(uint8_t big_index)
{
int ret;
if (big_index >= CONFIG_BT_ISO_MAX_BIG) {
LOG_ERR("Trying to disable BIG %d out of %d", big_index, CONFIG_BT_ISO_MAX_BIG);
return -EINVAL;
}
/* All streams in a broadcast source is in the same state,
* so we can just check the first stream
*/
if (le_audio_ep_state_check(cap_streams[big_index][0][0].bap_stream.ep,
BT_BAP_EP_STATE_STREAMING)) {
/* Deleting broadcast source in stream_stopped_cb() */
delete_broadcast_src[big_index] = true;
ret = bt_cap_initiator_broadcast_audio_stop(broadcast_sources[big_index]);
if (ret) {
LOG_WRN("Failed to stop broadcast source");
return ret;
}
} else if (broadcast_sources[big_index] != NULL) {
ret = bt_cap_initiator_broadcast_audio_delete(broadcast_sources[big_index]);
if (ret) {
LOG_WRN("Failed to delete broadcast source");
return ret;
}
broadcast_sources[big_index] = NULL;
}
initialized = false;
LOG_DBG("Broadcast source disabled");
return 0;
}
/* Will set up one BIG, one subgroup and two BISes */
void broadcast_source_default_create(struct broadcast_source_big *broadcast_param)
{
static enum bt_audio_location location[2] = {BT_AUDIO_LOCATION_FRONT_LEFT,
BT_AUDIO_LOCATION_FRONT_RIGHT};
static struct subgroup_config subgroups;
subgroups.group_lc3_preset = lc3_preset;
subgroups.num_bises = 2;
subgroups.context = BT_AUDIO_CONTEXT_TYPE_MEDIA;
subgroups.location = location;
broadcast_param->subgroups = &subgroups;
broadcast_param->num_subgroups = 1;
if (IS_ENABLED(CONFIG_BT_AUDIO_PACKING_INTERLEAVED)) {
broadcast_param->packing = BT_ISO_PACKING_INTERLEAVED;
} else {
broadcast_param->packing = BT_ISO_PACKING_SEQUENTIAL;
}
if (IS_ENABLED(CONFIG_BT_AUDIO_BROADCAST_ENCRYPTED)) {
broadcast_param->encryption = true;
memset(broadcast_param->broadcast_code, 0, sizeof(broadcast_param->broadcast_code));
memcpy(broadcast_param->broadcast_code, CONFIG_BT_AUDIO_BROADCAST_ENCRYPTION_KEY,
MIN(sizeof(CONFIG_BT_AUDIO_BROADCAST_ENCRYPTION_KEY),
sizeof(broadcast_param->broadcast_code)));
} else {
broadcast_param->encryption = false;
}
if (IS_ENABLED(CONFIG_BT_AUDIO_BROADCAST_IMMEDIATE_FLAG)) {
bt_audio_codec_cfg_meta_set_bcast_audio_immediate_rend_flag(
&subgroups.group_lc3_preset.codec_cfg);
}
bt_audio_codec_cfg_meta_set_lang(&subgroups.group_lc3_preset.codec_cfg, "eng");
}
int broadcast_source_enable(struct broadcast_source_big const *const broadcast_param,
uint8_t big_index)
{
int ret;
if (big_index >= CONFIG_BT_ISO_MAX_BIG) {
LOG_ERR("Trying to set up %d BIGS, but only allocated memory for %d", big_index,
CONFIG_BT_ISO_MAX_BIG);
return -EINVAL;
}
if (!initialized) {
bt_le_audio_tx_init();
}
LOG_INF("Enabling broadcast_source %d", big_index);
ret = create_param_produce(big_index, broadcast_param, &create_param[big_index]);
if (ret) {
LOG_ERR("Failed to create the create_param: %d", ret);
return ret;
}
/* Register callbacks per stream */
for (size_t j = 0U; j < create_param[big_index].subgroup_count; j++) {
for (size_t k = 0; k < create_param[big_index].subgroup_params[j].stream_count;
k++) {
bt_cap_stream_ops_register(
create_param[big_index].subgroup_params[j].stream_params[k].stream,
&stream_ops);
}
}
ret = bt_cap_initiator_broadcast_audio_create(&create_param[big_index],
&broadcast_sources[big_index]);
if (ret) {
LOG_ERR("Failed to create broadcast source, ret: %d", ret);
return ret;
}
initialized = true;
LOG_DBG("Broadcast source enabled");
return 0;
}

View File

@@ -0,0 +1,266 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _BROADCAST_SOURCE_H_
#define _BROADCAST_SOURCE_H_
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include "bt_le_audio_tx.h"
#if CONFIG_BT_AUDIO_BROADCAST_CONFIGURABLE
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_PRESET_CONFIGURABLE( \
BT_AUDIO_LOCATION_FRONT_LEFT | BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA, CONFIG_BT_AUDIO_BITRATE_BROADCAST_SRC)
#elif CONFIG_BT_BAP_BROADCAST_16_2_1
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_BROADCAST_PRESET_16_2_1(BT_AUDIO_LOCATION_FRONT_LEFT | \
BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA)
#elif CONFIG_BT_BAP_BROADCAST_16_2_2
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_BROADCAST_PRESET_16_2_2(BT_AUDIO_LOCATION_FRONT_LEFT | \
BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA)
#elif CONFIG_BT_BAP_BROADCAST_24_2_1
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_BROADCAST_PRESET_24_2_1(BT_AUDIO_LOCATION_FRONT_LEFT | \
BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA)
#elif CONFIG_BT_BAP_BROADCAST_24_2_2
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_BROADCAST_PRESET_24_2_2(BT_AUDIO_LOCATION_FRONT_LEFT | \
BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA)
#elif CONFIG_BT_BAP_BROADCAST_48_2_1
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_BROADCAST_PRESET_48_2_1(BT_AUDIO_LOCATION_FRONT_LEFT | \
BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA)
#elif CONFIG_BT_BAP_BROADCAST_48_2_2
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_BROADCAST_PRESET_48_2_2(BT_AUDIO_LOCATION_FRONT_LEFT | \
BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA)
#elif CONFIG_BT_BAP_BROADCAST_48_4_1
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_BROADCAST_PRESET_48_4_1(BT_AUDIO_LOCATION_FRONT_LEFT | \
BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA)
#elif CONFIG_BT_BAP_BROADCAST_48_4_2
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_BROADCAST_PRESET_48_4_2(BT_AUDIO_LOCATION_FRONT_LEFT | \
BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA)
#elif CONFIG_BT_BAP_BROADCAST_48_6_1
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_BROADCAST_PRESET_48_6_1(BT_AUDIO_LOCATION_FRONT_LEFT | \
BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA)
#elif CONFIG_BT_BAP_BROADCAST_48_6_2
#define BT_BAP_LC3_BROADCAST_PRESET_NRF5340_AUDIO \
BT_BAP_LC3_BROADCAST_PRESET_48_6_2(BT_AUDIO_LOCATION_FRONT_LEFT | \
BT_AUDIO_LOCATION_FRONT_RIGHT, \
BT_AUDIO_CONTEXT_TYPE_MEDIA)
#else
#error Unsupported LC3 codec preset for broadcast
#endif /* CONFIG_BT_AUDIO_BROADCAST_CONFIGURABLE */
/* Size of the Public Broadcast Announcement header, 2-octet Service UUID followed by
* an octet for the features and an octet for the length of the meta data field.
*/
#define BROADCAST_SOURCE_PBA_HEADER_SIZE (BT_UUID_SIZE_16 + (sizeof(uint8_t) * 2))
#define BROADCAST_SOURCE_ADV_NAME_MAX (32)
#define BROADCAST_SOURCE_ADV_ID_START (BT_UUID_SIZE_16)
struct subgroup_config {
enum bt_audio_location *location;
uint8_t num_bises;
enum bt_audio_context context;
struct bt_bap_lc3_preset group_lc3_preset;
char *preset_name;
};
struct broadcast_source_big {
struct subgroup_config *subgroups;
uint8_t num_subgroups;
uint8_t packing;
bool encryption;
uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE];
char broadcast_name[BROADCAST_SOURCE_ADV_NAME_MAX + 1];
char adv_name[CONFIG_BT_DEVICE_NAME_MAX + 1];
bool fixed_id;
uint32_t broadcast_id;
};
/**
* @brief Advertising data for broadcast source.
*/
struct broadcast_source_ext_adv_data {
/* Broadcast Audio Streaming UUIDs. */
struct net_buf_simple *uuid_buf;
/* Broadcast Audio Streaming Endpoint advertising data. */
uint8_t brdcst_id_buf[BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE];
/* Buffer for Appearance. */
uint8_t brdcst_appearance_buf[(sizeof(uint8_t) * 2)];
/* Broadcast name, must be between 4 and 32 UTF-8 encoded characters in length. */
uint8_t brdcst_name_buf[BROADCAST_SOURCE_ADV_NAME_MAX];
#if (CONFIG_AURACAST)
/* Number of free metadata items */
uint8_t pba_metadata_vacant_cnt;
/* Public Broadcast Announcement buffer. */
uint8_t *pba_buf;
#endif /* (CONFIG_AURACAST) */
};
/**
* @brief Periodic advertising data for broadcast source.
*/
struct broadcast_source_per_adv_data {
/* Buffer for periodic advertising data */
struct net_buf_simple *base_buf;
};
/**
* @brief Populate the extended advertising data buffer.
*
* @param[in] big_index Index of the Broadcast Isochronous Group (BIG) to get
* advertising data for.
* @param[in] fixed_id Flag to indicate if the broadcast ID will be random or not.
* @param[in] broadcast_id Broadcast ID to be used in the advertising data if
* @p fixed_id is set to true. The broadcast ID is three octets
* long.
* @param[in] ext_adv_data Pointer to the extended advertising buffers.
* @param[out] ext_adv_buf Pointer to the bt_data used for extended advertising.
* @param[out] ext_adv_buf_vacant Pointer to unused size of @p ext_adv_buf.
*
* @return Negative values for errors or number of elements added to @p ext_adv_buf.
*/
int broadcast_source_ext_adv_populate(uint8_t big_index, bool fixed_id, uint32_t broadcast_id,
struct broadcast_source_ext_adv_data *ext_adv_data,
struct bt_data *ext_adv_buf, size_t ext_adv_buf_vacant);
/**
* @brief Populate the periodic advertising data buffer.
*
* @param[in] big_index Index of the Broadcast Isochronous Group (BIG) to get
* advertising data for.
* @param[in] per_adv_data Pointer to a structure of periodic advertising buffers.
* @param[out] per_adv_buf Pointer to the bt_data used for periodic advertising.
* @param[out] per_adv_buf_vacant Pointer to unused size of @p per_adv_buf.
*
* @return Negative values for errors or number of elements added to @p per_adv_buf.
*/
int broadcast_source_per_adv_populate(uint8_t big_index,
struct broadcast_source_per_adv_data *per_adv_data,
struct bt_data *per_adv_buf, size_t per_adv_buf_vacant);
/**
* @brief Check if the broadcast source is streaming.
*
* @param[in] big_index Index of the Broadcast Isochronous Group (BIG) to check.
*
* @retval True The broadcast source is streaming.
* @retval False The broadcast source is not streaming.
*/
bool broadcast_source_is_streaming(uint8_t big_index);
/**
* @brief Start the Bluetooth LE Audio broadcast (BIS) source.
*
* @param[in] big_index Index of the Broadcast Isochronous Group (BIG) to start.
* @param[in] ext_adv Pointer to the extended advertising set; can be NULL if a stream
* is restarted.
*
* @return 0 for success, error otherwise.
*/
int broadcast_source_start(uint8_t big_index, struct bt_le_ext_adv *ext_adv);
/**
* @brief Stop the Bluetooth LE Audio broadcast (BIS) source.
*
* @param[in] big_index Index of the Broadcast Isochronous Group (BIG) to stop.
*
* @return 0 for success, error otherwise.
*/
int broadcast_source_stop(uint8_t big_index);
/**
* @brief Get the broadcast ID for the given Broadcast Isochronous Group (BIG).
*
* @note The broadcast ID is used to identify the broadcast. Its value is three octets long.
* This function should only be called after the BIG has been created.
*
* @param[in] big_index Index of the Broadcast Isochronous Group (BIG) to get the broadcast
* ID for.
* @param[out] broadcast_id Pointer to the broadcast ID.
*
* @return 0 for success, error otherwise.
*/
int broadcast_source_id_get(uint8_t big_index, uint32_t *broadcast_id);
/**
* @brief Broadcast the Bluetooth LE Audio data.
*
* @param[in] big_index Index of the Broadcast Isochronous Group (BIG) to broadcast.
* @param[in] subgroup_index Index of the subgroup to broadcast.
* @param[in] enc_audio Encoded audio struct.
*
* @return 0 for success, error otherwise.
*/
int broadcast_source_send(uint8_t big_index, uint8_t subgroup_index,
struct le_audio_encoded_audio enc_audio);
/**
* @brief Disable the LE Audio broadcast (BIS) source.
*
* @param[in] big_index Index of the Broadcast Isochronous Group (BIG) to disable.
*
* @return 0 for success, error otherwise.
*/
int broadcast_source_disable(uint8_t big_index);
/**
* @brief Create a set up for a default broadcaster.
*
* @note This will create the parameters for a simple broadcaster with 1 Broadcast
* Isochronous Group (BIG), 1 subgroup, and 2 BISes.
* The BISes will be front_left and front_right and language will be set to 'eng'.
*
* @param[out] broadcast_param Pointer to populate with parameters for setting up the broadcaster.
*/
void broadcast_source_default_create(struct broadcast_source_big *broadcast_param);
/**
* @brief Enable the LE Audio broadcast (BIS) source.
*
* @param[in] broadcast_param Array of create parameters for creating a Broadcast Isochronous
* Group (BIG).
* @param[in] big_index Index of the BIG to enable.
*
* @return 0 for success, error otherwise.
*/
int broadcast_source_enable(struct broadcast_source_big const *const broadcast_param,
uint8_t big_index);
#endif /* _BROADCAST_SOURCE_H_ */

View File

@@ -0,0 +1,10 @@
#
# Copyright (c) 2024 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
if (CONFIG_BT_AUDIO_TX)
target_sources(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/bt_le_audio_tx.c)
endif()

View File

@@ -0,0 +1,368 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "bt_le_audio_tx.h"
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/zbus/zbus.h>
#include <../subsys/bluetooth/audio/bap_stream.h>
#include <bluetooth/hci_vs_sdc.h>
#include "zbus_common.h"
#include "audio_sync_timer.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_le_audio_tx, CONFIG_BT_LE_AUDIO_TX_LOG_LEVEL);
ZBUS_CHAN_DEFINE(sdu_ref_chan, struct sdu_ref_msg, NULL, NULL, ZBUS_OBSERVERS_EMPTY,
ZBUS_MSG_INIT(0));
#define HANDLE_INVALID 0xFFFF
#define HCI_ISO_BUF_PER_CHAN 2
#if defined(CONFIG_BT_ISO_MAX_CIG) && defined(CONFIG_BT_ISO_MAX_BIG)
#define GROUP_MAX (CONFIG_BT_ISO_MAX_BIG + CONFIG_BT_ISO_MAX_CIG)
#elif defined(CONFIG_BT_ISO_MAX_CIG)
#define GROUP_MAX CONFIG_BT_ISO_MAX_CIG
#elif defined(CONFIG_BT_ISO_MAX_BIG)
#define GROUP_MAX CONFIG_BT_ISO_MAX_BIG
#else
#error Neither CIG nor BIG defined
#endif
#if (defined(CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT) && defined(CONFIG_BT_BAP_UNICAST))
/* 1 since CIGs doesn't have the concept of subgroups */
#define SUBGROUP_MAX (1 + CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT)
#elif (defined(CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT) && !defined(CONFIG_BT_BAP_UNICAST))
#define SUBGROUP_MAX CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT
#else
/* 1 since CIGs doesn't have the concept of subgroups */
#define SUBGROUP_MAX 1
#endif
/* Since we can't assume that the number of streams are equally distributed on the subgroups ,we
* need to allocate the max number per subgroup
*/
#define STREAMS_MAX (CONFIG_BT_ISO_MAX_CHAN)
#define NET_BUF_POOL_MAX ((GROUP_MAX) * (SUBGROUP_MAX) * (STREAMS_MAX) * (HCI_ISO_BUF_PER_CHAN))
NET_BUF_POOL_FIXED_DEFINE(iso_tx_pool, NET_BUF_POOL_MAX, BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
struct tx_inf {
uint16_t iso_conn_handle;
struct bt_iso_tx_info iso_tx;
struct bt_iso_tx_info iso_tx_readback;
atomic_t iso_tx_pool_alloc;
bool hci_wrn_printed;
};
static bool initialized;
static struct tx_inf tx_info_arr[GROUP_MAX][SUBGROUP_MAX][STREAMS_MAX];
/**
* @brief Sends audio data over a single BAP stream.
*
* @param data Audio data to send.
* @param size Size of data.
* @param bap_stream Pointer to BAP stream to use.
* @param tx_info Pointer to tx_info struct.
* @param ts_tx Timestamp to send. Note that for some controllers, BT_ISO_TIMESTAMP_NONE
* is used. This timestamp is used to ensure that SDUs are sent in the same
* connection interval.
* @return 0 if successful, error otherwise.
*/
static int iso_stream_send(uint8_t const *const data, size_t size, struct bt_cap_stream *cap_stream,
struct tx_inf *tx_info, uint32_t ts_tx)
{
int ret;
struct net_buf *buf;
/* net_buf_alloc allocates buffers for APP->NET transfer over HCI RPMsg,
* but when these buffers are released it is not guaranteed that the
* data has actually been sent. The data might be queued on the NET core,
* and this can cause delays in the audio.
* When the sent callback is called the data has been sent, and we can free the buffer.
* Data will be discarded if allocation becomes too high, to avoid audio delays.
* If the NET and APP core operates in clock sync, discarding should not occur.
*/
if (atomic_get(&tx_info->iso_tx_pool_alloc) >= HCI_ISO_BUF_PER_CHAN) {
if (!tx_info->hci_wrn_printed) {
struct bt_iso_chan *iso_chan;
iso_chan = bt_bap_stream_iso_chan_get(&cap_stream->bap_stream);
LOG_WRN("HCI ISO TX overrun on stream %p - Single print",
(void *)&cap_stream->bap_stream);
tx_info->hci_wrn_printed = true;
}
return -ENOMEM;
}
tx_info->hci_wrn_printed = false;
buf = net_buf_alloc(&iso_tx_pool, K_NO_WAIT);
if (buf == NULL) {
/* This should never occur because of the iso_tx_pool_alloc check above */
LOG_WRN("Out of TX buffers");
return -ENOMEM;
}
net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);
net_buf_add_mem(buf, data, size);
atomic_inc(&tx_info->iso_tx_pool_alloc);
if (ts_tx == 0) {
ret = bt_cap_stream_send(cap_stream, buf, tx_info->iso_tx.seq_num);
} else {
ret = bt_cap_stream_send_ts(cap_stream, buf, tx_info->iso_tx.seq_num, ts_tx);
}
if (ret < 0) {
if (ret != -ENOTCONN) {
LOG_WRN("Failed to send audio data: %d stream %p", ret,
(void *)&cap_stream->bap_stream);
}
net_buf_unref(buf);
atomic_dec(&tx_info->iso_tx_pool_alloc);
return ret;
} else {
tx_info->iso_tx.seq_num++;
}
return 0;
}
static int get_tx_sync_sdc(uint16_t iso_conn_handle, struct bt_iso_tx_info *info)
{
int ret;
sdc_hci_cmd_vs_iso_read_tx_timestamp_t cmd_read_tx_timestamp;
sdc_hci_cmd_vs_iso_read_tx_timestamp_return_t rsp_params;
cmd_read_tx_timestamp.conn_handle = iso_conn_handle;
ret = hci_vs_sdc_iso_read_tx_timestamp(&cmd_read_tx_timestamp, &rsp_params);
if (ret) {
return ret;
}
info->ts = rsp_params.tx_time_stamp;
info->seq_num = rsp_params.packet_sequence_number;
info->offset = 0;
return 0;
}
static int iso_conn_handle_set(struct bt_bap_stream *bap_stream, uint16_t *iso_conn_handle)
{
int ret;
if (*iso_conn_handle == HANDLE_INVALID) {
struct bt_bap_ep_info ep_info;
ret = bt_bap_ep_get_info(bap_stream->ep, &ep_info);
if (ret) {
LOG_WRN("Unable to get info for ep");
return -EACCES;
}
ret = bt_hci_get_conn_handle(ep_info.iso_chan->iso, iso_conn_handle);
if (ret) {
LOG_ERR("Failed obtaining conn_handle (ret:%d)", ret);
return ret;
}
} else {
/* Already set. */
}
return 0;
}
int bt_le_audio_tx_send(struct le_audio_tx_info *tx, uint8_t num_tx,
struct le_audio_encoded_audio enc_audio)
{
int ret;
size_t data_size_pr_stream = 0;
if (!initialized) {
return -EACCES;
}
if (tx == NULL) {
return -EINVAL;
}
data_size_pr_stream = enc_audio.size / enc_audio.num_ch;
/* When sending ISO data, we always send ts = 0 to the first active transmitting channel.
* The controller will populate with a ts which is fetched using bt_iso_chan_get_tx_sync.
* This timestamp will be submitted to all the other channels in order to place data on all
* channels in the same ISO interval.
*/
uint32_t common_tx_sync_ts_us = 0;
uint32_t curr_ts_us = 0;
bool ts_common_acquired = false;
uint32_t common_interval = 0;
for (int i = 0; i < num_tx; i++) {
struct tx_inf *tx_info =
&tx_info_arr[tx[i].idx.lvl1][tx[i].idx.lvl2][tx[i].idx.lvl3];
if (tx_info->iso_tx.seq_num == 0) {
/* Temporary fix until /zephyr/pull/68745/ is available
*/
#if defined(CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM)
tx[i].cap_stream->bap_stream._prev_seq_num = 0;
#endif /* CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM */
}
if (!le_audio_ep_state_check(tx[i].cap_stream->bap_stream.ep,
BT_BAP_EP_STATE_STREAMING)) {
/* This bap_stream is not streaming*/
continue;
}
if (tx[i].audio_channel > enc_audio.num_ch) {
LOG_WRN("Unsupported audio_channel: %d", tx[i].audio_channel);
continue;
}
uint32_t bitrate;
ret = le_audio_bitrate_get(tx[i].cap_stream->bap_stream.codec_cfg, &bitrate);
if (ret) {
LOG_ERR("Failed to calculate bitrate: %d", ret);
return ret;
}
if (data_size_pr_stream != LE_AUDIO_SDU_SIZE_OCTETS(bitrate)) {
LOG_ERR("The encoded data size does not match the SDU size");
return -EINVAL;
}
if (common_interval != 0 &&
(common_interval != tx[i].cap_stream->bap_stream.qos->interval)) {
LOG_ERR("Not all channels have the same ISO interval");
return -EINVAL;
}
common_interval = tx[i].cap_stream->bap_stream.qos->interval;
/* Check if same audio is sent to all channels */
if (enc_audio.num_ch == 1) {
ret = iso_stream_send(enc_audio.data, data_size_pr_stream, tx[i].cap_stream,
tx_info, common_tx_sync_ts_us);
} else {
ret = iso_stream_send(
&enc_audio.data[(data_size_pr_stream * tx[i].audio_channel)],
data_size_pr_stream, tx[i].cap_stream, tx_info,
common_tx_sync_ts_us);
}
if (ret) {
/* DBG used here as prints are handled within iso_stream_send */
LOG_DBG("Failed to send to idx: %d stream: %p, ret: %d ", i,
(void *)&tx[i].cap_stream->bap_stream, ret);
continue;
}
ret = iso_conn_handle_set(&tx[i].cap_stream->bap_stream, &tx_info->iso_conn_handle);
if (ret) {
continue;
}
/* Strictly, it is only required to call get_tx_sync_sdc on the first streaming
* channel to get the timestamp which is sent to all other channels.
* However, to be able to detect errors, this is called on each TX.
*/
ret = get_tx_sync_sdc(tx_info->iso_conn_handle, &tx_info->iso_tx_readback);
if (ret) {
if (ret != -ENOTCONN) {
LOG_WRN("Unable to get tx sync. ret: %d stream: %p", ret,
(void *)&tx[i].cap_stream->bap_stream);
}
continue;
}
if (!ts_common_acquired) {
curr_ts_us = audio_sync_timer_capture();
common_tx_sync_ts_us = tx_info->iso_tx_readback.ts;
ts_common_acquired = true;
}
}
if (ts_common_acquired) {
struct sdu_ref_msg msg;
msg.tx_sync_ts_us = common_tx_sync_ts_us;
msg.curr_ts_us = curr_ts_us;
msg.adjust = true;
ret = zbus_chan_pub(&sdu_ref_chan, &msg, K_NO_WAIT);
if (ret) {
LOG_WRN("Failed to publish timestamp: %d", ret);
}
}
return 0;
}
int bt_le_audio_tx_stream_started(struct stream_index idx)
{
if (!initialized) {
return -EACCES;
}
atomic_clear(&tx_info_arr[idx.lvl1][idx.lvl2][idx.lvl3].iso_tx_pool_alloc);
tx_info_arr[idx.lvl1][idx.lvl2][idx.lvl3].hci_wrn_printed = false;
tx_info_arr[idx.lvl1][idx.lvl2][idx.lvl3].iso_conn_handle = HANDLE_INVALID;
tx_info_arr[idx.lvl1][idx.lvl2][idx.lvl3].iso_tx.seq_num = 0;
tx_info_arr[idx.lvl1][idx.lvl2][idx.lvl3].iso_tx_readback.seq_num = 0;
return 0;
}
int bt_le_audio_tx_stream_sent(struct stream_index stream_idx)
{
if (!initialized) {
return -EACCES;
}
atomic_dec(
&tx_info_arr[stream_idx.lvl1][stream_idx.lvl2][stream_idx.lvl3].iso_tx_pool_alloc);
return 0;
}
void bt_le_audio_tx_init(void)
{
if (initialized) {
/* If TX is disabled and enabled again this should be called to reset the state */
LOG_DBG("Already initialized");
}
for (int i = 0; i < GROUP_MAX; i++) {
for (int j = 0; j < SUBGROUP_MAX; j++) {
for (int k = 0; k < STREAMS_MAX; k++) {
tx_info_arr[i][j][k].hci_wrn_printed = false;
tx_info_arr[i][j][k].iso_conn_handle = HANDLE_INVALID;
tx_info_arr[i][j][k].iso_tx.ts = 0;
tx_info_arr[i][j][k].iso_tx.offset = 0;
tx_info_arr[i][j][k].iso_tx.seq_num = 0;
tx_info_arr[i][j][k].iso_tx_readback.ts = 0;
tx_info_arr[i][j][k].iso_tx_readback.offset = 0;
tx_info_arr[i][j][k].iso_tx_readback.seq_num = 0;
}
}
}
initialized = true;
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _LE_AUDIO_TX_H_
#define _LE_AUDIO_TX_H_
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/cap.h>
#include "le_audio.h"
struct le_audio_tx_info {
struct stream_index idx;
struct bt_cap_stream *cap_stream;
uint8_t audio_channel;
};
/**
* @brief Allocates buffers and sends data to the controller.
*
* @note Send all available channels in a single call.
* Do not call this for each channel.
*
* @param[in] tx Pointer to an array of le_audio_tx_info elements.
* @param[in] num_tx Number of elements in @p tx.
* @param[in] enc_audio Encoded audio data.
*
* @return 0 if successful, error otherwise.
*/
int bt_le_audio_tx_send(struct le_audio_tx_info *tx, uint8_t num_tx,
struct le_audio_encoded_audio enc_audio);
/**
* @brief Initializes a stream. Must be called when a TX stream is started.
*
* @param[in] stream_idx Stream index.
*
* @retval -EACCES The module has not been initialized.
* @retval 0 Success.
*/
int bt_le_audio_tx_stream_started(struct stream_index stream_idx);
/**
* @brief Frees a TX buffer. Must be called when a TX stream has been sent.
*
* @param[in] stream_idx Stream index.
*
* @retval -EACCES The module has not been initialized.
* @retval 0 Success.
*/
int bt_le_audio_tx_stream_sent(struct stream_index stream_idx);
/**
* @brief Initializes the TX path for ISO transmission.
*/
void bt_le_audio_tx_init(void);
#endif /* _LE_AUDIO_TX_H_ */

View File

@@ -0,0 +1,318 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "le_audio.h"
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(le_audio, CONFIG_BLE_LOG_LEVEL);
int le_audio_ep_state_get(struct bt_bap_ep *ep, uint8_t *state)
{
int ret;
struct bt_bap_ep_info ep_info;
if (ep == NULL) {
/* If an endpoint is NULL it is not in any of the states */
return -EINVAL;
}
ret = bt_bap_ep_get_info(ep, &ep_info);
if (ret) {
LOG_WRN("Unable to get info for ep");
return ret;
}
*state = ep_info.state;
return 0;
}
/*TODO: Create helper function in host to perform this action. */
bool le_audio_ep_state_check(struct bt_bap_ep *ep, enum bt_bap_ep_state state)
{
int ret;
uint8_t ep_state;
ret = le_audio_ep_state_get(ep, &ep_state);
if (ret) {
return false;
}
if (ep_state == state) {
return true;
}
return false;
}
bool le_audio_ep_qos_configured(struct bt_bap_ep const *const ep)
{
int ret;
struct bt_bap_ep_info ep_info;
if (ep == NULL) {
LOG_DBG("EP is NULL");
/* If an endpoint is NULL it is not in any of the states */
return false;
}
ret = bt_bap_ep_get_info(ep, &ep_info);
if (ret) {
LOG_WRN("Unable to get info for ep");
return false;
}
if (ep_info.state == BT_BAP_EP_STATE_QOS_CONFIGURED ||
ep_info.state == BT_BAP_EP_STATE_ENABLING ||
ep_info.state == BT_BAP_EP_STATE_STREAMING ||
ep_info.state == BT_BAP_EP_STATE_DISABLING) {
return true;
}
return false;
}
int le_audio_freq_hz_get(const struct bt_audio_codec_cfg *codec, int *freq_hz)
{
int ret;
ret = bt_audio_codec_cfg_get_freq(codec);
if (ret < 0) {
return ret;
}
ret = bt_audio_codec_cfg_freq_to_freq_hz(ret);
if (ret < 0) {
return ret;
}
*freq_hz = ret;
return 0;
}
int le_audio_duration_us_get(const struct bt_audio_codec_cfg *codec, int *frame_dur_us)
{
int ret;
ret = bt_audio_codec_cfg_get_frame_dur(codec);
if (ret < 0) {
return ret;
}
ret = bt_audio_codec_cfg_frame_dur_to_frame_dur_us(ret);
if (ret < 0) {
return ret;
}
*frame_dur_us = ret;
return 0;
}
int le_audio_octets_per_frame_get(const struct bt_audio_codec_cfg *codec, uint32_t *octets_per_sdu)
{
int ret;
ret = bt_audio_codec_cfg_get_octets_per_frame(codec);
if (ret < 0) {
return ret;
}
*octets_per_sdu = ret;
return 0;
}
int le_audio_frame_blocks_per_sdu_get(const struct bt_audio_codec_cfg *codec,
uint32_t *frame_blks_per_sdu)
{
int ret;
ret = bt_audio_codec_cfg_get_frame_blocks_per_sdu(codec, true);
if (ret < 0) {
return ret;
}
*frame_blks_per_sdu = ret;
return 0;
}
int le_audio_bitrate_get(const struct bt_audio_codec_cfg *const codec, uint32_t *bitrate)
{
int ret;
int dur_us;
ret = le_audio_duration_us_get(codec, &dur_us);
if (ret) {
return ret;
}
int frames_per_sec = 1000000 / dur_us;
int octets_per_sdu;
ret = le_audio_octets_per_frame_get(codec, &octets_per_sdu);
if (ret) {
return ret;
}
*bitrate = frames_per_sec * (octets_per_sdu * 8);
return 0;
}
int le_audio_stream_dir_get(struct bt_bap_stream const *const stream)
{
int ret;
struct bt_bap_ep_info ep_info;
ret = bt_bap_ep_get_info(stream->ep, &ep_info);
if (ret) {
LOG_WRN("Failed to get ep_info");
return ret;
}
return ep_info.dir;
}
bool le_audio_bitrate_check(const struct bt_audio_codec_cfg *codec)
{
int ret;
uint32_t octets_per_sdu;
ret = le_audio_octets_per_frame_get(codec, &octets_per_sdu);
if (ret) {
LOG_ERR("Error retrieving octets per frame: %d", ret);
return false;
}
if (octets_per_sdu < LE_AUDIO_SDU_SIZE_OCTETS(CONFIG_LC3_BITRATE_MIN)) {
LOG_WRN("Bitrate too low");
return false;
} else if (octets_per_sdu > LE_AUDIO_SDU_SIZE_OCTETS(CONFIG_LC3_BITRATE_MAX)) {
LOG_WRN("Bitrate too high");
return false;
}
return true;
}
bool le_audio_freq_check(const struct bt_audio_codec_cfg *codec)
{
int ret;
uint32_t frequency_hz;
ret = le_audio_freq_hz_get(codec, &frequency_hz);
if (ret) {
LOG_ERR("Error retrieving sampling rate: %d", ret);
return false;
}
switch (frequency_hz) {
case 8000U:
return (BT_AUDIO_CODEC_CAP_FREQ_8KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 11025U:
return (BT_AUDIO_CODEC_CAP_FREQ_11KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 16000U:
return (BT_AUDIO_CODEC_CAP_FREQ_16KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 22050U:
return (BT_AUDIO_CODEC_CAP_FREQ_22KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 24000U:
return (BT_AUDIO_CODEC_CAP_FREQ_24KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 32000U:
return (BT_AUDIO_CODEC_CAP_FREQ_32KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 44100U:
return (BT_AUDIO_CODEC_CAP_FREQ_44KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 48000U:
return (BT_AUDIO_CODEC_CAP_FREQ_48KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 88200U:
return (BT_AUDIO_CODEC_CAP_FREQ_88KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 96000U:
return (BT_AUDIO_CODEC_CAP_FREQ_96KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 176400U:
return (BT_AUDIO_CODEC_CAP_FREQ_176KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 192000U:
return (BT_AUDIO_CODEC_CAP_FREQ_192KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
case 384000U:
return (BT_AUDIO_CODEC_CAP_FREQ_384KHZ & (BT_AUDIO_CODEC_CAPABILIY_FREQ));
default:
return false;
}
}
void le_audio_print_codec(const struct bt_audio_codec_cfg *codec, enum bt_audio_dir dir)
{
if (codec->id == BT_HCI_CODING_FORMAT_LC3) {
/* LC3 uses the generic LTV format - other codecs might do as well */
int ret;
enum bt_audio_location chan_allocation;
int freq_hz;
int dur_us;
uint32_t octets_per_sdu;
int frame_blks_per_sdu;
uint32_t bitrate;
ret = le_audio_freq_hz_get(codec, &freq_hz);
if (ret) {
LOG_ERR("Error retrieving sampling frequency: %d", ret);
return;
}
ret = le_audio_duration_us_get(codec, &dur_us);
if (ret) {
LOG_ERR("Error retrieving frame duration: %d", ret);
return;
}
ret = le_audio_octets_per_frame_get(codec, &octets_per_sdu);
if (ret) {
LOG_ERR("Error retrieving octets per frame: %d", ret);
return;
}
ret = le_audio_frame_blocks_per_sdu_get(codec, &frame_blks_per_sdu);
if (ret) {
LOG_ERR("Error retrieving frame blocks per SDU: %d", ret);
return;
}
ret = bt_audio_codec_cfg_get_chan_allocation(codec, &chan_allocation, false);
if (ret == -ENODATA) {
/* Codec channel allocation not set, defaulting to 0 */
chan_allocation = 0;
} else if (ret) {
LOG_ERR("Error retrieving channel allocation: %d", ret);
return;
}
ret = le_audio_bitrate_get(codec, &bitrate);
if (ret) {
LOG_ERR("Unable to calculate bitrate: %d", ret);
return;
}
if (dir == BT_AUDIO_DIR_SINK) {
LOG_INF("LC3 codec config for sink:");
} else if (dir == BT_AUDIO_DIR_SOURCE) {
LOG_INF("LC3 codec config for source:");
} else {
LOG_INF("LC3 codec config for <unknown dir>:");
}
LOG_INF("\tFrequency: %d Hz", freq_hz);
LOG_INF("\tDuration: %d us", dur_us);
LOG_INF("\tChannel allocation: 0x%x", chan_allocation);
LOG_INF("\tOctets per frame: %d (%d bps)", octets_per_sdu, bitrate);
LOG_INF("\tFrames per SDU: %d", frame_blks_per_sdu);
} else {
LOG_WRN("Codec is not LC3, codec_id: 0x%2x", codec->id);
}
}

View File

@@ -0,0 +1,201 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#ifndef _LE_AUDIO_H_
#define _LE_AUDIO_H_
#include <zephyr/bluetooth/audio/bap.h>
#include <audio_defines.h>
#define LE_AUDIO_ZBUS_EVENT_WAIT_TIME K_MSEC(5)
#define LE_AUDIO_SDU_SIZE_OCTETS(bitrate) (bitrate / (1000000 / CONFIG_AUDIO_FRAME_DURATION_US) / 8)
#if CONFIG_SAMPLE_RATE_CONVERTER && CONFIG_AUDIO_SAMPLE_RATE_48000_HZ
#define BT_AUDIO_CODEC_CAPABILIY_FREQ \
BT_AUDIO_CODEC_CAP_FREQ_48KHZ | BT_AUDIO_CODEC_CAP_FREQ_24KHZ | \
BT_AUDIO_CODEC_CAP_FREQ_16KHZ
#elif CONFIG_AUDIO_SAMPLE_RATE_16000_HZ
#define BT_AUDIO_CODEC_CAPABILIY_FREQ BT_AUDIO_CODEC_CAP_FREQ_16KHZ
#elif CONFIG_AUDIO_SAMPLE_RATE_24000_HZ
#define BT_AUDIO_CODEC_CAPABILIY_FREQ BT_AUDIO_CODEC_CAP_FREQ_24KHZ
#elif CONFIG_AUDIO_SAMPLE_RATE_48000_HZ
#define BT_AUDIO_CODEC_CAPABILIY_FREQ BT_AUDIO_CODEC_CAP_FREQ_48KHZ
#else
#error No sample rate supported
#endif /* CONFIG_SAMPLE_RATE_CONVERTER */
#define BT_BAP_LC3_PRESET_CONFIGURABLE(_loc, _stream_context, _bitrate) \
BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(CONFIG_BT_AUDIO_PREF_SAMPLE_RATE_VALUE, \
BT_AUDIO_CODEC_CFG_DURATION_10, _loc, \
LE_AUDIO_SDU_SIZE_OCTETS(_bitrate), 1, \
_stream_context), \
BT_BAP_QOS_CFG_UNFRAMED(10000u, LE_AUDIO_SDU_SIZE_OCTETS(_bitrate), \
CONFIG_BT_AUDIO_RETRANSMITS, \
CONFIG_BT_AUDIO_MAX_TRANSPORT_LATENCY_MS, \
CONFIG_BT_AUDIO_PRESENTATION_DELAY_US))
/**
* @brief Callback for receiving Bluetooth LE Audio data.
*
* @param data Pointer to received data.
* @param size Size of received data.
* @param bad_frame Indicating if the frame is a bad frame or not.
* @param sdu_ref ISO timestamp.
* @param channel_index Audio channel index.
* @param desired_size The expected data size.
*/
typedef void (*le_audio_receive_cb)(const uint8_t *const data, size_t size, bool bad_frame,
uint32_t sdu_ref, enum audio_channel channel_index,
size_t desired_size);
/**
* @brief Encoded audio data and information.
*
* @note Container for SW codec (typically LC3) compressed audio data.
*/
struct le_audio_encoded_audio {
uint8_t const *const data;
size_t size;
uint8_t num_ch;
};
struct stream_index {
uint8_t lvl1; /* BIG / CIG */
uint8_t lvl2; /* Subgroups (only applicable to Broadcast) */
uint8_t lvl3; /* BIS / CIS */
};
/**
* @brief Get the current state of an endpoint.
*
* @param[in] ep The endpoint to check.
* @param[out] state The state of the endpoint.
*
* @return 0 for success, error otherwise.
*/
int le_audio_ep_state_get(struct bt_bap_ep *ep, uint8_t *state);
/**
* @brief Check if an endpoint is in the given state.
* If the endpoint is NULL, it is not in the
* given state, and this function returns false.
*
* @param[in] ep The endpoint to check.
* @param[in] state The state to check for.
*
* @retval true The endpoint is in the given state.
* @retval false Otherwise.
*/
bool le_audio_ep_state_check(struct bt_bap_ep *ep, enum bt_bap_ep_state state);
/**
* @brief Check if an endpoint has had the QoS configured.
* If the endpoint is NULL, it is not in the state, and this function returns false.
*
* @param[in] ep The endpoint to check.
*
* @retval true The endpoint QoS is configured.
* @retval false Otherwise.
*/
bool le_audio_ep_qos_configured(struct bt_bap_ep const *const ep);
/**
* @brief Decode the audio sampling frequency in the codec configuration.
*
* @param[in] codec Pointer to the audio codec structure.
* @param[out] freq_hz Pointer to the sampling frequency in Hz.
*
* @return 0 for success, error otherwise.
*/
int le_audio_freq_hz_get(const struct bt_audio_codec_cfg *codec, int *freq_hz);
/**
* @brief Decode the audio frame duration in us in the codec configuration.
*
* @param[in] codec Pointer to the audio codec structure.
* @param[out] frame_dur_us Pointer to the frame duration in us.
*
* @return 0 for success, error otherwise.
*/
int le_audio_duration_us_get(const struct bt_audio_codec_cfg *codec, int *frame_dur_us);
/**
* @brief Decode the number of octets per frame in the codec configuration.
*
* @param[in] codec Pointer to the audio codec structure.
* @param[out] octets_per_sdu Pointer to the number of octets per SDU.
*
* @return 0 for success, error otherwise.
*/
int le_audio_octets_per_frame_get(const struct bt_audio_codec_cfg *codec, uint32_t *octets_per_sdu);
/**
* @brief Decode the number of frame blocks per SDU in the codec configuration.
*
* @param[in] codec Pointer to the audio codec structure.
* @param[out] frame_blks_per_sdu Pointer to the number of frame blocks per SDU.
*
* @return 0 for success, error otherwise.
*/
int le_audio_frame_blocks_per_sdu_get(const struct bt_audio_codec_cfg *codec,
uint32_t *frame_blks_per_sdu);
/**
* @brief Get the bitrate for the codec configuration.
*
* @details Decodes the audio frame duration and the number of octets per fram from the codec
* configuration, and calculates the bitrate.
*
* @param[in] codec Pointer to the audio codec structure.
* @param[out] bitrate Pointer to the bitrate in bps.
*/
int le_audio_bitrate_get(const struct bt_audio_codec_cfg *const codec, uint32_t *bitrate);
/**
* @brief Get the direction of the @p stream provided.
*
* @param[in] stream Stream to check direction for.
*
* @retval BT_AUDIO_DIR_SINK sink direction.
* @retval BT_AUDIO_DIR_SOURCE source direction.
* @retval Negative value Failed to get ep_info from host.
*
*/
int le_audio_stream_dir_get(struct bt_bap_stream const *const stream);
/**
* @brief Check that the bitrate is within the supported range.
*
* @param[in] codec The audio codec structure.
*
* retval true The bitrate is in the supported range.
* retval false Otherwise.
*/
bool le_audio_bitrate_check(const struct bt_audio_codec_cfg *codec);
/**
* @brief Check that the sample rate is supported.
*
* @param[in] codec The audio codec structure.
*
* retval true The sample rate is supported.
* retval false Otherwise.
*/
bool le_audio_freq_check(const struct bt_audio_codec_cfg *codec);
/**
* @brief Print the codec configuration
*
* @param[in] codec Pointer to the audio codec structure.
* @param[in] dir Direction to print the codec configuration for.
*/
void le_audio_print_codec(const struct bt_audio_codec_cfg *codec, enum bt_audio_dir dir);
#endif /* _LE_AUDIO_H_ */

View File

@@ -0,0 +1,161 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
rsource "Kconfig.defaults"
menu "Unicast"
choice BT_BAP_UNICAST_BAP_CONFIGURATION
prompt "Unicast codec configuration"
depends on TRANSPORT_CIS
default BT_BAP_UNICAST_CONFIGURABLE
help
Select the unicast codec configuration as given in
Table 5.2 of the Bluetooth Audio Profile Specification.
USB only supports 48 kHz samplig rate.
config BT_BAP_UNICAST_CONFIGURABLE
bool "Configurable unicast settings"
depends on TRANSPORT_CIS
help
Configurable option that doesn't follow any preset. Allows for more flexibility.
config BT_BAP_UNICAST_16_2_1
bool "16_2_1"
depends on TRANSPORT_CIS
help
Unicast mandatory codec capability 16_2_1.
16kHz, 32kbps, 2 retransmits, 10ms transport latency, and 40ms presentation delay.
config BT_BAP_UNICAST_24_2_1
bool "24_2_1"
depends on TRANSPORT_CIS
help
Unicast codec capability 24_2_1.
24kHz, 48kbps, 2 retransmits, 10ms transport latency, and 40ms presentation delay.
config BT_BAP_UNICAST_48_4_1
bool "48_4_1"
depends on TRANSPORT_CIS
help
Unicast codec capability 48_4_1.
48kHz, 96kbps, 5 retransmits, 20ms transport latency, and 40ms presentation delay.
endchoice
choice BT_AUDIO_PRES_DLY_SRCH
prompt "Default search mode for the presentation delay"
default BT_AUDIO_PRES_DELAY_SRCH_PREF_MIN
help
Set the default search mode for the presentation delay.
config BT_AUDIO_PRES_DELAY_SRCH_MIN
bool "Largest minimum delay over all audio receivers"
help
Search for the largest minimum delay over all audio receivers.
config BT_AUDIO_PRES_DELAY_SRCH_MAX
bool "Smallest maximum delay over all audio receivers"
help
Search for the smallest maximum delay over all audio receivers.
config BT_AUDIO_PRES_DELAY_SRCH_PREF_MIN
bool "Largest minimum preferred delay over all audio receivers"
help
Search for the largest minimum preferred delay over all audio receivers.
config BT_AUDIO_PRES_DELAY_SRCH_PREF_MAX
bool "Smallest maximum preferred delay over all audio receivers"
help
Search for the smallest maximum preferred delay over all audio receivers.
config BT_AUDIO_PRES_DELAY_SRCH_SOURCE
bool "Use the presentation delay of audio source or client"
help
Use the presentation delay defined by the broadcast_source or unicast_client if it
is within the range set by AUDIO_MIN_PRES_DLY_US and AUDIO_MAX_PRES_DLY_US. This will
override the audio receiver presentation delay as long as it is in range of
the max and min supported by the audio receivers. If it is outside this range,
then it will revert to the closest supported value.
endchoice
config BT_AUDIO_EP_PRINT
bool "Print discovered endpoint capabilities"
default n
help
Print the supported capabilities of an endpoint when it is discovered.
config CODEC_CAP_COUNT_MAX
int "Max storage of codec capabilities"
default 5
help
Max number of codec capabilities to store per stream.
config BT_AUDIO_PREFERRED_MIN_PRES_DLY_US
int "The preferred minimum presentation delay"
range AUDIO_MIN_PRES_DLY_US AUDIO_MAX_PRES_DLY_US
default AUDIO_MIN_PRES_DLY_US
help
The preferred minimum presentation delay in microseconds. This can not
be less than the absolute minimum presentation delay.
config BT_AUDIO_PREFERRED_MAX_PRES_DLY_US
int "The preferred maximum presentation delay"
range BT_AUDIO_PREFERRED_MIN_PRES_DLY_US AUDIO_MAX_PRES_DLY_US
default 40000
help
The preferred maximum presentation delay in microseconds. This can not
be less than the absolute maximum presentation delay.
config BT_AUDIO_BITRATE_UNICAST_SINK
int "ISO stream bitrate"
depends on TRANSPORT_CIS
default 64000 if BT_BAP_UNICAST_CONFIGURABLE && STREAM_BIDIRECTIONAL
default 96000 if BT_BAP_UNICAST_CONFIGURABLE
default 32000 if BT_BAP_UNICAST_16_2_1
default 48000 if BT_BAP_UNICAST_24_2_1
help
Bitrate for the unicast sink ISO stream.
config BT_AUDIO_BITRATE_UNICAST_SRC
int "ISO stream bitrate"
depends on TRANSPORT_CIS
default 32000 if BT_BAP_UNICAST_16_2_1
default 48000 if BT_BAP_UNICAST_24_2_1
default 64000
help
Bitrate for the unicast source ISO stream.
config BT_SET_IDENTITY_RESOLVING_KEY_DEFAULT
string
default "NRF5340_TWS_DEMO"
help
Default string to configure the Set Identify Resolving Key (SIRK), must
be changed before production uniquely for each coordinated set.
config BT_SET_IDENTITY_RESOLVING_KEY
string "String used to configure the SIRK"
depends on TRANSPORT_CIS && BT_BAP_UNICAST_SERVER
default BT_SET_IDENTITY_RESOLVING_KEY_DEFAULT
help
Defines a string to configure the Set Identify Resolving Key (SIRK), must
be changed before production uniquely for each coordinated set. The SIRK
must be 16 characters (16 bytes).
#----------------------------------------------------------------------------#
menu "Log levels"
module = UNICAST_SERVER
module-str = unicast_server
source "subsys/logging/Kconfig.template.log_config"
module = UNICAST_CLIENT
module-str = unicast_client
source "subsys/logging/Kconfig.template.log_config"
endmenu # Log levels
endmenu # Unicast

View File

@@ -0,0 +1,57 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
config BT_GATT_CLIENT
default y
config BT_BONDABLE
default y
config BT_PRIVACY
default y
config BT_FILTER_ACCEPT_LIST
default y
config BT_SMP
default y
config BT_GAP_AUTO_UPDATE_CONN_PARAMS
default n
config BT_AUTO_PHY_UPDATE
default n
config BT_AUTO_DATA_LEN_UPDATE
default n
config BT_L2CAP_TX_BUF_COUNT
default 12
config BT_BUF_ACL_RX_SIZE
default 502 if (AUDIO_DFU > 0)
default 259
config BT_BUF_ACL_TX_COUNT
default 12
config SETTINGS
default y
config BT_SETTINGS
default y
config FLASH
default y
config FLASH_MAP
default y
config NVS
default y
config NVS_LOG_LEVEL
default 2

Some files were not shown because too many files have changed in this diff Show More