diff --git a/samples/bluetooth/broadcast_audio_source/Kconfig b/samples/bluetooth/broadcast_audio_source/Kconfig index cc3e714ded7493..d88378d08488ac 100644 --- a/samples/bluetooth/broadcast_audio_source/Kconfig +++ b/samples/bluetooth/broadcast_audio_source/Kconfig @@ -28,4 +28,13 @@ config ENABLE_LC3 select LIBLC3 select FPU +config USE_USB_AUDIO_INPUT + bool "Use USB Audio as input" + # By default, use the USB Audio path is disabled. + default n + depends on ENABLE_LC3 + select USB_DEVICE_STACK + select USB_DEVICE_AUDIO + select RING_BUFFER + source "Kconfig.zephyr" diff --git a/samples/bluetooth/broadcast_audio_source/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf b/samples/bluetooth/broadcast_audio_source/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf index 8d28a7031d2359..ff8004f0dc050e 100644 --- a/samples/bluetooth/broadcast_audio_source/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf +++ b/samples/bluetooth/broadcast_audio_source/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf @@ -1,4 +1,8 @@ -# 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 CONFIG_MAIN_STACK_SIZE=4096 + +# Use USB Audio as input +CONFIG_USE_USB_AUDIO_INPUT=y +CONFIG_USB_DEVICE_PRODUCT="Zephyr Broadcast Source" + +# Two streams in one subgroup (stereo) +CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT=1 diff --git a/samples/bluetooth/broadcast_audio_source/boards/nrf5340dk_nrf5340_cpuapp.conf b/samples/bluetooth/broadcast_audio_source/boards/nrf5340dk_nrf5340_cpuapp.conf index 8d28a7031d2359..5df721fba26741 100644 --- a/samples/bluetooth/broadcast_audio_source/boards/nrf5340dk_nrf5340_cpuapp.conf +++ b/samples/bluetooth/broadcast_audio_source/boards/nrf5340dk_nrf5340_cpuapp.conf @@ -1,4 +1 @@ -# 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 CONFIG_MAIN_STACK_SIZE=4096 diff --git a/samples/bluetooth/broadcast_audio_source/prj.conf b/samples/bluetooth/broadcast_audio_source/prj.conf index 2e0fcb778de6c3..d74cbbe50ce46b 100644 --- a/samples/bluetooth/broadcast_audio_source/prj.conf +++ b/samples/bluetooth/broadcast_audio_source/prj.conf @@ -13,5 +13,3 @@ CONFIG_BT_ISO_TX_BUF_COUNT=4 CONFIG_BT_ISO_TX_MTU=60 CONFIG_BT_DEVICE_NAME="Broadcast Audio Source" - -CONFIG_BT_TINYCRYPT_ECC=y diff --git a/samples/bluetooth/broadcast_audio_source/src/main.c b/samples/bluetooth/broadcast_audio_source/src/main.c index f9a2a5330b2898..8a129e3f361930 100644 --- a/samples/bluetooth/broadcast_audio_source/src/main.c +++ b/samples/bluetooth/broadcast_audio_source/src/main.c @@ -35,19 +35,93 @@ BUILD_ASSERT(CONFIG_BT_ISO_TX_BUF_COUNT >= TOTAL_BUF_NEEDED, #if defined(CONFIG_BAP_BROADCAST_16_2_1) static struct bt_bap_lc3_preset preset_active = BT_BAP_LC3_BROADCAST_PRESET_16_2_1( - BT_AUDIO_LOCATION_FRONT_LEFT, BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED); + BT_AUDIO_LOCATION_FRONT_LEFT | BT_AUDIO_LOCATION_FRONT_RIGHT, + BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED); + +#define BROADCAST_SAMPLE_RATE 16000 #elif defined(CONFIG_BAP_BROADCAST_24_2_1) static struct bt_bap_lc3_preset preset_active = BT_BAP_LC3_BROADCAST_PRESET_24_2_1( - BT_AUDIO_LOCATION_FRONT_LEFT, BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED); + BT_AUDIO_LOCATION_FRONT_LEFT | BT_AUDIO_LOCATION_FRONT_RIGHT, + BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED); + +#define BROADCAST_SAMPLE_RATE 24000 + +#endif +#if defined(CONFIG_BAP_BROADCAST_16_2_1) +#define MAX_SAMPLE_RATE 16000 +#elif defined(CONFIG_BAP_BROADCAST_24_2_1) +#define MAX_SAMPLE_RATE 24000 #endif +#define MAX_FRAME_DURATION_US 10000 +#define MAX_NUM_SAMPLES ((MAX_FRAME_DURATION_US * MAX_SAMPLE_RATE) / USEC_PER_SEC) + +#if defined(CONFIG_LIBLC3) +#include "lc3.h" + +#if defined(CONFIG_USB_DEVICE_AUDIO) +#include +#include +#include + +/* USB Audio Data is downsampled from 48kHz to match broadcast preset when receiving data */ +#define USB_SAMPLE_RATE 48000 +#define USB_DOWNSAMPLE_RATE BROADCAST_SAMPLE_RATE +#define USB_FRAME_DURATION_US 1000 +#define USB_NUM_SAMPLES ((USB_FRAME_DURATION_US * USB_DOWNSAMPLE_RATE) / USEC_PER_SEC) +#define USB_BYTES_PER_SAMPLE 2 +#define USB_CHANNELS 2 + +#define RING_BUF_USB_FRAMES 20 +#define AUDIO_RING_BUF_BYTES USB_NUM_SAMPLES * USB_BYTES_PER_SAMPLE * RING_BUF_USB_FRAMES +#else /* !defined(CONFIG_USB_DEVICE_AUDIO) */ + +#include + +#define AUDIO_VOLUME (INT16_MAX - 3000) /* codec does clipping above INT16_MAX - 3000 */ +#define AUDIO_TONE_FREQUENCY_HZ 400 + +/** + * Use the math lib to generate a sine-wave using 16 bit samples into a buffer. + * + * @param buf Destination buffer + * @param length_us Length of the buffer in microseconds + * @param frequency_hz frequency in Hz + * @param sample_rate_hz sample-rate in Hz. + */ +static void fill_audio_buf_sin(int16_t *buf, int length_us, int frequency_hz, int sample_rate_hz) +{ + const int sine_period_samples = sample_rate_hz / frequency_hz; + const unsigned int num_samples = (length_us * sample_rate_hz) / USEC_PER_SEC; + const float step = 2 * 3.1415f / sine_period_samples; + + for (unsigned int i = 0; i < num_samples; i++) { + const float sample = sin(i * step); + + buf[i] = (int16_t)(AUDIO_VOLUME * sample); + } +} +#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */ +#endif /* defined(CONFIG_LIBLC3) */ static struct broadcast_source_stream { struct bt_bap_stream stream; uint16_t seq_num; size_t sent_cnt; +#if defined(CONFIG_LIBLC3) + lc3_encoder_t lc3_encoder; +#if defined(CONFIG_BAP_BROADCAST_16_2_1) + lc3_encoder_mem_16k_t lc3_encoder_mem; +#elif defined(CONFIG_BAP_BROADCAST_24_2_1) + lc3_encoder_mem_48k_t lc3_encoder_mem; +#endif +#if defined(CONFIG_USB_DEVICE_AUDIO) + struct ring_buf audio_ring_buf; + uint8_t _ring_buffer_memory[AUDIO_RING_BUF_BYTES]; +#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */ +#endif /* defined(CONFIG_LIBLC3) */ } streams[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT]; static struct bt_bap_broadcast_source *broadcast_source; @@ -55,10 +129,8 @@ NET_BUF_POOL_FIXED_DEFINE(tx_pool, TOTAL_BUF_NEEDED, BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); -#define MAX_SAMPLE_RATE 16000 -#define MAX_FRAME_DURATION_US 10000 -#define MAX_NUM_SAMPLES ((MAX_FRAME_DURATION_US * MAX_SAMPLE_RATE) / USEC_PER_SEC) -static int16_t mock_data[MAX_NUM_SAMPLES]; + +static int16_t send_pcm_data[MAX_NUM_SAMPLES]; static uint16_t seq_num; static bool stopping; @@ -68,17 +140,80 @@ static K_SEM_DEFINE(sem_stopped, 0U, ARRAY_SIZE(streams)); #define BROADCAST_SOURCE_LIFETIME 120U /* seconds */ #if defined(CONFIG_LIBLC3) - -#include "lc3.h" - -static lc3_encoder_t lc3_encoder; -static lc3_encoder_mem_16k_t lc3_encoder_mem; static int freq_hz; static int frame_duration_us; static int frames_per_sdu; static int octets_per_frame; -static void init_lc3(void) +static K_SEM_DEFINE(lc3_encoder_sem, 0U, ARRAY_SIZE(streams)); +#endif + +static void send_data(struct broadcast_source_stream *source_stream) +{ + struct bt_bap_stream *stream = &source_stream->stream; + struct net_buf *buf; + int ret; + + if (stopping) { + return; + } + + buf = net_buf_alloc(&tx_pool, K_FOREVER); + if (buf == NULL) { + printk("Could not allocate buffer when sending on %p\n", + stream); + return; + } + + net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); +#if defined(CONFIG_LIBLC3) + uint8_t lc3_encoded_buffer[preset_active.qos.sdu]; + + if (source_stream->lc3_encoder == NULL) { + printk("LC3 encoder not setup, cannot encode data.\n"); + return; + } + +#if defined(CONFIG_USB_DEVICE_AUDIO) + uint32_t size = ring_buf_get(&source_stream->audio_ring_buf, + (uint8_t *)send_pcm_data, sizeof(send_pcm_data)); + + if (size < sizeof(send_pcm_data)) { + const size_t padding_size = sizeof(send_pcm_data) - size; + + printk("Not enough bytes ready, padding %d!\n", padding_size); + memset(&((uint8_t *)send_pcm_data)[size], 0, padding_size); + } +#endif + + ret = lc3_encode(source_stream->lc3_encoder, LC3_PCM_FORMAT_S16, + send_pcm_data, 1, octets_per_frame, lc3_encoded_buffer); + if (ret == -1) { + printk("LC3 encoder failed - wrong parameters?: %d", ret); + return; + } + + net_buf_add_mem(buf, lc3_encoded_buffer, preset_active.qos.sdu); +#else + net_buf_add_mem(buf, send_pcm_data, preset_active.qos.sdu); +#endif /* defined(CONFIG_LIBLC3) */ + + ret = bt_bap_stream_send(stream, buf, source_stream->seq_num++, BT_ISO_TIMESTAMP_NONE); + if (ret < 0) { + /* This will end broadcasting on this stream. */ + printk("Unable to broadcast data on %p: %d\n", stream, ret); + net_buf_unref(buf); + return; + } + + source_stream->sent_cnt++; + if ((source_stream->sent_cnt % 1000U) == 0U) { + printk("Stream %p: Sent %u total ISO packets\n", stream, source_stream->sent_cnt); + } +} + +#if defined(CONFIG_LIBLC3) +static void init_lc3_thread(void *arg1, void *arg2, void *arg3) { const struct bt_audio_codec_cfg *codec_cfg = &preset_active.codec_cfg; int ret; @@ -116,14 +251,95 @@ static void init_lc3(void) return; } +#if !defined(CONFIG_USB_DEVICE_AUDIO) + /* If USB is not used as a sound source, generate a sine wave */ + fill_audio_buf_sin(send_pcm_data, frame_duration_us, AUDIO_TONE_FREQUENCY_HZ, freq_hz); +#endif + /* Create the encoder instance. This shall complete before stream_started() is called. */ - lc3_encoder = lc3_setup_encoder(frame_duration_us, freq_hz, 0, /* No resampling */ - &lc3_encoder_mem); + for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { + printk("Initializing lc3 encoder for stream %zu\n", i); + streams[i].lc3_encoder = lc3_setup_encoder(frame_duration_us, freq_hz, + 0, &streams[i].lc3_encoder_mem); - if (lc3_encoder == NULL) { - printk("ERROR: Failed to setup LC3 encoder - wrong parameters?\n"); + if (streams[i].lc3_encoder == NULL) { + printk("ERROR: Failed to setup LC3 encoder - wrong parameters?\n"); + } + } + + while (true) { + for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { + k_sem_take(&lc3_encoder_sem, K_FOREVER); + } + for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { + send_data(&streams[i]); + } } } + +#define LC3_ENCODER_STACK_SIZE 4 * 4096 +#define LC3_ENCODER_PRIORITY 5 + +K_THREAD_DEFINE(encoder, LC3_ENCODER_STACK_SIZE, init_lc3_thread, + NULL, NULL, NULL, LC3_ENCODER_PRIORITY, 0, -1); + +#if defined(CONFIG_USB_DEVICE_AUDIO) +static void data_received(const struct device *dev, + struct net_buf *buffer, + size_t size) +{ + static int count; + int16_t *pcm; + int nsamples, ratio; + int16_t usb_pcm_data[USB_CHANNELS][USB_NUM_SAMPLES]; + + if (!buffer) { + return; + } + + if (!size) { + net_buf_unref(buffer); + return; + } + + pcm = (int16_t *)net_buf_pull_mem(buffer, size); + + /* 'size' is in bytes, containing 1ms, 48kHz, stereo, 2 bytes per sample. + * Take left channel and do a simple downsample to 16kHz/24Khz + * matching the broadcast preset. + */ + + ratio = USB_SAMPLE_RATE / USB_DOWNSAMPLE_RATE; + nsamples = size / (sizeof(int16_t) * USB_CHANNELS * ratio); + for (size_t i = 0, j = 0; i < nsamples; i++, j += USB_CHANNELS * ratio) { + usb_pcm_data[0][i] = pcm[j]; + usb_pcm_data[1][i] = pcm[j + 1]; + } + + for (size_t i = 0U; i < MIN(ARRAY_SIZE(streams), 2); i++) { + const uint32_t size_put = ring_buf_put(&(streams[i].audio_ring_buf), + (uint8_t *)(usb_pcm_data[i]), nsamples * USB_BYTES_PER_SAMPLE); + if (size_put < nsamples * USB_BYTES_PER_SAMPLE) { + printk("Not enough room for samples in %s buffer: %u < %u, total capacity: %u\n", + i == 0 ? "left" : "right", + size_put, + nsamples * USB_BYTES_PER_SAMPLE, + ring_buf_capacity_get(&(streams[i].audio_ring_buf))); + } + } + + count++; + if ((count % 1000) == 0) { + printk("USB Data received (count = %d)\n", count); + } + + net_buf_unref(buffer); +} + +static const struct usb_audio_ops ops = { + .data_received_cb = data_received +}; +#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */ #endif /* defined(CONFIG_LIBLC3) */ static void stream_started_cb(struct bt_bap_stream *stream) @@ -143,57 +359,15 @@ static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) static void stream_sent_cb(struct bt_bap_stream *stream) { - struct broadcast_source_stream *source_stream = - CONTAINER_OF(stream, struct broadcast_source_stream, stream); - struct net_buf *buf; - int ret; - - if (stopping) { - return; - } - - buf = net_buf_alloc(&tx_pool, K_FOREVER); - if (buf == NULL) { - printk("Could not allocate buffer when sending on %p\n", - stream); - return; - } - - net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); #if defined(CONFIG_LIBLC3) - int lc3_ret; - uint8_t lc3_encoder_buffer[preset_active.qos.sdu]; - - if (lc3_encoder == NULL) { - printk("LC3 encoder not setup, cannot encode data.\n"); - return; - } - - lc3_ret = lc3_encode(lc3_encoder, LC3_PCM_FORMAT_S16, mock_data, 1, octets_per_frame, - lc3_encoder_buffer); - - if (lc3_ret == -1) { - printk("LC3 encoder failed - wrong parameters?: %d", lc3_ret); - return; - } - - net_buf_add_mem(buf, lc3_encoder_buffer, preset_active.qos.sdu); + k_sem_give(&lc3_encoder_sem); #else - net_buf_add_mem(buf, mock_data, preset_active.qos.sdu); -#endif /* defined(CONFIG_LIBLC3) */ - - ret = bt_bap_stream_send(stream, buf, source_stream->seq_num++, BT_ISO_TIMESTAMP_NONE); - if (ret < 0) { - /* This will end broadcasting on this stream. */ - printk("Unable to broadcast data on %p: %d\n", stream, ret); - net_buf_unref(buf); - return; - } + /* If no LC3 encoder is used, just send mock data directly */ + struct broadcast_source_stream *source_stream = + CONTAINER_OF(stream, struct broadcast_source_stream, stream); - source_stream->sent_cnt++; - if ((source_stream->sent_cnt % 1000U) == 0U) { - printk("Stream %p: Sent %u total ISO packets\n", stream, source_stream->sent_cnt); - } + send_data(source_stream); +#endif } static struct bt_bap_stream_ops stream_ops = { @@ -210,10 +384,12 @@ static int setup_broadcast_source(struct bt_bap_broadcast_source **source) subgroup_param[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT]; struct bt_bap_broadcast_source_param create_param; const size_t streams_per_subgroup = ARRAY_SIZE(stream_params) / ARRAY_SIZE(subgroup_param); + uint8_t left[] = {BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CONFIG_LC3_CHAN_ALLOC, + BT_BYTES_LIST_LE32(BT_AUDIO_LOCATION_FRONT_LEFT))}; + uint8_t right[] = {BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CONFIG_LC3_CHAN_ALLOC, + BT_BYTES_LIST_LE32(BT_AUDIO_LOCATION_FRONT_RIGHT))}; int err; - (void)memset(streams, 0, sizeof(streams)); - for (size_t i = 0U; i < ARRAY_SIZE(subgroup_param); i++) { subgroup_param[i].params_count = streams_per_subgroup; subgroup_param[i].params = stream_params + i * streams_per_subgroup; @@ -222,8 +398,8 @@ static int setup_broadcast_source(struct bt_bap_broadcast_source **source) for (size_t j = 0U; j < ARRAY_SIZE(stream_params); j++) { stream_params[j].stream = &streams[j].stream; - stream_params[j].data = NULL; - stream_params[j].data_len = 0U; + stream_params[j].data = j == 0 ? left : right; + stream_params[j].data_len = j == 0 ? sizeof(left) : sizeof(right); bt_bap_stream_cb_register(stream_params[j].stream, &stream_ops); } @@ -258,13 +434,43 @@ int main(void) } printk("Bluetooth initialized\n"); - for (size_t i = 0U; i < ARRAY_SIZE(mock_data); i++) { + for (size_t i = 0U; i < ARRAY_SIZE(send_pcm_data); i++) { /* Initialize mock data */ - mock_data[i] = i; + send_pcm_data[i] = i; } #if defined(CONFIG_LIBLC3) - init_lc3(); +#if defined(CONFIG_USB_DEVICE_AUDIO) + const struct device *hs_dev; + + hs_dev = DEVICE_DT_GET(DT_NODELABEL(hs_0)); + + if (!device_is_ready(hs_dev)) { + printk("Device USB Headset is not ready\n"); + return 0; + } + + printk("Found USB Headset Device\n"); + + (void)memset(streams, 0, sizeof(streams)); + + for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { + ring_buf_init(&(streams[i].audio_ring_buf), + sizeof(streams[i]._ring_buffer_memory), + streams[i]._ring_buffer_memory); + printk("Initialized ring buf %zu: capacity: %u\n", i, + ring_buf_capacity_get(&(streams[i].audio_ring_buf))); + } + + usb_audio_register(hs_dev, &ops); + + err = usb_enable(NULL); + if (err) { + printk("Failed to enable USB"); + return 0; + } +#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */ + k_thread_start(encoder); #endif /* defined(CONFIG_LIBLC3) */ while (true) { @@ -372,10 +578,13 @@ int main(void) } } +#if defined(CONFIG_LIBLC3) && defined(CONFIG_USB_DEVICE_AUDIO) + /* Never stop streaming when using USB Audio as input */ + k_sleep(K_FOREVER); +#endif /* defined(CONFIG_LIBLC3) && defined(CONFIG_USB_DEVICE_AUDIO) */ printk("Waiting %u seconds before stopped\n", BROADCAST_SOURCE_LIFETIME); k_sleep(K_SECONDS(BROADCAST_SOURCE_LIFETIME)); - printk("Stopping broadcast source\n"); stopping = true; err = bt_bap_broadcast_source_stop(broadcast_source);