Skip to content

Commit

Permalink
samples: Add USB Audio to broadcast audio source
Browse files Browse the repository at this point in the history
Adds support for using a connected host to
broadcast audio via USB Audio.

Sets BT_DATA_BROADCAST_NAME to BT_DEVICE_NAME
for discovery.

Limits streams to one channel.

Boost nRF5340 app core to 128 MHz

Signed-off-by: Lars Knudsen <[email protected]>
  • Loading branch information
larsgk committed Oct 31, 2023
1 parent 6e02f02 commit abdb9b2
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,17 @@ CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
CONFIG_NEWLIB_LIBC=y
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_BT_TINYCRYPT_ECC=y

# USB Audio related configs
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr Broadcast Source"
CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n
CONFIG_USB_DEVICE_AUDIO=y

# Only stream one channel
CONFIG_BT_ISO_MAX_CHAN=1
CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT=1
CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT=1

# Audio buffer
CONFIG_RING_BUFFER=y
150 changes: 136 additions & 14 deletions samples/bluetooth/broadcast_audio_source/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#if defined(CONFIG_SOC_NRF5340_CPUAPP)
#include "nrfx_clock.h"
#endif

/* Zephyr Controller works best while Extended Advertising interval to be a multiple
* of the ISO Interval minus 10 ms (max. advertising random delay). This is
Expand Down Expand Up @@ -48,7 +51,7 @@ NET_BUF_POOL_FIXED_DEFINE(tx_pool,
#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;

Expand All @@ -59,6 +62,22 @@ static K_SEM_DEFINE(sem_stopped, 0U, ARRAY_SIZE(streams));

#if 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>

/* USB Audio Data is downsampled from 48kHz stereo to 16kHz mono when receiving data */
#define USB_SAMPLE_RATE 16000
#define USB_FRAME_DURATION_US 1000
#define USB_NUM_SAMPLES ((MAX_FRAME_DURATION_US * MAX_SAMPLE_RATE) / USEC_PER_SEC)
#define USB_BYTES_PER_SAMPLE 2
static int16_t usb_pcm_data[USB_NUM_SAMPLES];

#define AUDIO_RING_BUF_BYTES USB_NUM_SAMPLES * USB_BYTES_PER_SAMPLE * 20
RING_BUF_DECLARE(audio_ring_buf, AUDIO_RING_BUF_BYTES);
#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */

#include "lc3.h"

static lc3_encoder_t lc3_encoder;
Expand Down Expand Up @@ -114,6 +133,61 @@ static void init_lc3(void)
printk("ERROR: Failed to setup LC3 encoder - wrong parameters?\n");
}
}

