Bluetooth: BAP: Shell: Add USB out support for the BAP shell

Add USB out support for the BAP shell, so that decoded LC3
data can be sent to the host (e.g. a PC).

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2024-02-28 12:26:21 +01:00 committed by Carles Cufí
parent 55ea78f30b
commit 65e787be58
6 changed files with 664 additions and 58 deletions

View file

@ -81,6 +81,9 @@ zephyr_library_sources_ifdef(
CONFIG_BT_BAP_STREAM
bap.c
)
if (CONFIG_BT_AUDIO_RX AND CONFIG_LIBLC3 AND CONFIG_USB_DEVICE_AUDIO)
zephyr_library_sources(bap_usb.c)
endif()
zephyr_library_sources_ifdef(
CONFIG_BT_BAP_SCAN_DELEGATOR
bap_scan_delegator.c

View file

@ -23,6 +23,7 @@
#include "shell/bt.h"
#define SHELL_PRINT_INDENT_LEVEL_SIZE 2
#define MAX_CODEC_FRAMES_PER_SDU 4U
extern struct bt_csip_set_member_svc_inst *svc_inst;
@ -47,8 +48,17 @@ ssize_t cap_initiator_pa_data_add(struct bt_data *data_array, const size_t data_
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include <zephyr/bluetooth/audio/cap.h>
unsigned long bap_get_recv_stats_interval(void);
#if defined(CONFIG_LIBLC3)
#include "lc3.h"
#define USB_SAMPLE_RATE 48000U
#define LC3_MAX_SAMPLE_RATE 48000U
#define LC3_MAX_FRAME_DURATION_US 10000U
#define LC3_MAX_NUM_SAMPLES_MONO ((LC3_MAX_FRAME_DURATION_US * LC3_MAX_SAMPLE_RATE) / \
USEC_PER_SEC)
#define LC3_MAX_NUM_SAMPLES_STEREO (LC3_MAX_NUM_SAMPLES_MONO * 2U)
#endif /* CONFIG_LIBLC3 */
#define LOCATION BT_AUDIO_LOCATION_FRONT_LEFT | BT_AUDIO_LOCATION_FRONT_RIGHT
@ -68,6 +78,12 @@ struct named_lc3_preset {
const struct named_lc3_preset *bap_get_named_preset(bool is_unicast, enum bt_audio_dir dir,
const char *preset_arg);
size_t bap_get_rx_streaming_cnt(void);
int bap_usb_init(void);
int bap_usb_add_frame_to_usb(enum bt_audio_location lc3_chan_allocation, const int16_t *frame,
size_t frame_size, uint32_t ts);
void bap_usb_clear_frames_to_usb(void);
struct shell_stream {
struct bt_cap_stream stream;
struct bt_audio_codec_cfg codec_cfg;
@ -91,7 +107,7 @@ struct shell_stream {
int64_t connected_at_ticks;
uint16_t seq_num;
struct k_work_delayable audio_send_work;
bool tx_active;
bool active;
#if defined(CONFIG_LIBLC3)
atomic_t lc3_enqueue_cnt;
size_t lc3_sdu_cnt;
@ -183,6 +199,22 @@ int cap_ac_unicast(const struct shell *sh, const struct bap_unicast_ac_param *pa
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
#endif /* CONFIG_BT_BAP_UNICAST */
static inline uint8_t get_chan_cnt(enum bt_audio_location chan_allocation)
{
uint8_t cnt = 0U;
if (chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO) {
return 1;
}
while (chan_allocation != 0) {
cnt += chan_allocation & 1U;
chan_allocation >>= 1;
}
return cnt;
}
static inline void print_qos(const struct shell *sh, const struct bt_audio_codec_qos *qos)
{
#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) || defined(CONFIG_BT_BAP_UNICAST)

View file

@ -36,10 +36,6 @@
#if defined(CONFIG_LIBLC3)
#define LC3_MAX_SAMPLE_RATE 48000
#define LC3_MAX_FRAME_DURATION_US 10000
#define LC3_MAX_NUM_SAMPLES ((LC3_MAX_FRAME_DURATION_US * LC3_MAX_SAMPLE_RATE) / USEC_PER_SEC)
static void clear_lc3_sine_data(struct bt_bap_stream *bap_stream);
static void lc3_decoder_stream_clear(struct shell_stream *sh_stream);
@ -224,7 +220,7 @@ NET_BUF_POOL_FIXED_DEFINE(sine_tx_pool, CONFIG_BT_ISO_TX_BUF_COUNT, SINE_TX_POOL
#define AUDIO_VOLUME (INT16_MAX - 3000) /* codec does clipping above INT16_MAX - 3000 */
#define AUDIO_TONE_FREQUENCY_HZ 400
static int16_t lc3_tx_buf[LC3_MAX_NUM_SAMPLES];
static int16_t lc3_tx_buf[LC3_MAX_NUM_SAMPLES_MONO];
static lc3_encoder_t lc3_encoder;
static lc3_encoder_mem_48k_t lc3_encoder_mem;
static int lc3_encoder_freq_hz;
@ -235,7 +231,6 @@ static void clear_lc3_sine_data(struct bt_bap_stream *bap_stream)
struct shell_stream *sh_stream = shell_stream_from_bap_stream(bap_stream);
if (sh_stream->is_tx) {
sh_stream->tx.tx_active = false;
(void)k_work_cancel_delayable(&sh_stream->tx.audio_send_work);
}
}
@ -314,7 +309,7 @@ static void lc3_audio_send_data(struct k_work *work)
off_t offset = 0;
int err;
if (!sh_stream->is_tx || !sh_stream->tx.tx_active) {
if (!sh_stream->is_tx || !sh_stream->tx.active) {
/* TX has been aborted */
return;
}
@ -415,7 +410,7 @@ static void lc3_sent_cb(struct bt_bap_stream *bap_stream)
atomic_inc(&sh_stream->tx.lc3_enqueue_cnt);
if (!sh_stream->tx.tx_active) {
if (!sh_stream->tx.active) {
/* TX has been aborted */
return;
}
@ -452,9 +447,9 @@ const struct named_lc3_preset *bap_get_named_preset(bool is_unicast, enum bt_aud
}
#if defined(CONFIG_BT_PACS)
static const struct bt_audio_codec_cap lc3_codec_cap =
BT_AUDIO_CODEC_CAP_LC3(BT_AUDIO_CODEC_CAP_FREQ_ANY, BT_AUDIO_CODEC_CAP_DURATION_ANY,
BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1, 2), 30, 240, 2, CONTEXT);
static const struct bt_audio_codec_cap lc3_codec_cap = BT_AUDIO_CODEC_CAP_LC3(
BT_AUDIO_CODEC_CAP_FREQ_ANY, BT_AUDIO_CODEC_CAP_DURATION_ANY,
BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1, 2), 30, 240, MAX_CODEC_FRAMES_PER_SDU, CONTEXT);
#if defined(CONFIG_BT_PAC_SNK)
static struct bt_pacs_cap cap_sink = {
@ -2364,21 +2359,36 @@ static struct bt_le_scan_cb bap_scan_cb = {
#endif /* CONFIG_BT_BAP_BROADCAST_SINK */
#if defined(CONFIG_BT_AUDIO_RX)
static unsigned long recv_stats_interval = 100U;
static unsigned long recv_stats_interval = 1000U;
static size_t rx_streaming_cnt;
size_t bap_get_rx_streaming_cnt(void)
{
return rx_streaming_cnt;
}
#if defined(CONFIG_LIBLC3)
struct lc3_data {
void *fifo_reserved; /* 1st word reserved for use by FIFO */
struct net_buf *buf;
struct shell_stream *sh_stream;
uint32_t ts;
bool do_plc;
};
K_MEM_SLAB_DEFINE(lc3_data_slab, sizeof(struct lc3_data), CONFIG_BT_ISO_RX_BUF_COUNT,
__alignof__(struct lc3_data));
static int16_t lc3_rx_buf[LC3_MAX_NUM_SAMPLES];
static int16_t lc3_rx_buf[LC3_MAX_NUM_SAMPLES_MONO];
static K_FIFO_DEFINE(lc3_in_fifo);
/* We only want to send USB to left/right from a single stream. If we have 2 left streams, the
* outgoing audio is going to be terrible.
* Since a stream can contain stereo data, both of these may be the same stream.
*/
static struct shell_stream *usb_left_stream;
static struct shell_stream *usb_right_stream;
static int init_lc3_decoder(struct shell_stream *sh_stream)
{
if (sh_stream == NULL) {
@ -2407,9 +2417,10 @@ static int init_lc3_decoder(struct shell_stream *sh_stream)
"Initializing the LC3 decoder with %u us duration and %u Hz frequency",
sh_stream->lc3_frame_duration_us, sh_stream->lc3_freq_hz);
/* Create the decoder instance. This shall complete before stream_started() is called. */
sh_stream->rx.lc3_decoder = lc3_setup_decoder(sh_stream->lc3_frame_duration_us,
sh_stream->lc3_freq_hz, 0, /* No resampling */
&sh_stream->rx.lc3_decoder_mem);
sh_stream->rx.lc3_decoder =
lc3_setup_decoder(sh_stream->lc3_frame_duration_us, sh_stream->lc3_freq_hz,
IS_ENABLED(CONFIG_USB_DEVICE_AUDIO) ? USB_SAMPLE_RATE : 0,
&sh_stream->rx.lc3_decoder_mem);
if (sh_stream->rx.lc3_decoder == NULL) {
shell_error(ctx_shell, "Failed to setup LC3 decoder - wrong parameters?\n");
return -EINVAL;
@ -2425,21 +2436,23 @@ static void lc3_decoder_stream_clear(struct shell_stream *sh_stream)
}
}
static bool decode_frame(struct net_buf *buf, struct shell_stream *sh_stream, size_t frame_cnt)
static bool decode_frame(struct lc3_data *data, size_t frame_cnt)
{
const struct shell_stream *sh_stream = data->sh_stream;
const size_t total_frames = sh_stream->lc3_chan_cnt * sh_stream->lc3_frame_blocks_per_sdu;
const uint16_t octets_per_frame = sh_stream->lc3_octets_per_frame;
struct net_buf *buf = data->buf;
void *iso_data;
int err;
if (buf->len == 0U) {
if (data->do_plc) {
iso_data = NULL; /* perform PLC */
if ((sh_stream->rx.decoded_cnt % recv_stats_interval) == 0) {
shell_print(ctx_shell, "[%zu]: Performing PLC", sh_stream->rx.decoded_cnt);
}
} else {
iso_data = net_buf_pull_mem(buf, octets_per_frame);
iso_data = net_buf_pull_mem(data->buf, octets_per_frame);
if ((sh_stream->rx.decoded_cnt % recv_stats_interval) == 0) {
shell_print(ctx_shell, "[%zu]: Decoding frame of size %u (%u/%u)",
@ -2459,35 +2472,96 @@ static bool decode_frame(struct net_buf *buf, struct shell_stream *sh_stream, si
return true;
}
static size_t decode_frame_block(struct net_buf *buf, struct shell_stream *sh_stream,
size_t frame_cnt)
static int get_chan_alloc_from_index(const struct shell_stream *sh_stream, uint8_t index,
enum bt_audio_location *chan_alloc)
{
const bool has_left = (sh_stream->lc3_chan_allocation & BT_AUDIO_LOCATION_FRONT_LEFT) != 0;
const bool has_right =
(sh_stream->lc3_chan_allocation & BT_AUDIO_LOCATION_FRONT_RIGHT) != 0;
const bool is_mono = sh_stream->lc3_chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO;
const bool is_left = index == 0 && has_left;
const bool is_right = has_right && (index == 0U || (index == 1U && has_left));
/* LC3 is always Left before Right, so we can use the index and the stream channel
* allocation to determine if index 0 is left or right.
*/
if (is_left) {
*chan_alloc = BT_AUDIO_LOCATION_FRONT_LEFT;
} else if (is_right) {
*chan_alloc = BT_AUDIO_LOCATION_FRONT_RIGHT;
} else if (is_mono) {
*chan_alloc = BT_AUDIO_LOCATION_MONO_AUDIO;
} else {
/* Not suitable for USB */
return -EINVAL;
}
return 0;
}
static int decode_frame_block(struct lc3_data *data, size_t frame_cnt)
{
const struct shell_stream *sh_stream = data->sh_stream;
const uint8_t chan_cnt = sh_stream->lc3_chan_cnt;
size_t decoded_frames = 0U;
for (uint8_t j = 0U; j < chan_cnt; j++) {
for (uint8_t i = 0U; i < chan_cnt; i++) {
/* We provide the total number of decoded frames to `decode_frame` for logging
* purposes
*/
if (!decode_frame(buf, sh_stream, frame_cnt + decoded_frames)) {
if (decode_frame(data, frame_cnt + decoded_frames)) {
decoded_frames++;
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
enum bt_audio_location chan_alloc;
int err;
err = get_chan_alloc_from_index(sh_stream, i, &chan_alloc);
if (err != 0) {
/* Not suitable for USB */
continue;
}
/* We only want to left or right from one stream to USB */
if ((chan_alloc == BT_AUDIO_LOCATION_FRONT_LEFT &&
sh_stream != usb_left_stream) ||
(chan_alloc == BT_AUDIO_LOCATION_FRONT_RIGHT &&
sh_stream != usb_right_stream)) {
continue;
}
err = bap_usb_add_frame_to_usb(chan_alloc, lc3_rx_buf,
sizeof(lc3_rx_buf), data->ts);
if (err == -EINVAL) {
continue;
}
}
} else {
/* If decoding failed, we clear the data to USB as it would contain
* invalid data
*/
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
bap_usb_clear_frames_to_usb();
}
break;
}
decoded_frames++;
}
return decoded_frames;
}
static void do_lc3_decode(struct shell_stream *sh_stream, struct net_buf *buf)
static void do_lc3_decode(struct lc3_data *data)
{
struct shell_stream *sh_stream = data->sh_stream;
if (sh_stream->is_rx && sh_stream->rx.lc3_decoder != NULL) {
const uint8_t frame_blocks_per_sdu = sh_stream->lc3_frame_blocks_per_sdu;
size_t frame_cnt;
frame_cnt = 0;
for (uint8_t i = 0U; i < frame_blocks_per_sdu; i++) {
const size_t decoded_frames = decode_frame_block(buf, sh_stream, frame_cnt);
const size_t decoded_frames = decode_frame_block(data, frame_cnt);
if (decoded_frames == 0) {
break;
@ -2499,7 +2573,7 @@ static void do_lc3_decode(struct shell_stream *sh_stream, struct net_buf *buf)
sh_stream->rx.decoded_cnt++;
}
net_buf_unref(buf);
net_buf_unref(data->buf);
}
static void lc3_decoder_thread_func(void *arg1, void *arg2, void *arg3)
@ -2514,7 +2588,7 @@ static void lc3_decoder_thread_func(void *arg1, void *arg2, void *arg3)
continue; /* Wait for new data */
}
do_lc3_decode(data->sh_stream, data->buf);
do_lc3_decode(data);
k_mem_slab_free(&lc3_data_slab, (void *)data);
}
@ -2522,6 +2596,11 @@ static void lc3_decoder_thread_func(void *arg1, void *arg2, void *arg3)
#endif /* CONFIG_LIBLC3*/
unsigned long bap_get_recv_stats_interval(void)
{
return recv_stats_interval;
}
static void audio_recv(struct bt_bap_stream *stream,
const struct bt_iso_recv_info *info,
struct net_buf *buf)
@ -2585,7 +2664,7 @@ static void audio_recv(struct bt_bap_stream *stream,
}
if ((info->flags & BT_ISO_FLAGS_VALID) == 0) {
buf->len = 0U; /* Set length to 0 to mark it as invalid for PLC */
data->do_plc = true;
} else if (buf->len != (octets_per_frame * chan_cnt * frame_blocks_per_sdu)) {
if (buf->len != 0U) {
shell_error(
@ -2593,12 +2672,18 @@ static void audio_recv(struct bt_bap_stream *stream,
"Expected %u frame blocks with %u channels of size %u, but "
"length is %u",
frame_blocks_per_sdu, chan_cnt, octets_per_frame, buf->len);
buf->len = 0U; /* Set length to 0 to mark it as invalid for PLC */
}
data->do_plc = true;
}
data->buf = net_buf_ref(buf);
data->sh_stream = sh_stream;
if (info->flags & BT_ISO_FLAGS_TS) {
data->ts = info->ts;
} else {
data->ts = 0U;
}
k_fifo_put(&lc3_in_fifo, data);
}
@ -2645,24 +2730,6 @@ static void stream_enabled_cb(struct bt_bap_stream *stream)
}
#endif /* CONFIG_BT_BAP_UNICAST */
#if defined(CONFIG_LIBLC3)
static uint8_t get_chan_cnt(enum bt_audio_location chan_allocation)
{
uint8_t cnt = 0U;
if (chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO) {
return 1;
}
while (chan_allocation != 0) {
cnt += chan_allocation & 1U;
chan_allocation >>= 1;
}
return cnt;
}
#endif /* CONFIG_LIBLC3 */
static void stream_started_cb(struct bt_bap_stream *bap_stream)
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(bap_stream);
@ -2765,6 +2832,34 @@ static void stream_started_cb(struct bt_bap_stream *bap_stream)
}
sh_stream->rx.decoded_cnt = 0U;
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
if ((sh_stream->lc3_chan_allocation &
BT_AUDIO_LOCATION_FRONT_LEFT) != 0) {
if (usb_left_stream == NULL) {
shell_info(ctx_shell,
"Setting USB left stream to %p",
sh_stream);
usb_left_stream = sh_stream;
} else {
shell_warn(ctx_shell,
"Multiple left streams started");
}
}
if ((sh_stream->lc3_chan_allocation &
BT_AUDIO_LOCATION_FRONT_RIGHT) != 0) {
if (usb_right_stream == NULL) {
shell_info(ctx_shell,
"Setting USB right stream to %p",
sh_stream);
usb_right_stream = sh_stream;
} else {
shell_warn(ctx_shell,
"Multiple right streams started");
}
}
}
}
#endif /* CONFIG_BT_AUDIO_RX */
}
@ -2784,12 +2879,101 @@ static void stream_started_cb(struct bt_bap_stream *bap_stream)
sh_stream->rx.dup_psn = 0U;
sh_stream->rx.rx_cnt = 0U;
sh_stream->rx.dup_ts = 0U;
rx_streaming_cnt++;
}
#endif
}
#if defined(CONFIG_LIBLC3)
static void update_usb_streams(struct shell_stream *sh_stream)
{
/* If the @p sh_stream is the left or right USB stream, we look through other streams to see
* if any of them can be assigned as the USB stream(s)
*/
if (usb_left_stream == sh_stream) {
shell_info(ctx_shell, "Clearing USB left stream (%p)", usb_left_stream);
usb_left_stream = NULL;
#if defined(CONFIG_BT_BAP_UNICAST)
for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) {
struct shell_stream *tmp_sh_stream = &unicast_streams[i];
if (usb_left_stream != NULL) {
break;
}
if (tmp_sh_stream->is_rx && (tmp_sh_stream->lc3_chan_allocation &
BT_AUDIO_LOCATION_FRONT_LEFT) != 0) {
usb_left_stream = tmp_sh_stream;
shell_info(ctx_shell, "Setting new USB left stream to %p",
tmp_sh_stream);
break;
}
}
#endif /* CONFIG_BT_BAP_UNICAST */
#if defined(CONFIG_BT_BAP_BROADCAST_SINK)
for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sink_streams); i++) {
struct shell_stream *tmp_sh_stream = &unicast_streams[i];
if (usb_left_stream != NULL) {
break;
}
if (tmp_sh_stream->is_rx && (tmp_sh_stream->lc3_chan_allocation &
BT_AUDIO_LOCATION_FRONT_LEFT) != 0) {
usb_left_stream = tmp_sh_stream;
shell_info(ctx_shell, "Setting new USB right stream to %p",
tmp_sh_stream);
break;
}
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
}
if (usb_right_stream == sh_stream) {
shell_info(ctx_shell, "Clearing USB right stream (%p)", usb_right_stream);
usb_right_stream = NULL;
#if defined(CONFIG_BT_BAP_UNICAST)
for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) {
struct shell_stream *tmp_sh_stream = &unicast_streams[i];
if (usb_right_stream != NULL) {
break;
}
if (tmp_sh_stream->is_rx && (tmp_sh_stream->lc3_chan_allocation &
BT_AUDIO_LOCATION_FRONT_RIGHT) != 0) {
usb_right_stream = tmp_sh_stream;
break;
}
}
#endif /* CONFIG_BT_BAP_UNICAST */
#if defined(CONFIG_BT_BAP_BROADCAST_SINK)
for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sink_streams); i++) {
struct shell_stream *tmp_sh_stream = &unicast_streams[i];
if (usb_right_stream != NULL) {
break;
}
if (tmp_sh_stream->is_rx && (tmp_sh_stream->lc3_chan_allocation &
BT_AUDIO_LOCATION_FRONT_RIGHT) != 0) {
usb_right_stream = tmp_sh_stream;
}
}
#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */
}
}
#endif /* CONFIG_LIBLC3 */
static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason)
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(stream);
printk("Stream %p stopped with reason 0x%02X\n", stream, reason);
#if defined(CONFIG_LIBLC3)
@ -2797,7 +2981,6 @@ static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason)
#endif /* CONFIG_LIBLC3 */
#if defined(CONFIG_BT_BAP_BROADCAST_SINK)
struct shell_stream *sh_stream = shell_stream_from_bap_stream(stream);
if (IS_ARRAY_ELEMENT(broadcast_sink_streams, sh_stream)) {
if (default_broadcast_sink.stream_cnt != 0) {
@ -2813,6 +2996,25 @@ static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason)
}
}
#endif /* CONFIG_BT_BAP_BROADCAST_SINK */
#if defined(CONFIG_BT_AUDIO_RX)
if (sh_stream->is_rx) {
rx_streaming_cnt--;
}
#endif
#if defined(CONFIG_BT_AUDIO_TX)
if (sh_stream->is_tx) {
sh_stream->tx.active = false;
}
#endif
sh_stream->is_rx = sh_stream->is_tx = false;
#if defined(CONFIG_LIBLC3)
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
update_usb_streams(sh_stream);
}
#endif /* CONFIG_LIBLC3 */
}
#if defined(CONFIG_BT_BAP_UNICAST)
@ -2871,6 +3073,12 @@ static void stream_released_cb(struct bt_bap_stream *stream)
sh_stream->is_tx = false;
sh_stream->is_rx = false;
#if defined(CONFIG_LIBLC3)
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
update_usb_streams(sh_stream);
}
#endif /* CONFIG_LIBLC3 */
}
#endif /* CONFIG_BT_BAP_UNICAST */
@ -3449,14 +3657,19 @@ static int cmd_init(const struct shell *sh, size_t argc, char *argv[])
#if defined(CONFIG_LIBLC3) && defined(CONFIG_BT_AUDIO_RX)
static K_KERNEL_STACK_DEFINE(lc3_decoder_thread_stack, 4096);
/* make it slightly lower priority than the RX thread */
int lc3_decoder_thread_prio = K_PRIO_COOP(CONFIG_BT_RX_PRIO + 1);
int lc3_decoder_thread_prio = K_PRIO_PREEMPT(5);
static struct k_thread lc3_decoder_thread;
k_thread_create(&lc3_decoder_thread, lc3_decoder_thread_stack,
K_KERNEL_STACK_SIZEOF(lc3_decoder_thread_stack), lc3_decoder_thread_func,
NULL, NULL, NULL, lc3_decoder_thread_prio, 0, K_NO_WAIT);
k_thread_name_set(&lc3_decoder_thread, "LC3 Decode");
k_thread_name_set(&lc3_decoder_thread, "LC3 Decoder");
if (IS_ENABLED(CONFIG_USB_DEVICE_AUDIO)) {
err = bap_usb_init();
__ASSERT(err == 0, "Failed to enable USB: %d", err);
}
#endif /* CONFIG_LIBLC3 && CONFIG_BT_AUDIO_RX */
initialized = true;
@ -3566,7 +3779,7 @@ static int stream_start_sine(struct shell_stream *sh_stream)
return -ENOEXEC;
}
sh_stream->tx.tx_active = true;
sh_stream->tx.active = true;
sh_stream->tx.seq_num = get_next_seq_num(bap_stream_from_shell_stream(sh_stream));
return 0;
@ -3694,7 +3907,7 @@ static int cmd_stop_sine(const struct shell *sh, size_t argc, char *argv[])
struct bt_bap_stream *bap_stream =
bap_stream_from_shell_stream(&unicast_streams[i]);
if (unicast_streams[i].is_tx && unicast_streams[i].tx.tx_active) {
if (unicast_streams[i].is_tx && unicast_streams[i].tx.active) {
clear_lc3_sine_data(bap_stream);
shell_print(sh, "Stopped transmitting on stream %p", bap_stream);
}
@ -3703,7 +3916,7 @@ static int cmd_stop_sine(const struct shell *sh, size_t argc, char *argv[])
for (size_t i = 0U; i < ARRAY_SIZE(broadcast_source_streams); i++) {
struct bt_bap_stream *bap_stream =
bap_stream_from_shell_stream(&broadcast_source_streams[i]);
if (unicast_streams[i].is_tx && unicast_streams[i].tx.tx_active) {
if (unicast_streams[i].is_tx && unicast_streams[i].tx.active) {
clear_lc3_sine_data(bap_stream);
shell_print(sh, "Stopped transmitting on stream %p", bap_stream);
}
@ -3711,7 +3924,7 @@ static int cmd_stop_sine(const struct shell *sh, size_t argc, char *argv[])
} else {
struct shell_stream *sh_stream = shell_stream_from_bap_stream(default_stream);
if (sh_stream->is_tx && sh_stream->tx.tx_active) {
if (sh_stream->is_tx && sh_stream->tx.active) {
clear_lc3_sine_data(default_stream);
shell_print(sh, "Stopped transmitting on stream %p", default_stream);
}

View file

@ -0,0 +1,339 @@
/**
* @file
* @brief Bluetooth Basic Audio Profile shell USB extension
*
* This files handles all the USB related functionality to audio in/out for the BAP shell
*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/logging/log.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/usb/class/usb_audio.h>
#if defined(CONFIG_SOC_NRF5340_CPUAPP)
#include <nrfx_clock.h>
#endif /* CONFIG_SOC_NRF5340_CPUAPP */
#include "shell/bt.h"
#include "audio.h"
LOG_MODULE_REGISTER(bap_usb, CONFIG_BT_BAP_STREAM_LOG_LEVEL);
#define USB_ENQUEUE_COUNT 30U /* 30ms */
#define USB_FRAME_DURATION_US 1000U
#define USB_MONO_SAMPLE_SIZE \
((USB_FRAME_DURATION_US * USB_SAMPLE_RATE * sizeof(int16_t)) / USEC_PER_SEC)
#define USB_STEREO_SAMPLE_SIZE (USB_MONO_SAMPLE_SIZE * 2U)
#define USB_RING_BUF_SIZE (CONFIG_BT_ISO_RX_BUF_COUNT * LC3_MAX_NUM_SAMPLES_STEREO)
struct decoded_sdu {
int16_t right_frames[MAX_CODEC_FRAMES_PER_SDU][LC3_MAX_NUM_SAMPLES_MONO];
int16_t left_frames[MAX_CODEC_FRAMES_PER_SDU][LC3_MAX_NUM_SAMPLES_MONO];
size_t right_frames_cnt;
size_t left_frames_cnt;
size_t mono_frames_cnt;
uint32_t ts;
} decoded_sdu;
RING_BUF_DECLARE(usb_out_ring_buf, USB_RING_BUF_SIZE);
NET_BUF_POOL_DEFINE(usb_tx_buf_pool, USB_ENQUEUE_COUNT, USB_STEREO_SAMPLE_SIZE, 0, net_buf_destroy);
/* USB consumer callback, called every 1ms, consumes data from ring-buffer */
static void usb_data_request_cb(const struct device *dev)
{
uint8_t usb_audio_data[USB_STEREO_SAMPLE_SIZE] = {0};
struct net_buf *pcm_buf;
uint32_t size;
int err;
if (bap_get_rx_streaming_cnt() == 0) {
/* no-op as we have no streams that receive data */
return;
}
pcm_buf = net_buf_alloc(&usb_tx_buf_pool, K_NO_WAIT);
if (pcm_buf == NULL) {
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));
if (size != 0) {
static size_t cnt;
if ((++cnt % bap_get_recv_stats_interval()) == 0U) {
LOG_INF("[%zu]: Sending USB audio", cnt);
}
} else {
static size_t cnt;
if ((++cnt % bap_get_recv_stats_interval()) == 0U) {
LOG_INF("[%zu]: Sending empty USB audio", cnt);
}
}
err = usb_audio_send(dev, pcm_buf, sizeof(usb_audio_data));
if (err != 0) {
static size_t cnt;
cnt++;
if ((cnt % 1000) == 0) {
LOG_ERR("Failed to send USB audio: %d (%zu)", err, cnt);
}
net_buf_unref(pcm_buf);
}
}
static void usb_data_written_cb(const struct device *dev, struct net_buf *buf, size_t size)
{
/* Unreference the buffer now that the USB is done with it */
net_buf_unref(buf);
}
static void bap_usb_send_frames_to_usb(void)
{
const bool is_left_only =
decoded_sdu.right_frames_cnt == 0U && decoded_sdu.mono_frames_cnt == 0U;
const bool is_right_only =
decoded_sdu.left_frames_cnt == 0U && decoded_sdu.mono_frames_cnt == 0U;
const bool is_mono_only =
decoded_sdu.left_frames_cnt == 0U && decoded_sdu.right_frames_cnt == 0U;
const bool is_single_channel = is_left_only || is_right_only || is_mono_only;
const size_t frame_cnt =
MAX(decoded_sdu.mono_frames_cnt,
MAX(decoded_sdu.left_frames_cnt, decoded_sdu.right_frames_cnt));
static size_t cnt;
/* Send frames to USB - If we only have a single channel we mix it to stereo */
for (size_t i = 0U; i < frame_cnt; i++) {
static int16_t stereo_frame[LC3_MAX_NUM_SAMPLES_STEREO];
const int16_t *right_frame = decoded_sdu.right_frames[i];
const int16_t *left_frame = decoded_sdu.left_frames[i];
const int16_t *mono_frame = decoded_sdu.left_frames[i]; /* use left as mono */
static size_t fail_cnt;
uint32_t rb_size;
/* Not enough space to store data */
if (ring_buf_space_get(&usb_out_ring_buf) < sizeof(stereo_frame)) {
if ((fail_cnt % bap_get_recv_stats_interval()) == 0U) {
LOG_WRN("[%zu] Could not send more than %zu frames to USB",
fail_cnt, i);
}
fail_cnt++;
break;
}
fail_cnt = 0U;
/* Generate the stereo frame
*
* If we only have single channel then we mix that to stereo
*/
for (int j = 0; j < LC3_MAX_NUM_SAMPLES_MONO; j++) {
if (is_single_channel) {
int16_t sample = 0;
/* Mix to stereo as LRLRLRLR */
if (is_left_only) {
sample = left_frame[j];
} else if (is_right_only) {
sample = right_frame[j];
} else if (is_mono_only) {
sample = mono_frame[j];
}
stereo_frame[j * 2] = sample;
stereo_frame[j * 2 + 1] = sample;
} else {
stereo_frame[j * 2] = left_frame[j];
stereo_frame[j * 2 + 1] = right_frame[j];
}
}
rb_size = ring_buf_put(&usb_out_ring_buf, (uint8_t *)stereo_frame,
sizeof(stereo_frame));
if (rb_size != sizeof(stereo_frame)) {
LOG_WRN("Failed to put frame on USB ring buf");
break;
}
}
if ((++cnt % bap_get_recv_stats_interval()) == 0U) {
LOG_INF("[%zu]: Sending %u USB audio frame", cnt, frame_cnt);
}
bap_usb_clear_frames_to_usb();
}
static bool ts_overflowed(uint32_t ts)
{
/* If the timestamp is a factor of 10 in difference, then we assume that TS overflowed
* We cannot simply check if `ts < decoded_sdu.ts` as that could also indicate old data
*/
return ((uint64_t)ts * 10 < decoded_sdu.ts);
}
int bap_usb_add_frame_to_usb(enum bt_audio_location chan_allocation, const int16_t *frame,
size_t frame_size, uint32_t ts)
{
const bool is_left = (chan_allocation & BT_AUDIO_LOCATION_FRONT_LEFT) != 0;
const bool is_right = (chan_allocation & BT_AUDIO_LOCATION_FRONT_RIGHT) != 0;
const bool is_mono = chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO;
const uint8_t ts_jitter_us = 100; /* timestamps may have jitter */
static size_t cnt;
if ((++cnt % bap_get_recv_stats_interval()) == 0U) {
LOG_INF("[%zu]: Adding USB audio frame", cnt);
}
if (frame_size > LC3_MAX_NUM_SAMPLES_MONO * sizeof(int16_t) || frame_size == 0U) {
LOG_DBG("Invalid frame of size %zu", frame_size);
return -EINVAL;
}
if (get_chan_cnt(chan_allocation) != 1) {
LOG_DBG("Invalid channel allocation %d", chan_allocation);
return -EINVAL;
}
if (((is_left || is_right) && decoded_sdu.mono_frames_cnt != 0) ||
(is_mono &&
(decoded_sdu.left_frames_cnt != 0U || decoded_sdu.right_frames_cnt != 0U))) {
LOG_DBG("Cannot mix and match mono with left or right");
return -EINVAL;
}
/* Check if the frame can be combined with a previous frame from another channel, of if
* we have to send previous data to USB and then store the current frame
*
* This is done by comparing the timestamps of the frames, and in the case that they are the
* same, there are additional checks to see if we have received more left than right frames,
* in which case we also send existing data
*/
if (ts + ts_jitter_us < decoded_sdu.ts && !ts_overflowed(ts)) {
/* Old data, discard */
return -ENOEXEC;
} else if (ts > decoded_sdu.ts + ts_jitter_us || ts_overflowed(ts)) {
/* We are getting new data - Send existing data to ring buffer */
bap_usb_send_frames_to_usb();
} else { /* same timestamp */
bool send = false;
if (is_left && decoded_sdu.left_frames_cnt > decoded_sdu.right_frames_cnt) {
/* We are receiving left again before a right, send to USB */
send = true;
} else if (is_right && decoded_sdu.right_frames_cnt > decoded_sdu.left_frames_cnt) {
/* We are receiving right again before a left, send to USB */
send = true;
} else if (is_mono) {
/* always send mono as it comes */
send = true;
}
if (send) {
bap_usb_send_frames_to_usb();
}
}
if (is_left) {
if (decoded_sdu.left_frames_cnt >= ARRAY_SIZE(decoded_sdu.left_frames)) {
LOG_WRN("Could not add more left frames");
return -ENOMEM;
}
memcpy(decoded_sdu.left_frames[decoded_sdu.left_frames_cnt++], frame, frame_size);
} else if (is_right) {
if (decoded_sdu.right_frames_cnt >= ARRAY_SIZE(decoded_sdu.right_frames)) {
LOG_WRN("Could not add more right frames");
return -ENOMEM;
}
memcpy(decoded_sdu.right_frames[decoded_sdu.right_frames_cnt++], frame, frame_size);
} else if (is_mono) {
/* Use left as mono*/
if (decoded_sdu.mono_frames_cnt >= ARRAY_SIZE(decoded_sdu.left_frames)) {
LOG_WRN("Could not add more mono frames");
return -ENOMEM;
}
memcpy(decoded_sdu.left_frames[decoded_sdu.mono_frames_cnt++], frame, frame_size);
} else {
/* Unsupported channel */
LOG_DBG("Unsupported channel %d", chan_allocation);
return -EINVAL;
}
decoded_sdu.ts = ts;
return 0;
}
void bap_usb_clear_frames_to_usb(void)
{
decoded_sdu.mono_frames_cnt = 0U;
decoded_sdu.right_frames_cnt = 0U;
decoded_sdu.left_frames_cnt = 0U;
decoded_sdu.ts = 0U;
}
int bap_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,
};
int err;
if (!device_is_ready(hs_dev)) {
LOG_ERR("Cannot get USB Headset Device");
return -EIO;
}
usb_audio_register(hs_dev, &usb_ops);
err = usb_enable(NULL);
if (err != 0) {
LOG_ERR("Failed to enable USB");
return err;
}
if (IS_ENABLED(CONFIG_SOC_NRF5340_CPUAPP)) {
/* Use this to turn on 128 MHz clock for the nRF5340 cpu_app
* This may not be required, but reduces the risk of not decoding fast enough
* to keep up with USB
*/
err = nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK, NRF_CLOCK_HFCLK_DIV_1);
err -= NRFX_ERROR_BASE_NUM;
if (err != 0) {
LOG_WRN("Failed to set 128 MHz: %d", err);
}
}
return 0;
}

View file

@ -1,6 +1,10 @@
# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3=y
CONFIG_RING_BUFFER=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_AUDIO=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr Shell USB"
# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence
# inctease stack size for that thread.
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096

View file

@ -349,3 +349,18 @@ tests:
platform_allow: native_posix
extra_configs:
- CONFIG_BT_CAP_COMMANDER=n
bluetooth.audio_shell.no_lc3:
extra_args: CONF_FILE="audio.conf"
build_only: true
platform_allow: native_posix
extra_configs:
- CONFIG_FPU=n
- CONFIG_LIBLC3=n
bluetooth.audio_shell.no_usb:
extra_args: CONF_FILE="audio.conf"
build_only: true
platform_allow: native_posix
extra_configs:
- CONFIG_RING_BUFFER=n
- CONFIG_USB_DEVICE_STACK=n
- CONFIG_USB_DEVICE_AUDIO=n