Bluetooth samples: Add USB Audio to Broadcast Sink sample
Added USB Audio output for the Broadcast Sink sample. In addition offloading of the LC3 codec was also made. The sample supports only mono, and a KConfig option was added to configure which audio location to sync to. Signed-off-by: Fredrik Danebjer <frdn@demant.com>
This commit is contained in:
parent
4fe3a8827f
commit
c948f831e0
3 changed files with 371 additions and 85 deletions
|
@ -43,4 +43,25 @@ config ENABLE_LC3
|
||||||
select LIBLC3
|
select LIBLC3
|
||||||
select FPU
|
select FPU
|
||||||
|
|
||||||
|
config USE_USB_AUDIO_OUTPUT
|
||||||
|
bool "Use USB Audio as output"
|
||||||
|
depends on ENABLE_LC3
|
||||||
|
select USB_DEVICE_STACK
|
||||||
|
select USB_DEVICE_AUDIO
|
||||||
|
select RING_BUFFER
|
||||||
|
help
|
||||||
|
Enables USB audio as output as a USB peripheral. This does not support providing USB
|
||||||
|
audio to e.g. speakers that are also USB peripherals, but can be connected to e.g. a
|
||||||
|
phone or PC as a USB-in device (such as a USB microphone).
|
||||||
|
USB audio only supports a single audio channel.
|
||||||
|
|
||||||
|
config TARGET_BROADCAST_CHANNEL
|
||||||
|
int "Broadcast Channel Audio Location to sync to"
|
||||||
|
range 0 2
|
||||||
|
default 1
|
||||||
|
depends on USE_USB_AUDIO_OUTPUT
|
||||||
|
help
|
||||||
|
Channel Audio Location to sync to. These corresponds to the bt_audio_location,
|
||||||
|
supporting mono, left and right channels
|
||||||
|
|
||||||
source "Kconfig.zephyr"
|
source "Kconfig.zephyr"
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence
|
# 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.
|
# inctease stack size for that thread.
|
||||||
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
|
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
|
||||||
|
CONFIG_ENABLE_LC3=y
|
||||||
|
CONFIG_TARGET_BROADCAST_CHANNEL=1
|
||||||
|
CONFIG_USE_USB_AUDIO_OUTPUT=y
|
||||||
|
CONFIG_USB_DEVICE_PRODUCT="USB Broadcast Sink"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022-2023 Nordic Semiconductor ASA
|
* Copyright (c) 2022-2023 Nordic Semiconductor ASA
|
||||||
|
* Copyright (c) 2024 Demant A/S
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
@ -12,6 +13,15 @@
|
||||||
#include <zephyr/bluetooth/audio/bap.h>
|
#include <zephyr/bluetooth/audio/bap.h>
|
||||||
#include <zephyr/bluetooth/audio/pacs.h>
|
#include <zephyr/bluetooth/audio/pacs.h>
|
||||||
#include <zephyr/sys/byteorder.h>
|
#include <zephyr/sys/byteorder.h>
|
||||||
|
#if defined(CONFIG_LIBLC3)
|
||||||
|
#include "lc3.h"
|
||||||
|
#endif /* defined(CONFIG_LIBLC3) */
|
||||||
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
#include <zephyr/usb/usb_device.h>
|
||||||
|
#include <zephyr/usb/class/usb_audio.h>
|
||||||
|
#include <zephyr/sys/ring_buffer.h>
|
||||||
|
#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */
|
||||||
|
|
||||||
|
|
||||||
BUILD_ASSERT(IS_ENABLED(CONFIG_SCAN_SELF) || IS_ENABLED(CONFIG_SCAN_OFFLOAD),
|
BUILD_ASSERT(IS_ENABLED(CONFIG_SCAN_SELF) || IS_ENABLED(CONFIG_SCAN_OFFLOAD),
|
||||||
"Either SCAN_SELF or SCAN_OFFLOAD must be enabled");
|
"Either SCAN_SELF or SCAN_OFFLOAD must be enabled");
|
||||||
|
@ -30,6 +40,28 @@ BUILD_ASSERT(IS_ENABLED(CONFIG_SCAN_SELF) || IS_ENABLED(CONFIG_SCAN_OFFLOAD),
|
||||||
#define PA_SYNC_SKIP 5
|
#define PA_SYNC_SKIP 5
|
||||||
#define NAME_LEN sizeof(CONFIG_TARGET_BROADCAST_NAME) + 1
|
#define NAME_LEN sizeof(CONFIG_TARGET_BROADCAST_NAME) + 1
|
||||||
|
|
||||||
|
#if defined(CONFIG_LIBLC3)
|
||||||
|
#define MAX_SAMPLE_RATE 48000U
|
||||||
|
#define MAX_FRAME_DURATION_US 10000U
|
||||||
|
#define MAX_NUM_SAMPLES_MONO ((MAX_FRAME_DURATION_US * MAX_SAMPLE_RATE) / USEC_PER_SEC)
|
||||||
|
#define MAX_NUM_SAMPLES_STEREO (MAX_NUM_SAMPLES_MONO * 2)
|
||||||
|
|
||||||
|
#define LC3_ENCODER_STACK_SIZE 4096
|
||||||
|
#define LC3_ENCODER_PRIORITY 5
|
||||||
|
#endif /* defined(CONFIG_LIBLC3) */
|
||||||
|
|
||||||
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
#define USB_SAMPLE_RATE 48000U
|
||||||
|
#define USB_FRAME_DURATION_US 1000U
|
||||||
|
#define USB_TX_BUF_NUM 10U
|
||||||
|
#define BROADCAST_DATA_ELEMENT_SIZE sizeof(int16_t)
|
||||||
|
#define BROADCAST_MONO_SAMPLE_SIZE (MAX_NUM_SAMPLES_MONO * BROADCAST_DATA_ELEMENT_SIZE)
|
||||||
|
#define BROADCAST_STEREO_SAMPLE_SIZE (BROADCAST_MONO_SAMPLE_SIZE * BROADCAST_DATA_ELEMENT_SIZE)
|
||||||
|
#define USB_STEREO_SAMPLE_SIZE ((USB_FRAME_DURATION_US * USB_SAMPLE_RATE * \
|
||||||
|
BROADCAST_DATA_ELEMENT_SIZE * 2) / USEC_PER_SEC)
|
||||||
|
#define AUDIO_RING_BUF_SIZE 10000U
|
||||||
|
#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */
|
||||||
|
|
||||||
static K_SEM_DEFINE(sem_connected, 0U, 1U);
|
static K_SEM_DEFINE(sem_connected, 0U, 1U);
|
||||||
static K_SEM_DEFINE(sem_disconnected, 0U, 1U);
|
static K_SEM_DEFINE(sem_disconnected, 0U, 1U);
|
||||||
static K_SEM_DEFINE(sem_broadcaster_found, 0U, 1U);
|
static K_SEM_DEFINE(sem_broadcaster_found, 0U, 1U);
|
||||||
|
@ -52,6 +84,7 @@ static struct bt_le_per_adv_sync *pa_sync;
|
||||||
static uint32_t broadcaster_broadcast_id;
|
static uint32_t broadcaster_broadcast_id;
|
||||||
static struct broadcast_sink_stream {
|
static struct broadcast_sink_stream {
|
||||||
struct bt_bap_stream stream;
|
struct bt_bap_stream stream;
|
||||||
|
bool has_data;
|
||||||
size_t recv_cnt;
|
size_t recv_cnt;
|
||||||
size_t loss_cnt;
|
size_t loss_cnt;
|
||||||
size_t error_cnt;
|
size_t error_cnt;
|
||||||
|
@ -61,7 +94,14 @@ static struct broadcast_sink_stream {
|
||||||
struct k_work_delayable lc3_decode_work;
|
struct k_work_delayable lc3_decode_work;
|
||||||
/* Internal lock for protecting net_buf from multiple access */
|
/* Internal lock for protecting net_buf from multiple access */
|
||||||
struct k_mutex lc3_decoder_mutex;
|
struct k_mutex lc3_decoder_mutex;
|
||||||
|
lc3_decoder_t lc3_decoder;
|
||||||
|
lc3_decoder_mem_48k_t lc3_decoder_mem;
|
||||||
#endif /* defined(CONFIG_LIBLC3) */
|
#endif /* defined(CONFIG_LIBLC3) */
|
||||||
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
struct ring_buf audio_ring_buf;
|
||||||
|
uint8_t _ring_buffer[AUDIO_RING_BUF_SIZE];
|
||||||
|
#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */
|
||||||
|
|
||||||
} streams[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT];
|
} streams[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT];
|
||||||
static struct bt_bap_stream *streams_p[ARRAY_SIZE(streams)];
|
static struct bt_bap_stream *streams_p[ARRAY_SIZE(streams)];
|
||||||
static struct bt_conn *broadcast_assistant_conn;
|
static struct bt_conn *broadcast_assistant_conn;
|
||||||
|
@ -83,70 +123,77 @@ static uint8_t sink_broadcast_code[BT_AUDIO_BROADCAST_CODE_SIZE];
|
||||||
|
|
||||||
uint64_t total_rx_iso_packet_count; /* This value is exposed to test code */
|
uint64_t total_rx_iso_packet_count; /* This value is exposed to test code */
|
||||||
|
|
||||||
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
static int16_t usb_audio_data[MAX_NUM_SAMPLES_STEREO] = {0};
|
||||||
|
static int16_t usb_audio_data_stereo[MAX_NUM_SAMPLES_STEREO] = {0};
|
||||||
|
|
||||||
|
RING_BUF_DECLARE(ring_buf_usb, AUDIO_RING_BUF_SIZE);
|
||||||
|
NET_BUF_POOL_DEFINE(usb_tx_buf_pool, USB_TX_BUF_NUM, BROADCAST_STEREO_SAMPLE_SIZE, 0,
|
||||||
|
net_buf_destroy);
|
||||||
|
|
||||||
|
static void mix_mono_to_stereo(enum bt_audio_location channels);
|
||||||
|
#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */
|
||||||
|
|
||||||
#if defined(CONFIG_LIBLC3)
|
#if defined(CONFIG_LIBLC3)
|
||||||
|
static int16_t audio_buf[MAX_NUM_SAMPLES_MONO];
|
||||||
#include "lc3.h"
|
|
||||||
|
|
||||||
#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 audio_buf[MAX_NUM_SAMPLES];
|
|
||||||
static lc3_decoder_t lc3_decoder;
|
|
||||||
static lc3_decoder_mem_16k_t lc3_decoder_mem;
|
|
||||||
static int frames_per_sdu;
|
static int frames_per_sdu;
|
||||||
|
static K_SEM_DEFINE(lc3_decoder_sem, 0, 1);
|
||||||
|
|
||||||
static int lc3_enable(const struct bt_audio_codec_cfg *codec_cfg)
|
static void do_lc3_decode(struct broadcast_sink_stream *sink_stream);
|
||||||
|
static void lc3_decoder_thread(void *arg1, void *arg2, void *arg3);
|
||||||
|
K_THREAD_DEFINE(decoder_tid, LC3_ENCODER_STACK_SIZE, lc3_decoder_thread,
|
||||||
|
NULL, NULL, NULL, LC3_ENCODER_PRIORITY, 0, -1);
|
||||||
|
|
||||||
|
/* Consumer thread of the decoded stream data */
|
||||||
|
static void lc3_decoder_thread(void *arg1, void *arg2, void *arg3)
|
||||||
{
|
{
|
||||||
int ret;
|
while (true) {
|
||||||
int freq_hz;
|
k_sem_take(&lc3_decoder_sem, K_FOREVER);
|
||||||
int frame_duration_us;
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
int err = 0;
|
||||||
|
enum bt_audio_location channels;
|
||||||
|
struct broadcast_sink_stream *stream_for_usb = &streams[0];
|
||||||
|
|
||||||
printk("Enable: stream with codec %p\n", codec_cfg);
|
/* For now we only handle one BIS, so always only decode the first element in
|
||||||
|
* streams.
|
||||||
|
*/
|
||||||
|
do_lc3_decode(&streams[0]);
|
||||||
|
|
||||||
ret = bt_audio_codec_cfg_get_freq(codec_cfg);
|
err = bt_audio_codec_cfg_get_chan_allocation(stream_for_usb->stream.codec_cfg,
|
||||||
if (ret > 0) {
|
&channels);
|
||||||
freq_hz = bt_audio_codec_cfg_freq_to_freq_hz(ret);
|
if (err != 0) {
|
||||||
} else {
|
printk("Could not get channel allocation (err=%d)\n", err);
|
||||||
printk("Error: Codec frequency not set, cannot start codec.");
|
continue;
|
||||||
return -1;
|
}
|
||||||
|
|
||||||
|
/* If the ring buffer usage is larger than zero, then there is data to process */
|
||||||
|
if (ring_buf_space_get(&stream_for_usb->audio_ring_buf)) {
|
||||||
|
mix_mono_to_stereo(channels);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
|
||||||
|
if (streams[i].has_data) {
|
||||||
|
do_lc3_decode(&streams[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = bt_audio_codec_cfg_get_frame_dur(codec_cfg);
|
#endif /* #if defined(CONFIG_USB_DEVICE_AUDIO) */
|
||||||
if (ret > 0) {
|
|
||||||
frame_duration_us = bt_audio_codec_cfg_frame_dur_to_frame_dur_us(ret);
|
|
||||||
} else {
|
|
||||||
printk("Error: Frame duration not set, cannot start codec.");
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
frames_per_sdu = bt_audio_codec_cfg_get_frame_blocks_per_sdu(codec_cfg, true);
|
|
||||||
|
|
||||||
lc3_decoder = lc3_setup_decoder(frame_duration_us, freq_hz, 0, /* No resampling */
|
|
||||||
&lc3_decoder_mem);
|
|
||||||
|
|
||||||
if (lc3_decoder == NULL) {
|
|
||||||
printk("ERROR: Failed to setup LC3 decoder - wrong parameters?\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lc3_decode_handler(struct k_work *work)
|
static void do_lc3_decode(struct broadcast_sink_stream *sink_stream)
|
||||||
{
|
{
|
||||||
int err = 0;
|
int err = 0;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
uint8_t *buf_data;
|
uint8_t *buf_data;
|
||||||
struct net_buf *ptr_net_buf;
|
struct net_buf *ptr_net_buf;
|
||||||
int octets_per_frame;
|
int octets_per_frame;
|
||||||
struct broadcast_sink_stream *sink_stream = CONTAINER_OF(
|
|
||||||
k_work_delayable_from_work(work), struct broadcast_sink_stream, lc3_decode_work);
|
|
||||||
|
|
||||||
k_mutex_lock(&sink_stream->lc3_decoder_mutex, K_FOREVER);
|
k_mutex_lock(&sink_stream->lc3_decoder_mutex, K_FOREVER);
|
||||||
|
|
||||||
|
sink_stream->has_data = false;
|
||||||
|
|
||||||
if (sink_stream->in_buf == NULL) {
|
if (sink_stream->in_buf == NULL) {
|
||||||
printk("buf data is NULL, nothing to be docoded\n");
|
|
||||||
k_mutex_unlock(&sink_stream->lc3_decoder_mutex);
|
k_mutex_unlock(&sink_stream->lc3_decoder_mutex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -160,23 +207,182 @@ static void lc3_decode_handler(struct k_work *work)
|
||||||
octets_per_frame = ptr_net_buf->len / frames_per_sdu;
|
octets_per_frame = ptr_net_buf->len / frames_per_sdu;
|
||||||
|
|
||||||
for (int i = 0; i < frames_per_sdu; i++) {
|
for (int i = 0; i < frames_per_sdu; i++) {
|
||||||
err = lc3_decode(lc3_decoder, buf_data + offset, octets_per_frame,
|
err = lc3_decode(sink_stream->lc3_decoder, buf_data + offset, octets_per_frame,
|
||||||
LC3_PCM_FORMAT_S16, audio_buf, 1);
|
LC3_PCM_FORMAT_S16, audio_buf, 1);
|
||||||
|
|
||||||
if (err == 1) {
|
if (err == 1) {
|
||||||
printk(" decoder performed PLC\n");
|
printk(" decoder performed PLC\n");
|
||||||
} else if (err < 0) {
|
} else if (err < 0) {
|
||||||
printk(" decoder failed - wrong parameters?\n");
|
printk(" decoder failed - wrong parameters? (err = %d)\n", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += octets_per_frame;
|
offset += octets_per_frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
net_buf_unref(ptr_net_buf);
|
net_buf_unref(ptr_net_buf);
|
||||||
|
|
||||||
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
uint32_t rbret;
|
||||||
|
|
||||||
|
if (ring_buf_space_get(&sink_stream->audio_ring_buf) == 0) {
|
||||||
|
/* Since the data in the buffer is old by now, and we add enough data for many
|
||||||
|
* request to consume at a time, just erase what is already in the buffer.
|
||||||
|
*/
|
||||||
|
ring_buf_reset(&sink_stream->audio_ring_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Put in ring-buffer to be consumed */
|
||||||
|
rbret = ring_buf_put(&sink_stream->audio_ring_buf, (uint8_t *)audio_buf,
|
||||||
|
BROADCAST_MONO_SAMPLE_SIZE);
|
||||||
|
if (rbret != BROADCAST_MONO_SAMPLE_SIZE) {
|
||||||
|
static int rb_add_failures;
|
||||||
|
|
||||||
|
rb_add_failures++;
|
||||||
|
if (rb_add_failures % 1000 == 0) {
|
||||||
|
printk("Failure to add to ring buffer %d, %u\n", rb_add_failures, rbret);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif /*#if defined(CONFIG_USB_DEVICE_AUDIO)*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int lc3_enable(struct broadcast_sink_stream *sink_stream)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int freq_hz;
|
||||||
|
int frame_duration_us;
|
||||||
|
|
||||||
|
printk("Enable: stream with codec %p\n", sink_stream->stream.codec_cfg);
|
||||||
|
|
||||||
|
ret = bt_audio_codec_cfg_get_freq(sink_stream->stream.codec_cfg);
|
||||||
|
if (ret > 0) {
|
||||||
|
freq_hz = bt_audio_codec_cfg_freq_to_freq_hz(ret);
|
||||||
|
} else {
|
||||||
|
printk("Error: Codec frequency not set, cannot start codec.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = bt_audio_codec_cfg_get_frame_dur(sink_stream->stream.codec_cfg);
|
||||||
|
if (ret > 0) {
|
||||||
|
frame_duration_us = bt_audio_codec_cfg_frame_dur_to_frame_dur_us(ret);
|
||||||
|
} else {
|
||||||
|
printk("Error: Frame duration not set, cannot start codec.");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
frames_per_sdu = bt_audio_codec_cfg_get_frame_blocks_per_sdu(sink_stream->stream.codec_cfg,
|
||||||
|
true);
|
||||||
|
|
||||||
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
sink_stream->lc3_decoder = lc3_setup_decoder(frame_duration_us, freq_hz, USB_SAMPLE_RATE,
|
||||||
|
&sink_stream->lc3_decoder_mem);
|
||||||
|
#else
|
||||||
|
sink_stream->lc3_decoder = lc3_setup_decoder(frame_duration_us, freq_hz, 0,
|
||||||
|
&sink_stream->lc3_decoder_mem);
|
||||||
|
#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */
|
||||||
|
|
||||||
|
if (sink_stream->lc3_decoder == NULL) {
|
||||||
|
printk("ERROR: Failed to setup LC3 decoder - wrong parameters?\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_thread_start(decoder_tid);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
#endif /* defined(CONFIG_LIBLC3) */
|
#endif /* defined(CONFIG_LIBLC3) */
|
||||||
|
|
||||||
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
static uint8_t get_channel_index(const enum bt_audio_location allocated_channels,
|
||||||
|
const enum bt_audio_location channel)
|
||||||
|
{
|
||||||
|
/* If we are looking for the right channel, and left channel is present, then the index is
|
||||||
|
* 1. For all other combinations the index has to be 0, since it would mean that it is the
|
||||||
|
* lowest possible bit enumeration
|
||||||
|
*/
|
||||||
|
if (channel == BT_AUDIO_LOCATION_FRONT_RIGHT &&
|
||||||
|
allocated_channels & BT_AUDIO_LOCATION_FRONT_LEFT) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Duplicate the audio from one channel and put it in both channels */
|
||||||
|
static void mix_mono_to_stereo(enum bt_audio_location channels)
|
||||||
|
{
|
||||||
|
uint32_t size;
|
||||||
|
uint8_t cidx;
|
||||||
|
|
||||||
|
size = ring_buf_get(&streams[0].audio_ring_buf, (uint8_t *)usb_audio_data,
|
||||||
|
MAX_NUM_SAMPLES_STEREO);
|
||||||
|
if (size != MAX_NUM_SAMPLES_STEREO) {
|
||||||
|
memset(&((uint8_t *)usb_audio_data)[size], 0, sizeof(usb_audio_data) - size);
|
||||||
|
}
|
||||||
|
|
||||||
|
cidx = get_channel_index(channels, CONFIG_TARGET_BROADCAST_CHANNEL);
|
||||||
|
|
||||||
|
/* Interleave the channel sample */
|
||||||
|
for (size_t i = 0U; i < MAX_NUM_SAMPLES_MONO; i++) {
|
||||||
|
usb_audio_data_stereo[i * 2] = usb_audio_data[MAX_NUM_SAMPLES_MONO * cidx + i];
|
||||||
|
usb_audio_data_stereo[i * 2 + 1] = usb_audio_data[MAX_NUM_SAMPLES_MONO * cidx + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
size = ring_buf_put(&ring_buf_usb, (uint8_t *)usb_audio_data_stereo,
|
||||||
|
BROADCAST_STEREO_SAMPLE_SIZE);
|
||||||
|
if (size != BROADCAST_STEREO_SAMPLE_SIZE) {
|
||||||
|
static int rb_put_failures;
|
||||||
|
|
||||||
|
rb_put_failures++;
|
||||||
|
if (rb_put_failures == 1000) {
|
||||||
|
printk("%s: Failure to add to ring buffer %d, %u\n", __func__,
|
||||||
|
rb_put_failures, size);
|
||||||
|
rb_put_failures = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* USB consumer callback, called every 1ms, consumes data from ring-buffer */
|
||||||
|
static void data_request(const struct device *dev)
|
||||||
|
{
|
||||||
|
static struct net_buf *pcm_buf;
|
||||||
|
int err;
|
||||||
|
uint32_t size;
|
||||||
|
void *out;
|
||||||
|
int16_t usb_audio_data[USB_STEREO_SAMPLE_SIZE] = {0};
|
||||||
|
|
||||||
|
size = ring_buf_get(&ring_buf_usb, (uint8_t *)usb_audio_data, USB_STEREO_SAMPLE_SIZE);
|
||||||
|
if (size != USB_STEREO_SAMPLE_SIZE) {
|
||||||
|
memset(&((uint8_t *)usb_audio_data)[size], 0, USB_STEREO_SAMPLE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
pcm_buf = net_buf_alloc(&usb_tx_buf_pool, K_NO_WAIT);
|
||||||
|
if (pcm_buf == NULL) {
|
||||||
|
printk("Couldnt allocate pcm_buf\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = net_buf_add(pcm_buf, USB_STEREO_SAMPLE_SIZE);
|
||||||
|
memcpy(out, usb_audio_data, USB_STEREO_SAMPLE_SIZE);
|
||||||
|
|
||||||
|
err = usb_audio_send(dev, pcm_buf, USB_STEREO_SAMPLE_SIZE);
|
||||||
|
if (err) {
|
||||||
|
net_buf_unref(pcm_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void data_written(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 const struct usb_audio_ops ops = {
|
||||||
|
.data_request_cb = data_request,
|
||||||
|
.data_written_cb = data_written,
|
||||||
|
};
|
||||||
|
#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */
|
||||||
|
|
||||||
static void stream_started_cb(struct bt_bap_stream *stream)
|
static void stream_started_cb(struct bt_bap_stream *stream)
|
||||||
{
|
{
|
||||||
struct broadcast_sink_stream *sink_stream =
|
struct broadcast_sink_stream *sink_stream =
|
||||||
|
@ -190,9 +396,23 @@ static void stream_started_cb(struct bt_bap_stream *stream)
|
||||||
sink_stream->valid_cnt = 0U;
|
sink_stream->valid_cnt = 0U;
|
||||||
sink_stream->error_cnt = 0U;
|
sink_stream->error_cnt = 0U;
|
||||||
|
|
||||||
|
|
||||||
#if defined(CONFIG_LIBLC3)
|
#if defined(CONFIG_LIBLC3)
|
||||||
k_work_init_delayable(&sink_stream->lc3_decode_work, lc3_decode_handler);
|
int err;
|
||||||
#endif /* defined(CONFIG_LIBLC3) */
|
|
||||||
|
if (stream->codec_cfg != 0 && stream->codec_cfg->id != BT_HCI_CODING_FORMAT_LC3) {
|
||||||
|
/* No subgroups with LC3 was found */
|
||||||
|
printk("Did not parse an LC3 codec\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lc3_enable(sink_stream);
|
||||||
|
if (err < 0) {
|
||||||
|
printk("Error: cannot enable LC3 codec: %d", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_LIBLC3 */
|
||||||
|
|
||||||
k_sem_give(&sem_bis_synced);
|
k_sem_give(&sem_bis_synced);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +453,8 @@ static void stream_recv_cb(struct bt_bap_stream *stream, const struct bt_iso_rec
|
||||||
|
|
||||||
sink_stream->in_buf = net_buf_ref(buf);
|
sink_stream->in_buf = net_buf_ref(buf);
|
||||||
k_mutex_unlock(&sink_stream->lc3_decoder_mutex);
|
k_mutex_unlock(&sink_stream->lc3_decoder_mutex);
|
||||||
k_work_schedule(&sink_stream->lc3_decode_work, K_NO_WAIT);
|
sink_stream->has_data = true;
|
||||||
|
k_sem_give(&lc3_decoder_sem);
|
||||||
#endif /* defined(CONFIG_LIBLC3) */
|
#endif /* defined(CONFIG_LIBLC3) */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,33 +473,62 @@ static struct bt_bap_stream_ops stream_ops = {
|
||||||
.recv = stream_recv_cb,
|
.recv = stream_recv_cb,
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(CONFIG_LIBLC3)
|
#if defined(CONFIG_TARGET_BROADCAST_CHANNEL)
|
||||||
static bool base_subgroup_cb(const struct bt_bap_base_subgroup *subgroup, void *user_data)
|
static bool find_valid_bis_cb(const struct bt_bap_base_subgroup_bis *bis,
|
||||||
|
void *user_data)
|
||||||
{
|
{
|
||||||
struct bt_audio_codec_cfg *codec_cfg = user_data;
|
int err;
|
||||||
struct bt_bap_base_codec_id codec_id;
|
struct bt_audio_codec_cfg codec_cfg = {0};
|
||||||
int ret;
|
enum bt_audio_location chan_allocation;
|
||||||
|
uint8_t *bis_index = user_data;
|
||||||
|
|
||||||
ret = bt_bap_base_get_subgroup_codec_id(subgroup, &codec_id);
|
err = bt_bap_base_subgroup_bis_codec_to_codec_cfg(bis, &codec_cfg);
|
||||||
if (ret < 0) {
|
if (err != 0) {
|
||||||
printk("Could not get codec id for subgroup %p: %d", subgroup, ret);
|
printk("Could not find codec configuration (err=%d)\n", err);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codec_id.id != BT_HCI_CODING_FORMAT_LC3) {
|
err = bt_audio_codec_cfg_get_chan_allocation(&codec_cfg, &chan_allocation);
|
||||||
printk("Unsupported codec for subgroup %p: 0x%02x", subgroup, codec_id.id);
|
if (err != 0) {
|
||||||
return true; /* parse next subgroup */
|
printk("Could not find channel allocation (err=%d)\n", err);
|
||||||
}
|
|
||||||
|
|
||||||
ret = bt_bap_base_subgroup_codec_to_codec_cfg(subgroup, codec_cfg);
|
|
||||||
if (ret < 0) {
|
|
||||||
printk("Could convert subgroup %p to codec_cfg: %d", subgroup, ret);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false; /* We only care about the first subgroup with LC3 */
|
if (((CONFIG_TARGET_BROADCAST_CHANNEL) == BT_AUDIO_LOCATION_MONO_AUDIO &&
|
||||||
|
chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO) ||
|
||||||
|
chan_allocation & CONFIG_TARGET_BROADCAST_CHANNEL) {
|
||||||
|
*bis_index = bis->index;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_LIBLC3 */
|
|
||||||
|
static bool find_valid_bis_in_subgroup_cb(const struct bt_bap_base_subgroup *subgroup,
|
||||||
|
void *user_data)
|
||||||
|
{
|
||||||
|
return bt_bap_base_subgroup_foreach_bis(subgroup, find_valid_bis_cb, user_data)
|
||||||
|
== -ECANCELED ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int base_get_first_valid_bis(const struct bt_bap_base *base, uint32_t *bis_index)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
uint8_t valid_bis_index = 0U;
|
||||||
|
|
||||||
|
err = bt_bap_base_foreach_subgroup(base, find_valid_bis_in_subgroup_cb, &valid_bis_index);
|
||||||
|
if (err != -ECANCELED) {
|
||||||
|
printk("Failed to parse subgroups: %d\n", err);
|
||||||
|
return err != 0 ? err : -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*bis_index = 0;
|
||||||
|
*bis_index |= ((uint8_t)1 << valid_bis_index);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_TARGET_BROADCAST_CHANNEL */
|
||||||
|
|
||||||
static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base,
|
static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base,
|
||||||
size_t base_size)
|
size_t base_size)
|
||||||
|
@ -293,31 +543,19 @@ static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap
|
||||||
printk("Received BASE with %d subgroups from broadcast sink %p\n",
|
printk("Received BASE with %d subgroups from broadcast sink %p\n",
|
||||||
bt_bap_base_get_subgroup_count(base), sink);
|
bt_bap_base_get_subgroup_count(base), sink);
|
||||||
|
|
||||||
#if defined(CONFIG_LIBLC3)
|
#if defined(CONFIG_TARGET_BROADCAST_CHANNEL)
|
||||||
struct bt_audio_codec_cfg codec_cfg = {0};
|
err = base_get_first_valid_bis(base, &base_bis_index_bitfield);
|
||||||
|
if (err != 0) {
|
||||||
err = bt_bap_base_foreach_subgroup(base, base_subgroup_cb, &codec_cfg);
|
printk("Failed to find a valid BIS\n");
|
||||||
if (err != 0 && err != -ECANCELED) {
|
|
||||||
printk("Failed to parse subgroups: %d\n", err);
|
|
||||||
return;
|
|
||||||
} else if (codec_cfg.id != BT_HCI_CODING_FORMAT_LC3) {
|
|
||||||
/* No subgroups with LC3 was found */
|
|
||||||
printk("Did not parse an LC3 codec\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
err = lc3_enable(&codec_cfg);
|
|
||||||
if (err < 0) {
|
|
||||||
printk("Error: cannot enable LC3 codec: %d", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif /* CONFIG_LIBLC3 */
|
|
||||||
|
|
||||||
err = bt_bap_base_get_bis_indexes(base, &base_bis_index_bitfield);
|
err = bt_bap_base_get_bis_indexes(base, &base_bis_index_bitfield);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
printk("Failed to BIS indexes: %d\n", err);
|
printk("Failed to BIS indexes: %d\n", err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#endif /* CONFIG_TARGET_BROADCAST_CHANNEL */
|
||||||
|
|
||||||
bis_index_bitfield = base_bis_index_bitfield & bis_index_mask;
|
bis_index_bitfield = base_bis_index_bitfield & bis_index_mask;
|
||||||
|
|
||||||
|
@ -721,6 +959,29 @@ static int init(void)
|
||||||
streams[i].stream.ops = &stream_ops;
|
streams[i].stream.ops = &stream_ops;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Initialize ring buffers and USB */
|
||||||
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
int ret;
|
||||||
|
const struct device *hs_dev = DEVICE_DT_GET(DT_NODELABEL(hs_0));
|
||||||
|
|
||||||
|
for (int i = 0U; i < CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT; i++) {
|
||||||
|
ring_buf_init(&streams[i].audio_ring_buf, AUDIO_RING_BUF_SIZE,
|
||||||
|
streams[i]._ring_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_is_ready(hs_dev)) {
|
||||||
|
printk("Cannot get USB Headset Device\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_audio_register(hs_dev, &ops);
|
||||||
|
ret = usb_enable(NULL);
|
||||||
|
if (ret != 0) {
|
||||||
|
printk("Failed to enable USB\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue