commit 310abbc8c3a6fe10e2faa62695e676403409ca29 Author: pstruebi Date: Tue Nov 26 17:51:52 2024 +0100 Initial commit for nrf_auraconfig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..635a99b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# editors +*.swp +*~ + +# build +/build*/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..524571c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(nrf_auraconfig) + +# Include application events and configuration headers +zephyr_library_include_directories(app PRIVATE + ${ZEPHYR_NRF_MODULE_DIR}/samples/bluetooth/nrf_auraconfig/include + ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/include + ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/bluetooth + ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/modules + ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/utils/macros +) + +add_subdirectory(${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/bluetooth bluetooth_build) + +target_sources(app PRIVATE ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/modules/audio_sync_timer.c) +target_sources(app PRIVATE ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/modules/led.c) + +target_sources_ifdef(CONFIG_NRF5340_AUDIO_SD_CARD_MODULE app PRIVATE ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/modules/sd_card.c) +target_sources_ifdef(CONFIG_NRF5340_AUDIO_SD_CARD_LC3_FILE app PRIVATE ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/modules/lc3_file.c) +target_sources_ifdef(CONFIG_NRF5340_AUDIO_SD_CARD_LC3_STREAMER app PRIVATE ${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/modules/lc3_streamer.c) + +FILE(GLOB app_sources src/*.c) + +target_sources(app PRIVATE ${app_sources}) diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..20d79d1 --- /dev/null +++ b/Kconfig @@ -0,0 +1,36 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +rsource "${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/audio/Kconfig" +rsource "${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/bluetooth/Kconfig" +rsource "${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/modules/Kconfig" +rsource "${ZEPHYR_NRF_MODULE_DIR}/applications/nrf5340_audio/src/utils/Kconfig" + +#----------------------------------------------------------------------------# + +config TRANSPORT_BIS + bool "Use BIS (Broadcast Isochronous Stream)" + +config NRF_AURACONFIG + bool "nRF Auraconfig" + depends on TRANSPORT_BIS + select EXPERIMENTAL + default y + +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 + +source "Kconfig.zephyr" diff --git a/Kconfig.sysbuild b/Kconfig.sysbuild new file mode 100644 index 0000000..ee679c1 --- /dev/null +++ b/Kconfig.sysbuild @@ -0,0 +1,10 @@ +# +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config NRF_DEFAULT_IPC_RADIO + default y + +source "${ZEPHYR_BASE}/share/sysbuild/Kconfig" diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..930fd5b --- /dev/null +++ b/README.rst @@ -0,0 +1,816 @@ +.. _nrf_auraconfig: + +nRF Auraconfig +############## + +.. contents:: + :local: + :depth: 2 + +The nRF Auraconfig sample implements the :ref:`BIS gateway mode ` and may act as an `Auracastâ„¢`_ broadcaster if you are using a preset compatible with Auracast. +The sample features a shell interface that allows you to configure the broadcast source in many different ways. + +In the BIS gateway mode, transmitting audio from the broadcast source happens using Broadcast Isochronous Stream (BIS) and Broadcast Isochronous Group (BIG). + +.. note:: + This sample is meant to be used with maximum two BIG with four BIS streams each. + +.. _nrf_auraconfig_requirements: + +Requirements +************ + +The sample supports only and exclusively the following development kits: + +.. table-from-rows:: /includes/sample_board_rows.txt + :header: heading + :rows: nrf5340_audio_dk_nrf5340 + +.. note:: + The sample supports PCA10121 revisions 1.0.0 or above. + The sample is also compatible with the following pre-launch revisions: + + * Revisions 0.8.0 and above. + +.. _nrf_auraconfig_ui: + +Shell commands list +******************* + +The nRF Auraconfig uses a shell interface for all user interactions. +This section lists the supported commands. + +Description convention +====================== + +The command argument description uses the following convention: + +* Angle brackets mean that an argument is mandatory: + + .. parsed-literal:: + :class: highlight + + nac <*arg*> + +* Square brackets mean that an argument is optional: + + .. parsed-literal:: + :class: highlight + + nac [*arg*] + +---- + +start +===== + +Start the broadcaster with the current configuration. +If no configuration is set, the broadcaster will not start. + +To view the current configuration, use the ``show`` command. +The optional argument sets the index of the BIG to start. + +Usage: + +.. code-block:: console + + nac start [BIG_index] + +Examples: + +* The following commands starts any BIG that is configured: + + .. code-block:: console + + nac start + +* The following command starts only the BIG 0: + + .. code-block:: console + + nac start 0 + +---- + +stop +==== + +Stop the broadcaster. +The optional argument sets the index of the BIG to stop. + +Usage: + +.. code-block:: console + + nac stop [BIG_index] + +Examples: + +* The following commands stops any BIG that is running: + + .. code-block:: console + + nac stop + +* The following command stops only the BIG 0: + + .. code-block:: console + + nac stop 0 + +---- + +show +==== + +Shows the configuration for the different BIGs that are currently configured. + +Usage: + +.. code-block:: console + + nac show + +Example output: + +.. code-block:: console + + BIG 0: + Streaming: false + Advertising name: Lecture hall + Broadcast name: Lecture + Packing: interleaved + Encryption: false + Broadcast code: + Subgroup 0: + Preset: 24_2_1 + PHY: 2M + Framing: unframed + RTN: 2 + SDU size: 60 + Max Transport Latency: 10 ms + Frame Interval: 10000 us + Presentation Delay: 40000 us + Sampling rate: 24000 Hz + Bitrate: 48000 bps + Frame duration: 10000 us + Octets per frame: 60 + Language: eng + Context(s): + Live + Program info: Mathematics 101 + Immediate rendering flag: set + Number of BIS: 1 + Location: + BIS 0: Mono Audio + +---- + +file list +========= + +Lists the files and directories in the given directory on the SD card. +If no directory is given, contents of the root directory is listed. + +Usage: + +.. code-block:: console + + nac file list [directory] + +Example output: + +.. code-block:: console + + nac file list + + [DIR ] 16000hz + [DIR ] 24000hz + [DIR ] 32000hz + [FILE] left-channel-announcement.wav + [FILE] right-channel-announcement.wav + +---- + +file select +=========== + +Selects a file from the SD card to be used as the audio source for the given stream. +The file must be in the LC3 format, and one file may be used for multiple streams at the same time. + +Usage: + +.. code-block:: console + + nac file select + +Example: + + .. code-block:: console + + nac file select 16000hz/24_kbps/left-channel-announcement_16kHz_left_24kbps.lc3 1 2 0 + +This command selects the file :file:`16000hz/24_kbps/left-channel-announcement_16kHz_left_24kbps.lc3` for the BIS 0 in the subgroup 2 in the BIG 1. + +---- + +packing +======= + +Set the type of packing for the BIS streams, either sequential or interleaved. + +Usage: + +.. code-block:: console + + nac packing + +Example: + +.. code-block:: console + + nac packing int 0 + +This command sets the packing for the BIG 0 to interleaved. + +---- + +preset +====== + +Set the BAP preset for a BIG or subgroup. +The presets are defined in the `Basic Audio Profile specification`_. +The supported presets can be listed with the ``nac preset print`` command. + +Usage: + +.. code-block:: console + + nac preset [subgroup index] + +Examples: + +* The following command sets the preset for the BIG 0 to ``24_2_1``: + + .. code-block:: console + + nac preset 24_2_1 0 + +* The following command sets the preset for the subgroup 0 in the BIG 0 to ``24_2_1``: + + .. code-block:: console + + nac preset 24_2_1 0 0 + +---- + +lang +==== + +Set the language metadata for a subgroup. + +Usage: + +.. code-block:: console + + nac lang + +The ``language`` argument is a three-letter `ISO 639-2 code`_. + +Example: + +.. code-block:: console + + nac lang eng 0 0 + +This command sets the language for the subgroup 0 in the BIG 0 to English. + +---- + +immediate +========= + +Set the immediate rendering flag for a subgroup. + +Usage: + +.. code-block:: console + + nac immediate <0/1> + +Example: + +.. code-block:: console + + nac immediate 1 0 0 + +This command sets the immediate rendering flag for the subgroup 0 in the BIG 0 to ``true``. + +---- + +num_subgroups +============= + +Set the number of subgroups for a BIG. +The maximum number of subgroups for each BIG is set by :kconfig:option:`CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT`. + +Usage: + +.. code-block:: console + + nac num_subgroups + +Example: + +.. code-block:: console + + nac num_subgroups 2 0 + +This command sets the number of subgroups for the BIG 0 to 2. + +---- + +num_bises +========= + +Set the number of BISes for a given subgroup. +The maximum number of BISes for each subgroup is set by :kconfig:option:`CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT`. + +Usage: + +.. code-block:: console + + nac num_bises + +Example: + +.. code-block:: console + + nac num_bises 2 0 0 + +This command sets the number of BISes for the subgroup 0 in the BIG 0 to 2. + +---- + +context +======= + +Sets the context metadata for a subgroup. +The supported contexts can be listed with the ``nac context print`` command. + +Usage: + +.. code-block:: console + + nac context + +Example: + +.. code-block:: console + + nac context Media 0 0 + +This command sets the context for the subgroup 0 in the BIG 0 to Media. + +---- + +location +======== + +Set the location metadata for a BIS. +The supported locations can be listed with the ``nac location print`` command. + +Usage: + +.. code-block:: console + + nac location + +Example: + +.. code-block:: console + + nac location fl 0 0 0 + +This command sets the location for the BIS 0 in the subgroup 0 in the BIG 0 to Front Left. + +---- + +broadcast_name +============== + +Set the broadcast name metadata for a BIG. + +Usage: + +.. code-block:: console + + nac broadcast_name + +Examples: + +* The following command sets the broadcast name for the BIG 0 to ``Lecture``: + + .. code-block:: console + + nac broadcast_name Lecture 0 + +* The following command uses quotation marks for a longer name with spaces: + + .. code-block:: console + + nac broadcast_name "Lecture hall" 0 + +---- + +encrypt +======= + +Set the broadcast code by enabling or disabling encryption for a given BIG. +The broadcast code is a 16-character string that is used to encrypt the broadcast, but shorter codes are also possible. + +Usage: + +.. code-block:: console + + nac encrypt <0/1> [broadcast_code] + +Examples: + +* The following command enables encryption for the BIG 0 with the broadcast code "Auratest": + + .. code-block:: console + + nac encrypt 1 0 Auratest + +* The following command disables encryption for the BIG 0 after setting it: + + .. code-block:: console + + nac encrypt 0 0 + +---- + +usecase +======= + +Set a pre-defined use case. +A use case is a set of configurations that are commonly used together. +The command typically sets the number of subgroups, the number of BISes, the context, the location, and potentially metadata. + +All pre-defined use cases can be listed with the ``nac usecase print`` command. + +Usage: + +.. code-block:: console + + nac usecase + +The ``use_case`` argument can either be an index or the name of a use case. + +Example: + +.. code-block:: console + + nac usecase 0 + +This command sets a unique configuration for the given use case and then calls ``show`` to display the configuration. + +---- + +clear +===== + +Clear any previous configuration. + +Usage: + +.. code-block:: console + + nac clear + +---- + +adv_name +======== + +Set the advertising name metadata for a BIG. + +.. note:: + Make sure each BIG has a unique advertising name. + +Usage: + +.. code-block:: console + + nac adv_name + +The maximum length of the advertising name is given by :kconfig:option:`CONFIG_BT_DEVICE_NAME_MAX`. + +Example: + +.. code-block:: console + + nac adv_name "Lecture hall" 0 + +This command sets the advertising name for the BIG 0 to "Lecture hall". + +.. note:: + The name must be enclosed in quotation marks if it contains spaces. + +---- + +program_info +============ + +Set the program info metadata for a subgroup. + +Usage: + +.. code-block:: console + + nac program_info + +Example: + +.. code-block:: console + + nac program_info "Mathematics 101" 0 0 + +This command sets the program info for the subgroup 0 in the BIG 0 to "Mathematics 101". + +---- + +phy +=== + +Set the PHY for a BIG. +The supported PHY values are: + +* ``1`` - 1M +* ``2`` - 2M +* ``4`` - Coded + +Usage: + +.. code-block:: console + + nac phy + +Example: + +.. code-block:: console + + nac phy 2 0 0 + +This command sets the PHY for the subgroup 0 in the BIG 0 to 2M. + +---- + +framing +======= + +Set the framing for a subgroup. + +Usage: + +.. code-block:: console + + nac framing + +Example: + +.. code-block:: console + + nac framing unframed 0 0 + +This command sets the framing for the subgroup 0 in the BIG 0 to ``unframed``. + +---- + +rtn +=== + +Set the number of retransmits for a subgroup. + +Usage: + +.. code-block:: console + + nac rtn + +Example: + +.. code-block:: console + + nac rtn 2 0 0 + +This command sets the number of retransmits for the subgroup 0 in the BIG 0 to 2. + +---- + +sdu +=== + +Set the Service Data Unit (SDU) size in octets for a subgroup. + +.. note:: + + This command does not change the bitrate, only the size of the SDU. + +Usage: + +.. code-block:: console + + nac sdu + +Example: + +.. code-block:: console + + nac sdu 60 0 0 + +This command sets the SDU size for the subgroup 0 in the BIG 0 to 60 octets. + +---- + +mtl +=== + +Set the maximum transport latency for a subgroup, in milliseconds. + +Usage: + +.. code-block:: console + + nac mtl + +Example: + +.. code-block:: console + + nac mtl 10 0 0 + +This command sets the maximum transport latency for the subgroup 0 in the BIG 0 to 10 ms. + +---- + +frame_interval +============== + +Set the frame interval for a subgroup, in microseconds. +The command supports the following values: + +* 7500 +* 10000 + +Usage: + +.. code-block:: console + + nac frame_interval + +Example: + +.. code-block:: console + + nac frame_interval 10000 0 0 + +This command sets the frame interval for the subgroup 0 in the BIG 0 to 10 ms (10000 us). + +---- + +pd +== + +Set the presentation delay for a subgroup, in microseconds. + +Usage: + +.. code-block:: console + + nac pd + +Example: + +.. code-block:: console + + nac pd 40000 0 0 + +This command sets the presentation delay for the subgroup 0 in BIG0 to 40 ms (40000 us). + +---- + +broadcast_id fixed +================== + +Set a fixed broadcast ID for a BIG. +The broadcast ID is used to identify the broadcast. +Its value is three octets long. + +Usage: + +.. code-block:: console + + nac broadcast_id fixed + +Examples: + +.. code-block:: console + + nac broadcast_id fixed 0 0xAA1234 + +This command sets a fixed broadcast ID for the BIG 0 to ``0xAA1234``. +This value will remain if the broadcast is stopped and started again. + +---- + +broadcast_id random +=================== + +Set a random broadcast ID for a BIG. +The broadcast ID is used to identify the broadcast. +The broadcast ID will be generated anew every time the broadcaster is started. + +Usage: + +.. code-block:: console + + nac broadcast_id random + +Examples: + +.. code-block:: console + + nac broadcast_id random 0 + +This command sets a random broadcast ID for the BIG 0 each time it is started. + +---- + +.. _nrf_auraconfig_configuration: + +Configuration +************* + +|config| + +The sample is pre-configured with a generous default memory allocation, suitable for a wide range of use cases. +You can modify these default settings in the :file:`prj.conf` file. +Using aggressive configurations can reduce air time availability for all streams, depending on the combination of options selected (like high bitrates, increased re-transmits, specific PHY settings). + +.. _nrf_auraconfig_configuration_sd: + +SD card setup +************* + +This sample can support pre-encoded LC3 data stored as LC3 files on an SD card. +You can use the `nRF Auracast configuration files`_ provided by Nordic Semiconductor for populating the SD-card. + +If you are not using an SD card, the system defaults to sending dummy data. +The purpose of the dummy data is to test that the broadcast source has been correctly configured. + +Make sure you format the SD card with a FAT file system. + +.. _nrf_auraconfig_building: + +Building and running +******************** + +.. |sample path| replace:: :file:`samples/bluetooth/nrf_auraconfig` + +.. include:: /includes/build_and_run.txt + +The nRF5340 Audio DK comes pre-programmed with basic firmware that indicates if the kit is functional. +See :ref:`nrf53_audio_app_dk_testing_out_of_the_box` for more information. + +.. _nrf_auraconfig_testing: + +Testing +******* + +In this testing procedure, the development kit is programmed with the nRF Auraconfig sample. + +To test the nRF Auraconfig sample, complete the following steps: + +1. If you are using an :ref:`SD card loaded with the pre-encoded LC3 data `, insert it into your development kit. +#. Turn on the development kit. +#. Set up the serial connection with the development kit. +#. Configure a BIG using use case 1: + + .. code-block:: console + + nac usecase 1 + +#. Start the broadcaster: + + .. code-block:: console + + nac start + +You can now send other shell commands, as listed in the :ref:`nrf_auraconfig_ui`. + +Dependencies +************ + +For the list of dependencies, check the application's source files. diff --git a/include/nrf_auraconfig.h b/include/nrf_auraconfig.h new file mode 100644 index 0000000..abbfdc0 --- /dev/null +++ b/include/nrf_auraconfig.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef _NRF_AURACONFIG_H_ +#define _NRF_AURACONFIG_H_ + +#include +#include + +#define PRESET_NAME_MAX 8 +#define LANGUAGE_LEN 3 + +/* The location and context type will be set by further down */ +/* Low latency settings */ +static struct bt_bap_lc3_preset lc3_preset_8_1_1 = + BT_BAP_LC3_BROADCAST_PRESET_8_1_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_8_2_1 = + BT_BAP_LC3_BROADCAST_PRESET_8_2_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_16_1_1 = + BT_BAP_LC3_BROADCAST_PRESET_16_1_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_16_2_1 = + BT_BAP_LC3_BROADCAST_PRESET_16_2_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_24_1_1 = + BT_BAP_LC3_BROADCAST_PRESET_24_1_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_24_2_1 = + BT_BAP_LC3_BROADCAST_PRESET_24_2_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_32_1_1 = + BT_BAP_LC3_BROADCAST_PRESET_32_1_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_32_2_1 = + BT_BAP_LC3_BROADCAST_PRESET_32_2_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_1_1 = + BT_BAP_LC3_BROADCAST_PRESET_48_1_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_2_1 = + BT_BAP_LC3_BROADCAST_PRESET_48_2_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_3_1 = + BT_BAP_LC3_BROADCAST_PRESET_48_3_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_4_1 = + BT_BAP_LC3_BROADCAST_PRESET_48_4_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_5_1 = + BT_BAP_LC3_BROADCAST_PRESET_48_5_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_6_1 = + BT_BAP_LC3_BROADCAST_PRESET_48_6_1(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +/* High reliability settings */ +static struct bt_bap_lc3_preset lc3_preset_8_1_2 = + BT_BAP_LC3_BROADCAST_PRESET_8_1_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_8_2_2 = + BT_BAP_LC3_BROADCAST_PRESET_8_2_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_16_1_2 = + BT_BAP_LC3_BROADCAST_PRESET_16_1_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_16_2_2 = + BT_BAP_LC3_BROADCAST_PRESET_16_2_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_24_1_2 = + BT_BAP_LC3_BROADCAST_PRESET_24_1_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_24_2_2 = + BT_BAP_LC3_BROADCAST_PRESET_24_2_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_32_1_2 = + BT_BAP_LC3_BROADCAST_PRESET_32_1_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_32_2_2 = + BT_BAP_LC3_BROADCAST_PRESET_32_2_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_1_2 = + BT_BAP_LC3_BROADCAST_PRESET_48_1_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_2_2 = + BT_BAP_LC3_BROADCAST_PRESET_48_2_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_3_2 = + BT_BAP_LC3_BROADCAST_PRESET_48_3_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_4_2 = + BT_BAP_LC3_BROADCAST_PRESET_48_4_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_5_2 = + BT_BAP_LC3_BROADCAST_PRESET_48_5_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); +static struct bt_bap_lc3_preset lc3_preset_48_6_2 = + BT_BAP_LC3_BROADCAST_PRESET_48_6_2(BT_AUDIO_LOCATION_MONO_AUDIO, BT_AUDIO_CONTEXT_TYPE_ANY); + +struct bap_preset { + struct bt_bap_lc3_preset *preset; + char name[PRESET_NAME_MAX]; +}; + +static struct bap_preset bap_presets[] = { + {.preset = &lc3_preset_8_1_1, .name = "8_1_1"}, + {.preset = &lc3_preset_8_2_1, .name = "8_2_1"}, + {.preset = &lc3_preset_16_1_1, .name = "16_1_1"}, + {.preset = &lc3_preset_16_2_1, .name = "16_2_1"}, + {.preset = &lc3_preset_24_1_1, .name = "24_1_1"}, + {.preset = &lc3_preset_24_2_1, .name = "24_2_1"}, + {.preset = &lc3_preset_32_1_1, .name = "32_1_1"}, + {.preset = &lc3_preset_32_2_1, .name = "32_2_1"}, + {.preset = &lc3_preset_48_1_1, .name = "48_1_1"}, + {.preset = &lc3_preset_48_2_1, .name = "48_2_1"}, + {.preset = &lc3_preset_48_3_1, .name = "48_3_1"}, + {.preset = &lc3_preset_48_4_1, .name = "48_4_1"}, + {.preset = &lc3_preset_48_5_1, .name = "48_5_1"}, + {.preset = &lc3_preset_48_6_1, .name = "48_6_1"}, + {.preset = &lc3_preset_8_1_2, .name = "8_1_2"}, + {.preset = &lc3_preset_8_2_2, .name = "8_2_2"}, + {.preset = &lc3_preset_16_1_2, .name = "16_1_2"}, + {.preset = &lc3_preset_16_2_2, .name = "16_2_2"}, + {.preset = &lc3_preset_24_1_2, .name = "24_1_2"}, + {.preset = &lc3_preset_24_2_2, .name = "24_2_2"}, + {.preset = &lc3_preset_32_1_2, .name = "32_1_2"}, + {.preset = &lc3_preset_32_2_2, .name = "32_2_2"}, + {.preset = &lc3_preset_48_1_2, .name = "48_1_2"}, + {.preset = &lc3_preset_48_2_2, .name = "48_2_2"}, + {.preset = &lc3_preset_48_3_2, .name = "48_3_2"}, + {.preset = &lc3_preset_48_4_2, .name = "48_4_2"}, + {.preset = &lc3_preset_48_5_2, .name = "48_5_2"}, + {.preset = &lc3_preset_48_6_2, .name = "48_6_2"}, +}; + +#define LOCATION_NAME_LEN_MAX 4 +struct audio_location { + enum bt_audio_location location; + char name[LOCATION_NAME_LEN_MAX + 1]; +}; + +/* Note: If there is any change to the specification of audio + * locations then this structure must be checked for conformance. + */ +static struct audio_location locations[] = { + {.location = BT_AUDIO_LOCATION_MONO_AUDIO, .name = "MA"}, + {.location = BT_AUDIO_LOCATION_FRONT_LEFT, .name = "FL"}, + {.location = BT_AUDIO_LOCATION_FRONT_RIGHT, .name = "FR"}, + {.location = BT_AUDIO_LOCATION_FRONT_CENTER, .name = "FC"}, + {.location = BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_1, .name = "LFE1"}, + {.location = BT_AUDIO_LOCATION_BACK_LEFT, .name = "BL"}, + {.location = BT_AUDIO_LOCATION_BACK_RIGHT, .name = "BR"}, + {.location = BT_AUDIO_LOCATION_FRONT_LEFT_OF_CENTER, .name = "FLC"}, + {.location = BT_AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER, .name = "FRC"}, + {.location = BT_AUDIO_LOCATION_BACK_CENTER, .name = "BC"}, + {.location = BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_2, .name = "LFE2"}, + {.location = BT_AUDIO_LOCATION_SIDE_LEFT, .name = "SL"}, + {.location = BT_AUDIO_LOCATION_SIDE_RIGHT, .name = "SR"}, + {.location = BT_AUDIO_LOCATION_TOP_FRONT_LEFT, .name = "TFL"}, + {.location = BT_AUDIO_LOCATION_TOP_FRONT_RIGHT, .name = "TFR"}, + {.location = BT_AUDIO_LOCATION_TOP_FRONT_CENTER, .name = "TFC"}, + {.location = BT_AUDIO_LOCATION_TOP_CENTER, .name = "TC"}, + {.location = BT_AUDIO_LOCATION_TOP_BACK_LEFT, .name = "TBL"}, + {.location = BT_AUDIO_LOCATION_TOP_BACK_RIGHT, .name = "TBR"}, + {.location = BT_AUDIO_LOCATION_TOP_SIDE_LEFT, .name = "TSL"}, + {.location = BT_AUDIO_LOCATION_TOP_SIDE_RIGHT, .name = "TSR"}, + {.location = BT_AUDIO_LOCATION_TOP_BACK_CENTER, .name = "TBC"}, + {.location = BT_AUDIO_LOCATION_BOTTOM_FRONT_CENTER, .name = "BFC"}, + {.location = BT_AUDIO_LOCATION_BOTTOM_FRONT_LEFT, .name = "BFL"}, + {.location = BT_AUDIO_LOCATION_BOTTOM_FRONT_RIGHT, .name = "BFR"}, + {.location = BT_AUDIO_LOCATION_FRONT_LEFT_WIDE, .name = "FLW"}, + {.location = BT_AUDIO_LOCATION_FRONT_RIGHT_WIDE, .name = "FRW"}, + {.location = BT_AUDIO_LOCATION_LEFT_SURROUND, .name = "LS"}, + {.location = BT_AUDIO_LOCATION_RIGHT_SURROUND, .name = "RS"}, +}; + +enum usecase_type { + LECTURE, + SILENT_TV_1, + SILENT_TV_2, + MULTI_LANGUAGE, + PERSONAL_SHARING, + PERSONAL_MULTI_LANGUAGE, +}; + +struct usecase_info { + enum usecase_type use_case; + char name[40]; +}; + +static struct usecase_info pre_defined_use_cases[] = { + {.use_case = LECTURE, .name = "Lecture"}, + {.use_case = SILENT_TV_1, .name = "Silent TV 1"}, + {.use_case = SILENT_TV_2, .name = "Silent TV 2"}, + {.use_case = MULTI_LANGUAGE, .name = "Multi-language"}, + {.use_case = PERSONAL_SHARING, .name = "Personal sharing"}, + {.use_case = PERSONAL_MULTI_LANGUAGE, .name = "Personal multi-language"}, +}; + +#endif /* _NRF_AURACONFIG_H_ */ diff --git a/prj.conf b/prj.conf new file mode 100644 index 0000000..77e3876 --- /dev/null +++ b/prj.conf @@ -0,0 +1,156 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + + +# General +CONFIG_REBOOT=y +CONFIG_DEBUG=y +CONFIG_DEBUG_INFO=y +CONFIG_ASSERT=y +CONFIG_STACK_USAGE=y +CONFIG_THREAD_RUNTIME_STATS=y +CONFIG_MAIN_THREAD_PRIORITY=10 +CONFIG_STACK_SENTINEL=y +CONFIG_INIT_STACKS=y +CONFIG_MAIN_STACK_SIZE=12000 +CONFIG_THREAD_NAME=y +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=1200 + +# 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_BUFFER_SIZE=4096 +CONFIG_USE_SEGGER_RTT=n +CONFIG_LOG_BACKEND_RTT=n + +CONFIG_SOC_NRF53_CPUNET_ENABLE=y + +CONFIG_ZBUS=y +CONFIG_ZBUS_RUNTIME_OBSERVERS=y +CONFIG_ZBUS_MSG_SUBSCRIBER=y + +CONFIG_REGULATOR=y +CONFIG_CONTIN_ARRAY=y +CONFIG_DATA_FIFO=y + +# Enable NRFX_CLOCK for ACLK control +CONFIG_NRFX_CLOCK=y + +CONFIG_NEWLIB_LIBC=y + +# 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=y +CONFIG_FPU_SHARING=y + +# Enable SDHC interface +CONFIG_DISK_DRIVERS=y +CONFIG_DISK_DRIVER_SDMMC=y + +# Allocate buffer on RAM for transferring chunck of data +# from Flash to SPI +CONFIG_SPI_NRFX_RAM_BUFFER_SIZE=8 + +# CONFIG_the file system +CONFIG_FILE_SYSTEM=y +CONFIG_FAT_FILESYSTEM_ELM=y +CONFIG_FS_FATFS_LFN=y +CONFIG_FS_FATFS_LFN_MODE_STACK=y + +# exFAT enabled to support longer file names and higher transfer speed +CONFIG_FS_FATFS_EXFAT=y +# Set the maximum file name length to 255 +CONFIG_FS_FATFS_MAX_LFN=255 + +# Enable SPI interface +CONFIG_SPI=y + +# Enable ADC for board version readback +CONFIG_ADC=y + +CONFIG_WATCHDOG=y +CONFIG_TASK_WDT=y + + +# Use this for debugging thread usage +#CONFIG_LOG_THREAD_ID_PREFIX=y + +# Console related defines +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y + +# Shell related defines +CONFIG_SHELL=y +CONFIG_KERNEL_SHELL=y +CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_SHELL_VT100_COMMANDS=y +CONFIG_SHELL_VT100_COLORS=y +CONFIG_SHELL_STACK_SIZE=8096 +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 + +## ISO related configs ## +CONFIG_BT=y +CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_CAP_INITIATOR=y + +CONFIG_BT_ISO_BROADCASTER=y +CONFIG_BT_ISO_MAX_BIG=2 +CONFIG_BT_ISO_MAX_CHAN=8 +# Should be twice that of BT_ISO_MAX_CHAN +CONFIG_BT_ISO_TX_BUF_COUNT=16 + +CONFIG_BT_BAP_BROADCAST_SOURCE=y +CONFIG_BT_BAP_BROADCAST_SRC_COUNT=2 +CONFIG_BT_EXT_ADV_MAX_ADV_SET=2 +CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT=4 +CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT=8 + +# Broadcasting Device - 0x0885 +CONFIG_BT_DEVICE_APPEARANCE=2181 + +CONFIG_TRANSPORT_BIS=y +CONFIG_BT_AUDIO_BROADCAST_CONFIGURABLE=y +CONFIG_BT_AUDIO_BROADCAST_ZBUS_EVT_STREAM_SENT=y + +CONFIG_LE_AUDIO_MSG_SUB_THREAD_PRIO=3 + +CONFIG_SW_CODEC_LC3=n + +CONFIG_NRF5340_AUDIO_SD_CARD_MODULE=y +CONFIG_NRF5340_AUDIO_SD_CARD_LC3_FILE=y +CONFIG_NRF5340_AUDIO_SD_CARD_LC3_STREAMER=y + +CONFIG_SD_CARD_LC3_STREAMER_STACK_SIZE=8000 + +CONFIG_MODULE_SD_CARD_LOG_LEVEL_WRN=y diff --git a/sample.yaml b/sample.yaml new file mode 100644 index 0000000..61f141d --- /dev/null +++ b/sample.yaml @@ -0,0 +1,14 @@ +sample: + name: Bluetooth Low Energy nRF Auraconfig + description: Bluetooth LE shell configurable Auracast tool +common: + integration_platforms: + - nrf5340_audio_dk/nrf5340/cpuapp + platform_allow: nrf5340_audio_dk/nrf5340/cpuapp + platform_exclude: nrf5340_audio_dk/nrf5340/cpuapp_ns + sysbuild: true + build_only: true + tags: ci_build sysbuild bluetooth +tests: + sample.bluetooth.nrf_auraconfig.build: + extra_args: [] diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..18b3762 --- /dev/null +++ b/src/main.c @@ -0,0 +1,2651 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nrf_auraconfig.h" +#include "broadcast_source.h" +#include "zbus_common.h" +#include "macros_common.h" +#include "bt_mgmt.h" +#include "sd_card.h" +#include "lc3_streamer.h" +#include "led.h" + +#include +LOG_MODULE_REGISTER(main, CONFIG_MAIN_LOG_LEVEL); + +ZBUS_CHAN_DECLARE(bt_mgmt_chan); +ZBUS_CHAN_DECLARE(sdu_ref_chan); +ZBUS_CHAN_DECLARE(le_audio_chan); +ZBUS_MSG_SUBSCRIBER_DEFINE(le_audio_evt_sub); + +/* SD card detection */ +static bool sd_card_present = true; + +static struct k_thread le_audio_msg_sub_thread_data; +static k_tid_t le_audio_msg_sub_thread_id; +K_THREAD_STACK_DEFINE(le_audio_msg_sub_thread_stack, CONFIG_LE_AUDIO_MSG_SUB_STACK_SIZE); + +struct bt_le_ext_adv *ext_adv; + +/* Buffer for the UUIDs. */ +#define EXT_ADV_UUID_BUF_SIZE (128) +NET_BUF_SIMPLE_DEFINE_STATIC(uuid_data0, EXT_ADV_UUID_BUF_SIZE); +NET_BUF_SIMPLE_DEFINE_STATIC(uuid_data1, EXT_ADV_UUID_BUF_SIZE); + +/* Buffer for periodic advertising BASE data. */ +#define BASE_DATA_BUF_SIZE (256) +NET_BUF_SIMPLE_DEFINE_STATIC(base_data0, BASE_DATA_BUF_SIZE); +NET_BUF_SIMPLE_DEFINE_STATIC(base_data1, BASE_DATA_BUF_SIZE); + +/* 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]; + +/* 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_data0, + .pba_metadata_vacant_cnt = BROADCAST_SOURCE_PBA_METADATA_VACANT, + .pba_buf = pba_data[0]}, + {.uuid_buf = &uuid_data1, + .pba_metadata_vacant_cnt = BROADCAST_SOURCE_PBA_METADATA_VACANT, + .pba_buf = pba_data[1]}}; + +/** + * @brief Broadcast source static periodic advertising data. + */ +static struct broadcast_source_per_adv_data per_adv_data[] = {{.base_buf = &base_data0}, + {.base_buf = &base_data1}}; + +static struct broadcast_source_big broadcast_param[CONFIG_BT_ISO_MAX_BIG]; +static struct subgroup_config subgroups[CONFIG_BT_ISO_MAX_BIG] + [CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT]; + +static enum bt_audio_location stream_location[CONFIG_BT_ISO_MAX_BIG] + [CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT] + [CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT] = { + {{BT_AUDIO_LOCATION_MONO_AUDIO}}}; + +#define LC3_STREAMER_INDEX_UNUSED 0xFF + +struct subgroup_lc3_stream_info { + size_t frame_size; + uint8_t lc3_streamer_idx[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT]; + bool frame_loaded[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT]; + uint8_t *frame_ptrs[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT]; +}; + +static struct subgroup_lc3_stream_info lc3_stream_infos[CONFIG_BT_ISO_MAX_BIG] + [CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT]; + +/* Find the most significant 1 in bits, bits will not be 0. */ +static int8_t context_msb_one(uint16_t bits) +{ + uint8_t pos = 0; + + while (bits >>= 1) { + pos++; + } + + return pos; +} + +static struct bt_bap_lc3_preset *preset_find(const char *name) +{ + for (size_t i = 0; i < ARRAY_SIZE(bap_presets); i++) { + if (strcmp(bap_presets[i].name, name) == 0) { + return bap_presets[i].preset; + } + } + + return NULL; +} + +static char *preset_name_find(struct bt_bap_lc3_preset *preset) +{ + for (size_t i = 0; i < ARRAY_SIZE(bap_presets); i++) { + if (bap_presets[i].preset == preset) { + return bap_presets[i].name; + } + } + + return "Custom"; +} + +/** + * @brief Check if a given string is a hexadecimal value. + * + * @param[in] str Pointer to the string to check. + * @param[out] len Pointer to the length of the string. + * + * @return True if the string is a hexadecimal value, false otherwise. + */ +static bool is_hex(const char *str, uint8_t *len) +{ + /* Check if the string is empty */ + if (*str == '\0') { + return false; + } + + *len = strlen(str); + + /* Skip 0x if present */ + if (*str == '0' && (*(str + 1) == 'x' || *(str + 1) == 'X')) { + str += 2; + *len -= 2; + } + + /* Iterate through each character of the string */ + for (uint8_t i = 0; i < *len; i++) { + /* Check if the character is not a hexadecimal digit */ + if (!isxdigit((int)*str)) { + return false; + } + + str++; + } + + /* All characters are hexadecimal digits */ + return true; +} + +static bool is_number(const char *str) +{ + /* Check if the string is empty */ + if (*str == '\0') { + return false; + } + + uint8_t len = strlen(str); + + /* Iterate through each character of the string */ + for (uint8_t i = 0; i < len; i++) { + /* Check if the character is not a digit */ + if (!isdigit((int)*str)) { + return false; + } + str++; + } + + /* All characters are digits */ + return true; +} + +static void subgroup_send(struct stream_index stream_idx) +{ + int ret; + static int prev_ret; + + uint8_t num_bis = subgroups[stream_idx.lvl1][stream_idx.lvl2].num_bises; + size_t frame_size = lc3_stream_infos[stream_idx.lvl1][stream_idx.lvl2].frame_size; + uint8_t frame_buffer[num_bis][frame_size]; + + for (int i = 0; i < num_bis; i++) { + uint8_t *frame_ptr = + lc3_stream_infos[stream_idx.lvl1][stream_idx.lvl2].frame_ptrs[i]; + + if (frame_ptr == NULL) { + memset(frame_buffer[i], 0, frame_size); + } else { + memcpy(frame_buffer[i], frame_ptr, frame_size); + } + } + + struct le_audio_encoded_audio enc_audio = { + .data = (uint8_t *)frame_buffer, .size = frame_size * num_bis, .num_ch = num_bis}; + + ret = broadcast_source_send(stream_idx.lvl1, stream_idx.lvl2, enc_audio); + + if (ret != 0 && ret != prev_ret) { + if (ret == -ECANCELED) { + LOG_WRN("Sending cancelled"); + } else { + LOG_WRN("broadcast_source_send returned: %d", ret); + } + } + + prev_ret = ret; +} + +static void stream_frame_dummy_get_and_send(struct stream_index stream_idx) +{ + int ret; + static int prev_ret; + static uint8_t frame_value; + + uint8_t num_bis = subgroups[stream_idx.lvl1][stream_idx.lvl2].num_bises; + size_t frame_size = subgroups[stream_idx.lvl1][stream_idx.lvl2].group_lc3_preset.qos.sdu; + uint8_t frame_buffer[num_bis][frame_size]; + + for (int i = 0; i < num_bis; i++) { + memset(frame_buffer[i], frame_value, frame_size); + } + frame_value++; + + struct le_audio_encoded_audio enc_audio = { + .data = (uint8_t *)frame_buffer, .size = frame_size * num_bis, .num_ch = num_bis}; + + ret = broadcast_source_send(stream_idx.lvl1, stream_idx.lvl2, enc_audio); + + if (ret != 0 && ret != prev_ret) { + if (ret == -ECANCELED) { + LOG_WRN("Sending cancelled"); + } else { + LOG_WRN("broadcast_source_send returned: %d", ret); + } + } + + prev_ret = ret; +} + +static void stream_frame_get_and_send(struct stream_index stream_idx) +{ + int ret; + + struct subgroup_lc3_stream_info *bis_info = + &(lc3_stream_infos[stream_idx.lvl1][stream_idx.lvl2]); + + uint8_t num_bis = subgroups[stream_idx.lvl1][stream_idx.lvl2].num_bises; + uint8_t stream_file_idx = bis_info->lc3_streamer_idx[stream_idx.lvl3]; + + if (stream_file_idx == LC3_STREAMER_INDEX_UNUSED) { + LOG_ERR("Stream index for stream big %d sub: %d bis: %d is unused", stream_idx.lvl1, + stream_idx.lvl2, stream_idx.lvl3); + return; + } + + ret = lc3_streamer_next_frame_get( + stream_file_idx, (const uint8_t **const)&(bis_info->frame_ptrs[stream_idx.lvl3])); + if (ret == -ENODATA) { + LOG_WRN("No more frames to read"); + ret = lc3_streamer_stream_close(stream_file_idx); + if (ret) { + LOG_ERR("Failed to close stream: %d", ret); + } + + bis_info->lc3_streamer_idx[stream_idx.lvl3] = LC3_STREAMER_INDEX_UNUSED; + + return; + } else if (ret == -ENOMSG) { + LOG_DBG("Frame from SD card not ready for stream %d, using zero packet", + stream_idx.lvl3); + bis_info->frame_ptrs[stream_idx.lvl3] = NULL; + } else if (ret) { + LOG_ERR("Failed to get next frame: %d", ret); + return; + } + + bis_info->frame_loaded[stream_idx.lvl3] = true; + + int loaded_count = 0; + + for (int i = 0; i < num_bis; i++) { + if (bis_info->frame_loaded[i]) { + ++loaded_count; + } + } + if (loaded_count == num_bis) { + subgroup_send(stream_idx); + + for (int i = 0; i < num_bis; i++) { + bis_info->frame_loaded[i] = false; + bis_info->frame_ptrs[i] = NULL; + } + } +} + +/** + * @brief Handle Bluetooth LE audio events. + */ +static void le_audio_msg_sub_thread(void) +{ + int ret; + const struct zbus_channel *chan; + + LOG_DBG("Zbus subscription thread started"); + + while (1) { + struct le_audio_msg msg; + + ret = zbus_sub_wait_msg(&le_audio_evt_sub, &chan, &msg, K_FOREVER); + ERR_CHK(ret); + + switch (msg.event) { + case LE_AUDIO_EVT_STREAM_SENT: + LOG_DBG("LE_AUDIO_EVT_STREAM_SENT for stream BIG %d sub: %d BIS: %d", + msg.idx.lvl1, msg.idx.lvl2, msg.idx.lvl3); + + if (sd_card_present) { + stream_frame_get_and_send(msg.idx); + } else { + stream_frame_dummy_get_and_send(msg.idx); + } + + break; + + case LE_AUDIO_EVT_STREAMING: + LOG_DBG("LE audio evt streaming for stream BIG %d sub: %d BIS: %d", + msg.idx.lvl1, msg.idx.lvl2, msg.idx.lvl3); + + if (sd_card_present) { + stream_frame_get_and_send(msg.idx); + } else { + stream_frame_dummy_get_and_send(msg.idx); + } + + break; + + case LE_AUDIO_EVT_NOT_STREAMING: + LOG_DBG("LE audio evt not_streaming"); + + 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; + + 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; + } + + 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: + 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(&bt_mgmt_chan, &bt_mgmt_evt_listen, ZBUS_ADD_OBS_TIMEOUT_MS); + if (ret) { + LOG_ERR("Failed to add bt_mgmt listener"); + 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; + } + + 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; + + 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; + } + + ret = broadcast_source_ext_adv_populate(big_index, broadcast_param[big_index].fixed_id, + broadcast_param[big_index].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; +} + +static void broadcast_create(uint8_t big_index) +{ + if (big_index >= CONFIG_BT_ISO_MAX_BIG) { + LOG_ERR("BIG index out of range"); + return; + } + + struct broadcast_source_big *brdcst_param = &broadcast_param[big_index]; + + for (size_t i = 0; i < ARRAY_SIZE(subgroups[big_index]); i++) { + if (subgroups[big_index][i].num_bises == 0) { + /* Default to 2 BISes */ + subgroups[big_index][i].num_bises = 2; + } + + if (subgroups[big_index][i].context == 0) { + subgroups[big_index][i].context = BT_AUDIO_CONTEXT_TYPE_MEDIA; + } + + subgroups[big_index][i].location = stream_location[big_index][i]; + } + + brdcst_param->subgroups = subgroups[big_index]; + + if (brdcst_param->broadcast_name[0] == '\0') { + /* Name not set, using default */ + if (sizeof(CONFIG_BT_AUDIO_BROADCAST_NAME) > sizeof(brdcst_param->broadcast_name)) { + LOG_ERR("Broadcast name too long"); + return; + } + + size_t brdcst_name_size = sizeof(CONFIG_BT_AUDIO_BROADCAST_NAME) - 1; + + memcpy(brdcst_param->broadcast_name, CONFIG_BT_AUDIO_BROADCAST_NAME, + brdcst_name_size); + } + + /* If no subgroups are defined, set to 1 */ + if (brdcst_param->num_subgroups == 0) { + brdcst_param->num_subgroups = 1; + } +} + +/** + * @brief Clear all broadcast configurations. + */ +static void nrf_auraconfig_clear(void) +{ + int ret; + + if (sd_card_present) { + ret = lc3_streamer_close_all_streams(); + if (ret) { + LOG_ERR("Failed to close all LC3 streams: %d", ret); + } + } + + for (size_t i = 0; i < ARRAY_SIZE(broadcast_param); i++) { + memset(&broadcast_param[i], 0, sizeof(broadcast_param[i])); + for (size_t j = 0; j < ARRAY_SIZE(subgroups[i]); j++) { + memset(&subgroups[i][j], 0, sizeof(subgroups[i][j])); + for (size_t k = 0; k < ARRAY_SIZE(stream_location[i][j]); k++) { + stream_location[i][j][k] = BT_AUDIO_LOCATION_MONO_AUDIO; + lc3_stream_infos[i][j].lc3_streamer_idx[k] = + LC3_STREAMER_INDEX_UNUSED; + } + } + } +} + +#define SD_FILECOUNT_MAX 420 +#define SD_PATHLEN_MAX 190 +static char sd_paths_and_files[SD_FILECOUNT_MAX][SD_PATHLEN_MAX]; +static uint32_t num_files_added; + +static int sd_card_toc_gen(void) +{ + /* Traverse SD tree */ + int ret; + + ret = sd_card_list_files_match(SD_FILECOUNT_MAX, SD_PATHLEN_MAX, sd_paths_and_files, NULL, + ".lc3"); + if (ret < 0) { + return ret; + } + + num_files_added = ret; + + LOG_INF("Number of *.lc3 files on SD card: %d", num_files_added); + + return 0; +} + +int main(void) +{ + int ret; + + LOG_DBG("Main started"); + + ret = nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK, NRF_CLOCK_HFCLK_DIV_1); + ret -= NRFX_ERROR_BASE_NUM; + if (ret) { + return ret; + } + + ret = led_init(); + ERR_CHK_MSG(ret, "Failed to initialize LED module"); + + ret = led_on(LED_APP_RGB, LED_COLOR_GREEN); + ERR_CHK(ret); + + ret = bt_mgmt_init(); + ERR_CHK(ret); + + ret = zbus_link_producers_observers(); + ERR_CHK_MSG(ret, "Failed to link zbus producers and observers"); + + ret = lc3_streamer_init(); + if (ret) { + LOG_WRN("Failed to initialize LC3 streamer. Transmitting fixed buffers"); + sd_card_present = false; + } + + 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 < CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT; k++) { + lc3_stream_infos[i][j].lc3_streamer_idx[k] = + LC3_STREAMER_INDEX_UNUSED; + } + } + } + + ret = zbus_subscribers_create(); + ERR_CHK_MSG(ret, "Failed to create zbus subscriber threads"); + + if (sd_card_present) { + ret = sd_card_toc_gen(); + ERR_CHK_MSG(ret, "Failed to generate SD card table"); + } + + return 0; +} + +static void context_print(const struct shell *shell) +{ + shell_print(shell, "%s", bt_audio_context_bit_to_str(0)); + + for (int i = 0; i <= context_msb_one(BT_AUDIO_CONTEXT_TYPE_ANY); i++) { + shell_print(shell, "%s", bt_audio_context_bit_to_str(BIT(i))); + } +} + +static void codec_qos_print(const struct shell *shell, struct bt_audio_codec_qos *qos) +{ + if (qos->phy == BT_AUDIO_CODEC_QOS_1M || qos->phy == BT_AUDIO_CODEC_QOS_2M) { + shell_print(shell, "\t\t\tPHY: %dM", qos->phy); + } else if (qos->phy == BT_AUDIO_CODEC_QOS_CODED) { + shell_print(shell, "\t\t\tPHY: LE Coded"); + } else { + shell_print(shell, "\t\t\tPHY: Unknown"); + } + + shell_print(shell, "\t\t\tFraming: %s", + (qos->framing == BT_AUDIO_CODEC_QOS_FRAMING_UNFRAMED ? "unframed" : "framed")); + shell_print(shell, "\t\t\tRTN: %d", qos->rtn); + shell_print(shell, "\t\t\tSDU size: %d", qos->sdu); + shell_print(shell, "\t\t\tMax Transport Latency: %d ms", qos->latency); + shell_print(shell, "\t\t\tFrame Interval: %d us", qos->interval); + shell_print(shell, "\t\t\tPresentation Delay: %d us", qos->pd); +} + +static void nrf_auraconfig_print(const struct shell *shell, uint8_t group_index) +{ + int ret; + + struct broadcast_source_big *brdcst_param = &broadcast_param[group_index]; + + shell_print(shell, "\tAdvertising name: %s", + strlen(brdcst_param->adv_name) > 0 ? brdcst_param->adv_name + : CONFIG_BT_DEVICE_NAME); + + shell_print(shell, "\tBroadcast name: %s", brdcst_param->broadcast_name); + + if (brdcst_param->fixed_id) { + shell_print(shell, "\tBroadcast ID (fixed): 0x%04x", brdcst_param->broadcast_id); + } else if (!broadcast_source_is_streaming(group_index)) { + shell_print(shell, "\tBroadcast ID (random): Will be set on start"); + } else { + uint32_t broadcast_id; + + broadcast_source_id_get(group_index, &broadcast_id); + shell_print(shell, "\tBroadcast ID (random): 0x%04x", broadcast_id); + } + + shell_print(shell, "\tPacking: %s", + (brdcst_param->packing == BT_ISO_PACKING_INTERLEAVED ? "interleaved" + : "sequential")); + shell_print(shell, "\tEncryption: %s", + (brdcst_param->encryption == true ? "true" : "false")); + + shell_print(shell, "\tBroadcast code: %s", brdcst_param->broadcast_code); + + for (size_t i = 0; i < brdcst_param->num_subgroups; i++) { + struct bt_audio_codec_cfg *codec_cfg = + &brdcst_param->subgroups[i].group_lc3_preset.codec_cfg; + + shell_print(shell, "\tSubgroup %d:", i); + + shell_print(shell, "\t\tPreset: %s", brdcst_param->subgroups[i].preset_name); + + codec_qos_print(shell, &brdcst_param->subgroups[i].group_lc3_preset.qos); + + int freq_hz = 0; + + ret = le_audio_freq_hz_get(codec_cfg, &freq_hz); + if (ret) { + shell_error(shell, "Failed to get sampling rate: %d", ret); + } else { + shell_print(shell, "\t\tSampling rate: %d Hz", freq_hz); + } + + uint32_t bitrate = 0; + + ret = le_audio_bitrate_get(codec_cfg, &bitrate); + if (ret) { + shell_error(shell, "Failed to get bit rate: %d", ret); + } else { + shell_print(shell, "\t\tBit rate: %d bps", bitrate); + } + + int frame_duration = 0; + + ret = le_audio_duration_us_get(codec_cfg, &frame_duration); + if (ret) { + shell_error(shell, "Failed to get frame duration: %d", ret); + } else { + shell_print(shell, "\t\tFrame duration: %d us", frame_duration); + } + + uint32_t octets_per_sdu = 0; + + ret = le_audio_octets_per_frame_get(codec_cfg, &octets_per_sdu); + if (ret) { + shell_error(shell, "Failed to get octets per frame: %d", ret); + } else { + shell_print(shell, "\t\tOctets per frame: %d", octets_per_sdu); + } + + const uint8_t *language; + + ret = bt_audio_codec_cfg_meta_get_lang(codec_cfg, &language); + + if (ret) { + shell_print(shell, "\t\tLanguage: not_set"); + } else { + char lang[LANGUAGE_LEN + 1] = {'\0'}; + + memcpy(lang, language, LANGUAGE_LEN); + + shell_print(shell, "\t\tLanguage: %s", lang); + } + + shell_print(shell, "\t\tContext(s):"); + + /* Context container is a bit field */ + for (size_t j = 0U; j <= context_msb_one(BT_AUDIO_CONTEXT_TYPE_ANY); j++) { + const uint16_t bit_val = BIT(j); + + if (brdcst_param->subgroups[i].context & bit_val) { + shell_print(shell, "\t\t\t%s", + bt_audio_context_bit_to_str(bit_val)); + } + } + + const unsigned char *program_info; + + ret = bt_audio_codec_cfg_meta_get_program_info(codec_cfg, &program_info); + if (ret > 0) { + shell_print(shell, "\t\tProgram info: %s", program_info); + } + + int immediate = + bt_audio_codec_cfg_meta_get_bcast_audio_immediate_rend_flag(codec_cfg); + + shell_print(shell, "\t\tImmediate rendering flag: %s", + (immediate == 0 ? "set" : "not set")); + shell_print(shell, "\t\tNumber of BIS: %d", brdcst_param->subgroups[i].num_bises); + shell_print(shell, "\t\tLocation:"); + + for (size_t j = 0; j < brdcst_param->subgroups[i].num_bises; j++) { + shell_print(shell, "\t\t\tBIS %d: %s", j, + bt_audio_location_bit_to_str( + brdcst_param->subgroups[i].location[j])); + } + + shell_print(shell, "\t\tFiles:"); + + for (size_t j = 0; j < brdcst_param->subgroups[i].num_bises; j++) { + uint8_t streamer_idx = lc3_stream_infos[group_index][i].lc3_streamer_idx[j]; + + if (!sd_card_present) { + shell_print(shell, "\t\t\tBIS %d: Fixed data", j); + } else if (streamer_idx == LC3_STREAMER_INDEX_UNUSED) { + shell_print(shell, "\t\t\tBIS %d: Not set", j); + } else { + char file_name[CONFIG_FS_FATFS_MAX_LFN]; + bool looping = lc3_streamer_is_looping(streamer_idx); + + lc3_streamer_file_path_get(streamer_idx, file_name, + sizeof(file_name)); + shell_print(shell, "\t\t\tBIS %d: %s %s", j, file_name, + looping ? "(looping)" : ""); + } + } + } +} + +static int cmd_list(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + for (size_t i = 0; i < ARRAY_SIZE(bap_presets); i++) { + shell_print(shell, "%s", bap_presets[i].name); + } + + return 0; +} + +static int adv_create_and_start(const struct shell *shell, uint8_t big_index) +{ + int ret; + + size_t ext_adv_buf_cnt = 0; + size_t per_adv_buf_cnt = 0; + + if (big_index >= CONFIG_BT_ISO_MAX_BIG) { + shell_error(shell, "BIG index out of range"); + return -EINVAL; + } + + if (broadcast_param[big_index].broadcast_name[0] == '\0') { + /* Name not set, using default */ + size_t brdcst_name_size = sizeof(CONFIG_BT_AUDIO_BROADCAST_NAME) - 1; + + if (brdcst_name_size >= ARRAY_SIZE(ext_adv_data[big_index].brdcst_name_buf)) { + shell_error(shell, "Broadcast name too long, using parts of the name"); + brdcst_name_size = ARRAY_SIZE(ext_adv_data[big_index].brdcst_name_buf) - 1; + } + + memcpy(ext_adv_data[big_index].brdcst_name_buf, CONFIG_BT_AUDIO_BROADCAST_NAME, + brdcst_name_size); + } else { + size_t brdcst_name_size = strlen(broadcast_param[big_index].broadcast_name); + + if (brdcst_name_size >= ARRAY_SIZE(ext_adv_data[big_index].brdcst_name_buf)) { + shell_error(shell, "Broadcast name too long, using parts of the name"); + brdcst_name_size = ARRAY_SIZE(ext_adv_data[big_index].brdcst_name_buf) - 1; + } + + memcpy(ext_adv_data[big_index].brdcst_name_buf, + broadcast_param[big_index].broadcast_name, brdcst_name_size); + } + + if (broadcast_param[big_index].adv_name[0] == '\0') { + /* Name not set, using default */ + size_t adv_name_size = sizeof(CONFIG_BT_AUDIO_BROADCAST_NAME) - 1; + + memcpy(broadcast_param[big_index].adv_name, CONFIG_BT_AUDIO_BROADCAST_NAME, + adv_name_size); + } + + bt_set_name(broadcast_param[big_index].adv_name); + + /* Get advertising set for BIG0 */ + ret = ext_adv_populate(big_index, &ext_adv_data[big_index], ext_adv_buf[big_index], + ARRAY_SIZE(ext_adv_buf[big_index]), &ext_adv_buf_cnt); + if (ret) { + return ret; + } + + ret = per_adv_populate(big_index, &per_adv_data[big_index], &per_adv_buf[big_index], 1, + &per_adv_buf_cnt); + if (ret) { + return ret; + } + + /* Start broadcaster */ + ret = bt_mgmt_adv_start(big_index, ext_adv_buf[big_index], ext_adv_buf_cnt, + &per_adv_buf[big_index], per_adv_buf_cnt, false); + if (ret) { + shell_error(shell, "Failed to start advertising for BIG%d", big_index); + return ret; + } + + return 0; +} + +/** + * @brief Converts the arguments to BIG and subgroup indexes. + * + * @param[in] shell Pointer to the shell instance. + * @param[in] argc Number of arguments. + * @param[in] argv Pointer to the arguments. + * @param[out] big_index Pointer to the BIG index. + * @param[in] argv_big Index of the BIG index in the arguments. + * @param[out] subgroup_index Pointer to the subgroup index. + * @param[in] argv_subgroup Index of the subgroup index in the arguments. + * + * @return 0 for success, error otherwise. + */ +static int argv_to_indexes(const struct shell *shell, size_t argc, char **argv, uint8_t *big_index, + uint8_t argv_big, uint8_t *subgroup_index, uint8_t argv_subgroup) +{ + + if (big_index == NULL) { + shell_error(shell, "BIG index not provided"); + return -EINVAL; + } + + if (argv_big >= argc) { + shell_error(shell, "BIG index not provided"); + return -EINVAL; + } + + if (!is_number(argv[argv_big])) { + shell_error(shell, "BIG index must be a digit"); + return -EINVAL; + } + + *big_index = (uint8_t)atoi(argv[argv_big]); + + if (*big_index >= CONFIG_BT_ISO_MAX_BIG) { + shell_error(shell, "BIG index out of range"); + return -EINVAL; + } + + if (subgroup_index != NULL) { + if (argv_subgroup >= argc) { + shell_error(shell, "Subgroup index not provided"); + return -EINVAL; + } + + if (!is_number(argv[argv_subgroup])) { + shell_error(shell, "Subgroup index must be a digit"); + return -EINVAL; + } + + *subgroup_index = (uint8_t)atoi(argv[argv_subgroup]); + + if (*subgroup_index >= CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT) { + shell_error(shell, "Subgroup index exceeds max value: %d", + CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT - 1); + return -EINVAL; + } + + if (broadcast_param[*big_index].subgroups == NULL) { + shell_error(shell, "No subgroups defined for BIG%d", *big_index); + return -EINVAL; + } + + if (*subgroup_index >= broadcast_param[*big_index].num_subgroups) { + shell_error(shell, "Subgroup index out of range"); + return -EINVAL; + } + } + + return 0; +} + +static int big_enable(const struct shell *shell, uint8_t big_index) +{ + int ret; + + if (big_index >= CONFIG_BT_ISO_MAX_BIG) { + return -EINVAL; + } + + if ((broadcast_param[big_index].subgroups == NULL) || + (broadcast_param[big_index].num_subgroups == 0)) { + LOG_ERR("No subgroups defined for BIG%d", big_index); + return -EINVAL; + } + + if (broadcast_source_is_streaming(big_index)) { + LOG_WRN("BIG %d is already streaming", big_index); + /* Do not return error code as this might be called from a for-loop */ + return 0; + } + + ret = broadcast_source_enable(&broadcast_param[big_index], big_index); + if (ret) { + shell_error(shell, "Failed to enable broadcaster: %d", ret); + return ret; + } + + ret = adv_create_and_start(shell, big_index); + if (ret) { + shell_error(shell, "Failed to start advertising for BIG%d: %d", big_index, ret); + return ret; + } + + return 0; +} + +static int cmd_start(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + + if (argc == 2) { + uint8_t big_index; + + ret = argv_to_indexes(shell, argc, argv, &big_index, 1, NULL, 0); + if (ret) { + return ret; + } + + ret = big_enable(shell, big_index); + if (ret) { + return ret; + } + + } else { + for (int i = 0; i < CONFIG_BT_ISO_MAX_BIG; i++) { + if (broadcast_param[i].subgroups != NULL || + broadcast_param[i].num_subgroups > 0) { + ret = big_enable(shell, i); + if (ret) { + return ret; + } + } + } + } + + led_blink(LED_APP_RGB, LED_COLOR_GREEN); + + return 0; +} + +static int broadcaster_stop(const struct shell *shell, uint8_t big_index) +{ + int ret; + + if (!broadcast_source_is_streaming(big_index)) { + return 0; + } + + ret = broadcast_source_disable(big_index); + if (ret) { + shell_error(shell, "Failed to stop broadcaster(s) %d", ret); + return ret; + } + + ret = bt_mgmt_per_adv_stop(big_index); + if (ret) { + shell_error(shell, "Failed to stop periodic advertiser"); + return ret; + } + + ret = bt_mgmt_ext_adv_stop(big_index); + if (ret) { + shell_error(shell, "Failed to stop extended advertiser"); + return ret; + } + + if (big_index == 0) { + net_buf_simple_reset(&base_data0); + net_buf_simple_reset(&uuid_data0); + } else if (big_index == 1) { + net_buf_simple_reset(&base_data1); + net_buf_simple_reset(&uuid_data1); + } else { + shell_error(shell, "BIG index out of range"); + return -EINVAL; + } + + per_adv_buf[big_index].data_len = 0; + per_adv_buf[big_index].type = 0; + + memset(pba_data[big_index], 0, sizeof(pba_data[big_index])); + + for (size_t j = 0; j < ARRAY_SIZE(ext_adv_buf[big_index]); j++) { + ext_adv_buf[big_index][j].data_len = 0; + ext_adv_buf[big_index][j].type = 0; + } + + return 0; +} + +static int cmd_stop(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + + if (argc == 2) { + uint8_t big_index; + + ret = argv_to_indexes(shell, argc, argv, &big_index, 1, NULL, 0); + if (ret) { + return ret; + } + + ret = broadcaster_stop(shell, big_index); + + } else { + for (size_t i = 0; i < CONFIG_BT_ISO_MAX_BIG; i++) { + if (broadcast_param[i].subgroups != NULL) { + ret = broadcaster_stop(shell, i); + if (ret) { + return ret; + } + } + } + } + + led_on(LED_APP_RGB, LED_COLOR_GREEN); + + return 0; +} + +static int cmd_show(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + for (size_t i = 0; i < CONFIG_BT_ISO_MAX_BIG; i++) { + if (broadcast_param[i].subgroups == NULL) { + continue; + } + + bool streaming = broadcast_source_is_streaming(i); + + shell_print(shell, "BIG %d:", i); + shell_print(shell, "\tStreaming: %s", (streaming ? "true" : "false")); + + nrf_auraconfig_print(shell, i); + } + + return 0; +} + +static int cmd_preset(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + + if ((argc >= 2) && (strcmp(argv[1], "print") == 0)) { + for (size_t i = 0; i < ARRAY_SIZE(bap_presets); i++) { + shell_print(shell, "%s", bap_presets[i].name); + } + return 0; + } + + if (argc < 3) { + shell_error(shell, + "Usage: nac preset optional:"); + return -EINVAL; + } + + struct bt_bap_lc3_preset *preset = preset_find(argv[1]); + + if (!preset) { + shell_error(shell, + "Preset not found, use 'nac preset print' to see available presets"); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, NULL, 0); + if (ret) { + return ret; + } + + broadcast_create(big_index); + + if (argc == 4) { + uint8_t sub_index; + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + + broadcast_param[big_index].subgroups[sub_index].group_lc3_preset = *preset; + broadcast_param[big_index].subgroups[sub_index].preset_name = + preset_name_find(preset); + } else { + for (size_t i = 0; i < broadcast_param[big_index].num_subgroups; i++) { + broadcast_param[big_index].subgroups[i].group_lc3_preset = *preset; + broadcast_param[big_index].subgroups[i].preset_name = + preset_name_find(preset); + } + } + + return 0; +} + +static int cmd_packing(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + + if (argc < 3) { + shell_error(shell, "Usage: nac packing "); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, NULL, 0); + if (ret) { + return ret; + } + + if (strcmp(argv[1], "seq") == 0) { + broadcast_param[big_index].packing = BT_ISO_PACKING_SEQUENTIAL; + } else if (strcmp(argv[1], "int") == 0) { + broadcast_param[big_index].packing = BT_ISO_PACKING_INTERLEAVED; + } else { + shell_error(shell, "Invalid packing type"); + return -EINVAL; + } + + return 0; +} + +static int cmd_lang_set(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + + if (argc < 4) { + shell_error(shell, "Usage: nac lang_set "); + return -EINVAL; + } + + if (strlen(argv[1]) != LANGUAGE_LEN) { + shell_error(shell, "Language must be %d characters long", LANGUAGE_LEN); + return -EINVAL; + } + + char *language = argv[1]; + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + return ret; + } + + bt_audio_codec_cfg_meta_set_lang( + &broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.codec_cfg, + language); + + return 0; +} + +static int cmd_immediate_set(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + + if (argc < 3) { + shell_error(shell, "Usage: nac immediate <0/1> "); + return -EINVAL; + } + + bool immediate = strtoul(argv[1], NULL, 10); + + if ((immediate != 0) && (immediate != 1)) { + shell_error(shell, "Immediate must be 0 or 1"); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + return ret; + } + + if (immediate) { + ret = bt_audio_codec_cfg_meta_set_bcast_audio_immediate_rend_flag( + &broadcast_param[big_index] + .subgroups[sub_index] + .group_lc3_preset.codec_cfg); + if (ret < 0) { + shell_error(shell, "Failed to set immediate rendering flag: %d", ret); + return ret; + } + } else { + ret = bt_audio_codec_cfg_meta_unset_val( + &broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.codec_cfg, + BT_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE); + if (ret < 0) { + shell_error(shell, "Failed to unset immediate rendering flag: %d", ret); + return ret; + } + } + + return 0; +} + +static int cmd_num_subgroups(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + + if (argc < 3) { + shell_error(shell, "Usage: nac num_subgroups "); + return -EINVAL; + } + + if (!is_number(argv[1])) { + shell_error(shell, "Number of subgroups must be a digit"); + return -EINVAL; + } + + uint8_t num_subgroups = (uint8_t)atoi(argv[1]); + + if (num_subgroups > CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT) { + shell_error(shell, "Max allowed subgroups is: %d", + CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, NULL, 0); + if (ret) { + return ret; + } + + broadcast_param[big_index].num_subgroups = num_subgroups; + + return 0; +} + +static int cmd_num_bises(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + + if (argc < 4) { + shell_error(shell, "Usage: nac num_bises "); + return -EINVAL; + } + + if (!is_number(argv[1])) { + shell_error(shell, "Number of BISes must be a digit"); + return -EINVAL; + } + + uint8_t num_bises = (uint8_t)atoi(argv[1]); + + if (!is_number(argv[2])) { + shell_error(shell, "BIG index must be a digit"); + return -EINVAL; + } + + if (num_bises > CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT) { + shell_error(shell, "Max allowed BISes is: %d", + CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + return ret; + } + + broadcast_param[big_index].subgroups[sub_index].num_bises = num_bises; + + return 0; +} + +static int cmd_context(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + uint16_t context = 0; + + if ((argc >= 2) && (strcmp(argv[1], "print") == 0)) { + context_print(shell); + return 0; + } + + if (argc < 4) { + shell_error(shell, "Usage: nac context "); + return -EINVAL; + } + + /* Context container is a bit field */ + for (size_t i = 0U; i <= context_msb_one(BT_AUDIO_CONTEXT_TYPE_ANY); i++) { + if (strcasecmp(argv[1], bt_audio_context_bit_to_str(BIT(i))) == 0) { + context = BIT(i); + break; + } + } + + if (!context) { + shell_error(shell, "Context not found, use 'nac context print' to see " + "available contexts"); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + return ret; + } + + broadcast_param[big_index].subgroups[sub_index].context = context; + + return 0; +} + +static int location_find(const struct shell *shell, const char *name) +{ + for (size_t i = 0; i < ARRAY_SIZE(locations); i++) { + if (strcasecmp(locations[i].name, name) == 0) { + return locations[i].location; + } + } + + shell_error(shell, "Location not found"); + + return -ESRCH; +} + +static void location_print(const struct shell *shell) +{ + for (size_t i = 0; i < ARRAY_SIZE(locations); i++) { + shell_print(shell, "%s - %s", locations[i].name, + bt_audio_location_bit_to_str(locations[i].location)); + } +} + +static int cmd_location(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + + if ((argc >= 2) && (strcmp(argv[1], "print") == 0)) { + location_print(shell); + return 0; + } + + if (argc < 4) { + shell_error(shell, "Usage: nac location "); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + return ret; + } + + if (!is_number(argv[4])) { + shell_error(shell, "BIS index must be a digit"); + return -EINVAL; + } + + uint8_t bis_index = (uint8_t)atoi(argv[4]); + + if ((bis_index >= CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT) || + (bis_index >= broadcast_param[big_index].subgroups[sub_index].num_bises)) { + shell_error(shell, "BIS index out of range"); + return -EINVAL; + } + + int location = location_find(shell, argv[1]); + + if (location < 0) { + shell_error(shell, "Location not found"); + return -EINVAL; + } + + broadcast_param[big_index].subgroups[sub_index].location[bis_index] = location; + + return 0; +} + +static int cmd_broadcast_name(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + + if (argc < 2) { + shell_error(shell, "Usage: nac broadcast_name "); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, NULL, 0); + if (ret) { + return ret; + } + + size_t name_length = strlen(argv[1]); + + /* Leave space for null termination */ + if (name_length >= ARRAY_SIZE(broadcast_param[big_index].broadcast_name) - 1) { + shell_error(shell, "Name too long"); + return -EINVAL; + } + + /* Delete old name if set */ + memset(broadcast_param[big_index].broadcast_name, '\0', + ARRAY_SIZE(broadcast_param[big_index].broadcast_name)); + memcpy(broadcast_param[big_index].broadcast_name, argv[1], name_length); + + return 0; +} + +static int cmd_encrypt(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + + if (argc < 3) { + shell_error(shell, + "Usage: nac encrypt <0/1> Optional:"); + return -EINVAL; + } + + if (!is_number(argv[1])) { + shell_error(shell, "Encryption must be 0 or 1"); + return -EINVAL; + } + + uint8_t encrypt = (uint8_t)atoi(argv[1]); + + if (encrypt != 1 && encrypt != 0) { + shell_error(shell, "Encryption must be 0 or 1"); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, NULL, 0); + if (ret) { + return ret; + } + + broadcast_param[big_index].encryption = encrypt; + + if (encrypt == 1) { + if (argc < 4) { + shell_error(shell, "Broadcast code must be set"); + return -EINVAL; + } + + if (strlen(argv[3]) > BT_ISO_BROADCAST_CODE_SIZE) { + shell_error(shell, "Broadcast code must be %d characters long", + BT_ISO_BROADCAST_CODE_SIZE); + return -EINVAL; + } + memset(broadcast_param[big_index].broadcast_code, '\0', + ARRAY_SIZE(broadcast_param[big_index].broadcast_code)); + memcpy(broadcast_param[big_index].broadcast_code, argv[3], strlen(argv[3])); + } + + return 0; +} + +static int cmd_adv_name(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + + if (argc < 2) { + shell_error(shell, "Usage: nac device_name "); + return -EINVAL; + } + + if (strlen(argv[1]) > CONFIG_BT_DEVICE_NAME_MAX) { + shell_error(shell, "Device name too long"); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, NULL, 0); + if (ret) { + return ret; + } + + memset(broadcast_param[big_index].adv_name, '\0', + ARRAY_SIZE(broadcast_param[big_index].adv_name)); + memcpy(broadcast_param[big_index].adv_name, argv[1], strlen(argv[1])); + + return 0; +} + +static int cmd_program_info(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + + if (argc != 4) { + shell_error(shell, "Usage: nac program_info \"info\" "); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + return ret; + } + + ret = bt_audio_codec_cfg_meta_set_program_info( + &broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.codec_cfg, + argv[1], strlen(argv[1])); + if (ret < 0) { + shell_error(shell, "Failed to set program info: %d", ret); + return ret; + } + + return 0; +} + +#define FILE_LIST_BUF_SIZE 1024 +static int cmd_file_list(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + char buf[FILE_LIST_BUF_SIZE]; + size_t buf_size = FILE_LIST_BUF_SIZE; + char *dir_path = NULL; + + if (!sd_card_present) { + shell_error(shell, "No SD card present: files cannot be listed"); + return -EFAULT; + } + + if (argc > 2) { + shell_error(shell, "Usage: nac file list [dir path]"); + return -EINVAL; + } + + if (argc == 2) { + dir_path = argv[1]; + } + + ret = sd_card_list_files(dir_path, buf, &buf_size, true); + if (ret) { + shell_error(shell, "List files err: %d", ret); + return ret; + } + + shell_print(shell, "%s", buf); + + return 0; +} + +static int cmd_file_select(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + uint8_t bis_index; + + if (!sd_card_present) { + shell_error(shell, "No SD card present: files cannot be selected"); + return -EFAULT; + } + + if (argc != 5) { + shell_error(shell, + "Usage: nac file select " + ""); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + shell_error(shell, "Failed to get indexes: %d", ret); + return ret; + } + + if (broadcast_source_is_streaming(big_index)) { + shell_error(shell, "Files cannot be selected while streaming"); + return -EFAULT; + } + + if (!is_number(argv[4])) { + shell_error(shell, "BIS index must be a digit"); + return -EINVAL; + } + + bis_index = (uint8_t)atoi(argv[4]); + + if ((bis_index >= CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT) || + (bis_index >= broadcast_param[big_index].subgroups[sub_index].num_bises)) { + shell_error(shell, "BIS index %d is out of range (max %d)", bis_index, + broadcast_param[big_index].subgroups[sub_index].num_bises - 1); + return -EINVAL; + } + + char *file_name = argv[1]; + + LOG_DBG("Selecting file %s for stream big: %d sub: %d bis: %d", file_name, big_index, + sub_index, bis_index); + + struct bt_audio_codec_cfg *codec_cfg = + &broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.codec_cfg; + + struct lc3_stream_cfg cfg; + + ret = le_audio_freq_hz_get(codec_cfg, &cfg.sample_rate_hz); + if (ret) { + shell_error(shell, "Failed to get frequency: %d", ret); + return ret; + } + + ret = le_audio_duration_us_get(codec_cfg, &cfg.frame_duration_us); + if (ret) { + shell_error(shell, "Failed to get frame duration: %d", ret); + } + + ret = le_audio_bitrate_get(codec_cfg, &cfg.bit_rate_bps); + if (ret) { + shell_error(shell, "Failed to get bit rate: %d", ret); + } + + /* Verify that the file header matches the stream configurationn */ + /* NOTE: This will not abort the streamer if the file is not valid, only give a warning */ + bool header_valid = lc3_streamer_file_compatible_check(file_name, &cfg); + + if (!header_valid) { + shell_warn(shell, "File header verification failed. File may not be compatible " + "with stream config."); + } + + ret = lc3_streamer_stream_register( + file_name, &lc3_stream_infos[big_index][sub_index].lc3_streamer_idx[bis_index], + true); + if (ret) { + shell_error(shell, "Failed to register stream: %d", ret); + return ret; + } + + lc3_stream_infos[big_index][sub_index].frame_size = + broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.qos.sdu; + + return 0; +} + +static int cmd_phy(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + + if (argc != 4) { + shell_error(shell, + "Usage: nac phy <1, 2 or 4 (coded)> "); + return -EINVAL; + } + + if (!is_number(argv[1])) { + shell_error(shell, "PHY must be a digit"); + return -EINVAL; + } + + uint8_t phy = (uint8_t)atoi(argv[1]); + + if (phy != BT_AUDIO_CODEC_QOS_1M && phy != BT_AUDIO_CODEC_QOS_2M && + phy != BT_AUDIO_CODEC_QOS_CODED) { + shell_error(shell, "Invalid PHY"); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + return ret; + } + + broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.qos.phy = phy; + broadcast_param[big_index].subgroups[sub_index].preset_name = "Custom"; + + return 0; +} + +static int cmd_framing(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + + if (argc != 4) { + shell_error(shell, + "Usage: nac framing "); + return -EINVAL; + } + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + return ret; + } + + if (strcasecmp(argv[1], "unframed") == 0) { + broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.qos.framing = + BT_AUDIO_CODEC_QOS_FRAMING_UNFRAMED; + broadcast_param[big_index].subgroups[sub_index].preset_name = "Custom"; + } else if (strcasecmp(argv[1], "framed") == 0) { + broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.qos.framing = + BT_AUDIO_CODEC_QOS_FRAMING_FRAMED; + broadcast_param[big_index].subgroups[sub_index].preset_name = "Custom"; + } else { + shell_error(shell, "Invalid framing type"); + return -EINVAL; + } + + return 0; +} + +static int cmd_rtn(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + + if (argc != 4) { + shell_error(shell, "Usage: nac rtn "); + return -EINVAL; + } + + if (!is_number(argv[1])) { + shell_error(shell, "RTN must be a digit"); + return -EINVAL; + } + + if (strtoul(argv[1], NULL, 10) > UINT8_MAX) { + shell_error(shell, "RTN must be less than %d", UINT8_MAX); + return -EINVAL; + } + + uint8_t rtn = (uint8_t)atoi(argv[1]); + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + return ret; + } + + broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.qos.rtn = rtn; + broadcast_param[big_index].subgroups[sub_index].preset_name = "Custom"; + + return 0; +} + +static int cmd_sdu(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + + if (argc != 4) { + shell_error(shell, "Usage: nac sdu "); + return -EINVAL; + } + + if (!is_number(argv[1])) { + shell_error(shell, "SDU must be a digit"); + return -EINVAL; + } + + if (strtoul(argv[1], NULL, 10) > UINT16_MAX) { + shell_error(shell, "SDU must be less than %d", UINT16_MAX); + return -EINVAL; + } + + uint16_t sdu = (uint16_t)atoi(argv[1]); + + ret = argv_to_indexes(shell, argc, argv, &big_index, 2, &sub_index, 3); + if (ret) { + return ret; + } + + broadcast_param[big_index].subgroups[sub_index].group_lc3_preset.qos.sdu = sdu; + broadcast_param[big_index].subgroups[sub_index].preset_name = "Custom"; + + return 0; +} + +static int cmd_mtl(const struct shell *shell, size_t argc, char **argv) +{ + int ret; + uint8_t big_index; + uint8_t sub_index; + + if (argc != 4) { + shell_error(shell, "Usage: nac mtl