Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions modules/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ comment "hal_gigadevice module not available."
comment "hal_nordic module not available."
depends on !ZEPHYR_HAL_NORDIC_MODULE

comment "liblc3codec module not available."
depends on !ZEPHYR_LIBLC3CODEC_MODULE

comment "LittleFS module not available."
depends on !ZEPHYR_LITTLEFS_MODULE

Expand Down
22 changes: 22 additions & 0 deletions modules/liblc3codec/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
if(CONFIG_LIBLC3CODEC)

zephyr_library_named(liblc3codec)
zephyr_library_compile_options(-O3 -ffast-math -Wno-array-bounds)

zephyr_include_directories(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/include)
zephyr_include_directories(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src)

zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/attdet.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/bits.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/bwdet.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/energy.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/lc3.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/ltpf.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/mdct.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/plc.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/sns.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/spec.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/tables.c)
zephyr_library_sources(${ZEPHYR_LIBLC3CODEC_MODULE_DIR}/src/tns.c)

endif()
8 changes: 8 additions & 0 deletions modules/liblc3codec/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2022 Bose Corporation
# SPDX-License-Identifier: Apache-2.0

config LIBLC3CODEC
bool "liblc3codec Support"
depends on FPU
help
This option enables the Android liblc3codec library for Bluetooth LE Audio
10 changes: 10 additions & 0 deletions samples/bluetooth/unicast_audio_client/boards/native_posix.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_BT_TINYCRYPT_ECC=y

CONFIG_LIBLC3CODEC=y
CONFIG_FPU=y

# For LE-audio at 10ms intervals we need the tick counter to occur more frequently
# than every 10 ms as each PDU for some reason takes 2 ticks to process.
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000
CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME=y
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CONFIG_BT_CTLR_ADV_EXT=y
CONFIG_BT_CTLR_ADV_PERIODIC=y

# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3CODEC=y
# 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
# LC3 lib requires floating point support in the c-lib NEWLIB is one way of getting that.
CONFIG_NEWLIB_LIBC=y
2 changes: 1 addition & 1 deletion samples/bluetooth/unicast_audio_client/prj.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_AUDIO=y
CONFIG_BT_AUDIO_UNICAST_CLIENT=y

CONFIG_BT_ISO_TX_BUF_COUNT=4
CONFIG_BT_EXT_ADV=y
CONFIG_BT_CTLR_ADV_EXT=y
185 changes: 163 additions & 22 deletions samples/bluetooth/unicast_audio_client/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,39 +37,137 @@ static K_SEM_DEFINE(sem_stream_qos, 0, 1);
static K_SEM_DEFINE(sem_stream_enabled, 0, 1);
static K_SEM_DEFINE(sem_stream_started, 0, 1);

void print_hex(const uint8_t *ptr, size_t len)

#if defined(CONFIG_LIBLC3CODEC)

#include "lc3.h"
#include "math.h"

/* Current sample do not use codec configuration parameters, hence below shall match the selected
* codec configuration.
* One sample data buffer en generated and repeated.
*/
#define AUDIO_SAMPLE_RATE_HZ 16000
#define AUDIO_FREQUENCY_HZ 400
#define AUDIO_LENGTH_US 10000 /* amount of sample data - shall match LC3 frame length */
#define AUDIO_LENGTH_100US (AUDIO_LENGTH_US / 100)
#define NUM_SAMPLES ((AUDIO_LENGTH_US * AUDIO_SAMPLE_RATE_HZ) / USEC_PER_SEC)
#define AUDIO_VOLUME (INT16_MAX - 3000) /* codec does clipping above INT16_MAX - 3000 */

static int16_t audio_buf[NUM_SAMPLES];
static lc3_encoder_t lc3_encoder;
static lc3_encoder_mem_48k_t lc3_encoder_mem;


/**
* 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)
{
while (len-- != 0) {
printk("%02x", *ptr++);
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.1415 / sine_period_samples;

for (int i = 0; i < num_samples; i++) {
const float sample = sin(i * step);

buf[i] = (int16_t)(AUDIO_VOLUME * sample);
}
}

static void print_codec(const struct bt_codec *codec)
static void lc3_audio_timer_timeout(struct k_work *work)
{
printk("codec 0x%02x cid 0x%04x vid 0x%04x count %u\n",
codec->id, codec->cid, codec->vid, codec->data_count);
/* For the first call-back we push multiple audio frames to the buffer to use the
* controller ISO buffer to handle jitter.
*/
const uint8_t prime_count = 2;
static int64_t start_time;
static int32_t pdu_cnt;
int32_t pdu_goal_cnt;
int64_t uptime, run_time_ms, run_time_100us;

