samples: Bluetooth: BAP: Broadcast sink use new USB stack

Refactor the USB of the BAP broadcast sink to use the new
USB stack.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2025-01-22 08:37:20 +01:00 committed by Benjamin Cabé
commit 7f379fb963
17 changed files with 192 additions and 122 deletions

View file

@ -2,14 +2,16 @@
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bap_unicast_server)
project(bap_unicast_sink)
target_sources(app PRIVATE
src/main.c
src/stream_rx.c
)
zephyr_sources_ifdef(CONFIG_LIBLC3 src/lc3.c)
zephyr_sources_ifdef(CONFIG_USB_DEVICE_AUDIO src/usb.c)
target_sources_ifdef(CONFIG_ENABLE_LC3 app PRIVATE src/lc3.c)
zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)
if (CONFIG_USE_USB_AUDIO_OUTPUT)
include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake)
target_sources(app PRIVATE src/usb.c)
endif()

View file

@ -55,8 +55,8 @@ config ENABLE_LC3
config USE_USB_AUDIO_OUTPUT
bool "Use USB Audio as output"
depends on ENABLE_LC3
select USB_DEVICE_STACK
select USB_DEVICE_AUDIO
select USB_DEVICE_STACK_NEXT
select USBD_AUDIO2_CLASS
select RING_BUFFER
help
Enables USB audio as output as a USB peripheral. This does not support providing USB
@ -87,4 +87,9 @@ config INFO_REPORTING_INTERVAL
Determines how often information about received data is logged.
Set to 0 to disable reporting.
# Source common USB sample options used to initialize new experimental USB device stack.
# The scope of these options is limited to USB samples in project tree,
# you cannot use them in your own application.
source "samples/subsys/usb/common/Kconfig.sample_usbd"
source "Kconfig.zephyr"

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <dt-bindings/usb/audio.h>
/ {
uac2_microphone: usb_audio2 {
compatible = "zephyr,uac2";
status = "okay";
full-speed;
audio-function = <AUDIO_FUNCTION_MICROPHONE>;
/* Clock supporting 48KHz
*
* Since the incoming audio may be between 8 and 48 KHz, we always upsample to 48KHz
*/
uac_aclk: aclk {
compatible = "zephyr,uac2-clock-source";
clock-type = "internal-programmable";
frequency-control = "read-only";
sampling-frequencies = <48000>;
/* Falsely claim synchronous audio because we
* currently don't calculate feedback value
*/
sof-synchronized;
};
/* The out_terminal supports the incoming Bluetooth LE Audio over ISO
* and treats it as a microphone towards the USB host
*/
out_terminal: out_terminal {
compatible = "zephyr,uac2-input-terminal";
clock-source = <&uac_aclk>;
terminal-type = <INPUT_TERMINAL_MICROPHONE>;
front-left;
front-right;
};
/* USB Audio terminal from USB device to USB host */
in_terminal: in_terminal {
compatible = "zephyr,uac2-output-terminal";
data-source = <&out_terminal>;
clock-source = <&uac_aclk>;
terminal-type = <USB_TERMINAL_STREAMING>;
};
as_iso_in: in_interface {
compatible = "zephyr,uac2-audio-streaming";
linked-terminal = <&in_terminal>;
subslot-size = <2>;
bit-resolution = <16>;
};
};
};

View file

@ -1,3 +1,3 @@
# Use USB Audio as audio sink
CONFIG_USE_USB_AUDIO_OUTPUT=y
CONFIG_USB_DEVICE_PRODUCT="USB Broadcast Sink"
CONFIG_SAMPLE_USBD_PRODUCT="USB Broadcast Sink sample"

View file

@ -1,15 +1,7 @@
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
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;
};
};
#include "../app.overlay"

View file

@ -0,0 +1,3 @@
# Use USB Audio as audio sink
CONFIG_USE_USB_AUDIO_OUTPUT=y
CONFIG_SAMPLE_USBD_PRODUCT="USB Broadcast Sink sample"

View file

@ -0,0 +1,7 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "../app.overlay"

View file

@ -1,3 +1,5 @@
# Use USB Audio as audio sink
CONFIG_USE_USB_AUDIO_OUTPUT=y
CONFIG_USB_DEVICE_PRODUCT="USB Broadcast Sink"
CONFIG_SAMPLE_USBD_PRODUCT="USB Broadcast Sink sample"
CONFIG_BOARD_SERIAL_BACKEND_CDC_ACM=n

View file

@ -1,15 +1,7 @@
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
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;
};
};
#include "../app.overlay"

View file

@ -1,3 +1,3 @@
# Use USB Audio as audio sink
CONFIG_USE_USB_AUDIO_OUTPUT=y
CONFIG_USB_DEVICE_PRODUCT="USB Broadcast Sink"
CONFIG_SAMPLE_USBD_PRODUCT="USB Broadcast Sink sample"

