diff --git a/subsys/bluetooth/audio/shell/CMakeLists.txt b/subsys/bluetooth/audio/shell/CMakeLists.txt index 9913d92994a72..cb86f9042a672 100644 --- a/subsys/bluetooth/audio/shell/CMakeLists.txt +++ b/subsys/bluetooth/audio/shell/CMakeLists.txt @@ -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 diff --git a/subsys/bluetooth/audio/shell/audio.h b/subsys/bluetooth/audio/shell/audio.h index 9e777337e7eb2..23df40401aa47 100644 --- a/subsys/bluetooth/audio/shell/audio.h +++ b/subsys/bluetooth/audio/shell/audio.h @@ -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; @@ -46,8 +47,17 @@ ssize_t cap_initiator_pa_data_add(struct bt_data *data_array, const size_t data_ #include #include +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 @@ -67,6 +77,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; @@ -90,7 +106,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; @@ -182,6 +198,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) diff --git a/subsys/bluetooth/audio/shell/bap.c b/subsys/bluetooth/audio/shell/bap.c index 723afd3837487..06781baeabf08 100644 --- a/subsys/bluetooth/audio/shell/bap.c +++ b/subsys/bluetooth/audio/shell/bap.c @@ -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); } diff --git a/subsys/bluetooth/audio/shell/bap_usb.c b/subsys/bluetooth/audio/shell/bap_usb.c new file mode 100644 index 0000000000000..97c7309bca8fc --- /dev/null +++ b/subsys/bluetooth/audio/shell/bap_usb.c @@ -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 + +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_SOC_NRF5340_CPUAPP) +#include +#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; +} diff --git a/tests/bluetooth/shell/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf b/tests/bluetooth/shell/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf index 8d48d5e1c2072..ff527ea848bb7 100644 --- a/tests/bluetooth/shell/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf +++ b/tests/bluetooth/shell/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf @@ -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 diff --git a/tests/bluetooth/shell/testcase.yaml b/tests/bluetooth/shell/testcase.yaml index 7ce46d768fe18..e10218c36b5d9 100644 --- a/tests/bluetooth/shell/testcase.yaml +++ b/tests/bluetooth/shell/testcase.yaml @@ -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