for (size_t i = 0; i < codec->data_count; i++) {
printk("data #%zu: type 0x%02x len %u\n",
i, codec->data[i].data.type,
codec->data[i].data.data_len);
print_hex(codec->data[i].data.data,
codec->data[i].data.data_len -
sizeof(codec->data[i].data.type));
printk("\n");
k_work_schedule(&audio_send_work, K_USEC(preset_16_2_1.qos.interval));

if (start_time == 0) {
/* Read start time and produce the number of frames needed to catch up with any
* inaccuracies in the timer. by calculating the number of frames we should
* have sent and compare to how many were actually sent.
*/
start_time = k_uptime_get();
}

for (size_t i = 0; i < codec->meta_count; i++) {
printk("meta #%zu: type 0x%02x len %u\n",
i, codec->meta[i].data.type,
codec->meta[i].data.data_len);
print_hex(codec->meta[i].data.data,
codec->meta[i].data.data_len -
sizeof(codec->meta[i].data.type));
printk("\n");
uptime = k_uptime_get();
run_time_ms = uptime - start_time;

/* PDU count calculations done in 100us units to allow 7.5ms framelength in fixed-point */
run_time_100us = run_time_ms * 10;
pdu_goal_cnt = run_time_100us / AUDIO_LENGTH_100US;

/* Add primer value to ensure the controller do not run low on data due to jitter */
pdu_goal_cnt += prime_count;

printk("LC3 encode %d frames\n", pdu_goal_cnt - pdu_cnt);

while (pdu_cnt < pdu_goal_cnt) {

int ret;
struct net_buf *buf;
uint8_t *net_buffer;
uint16_t lc3_ret;
const uint16_t tx_sdu_len = audio_stream.iso->qos->tx->sdu;

buf = net_buf_alloc(&tx_pool, K_FOREVER);
net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);

net_buffer = net_buf_tail(buf);
buf->len += tx_sdu_len;

lc3_ret = lc3_encode(lc3_encoder, LC3_PCM_FORMAT_S16, audio_buf,
1, tx_sdu_len, net_buffer);

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

ret = bt_audio_stream_send(&audio_stream, buf);

if (ret < 0) {
printk(" Failed to send LC3 audio data (%d)\n", ret);
net_buf_unref(buf);
} else {
printk(" TX LC3 l: %zu\n", tx_sdu_len);
pdu_cnt++;
}
}
}

static void init_lc3(void)
{
/* Fill audio buffer with Sine wave */
fill_audio_buf_sin(audio_buf, AUDIO_LENGTH_US, AUDIO_FREQUENCY_HZ,
AUDIO_SAMPLE_RATE_HZ);

for (int i = 0; i < NUM_SAMPLES; i++) {
printk("%3i: %6i\n", i, audio_buf[i]);
}

/* Create the encoder instance. This shall complete before stream_started() is called. */
lc3_encoder = lc3_setup_encoder(AUDIO_LENGTH_US, AUDIO_SAMPLE_RATE_HZ, 0, &lc3_encoder_mem);

if (lc3_encoder == NULL) {
printk("ERROR: Failed to setup LC3 encoder - wrong parameters?\n");
}
}

#else

#define init_lc3(...)

/**
* @brief Send audio data on timeout
*
Expand Down Expand Up @@ -122,6 +220,41 @@ static void audio_timer_timeout(struct k_work *work)
}
}

#endif

static void print_hex(const uint8_t *ptr, size_t len)
{
while (len-- != 0) {
printk("%02x", *ptr++);
}
}

static void print_codec(const struct bt_codec *codec)
{
printk("codec 0x%02x cid 0x%04x vid 0x%04x count %u\n",
codec->id, codec->cid, codec->vid, codec->data_count);

for (size_t i = 0; i < codec->data_count; i++) {
printk("data #%zu: type 0x%02x len %u\n",
i, codec->data[i].data.type,
codec->data[i].data.data_len);
print_hex(codec->data[i].data.data,
codec->data[i].data.data_len -
sizeof(codec->data[i].data.type));
printk("\n");
}

for (size_t i = 0; i < codec->meta_count; i++) {
printk("meta #%zu: type 0x%02x len %u\n",
i, codec->meta[i].data.type,
codec->meta[i].data.data_len);
print_hex(codec->meta[i].data.data,
codec->meta[i].data.data_len -
sizeof(codec->meta[i].data.type));
printk("\n");
}
}

static bool check_audio_support_and_connect(struct bt_data *data,
void *user_data)
{
Expand Down Expand Up @@ -392,7 +525,11 @@ static int init(void)

audio_stream.ops = &stream_ops;

#if defined(CONFIG_LIBLC3CODEC)
k_work_init_delayable(&audio_send_work, lc3_audio_timer_timeout);
#else
k_work_init_delayable(&audio_send_work, audio_timer_timeout);
#endif

return 0;
}
Expand Down Expand Up @@ -492,6 +629,10 @@ static int enable_stream(struct bt_audio_stream *stream)
{
int err;

if (IS_ENABLED(CONFIG_LIBLC3CODEC)) {
init_lc3();
}

err = bt_audio_stream_enable(stream, preset_16_2_1.codec.meta,
preset_16_2_1.codec.meta_count);
if (err != 0) {
Expand Down
10 changes: 10 additions & 0 deletions samples/bluetooth/unicast_audio_server/boards/native_posix.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_BT_TINYCRYPT_ECC=y

CONFIG_LIBLC3CODEC=y
CONFIG_FPU=y

# For LE-audio at 10ms intervals we need the tick counter to occur more frequently
# than every 10 ms as each PDU for some reason takes 2 ticks to process.
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000
CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME=y
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
CONFIG_BT_CTLR_ADV_EXT=y
CONFIG_BT_CTLR_ADV_PERIODIC=y

# For LC3 the following configs are needed
CONFIG_FPU=y
CONFIG_LIBLC3CODEC=y
# 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
# LC3 lib requires floating point support in the c-lib NEWLIB is one way of getting that.
CONFIG_NEWLIB_LIBC=y
Loading