View file

@ -0,0 +1,7 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "../app.overlay"

View file

@ -1,3 +1,3 @@
# Use USB Audio as audio sink
CONFIG_USE_USB_AUDIO_OUTPUT=y
CONFIG_USB_DEVICE_PRODUCT="USB Broadcast Sink"
CONFIG_SAMPLE_USBD_PRODUCT="USB Broadcast Sink sample"

View file

@ -1,15 +1,7 @@
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
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;
};
};
#include "../app.overlay"

View file

@ -65,7 +65,6 @@ static K_FIFO_DEFINE(lc3_in_fifo);
*/
static struct stream_rx *usb_left_stream;
static struct stream_rx *usb_right_stream;
static size_t rx_streaming_cnt;
static int init_lc3_decoder(struct stream_rx *stream, uint32_t lc3_frame_duration_us,
uint32_t lc3_freq_hz)
@ -92,7 +91,7 @@ static int init_lc3_decoder(struct stream_rx *stream, uint32_t lc3_frame_duratio
/* Create the decoder instance. This shall complete before stream_started() is called. */
stream->lc3_decoder =
lc3_setup_decoder(lc3_frame_duration_us, lc3_freq_hz,
IS_ENABLED(CONFIG_USB_DEVICE_AUDIO) ? USB_SAMPLE_RATE_HZ : 0,
IS_ENABLED(CONFIG_USE_USB_AUDIO_OUTPUT) ? USB_SAMPLE_RATE_HZ : 0,
&stream->lc3_decoder_mem);
if (stream->lc3_decoder == NULL) {
LOG_ERR("Failed to setup LC3 decoder - wrong parameters?\n");
@ -100,7 +99,6 @@ static int init_lc3_decoder(struct stream_rx *stream, uint32_t lc3_frame_duratio
}
LOG_INF("Initialized LC3 decoder for %p", stream);
rx_streaming_cnt++;
return 0;
}
@ -152,7 +150,7 @@ static bool decode_frame(struct lc3_data *data, size_t frame_cnt)
static int get_lc3_chan_alloc_from_index(const struct stream_rx *stream, uint8_t index,
enum bt_audio_location *chan_alloc)
{
#if defined(CONFIG_USB_DEVICE_AUDIO)
#if defined(CONFIG_USE_USB_AUDIO_OUTPUT)
const bool has_left = (stream->lc3_chan_allocation & BT_AUDIO_LOCATION_FRONT_LEFT) != 0;
const bool has_right = (stream->lc3_chan_allocation & BT_AUDIO_LOCATION_FRONT_RIGHT) != 0;
const bool is_mono = stream->lc3_chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO;
@ -174,9 +172,9 @@ static int get_lc3_chan_alloc_from_index(const struct stream_rx *stream, uint8_t
}
return 0;
#else /* !CONFIG_USB_DEVICE_AUDIO */
#else /* !CONFIG_USE_USB_AUDIO_OUTPUT */
return -EINVAL;
#endif /* CONFIG_USB_DEVICE_AUDIO */
#endif /* CONFIG_USE_USB_AUDIO_OUTPUT */
}
static size_t decode_frame_block(struct lc3_data *data, size_t frame_cnt)
@ -192,7 +190,7 @@ static size_t decode_frame_block(struct lc3_data *data, size_t frame_cnt)
if (decode_frame(data, frame_cnt + decoded_frames)) {
decoded_frames++;
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
if (IS_ENABLED(CONFIG_USE_USB_AUDIO_OUTPUT)) {
enum bt_audio_location chan_alloc;
int err;
@ -223,7 +221,7 @@ static size_t decode_frame_block(struct lc3_data *data, size_t frame_cnt)
/* If decoding failed, we clear the data to USB as it would contain
* invalid data
*/
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
if (IS_ENABLED(CONFIG_USE_USB_AUDIO_OUTPUT)) {
usb_clear_frames_to_usb();
}
@ -379,7 +377,7 @@ int lc3_enable(struct stream_rx *stream)
}
}
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
if (IS_ENABLED(CONFIG_USE_USB_AUDIO_OUTPUT)) {
if ((stream->lc3_chan_allocation & BT_AUDIO_LOCATION_FRONT_LEFT) != 0) {
if (usb_left_stream == NULL) {
LOG_INF("Setting USB left stream to %p", stream);
@ -404,12 +402,11 @@ int lc3_enable(struct stream_rx *stream)
int lc3_disable(struct stream_rx *stream)
{
if (rx_streaming_cnt == 0 || stream->lc3_decoder == NULL) {
if (stream->lc3_decoder == NULL) {
return -EINVAL;
}
stream->lc3_decoder = NULL;
rx_streaming_cnt--;
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
if (usb_left_stream == stream) {
@ -488,8 +485,3 @@ int lc3_init(void)
return 0;
}
size_t lc3_get_rx_streaming_cnt(void)
{
return rx_streaming_cnt;
}

View file

@ -41,13 +41,6 @@
((LC3_MAX_FRAME_DURATION_US * LC3_MAX_SAMPLE_RATE_HZ) / USEC_PER_SEC)
#define LC3_MAX_NUM_SAMPLES_STEREO (LC3_MAX_NUM_SAMPLES_MONO * 2U)
/**
* @brief Returns the number of active streams using an LC3 codec
*
* @return the number of active streams using an LC3 codec
*/
size_t lc3_get_rx_streaming_cnt(void);
/**
* @brief Enables LC3 for a stream
*

View file

@ -890,7 +890,7 @@ static int init(void)
lc3_init();
}
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
if (IS_ENABLED(CONFIG_USE_USB_AUDIO_OUTPUT)) {
usb_init();
}

View file

@ -19,6 +19,7 @@
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/usb/udc_buf.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net_buf.h>
@ -28,11 +29,12 @@
#include <zephyr/sys/util_macro.h>
#include <zephyr/sys_clock.h>
#include <zephyr/toolchain.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/usb/class/usb_audio.h>
#include <zephyr/usb/class/usbd_uac2.h>
#include <zephyr/usb/usbd.h>
#include <sample_usbd.h>
#include "lc3.h"
#include "stream_rx.h"
#include "usb.h"
LOG_MODULE_REGISTER(usb, CONFIG_LOG_DEFAULT_LEVEL);
@ -47,6 +49,8 @@ LOG_MODULE_REGISTER(usb, CONFIG_LOG_DEFAULT_LEVEL);
#define USB_OUT_RING_BUF_SIZE (CONFIG_BT_ISO_RX_BUF_COUNT * LC3_MAX_NUM_SAMPLES_STEREO)
#define USB_IN_RING_BUF_SIZE (USB_MONO_FRAME_SIZE * USB_ENQUEUE_COUNT)
#define IN_TERMINAL_ID UAC2_ENTITY_ID(DT_NODELABEL(in_terminal))
struct decoded_sdu {
int16_t right_frames[CONFIG_MAX_CODEC_FRAMES_PER_SDU][LC3_MAX_NUM_SAMPLES_MONO];
int16_t left_frames[CONFIG_MAX_CODEC_FRAMES_PER_SDU][LC3_MAX_NUM_SAMPLES_MONO];
@ -57,66 +61,75 @@ struct decoded_sdu {
} decoded_sdu;
RING_BUF_DECLARE(usb_out_ring_buf, USB_OUT_RING_BUF_SIZE);
NET_BUF_POOL_DEFINE(usb_out_buf_pool, USB_ENQUEUE_COUNT, USB_STEREO_FRAME_SIZE, 0, net_buf_destroy);
K_MEM_SLAB_DEFINE_STATIC(usb_out_buf_pool, ROUND_UP(USB_STEREO_FRAME_SIZE, UDC_BUF_GRANULARITY),
USB_ENQUEUE_COUNT, UDC_BUF_ALIGN);
static volatile bool terminal_enabled;
/* USB consumer callback, called every 1ms, consumes data from ring-buffer */
static void usb_data_request_cb(const struct device *dev)
static void uac2_sof_cb(const struct device *dev, void *user_data)
{
uint8_t usb_audio_data[USB_STEREO_FRAME_SIZE] = {0};
struct net_buf *pcm_buf;
void *pcm_buf;
uint32_t size;
int err;
if (lc3_get_rx_streaming_cnt() == 0) {
/* no-op as we have no streams that receive data */
if (!terminal_enabled) {
/* Simply discard the data then */
(void)ring_buf_get(&usb_out_ring_buf, NULL, USB_STEREO_FRAME_SIZE);
return;
}
pcm_buf = net_buf_alloc(&usb_out_buf_pool, K_NO_WAIT);
if (pcm_buf == NULL) {
err = k_mem_slab_alloc(&usb_out_buf_pool, &pcm_buf, K_NO_WAIT);
if (err != 0) {
LOG_WRN("Could not allocate pcm_buf");
return;
}
/* This may fail without causing issues since usb_audio_data is 0-initialized */
size = ring_buf_get(&usb_out_ring_buf, usb_audio_data, sizeof(usb_audio_data));
net_buf_add_mem(pcm_buf, usb_audio_data, sizeof(usb_audio_data));
size = ring_buf_get(&usb_out_ring_buf, pcm_buf, USB_STEREO_FRAME_SIZE);
if (size != USB_STEREO_FRAME_SIZE) {
/* If we could not fill the buffer, zero-fill the rest (possibly all) */
memset(((uint8_t *)pcm_buf) + size, 0, USB_STEREO_FRAME_SIZE - size);
}
if (CONFIG_INFO_REPORTING_INTERVAL > 0) {
if (size != 0) {
static size_t cnt;
if (CONFIG_INFO_REPORTING_INTERVAL > 0 &&
(++cnt % (CONFIG_INFO_REPORTING_INTERVAL * 10)) == 0U) {
if (++cnt % (CONFIG_INFO_REPORTING_INTERVAL * 10) == 0U) {
LOG_INF("[%zu]: Sending USB audio", cnt);
}
} else {
static size_t cnt;
if (CONFIG_INFO_REPORTING_INTERVAL > 0 &&
(++cnt % (CONFIG_INFO_REPORTING_INTERVAL * 10)) == 0U) {
if (++cnt % (CONFIG_INFO_REPORTING_INTERVAL * 10) == 0U) {
LOG_INF("[%zu]: Sending empty USB audio", cnt);
}
}
}
err = usb_audio_send(dev, pcm_buf, sizeof(usb_audio_data));
err = usbd_uac2_send(dev, IN_TERMINAL_ID, pcm_buf, USB_STEREO_FRAME_SIZE);
if (err != 0) {
if (CONFIG_INFO_REPORTING_INTERVAL > 0) {
static size_t cnt;
cnt++;
if (CONFIG_INFO_REPORTING_INTERVAL > 0 &&
(cnt % (CONFIG_INFO_REPORTING_INTERVAL * 10)) == 0) {
LOG_ERR("Failed to send USB audio: %d (%zu)", err, cnt);
}
net_buf_unref(pcm_buf);
if (cnt++ % (CONFIG_INFO_REPORTING_INTERVAL * 10) == 0) {
LOG_ERR("[%zu]: Failed to send USB audio: %d", cnt, err);
}
}
static void usb_data_written_cb(const struct device *dev, struct net_buf *buf, size_t size)
k_mem_slab_free(&usb_out_buf_pool, pcm_buf);
} /* USB owns the buffer which will be released in uac2_buf_release_cb */
}
static void uac2_buf_release_cb(const struct device *dev, uint8_t terminal, void *buf,
void *user_data)
{
/* Unreference the buffer now that the USB is done with it */
net_buf_unref(buf);
k_mem_slab_free(&usb_out_buf_pool, buf);
}
static void terminal_update_cb(const struct device *dev, uint8_t terminal, bool enabled,
bool microframes, void *user_data)
{
terminal_enabled = enabled;
}
static void usb_send_frames_to_usb(void)
@ -215,6 +228,12 @@ int usb_add_frame_to_usb(enum bt_audio_location chan_allocation, const int16_t *
const uint8_t ts_jitter_us = 100; /* timestamps may have jitter */
static size_t cnt;
if (!terminal_enabled) {
/* Simply discard the data then */
/* TODO: Consider if we still want to decode the incoming audio */
return 0;
}
if (CONFIG_INFO_REPORTING_INTERVAL > 0 && (++cnt % CONFIG_INFO_REPORTING_INTERVAL) == 0U) {
LOG_INF("[%zu]: Adding USB audio frame", cnt);
}
@ -319,11 +338,13 @@ void usb_clear_frames_to_usb(void)
int usb_init(void)
{
const struct device *hs_dev = DEVICE_DT_GET(DT_NODELABEL(hs_0));
static const struct usb_audio_ops usb_ops = {
.data_request_cb = usb_data_request_cb,
.data_written_cb = usb_data_written_cb,
const struct device *mic_dev = DEVICE_DT_GET(DT_NODELABEL(uac2_microphone));
static struct uac2_ops usb_audio_ops = {
.sof_cb = uac2_sof_cb,
.buf_release_cb = uac2_buf_release_cb,
.terminal_update_cb = terminal_update_cb,
};
struct usbd_context *sample_usbd;
static bool initialized;
int err;
@ -331,15 +352,20 @@ int usb_init(void)
return -EALREADY;
}
if (!device_is_ready(hs_dev)) {
LOG_ERR("Cannot get USB Headset Device");
if (!device_is_ready(mic_dev)) {
LOG_ERR("Cannot get USB Microphone Device");
return -EIO;
}
usb_audio_register(hs_dev, &usb_ops);
err = usb_enable(NULL);
usbd_uac2_set_ops(mic_dev, &usb_audio_ops, NULL);
sample_usbd = sample_usbd_init_device(NULL);
if (sample_usbd == NULL) {
return -ENODEV;
}
err = usbd_enable(sample_usbd);
if (err != 0) {
LOG_ERR("Failed to enable USB");
return err;
}