#if defined(CONFIG_USB_DEVICE_AUDIO)
static void data_received(const struct device *dev,
struct net_buf *buffer,
size_t size)
{
int ret;
static int count;
int16_t *pcm;
int nsamples;

if (!buffer || !size) {
/* This should never happen */
return;
}

/* TODO: Figure out why this is happening at regular ~10 sec intervals
* for approx 3 secs
*/
if (size != 48 * 2 * 2) {
printk("Other than 192 bytes received (48 samples, 16bit, stereo): %d\n", size);
}

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
*/
nsamples = size / (sizeof(int16_t) * 2 * (48000 / 16000));
for (size_t i = 0, j = 0; i < nsamples; i++, j += 6) {
usb_pcm_data[i] = pcm[j];
}

count++;

ret = ring_buf_put(&audio_ring_buf, (uint8_t *)usb_pcm_data, nsamples * 2);

if (ret < nsamples * 2) {
printk("Not enough room for samples in buffer: size = %d, free = %d, put = %d\n",
ring_buf_size_get(&audio_ring_buf),
ring_buf_space_get(&audio_ring_buf),
ret);
}

if (count % 1000 == 0) {
printk("%d bytes written to PCM ring buffer (count = %d)\n", ret, 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)
Expand Down Expand Up @@ -151,25 +225,33 @@ static void stream_sent_cb(struct bt_bap_stream *stream)

net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);
#if defined(CONFIG_LIBLC3)
int lc3_ret;
uint8_t lc3_encoder_buffer[preset_16_2_1.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,
#if defined(CONFIG_USB_DEVICE_AUDIO)
int size = ring_buf_get(&audio_ring_buf, (uint8_t *)send_pcm_data, sizeof(send_pcm_data));

if (size < sizeof(send_pcm_data)) {
printk("Not enough bytes ready, padding %d!\n", sizeof(send_pcm_data) - size);
memset(&((uint8_t *)send_pcm_data)[size], 0, sizeof(send_pcm_data) - size);
}
#endif

ret = lc3_encode(lc3_encoder, LC3_PCM_FORMAT_S16, send_pcm_data, 1, octets_per_frame,
lc3_encoder_buffer);

if (lc3_ret == -1) {
printk("LC3 encoder failed - wrong parameters?: %d", lc3_ret);
if (ret == -1) {
printk("LC3 encoder failed - wrong parameters?: %d", ret);
return;
}

net_buf_add_mem(buf, lc3_encoder_buffer, preset_16_2_1.qos.sdu);
#else
net_buf_add_mem(buf, mock_data, preset_16_2_1.qos.sdu);
net_buf_add_mem(buf, send_pcm_data, preset_16_2_1.qos.sdu);
#endif /* defined(CONFIG_LIBLC3) */

ret = bt_bap_stream_send(stream, buf, source_stream->seq_num++, BT_ISO_TIMESTAMP_NONE);
Expand Down Expand Up @@ -241,28 +323,62 @@ int main(void)
struct bt_le_ext_adv *adv;
int err;

#if defined(CONFIG_SOC_NRF5340_CPUAPP)
/* Set nRF5340 cpu app core to 128 MHz */
err = nrfx_clock_divider_set(NRF_CLOCK_DOMAIN_HFCLK, NRF_CLOCK_HFCLK_DIV_1);

if (err != NRFX_ERROR_BASE_NUM) {
printk("Error changing clock to 128 MHz\n");
} else {
nrfx_clock_hfclk_start();
while (!nrfx_clock_hfclk_is_running()) {
}
}
#endif

err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return 0;
}
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");
return 0;
}

printk("Found USB Headset Device");

usb_audio_register(hs_dev, &ops);

err = usb_enable(NULL);
if (err) {
printk("Failed to enable USB");
return 0;
}
#endif /* defined(CONFIG_USB_AUDIO) */
#endif /* defined(CONFIG_LIBLC3) */

while (true) {
/* Broadcast Audio Streaming Endpoint advertising data */
NET_BUF_SIMPLE_DEFINE(ad_buf,
BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE);
NET_BUF_SIMPLE_DEFINE(base_buf, 128);
struct bt_data ext_ad;
struct bt_data ext_ad[2];
struct bt_data per_ad;
uint32_t broadcast_id;

Expand Down Expand Up @@ -298,10 +414,12 @@ int main(void)
/* Setup extended advertising data */
net_buf_simple_add_le16(&ad_buf, BT_UUID_BROADCAST_AUDIO_VAL);
net_buf_simple_add_le24(&ad_buf, broadcast_id);
ext_ad.type = BT_DATA_SVC_DATA16;
ext_ad.data_len = ad_buf.len;
ext_ad.data = ad_buf.data;
err = bt_le_ext_adv_set_data(adv, &ext_ad, 1, NULL, 0);
ext_ad[0] = (struct bt_data)BT_DATA(BT_DATA_BROADCAST_NAME, CONFIG_BT_DEVICE_NAME,
sizeof(CONFIG_BT_DEVICE_NAME) - 1);
ext_ad[1].type = BT_DATA_SVC_DATA16;
ext_ad[1].data_len = ad_buf.len;
ext_ad[1].data = ad_buf.data;
err = bt_le_ext_adv_set_data(adv, ext_ad, 2, NULL, 0);
if (err != 0) {
printk("Failed to set extended advertising data: %d\n",
err);
Expand Down Expand Up @@ -362,10 +480,14 @@ int main(void)
}
}

#if defined(CONFIG_USB_DEVICE_AUDIO)
/* Never stop streaming when using USB Audio as input */
k_sleep(K_FOREVER);
#else
printk("Waiting %u seconds before stopped\n",
BROADCAST_SOURCE_LIFETIME);
k_sleep(K_SECONDS(BROADCAST_SOURCE_LIFETIME));

#endif
printk("Stopping broadcast source\n");
stopping = true;
err = bt_bap_broadcast_source_stop(broadcast_source);
Expand Down

0 comments on commit abdb9b2

Please sign in to comment.