Bluetooth: BAP: Shell: Add support for USB audio in

Add support for receiving audio data from e.g. a PC
over USB and LC3 encode it before sending it
on BAP audio streams.

This refactores the entire TX path, as it has moved
from only support the sine wave generator, to also
supporting USB.

The encoding and sending of data is now in it's own
thread, instead of relying on the system workqueue thread
and k_work items.

Several other refactors have taken place to reduce lines
of codec (such as the introduction of the bap_foreach_stream
function.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2024-03-16 11:02:35 +01:00 committed by Henrik Brix Andersen
commit 36c1aeaf19
4 changed files with 608 additions and 459 deletions

View file

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

View file

@ -48,7 +48,7 @@ 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);
unsigned long bap_get_stats_interval(void);
#if defined(CONFIG_LIBLC3)
#include "lc3.h"
@ -61,7 +61,7 @@ unsigned long bap_get_recv_stats_interval(void);
#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
#define LOCATION BT_AUDIO_LOCATION_FRONT_LEFT
#define CONTEXT \
(BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED | BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | \
BT_AUDIO_CONTEXT_TYPE_MEDIA | \
@ -75,15 +75,6 @@ struct named_lc3_preset {
struct bt_bap_lc3_preset 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;
@ -106,11 +97,20 @@ struct shell_stream {
/* The uptime tick measured when stream was connected */
int64_t connected_at_ticks;
uint16_t seq_num;
struct k_work_delayable audio_send_work;
bool active;
#if defined(CONFIG_LIBLC3)
atomic_t lc3_enqueue_cnt;
bool active;
size_t encoded_cnt;
size_t lc3_sdu_cnt;
lc3_encoder_mem_48k_t lc3_encoder_mem;
lc3_encoder_t lc3_encoder;
#if defined(CONFIG_USB_DEVICE_AUDIO)
/* Indicates where to read left USB data in the ring buffer */
size_t left_read_idx;
/* Indicates where to read right USB data in the ring buffer */
size_t right_read_idx;
size_t right_ring_buf_fail_cnt;
#endif /* CONFIG_USB_DEVICE_AUDIO */
#endif /* CONFIG_LIBLC3 */
} tx;
#endif /* CONFIG_BT_AUDIO_TX */
@ -119,6 +119,7 @@ struct shell_stream {
struct {
struct bt_iso_recv_info last_info;
size_t empty_sdu_pkts;
size_t valid_sdu_pkts;
size_t lost_pkts;
size_t err_pkts;
size_t dup_psn;
@ -134,6 +135,26 @@ struct shell_stream {
};
};
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);
size_t bap_get_tx_streaming_cnt(void);
void bap_foreach_stream(void (*func)(struct shell_stream *sh_stream, void *data), void *data);
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);
uint16_t get_next_seq_num(struct bt_bap_stream *bap_stream);
struct shell_stream *shell_stream_from_bap_stream(struct bt_bap_stream *bap_stream);
struct bt_bap_stream *bap_stream_from_shell_stream(struct shell_stream *sh_stream);
bool bap_usb_can_get_full_sdu(struct shell_stream *sh_stream);
void bap_usb_get_frame(struct shell_stream *sh_stream, enum bt_audio_location chan_alloc,
int16_t buffer[]);
size_t bap_usb_get_frame_size(const struct shell_stream *sh_stream);
struct broadcast_source {
bool is_cap;
union {

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,7 @@
*/
#include <stdlib.h>
#include <stdint.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/logging/log.h>
@ -27,13 +28,17 @@
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)
#define USB_ENQUEUE_COUNT 30U /* 30ms */
#define USB_FRAME_DURATION_US 1000U
#define USB_SAMPLE_CNT ((USB_FRAME_DURATION_US * USB_SAMPLE_RATE) / USEC_PER_SEC)
#define USB_BYTES_PER_SAMPLE sizeof(int16_t)
#define USB_MONO_FRAME_SIZE (USB_SAMPLE_CNT * USB_BYTES_PER_SAMPLE)
#define USB_CHANNELS 2U
#define USB_STEREO_FRAME_SIZE (USB_MONO_FRAME_SIZE * USB_CHANNELS)
#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)
#if defined CONFIG_BT_AUDIO_RX
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];
@ -43,13 +48,13 @@ struct decoded_sdu {
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);
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);
/* 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};
uint8_t usb_audio_data[USB_STEREO_FRAME_SIZE] = {0};
struct net_buf *pcm_buf;
uint32_t size;
int err;
@ -59,7 +64,7 @@ static void usb_data_request_cb(const struct device *dev)
return;
}
pcm_buf = net_buf_alloc(&usb_tx_buf_pool, K_NO_WAIT);
pcm_buf = net_buf_alloc(&usb_out_buf_pool, K_NO_WAIT);
if (pcm_buf == NULL) {
LOG_WRN("Could not allocate pcm_buf");
return;
@ -73,13 +78,13 @@ static void usb_data_request_cb(const struct device *dev)
if (size != 0) {
static size_t cnt;
if ((++cnt % bap_get_recv_stats_interval()) == 0U) {
if ((++cnt % bap_get_stats_interval()) == 0U) {
LOG_INF("[%zu]: Sending USB audio", cnt);
}
} else {
static size_t cnt;
if ((++cnt % bap_get_recv_stats_interval()) == 0U) {
if ((++cnt % bap_get_stats_interval()) == 0U) {
LOG_INF("[%zu]: Sending empty USB audio", cnt);
}
}
@ -128,7 +133,7 @@ static void bap_usb_send_frames_to_usb(void)
/* 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) {
if ((fail_cnt % bap_get_stats_interval()) == 0U) {
LOG_WRN("[%zu] Could not send more than %zu frames to USB",
fail_cnt, i);
}
@ -174,7 +179,7 @@ static void bap_usb_send_frames_to_usb(void)
}
}
if ((++cnt % bap_get_recv_stats_interval()) == 0U) {
if ((++cnt % bap_get_stats_interval()) == 0U) {
LOG_INF("[%zu]: Sending %u USB audio frame", cnt, frame_cnt);
}
@ -199,7 +204,7 @@ int bap_usb_add_frame_to_usb(enum bt_audio_location chan_allocation, const int16
static size_t cnt;
if ((++cnt % bap_get_recv_stats_interval()) == 0U) {
if ((++cnt % bap_get_stats_interval()) == 0U) {
LOG_INF("[%zu]: Adding USB audio frame", cnt);
}
@ -300,13 +305,221 @@ void bap_usb_clear_frames_to_usb(void)
decoded_sdu.left_frames_cnt = 0U;
decoded_sdu.ts = 0U;
}
#endif /* CONFIG_BT_AUDIO_RX */
#if defined(CONFIG_BT_AUDIO_TX)
BUILD_ASSERT((USB_IN_RING_BUF_SIZE % USB_MONO_FRAME_SIZE) == 0);
static int16_t usb_in_left_ring_buffer[USB_IN_RING_BUF_SIZE];
static int16_t usb_in_right_ring_buffer[USB_IN_RING_BUF_SIZE];
static size_t write_index; /* Points to the oldest/uninitialized data */
size_t bap_usb_get_read_cnt(const struct shell_stream *sh_stream)
{
return (USB_SAMPLE_CNT * sh_stream->lc3_frame_duration_us) / USEC_PER_MSEC;
}
size_t bap_usb_get_frame_size(const struct shell_stream *sh_stream)
{
return USB_BYTES_PER_SAMPLE * bap_usb_get_read_cnt(sh_stream);
}
static void stream_cb(struct shell_stream *sh_stream, void *user_data)
{
if (sh_stream->is_tx) {
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 has_stereo = has_right && has_left;
const bool is_mono = sh_stream->lc3_chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO;
const size_t old_write_index = POINTER_TO_UINT(user_data);
const bool overflowed = write_index < old_write_index;
size_t read_idx;
if (has_stereo) {
/* These should always be the same */
read_idx = MIN(sh_stream->tx.left_read_idx, sh_stream->tx.right_read_idx);
} else if (has_left || is_mono) {
read_idx = sh_stream->tx.left_read_idx;
} else if (has_right) {
read_idx = sh_stream->tx.right_read_idx;
} else {
/* Not a valid USB stream */
return;
}
/* If we are overwriting data that the stream is currently pointing to, then we
* need to update the index so that the stream will point to the oldest valid data
*/
if (read_idx > old_write_index) {
if (read_idx < write_index || (overflowed && read_idx < write_index)) {
sh_stream->tx.left_read_idx = write_index;
sh_stream->tx.right_read_idx = write_index;
}
}
}
}
static void usb_data_received_cb(const struct device *dev, struct net_buf *buf, size_t size)
{
const size_t old_write_index = write_index;
static size_t cnt;
int16_t *pcm;
if (buf == NULL) {
return;
}
if (size != USB_STEREO_FRAME_SIZE) {
net_buf_unref(buf);
return;
}
pcm = (int16_t *)buf->data;
/* Split the data into left and right as LC3 uses LLLLRRRR instead of LRLRLRLR as USB
*
* Since the left and right buffer sizes are a factor of USB_SAMPLE_CNT, then we can always
* add USB_SAMPLE_CNT in a single go without needing to check the remaining size as that
* can be done once afterwards
*/
for (size_t i = 0U, j = 0U; i < USB_SAMPLE_CNT; i++, j += USB_CHANNELS) {
usb_in_left_ring_buffer[write_index + i] = pcm[j];
usb_in_right_ring_buffer[write_index + i] = pcm[j + 1];
}
write_index += USB_SAMPLE_CNT;
if (write_index == USB_IN_RING_BUF_SIZE) {
/* Overflow so that we start overwriting oldest */
write_index = 0U;
}
/* Update the read pointers of each stream to ensure that the new write index is not larger
* than their read indexes
*/
bap_foreach_stream(stream_cb, UINT_TO_POINTER(old_write_index));
if ((++cnt % bap_get_stats_interval()) == 0U) {
LOG_DBG("USB Data received (count = %d)", cnt);
}
net_buf_unref(buf);
}
bool bap_usb_can_get_full_sdu(struct shell_stream *sh_stream)
{
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 has_stereo = has_right && has_left;
const bool is_mono = sh_stream->lc3_chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO;
const uint32_t read_cnt = bap_usb_get_read_cnt(sh_stream);
const uint32_t retrieve_cnt = read_cnt * sh_stream->lc3_frame_blocks_per_sdu;
static bool failed_last_time;
size_t read_idx;
size_t buffer_cnt;
if (has_stereo) {
/* These should always be the same */
read_idx = MIN(sh_stream->tx.left_read_idx, sh_stream->tx.right_read_idx);
} else if (has_left || is_mono) {
read_idx = sh_stream->tx.left_read_idx;
} else if (has_right) {
read_idx = sh_stream->tx.right_read_idx;
} else {
return false;
}
if (read_idx <= write_index) {
buffer_cnt = write_index - read_idx;
} else {
/* Handle the case where the read spans across the end of the buffer */
buffer_cnt = write_index + (USB_IN_RING_BUF_SIZE - read_idx);
}
if (buffer_cnt < retrieve_cnt) {
/* Not enough for a frame yet */
if (!failed_last_time) {
LOG_WRN("Ring buffer (%u/%u) does not contain enough for an entire SDU %u",
buffer_cnt, USB_IN_RING_BUF_SIZE, retrieve_cnt);
}
failed_last_time = true;
return false;
}
failed_last_time = false;
return true;
}
/**
* Reads @p size octets from src, handling wrapping and returns the new idx
* (which is lower than @p idx in the case of wrapping)
*
* bap_usb_can_get_full_sdu should always be called before this to ensure that we are getting
* valid data
*/
static size_t usb_ring_buf_get(int16_t dest[], int16_t src[], size_t idx, size_t cnt)
{
size_t new_idx;
if (idx >= USB_IN_RING_BUF_SIZE) {
LOG_ERR("Invalid idx %zu", idx);
return 0;
}
if ((idx + cnt) < USB_IN_RING_BUF_SIZE) {
/* Simply copy of the data and increment the index*/
memcpy(dest, &src[idx], cnt * USB_BYTES_PER_SAMPLE);
new_idx = idx + cnt;
} else {
/* Handle wrapping */
const size_t first_read_cnt = USB_IN_RING_BUF_SIZE - idx;
const size_t second_read_cnt = cnt - first_read_cnt;
memcpy(dest, &src[idx], first_read_cnt * USB_BYTES_PER_SAMPLE);
memcpy(&dest[first_read_cnt], &src[0], second_read_cnt * USB_BYTES_PER_SAMPLE);
new_idx = second_read_cnt;
}
return new_idx;
}
void bap_usb_get_frame(struct shell_stream *sh_stream, enum bt_audio_location chan_alloc,
int16_t buffer[])
{
const bool is_left = (chan_alloc & BT_AUDIO_LOCATION_FRONT_LEFT) != 0;
const bool is_right = (chan_alloc & BT_AUDIO_LOCATION_FRONT_RIGHT) != 0;
const bool is_mono = chan_alloc == BT_AUDIO_LOCATION_MONO_AUDIO;
const uint32_t read_cnt = bap_usb_get_read_cnt(sh_stream);
if (is_left || is_mono) {
sh_stream->tx.left_read_idx = usb_ring_buf_get(
buffer, usb_in_left_ring_buffer, sh_stream->tx.left_read_idx, read_cnt);
} else if (is_right) {
sh_stream->tx.right_read_idx = usb_ring_buf_get(
buffer, usb_in_right_ring_buffer, sh_stream->tx.right_read_idx, read_cnt);
}
}
#endif /* CONFIG_BT_AUDIO_TX */
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 = {
#if defined(CONFIG_BT_AUDIO_RX)
.data_request_cb = usb_data_request_cb,
.data_written_cb = usb_data_written_cb,
#endif /* CONFIG_BT_AUDIO_RX */
#if defined(CONFIG_BT_AUDIO_TX)
.data_received_cb = usb_data_received_cb,
#endif /* CONFIG_BT_AUDIO_TX */
};
int err;