Skip to content

Commit

Permalink
applications: nrf5340_audio: Audio module integration example
Browse files Browse the repository at this point in the history
    Implement the LC3 audio decoder as an example module with
	in the audio application.

Signed-off-by: Graham Wacey <[email protected]>
  • Loading branch information
gWacey committed Jul 18, 2024
1 parent 84345f0 commit 951a8d9
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 97 deletions.
2 changes: 1 addition & 1 deletion applications/nrf5340_audio/prj.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# nRF5340 Audio
CONFIG_NRF5340_AUDIO=y

CONFIG_AUDIO_MODULE=y
CONFIG_SAMPLE_RATE_CONVERTER=y
CONFIG_SAMPLE_RATE_CONVERTER_FILTER_SIMPLE=y

Expand Down
2 changes: 1 addition & 1 deletion applications/nrf5340_audio/prj_release.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# nRF5340 Audio
CONFIG_NRF5340_AUDIO=y

CONFIG_AUDIO_MODULE=y
CONFIG_SAMPLE_RATE_CONVERTER=y
CONFIG_SAMPLE_RATE_CONVERTER_FILTER_SIMPLE=y

Expand Down
1 change: 1 addition & 0 deletions applications/nrf5340_audio/src/audio/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ choice SW_CODEC_DEFAULT
config SW_CODEC_LC3
bool "LC3"
select SW_CODEC_LC3_T2_SOFTWARE
select AUDIO_MODULE_T2_LC3_DECODER
help
LC3 is the mandatory codec for LE Audio.

Expand Down
253 changes: 159 additions & 94 deletions applications/nrf5340_audio/src/audio/sw_codec_select.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
#include <zephyr/kernel.h>
#include <errno.h>
#include <pcm_stream_channel_modifier.h>
#include <audio_module.h>
#include <sample_rate_converter.h>

#if (CONFIG_SW_CODEC_LC3)
#include <zephyr/bluetooth/audio/audio.h>

#include "lc3_decoder.h"
#include "sw_codec_lc3.h"
#endif /* (CONFIG_SW_CODEC_LC3) */

Expand All @@ -23,6 +27,29 @@ static struct sw_codec_config m_config;
static struct sample_rate_converter_ctx encoder_converters[AUDIO_CH_NUM];
static struct sample_rate_converter_ctx decoder_converters[AUDIO_CH_NUM];

#define LC3_DECODER_MSG_QUEUE_SIZE (2)
#define LC3_DATA_OBJECTS_NUM (2)

/* @brief Index into the module handles array for a given module. */
enum stream_module_id {
LC3_MODULE_ID_DECODER = 0,
LC3_MODULE_ID_ENCODER,
LC3_MODULE_ID_NUM
};

/* Streams module handles */
struct audio_module_handle handle[LC3_MODULE_ID_NUM];

K_THREAD_STACK_DEFINE(lc3_dec_thread_stack, CONFIG_LC3_DECODER_STACK_SIZE);
DATA_FIFO_DEFINE(lc3_dec_fifo_tx, LC3_DECODER_MSG_QUEUE_SIZE, sizeof(struct audio_module_message));
DATA_FIFO_DEFINE(lc3_dec_fifo_rx, LC3_DECODER_MSG_QUEUE_SIZE, sizeof(struct audio_module_message));

static char audio_data_memory[PCM_NUM_BYTES_MONO * LC3_DATA_OBJECTS_NUM];
struct k_mem_slab audio_data_slab;

/* Decoder modules private context */
struct lc3_decoder_context decoder_ctx;

/**
* @brief Converts the sample rate of the uncompressed audio stream if needed.
*
Expand Down Expand Up @@ -196,8 +223,11 @@ int sw_codec_decode(uint8_t const *const encoded_data, size_t encoded_size, bool

static char pcm_data_stereo[PCM_NUM_BYTES_STEREO];

char decoded_data_mono[AUDIO_CH_NUM][PCM_NUM_BYTES_MONO] = {0};
char decoded_data_mono_system_sample_rate[AUDIO_CH_NUM][PCM_NUM_BYTES_MONO] = {0};
char decoded_data_stereo[PCM_NUM_BYTES_MONO * m_config.decoder.num_ch];
char decoded_data_stereo_system_sample_rate[PCM_NUM_BYTES_MONO * m_config.decoder.num_ch];

char *dec_data;
char *dec_data_resampled;

size_t pcm_size_stereo = 0;
size_t pcm_size_mono = 0;
Expand All @@ -206,109 +236,90 @@ int sw_codec_decode(uint8_t const *const encoded_data, size_t encoded_size, bool
switch (m_config.sw_codec) {
case SW_CODEC_LC3: {
#if (CONFIG_SW_CODEC_LC3)
char *pcm_in_data_ptrs[m_config.decoder.channel_mode];
if (bad_frame && IS_ENABLED(CONFIG_SW_CODEC_OVERRIDE_PLC)) {
/* Could just clear the stereo buffer and update the size to not have to do
* resampling on a zero buffer.
*/

switch (m_config.decoder.channel_mode) {
case SW_CODEC_MONO: {
if (bad_frame && IS_ENABLED(CONFIG_SW_CODEC_OVERRIDE_PLC)) {
memset(decoded_data_mono[AUDIO_CH_L], 0, PCM_NUM_BYTES_MONO);
decoded_data_size = PCM_NUM_BYTES_MONO;
} else {
ret = sw_codec_lc3_dec_run(
encoded_data, encoded_size, LC3_PCM_NUM_BYTES_MONO, 0,
decoded_data_mono[AUDIO_CH_L],
(uint16_t *)&decoded_data_size, bad_frame);
if (ret) {
return ret;
}

ret = sw_codec_sample_rate_convert(
&decoder_converters[AUDIO_CH_L],
m_config.decoder.sample_rate_hz,
CONFIG_AUDIO_SAMPLE_RATE_HZ, decoded_data_mono[AUDIO_CH_L],
decoded_data_size,
decoded_data_mono_system_sample_rate[AUDIO_CH_L],
&pcm_in_data_ptrs[AUDIO_CH_L], &pcm_size_mono);
if (ret) {
LOG_ERR("Sample rate conversion failed for mono: %d", ret);
return ret;
}
}
memset(dec_data, 0, PCM_NUM_BYTES_MONO * m_config.decoder.num_ch);
decoded_data_size = PCM_NUM_BYTES_MONO * m_config.decoder.num_ch;
} else {
struct audio_data audio_data_tx;
struct audio_data audio_data_rx;

/* For now, i2s is only stereo, so in order to send
* just one channel, we need to insert 0 for the
* other channel
/* The setting of the meta data will happen when the audio data is passed
* from the BT to the audio framework.
*/
ret = pscm_zero_pad(pcm_in_data_ptrs[AUDIO_CH_L], pcm_size_mono,
m_config.decoder.audio_ch, CONFIG_AUDIO_BIT_DEPTH_BITS,
pcm_data_stereo, &pcm_size_stereo);
audio_data_tx.data = (void *)encoded_data;
audio_data_tx.data_size = encoded_size;
audio_data_tx.meta.data_coding = LC3;
audio_data_tx.meta.data_len_us = CONFIG_AUDIO_FRAME_DURATION_US;
audio_data_tx.meta.sample_rate_hz = m_config.decoder.sample_rate_hz;
audio_data_tx.meta.bits_per_sample = CONFIG_AUDIO_BIT_DEPTH_BITS;
audio_data_tx.meta.carried_bits_pr_sample = CONFIG_AUDIO_BIT_DEPTH_BITS;
if (m_config.decoder.channel_mode == SW_CODEC_MONO) {
audio_data_tx.meta.locations = BIT(m_config.decoder.audio_ch);
} else {
audio_data_tx.meta.locations = BT_AUDIO_LOCATION_FRONT_LEFT +
BT_AUDIO_LOCATION_FRONT_RIGHT;
}
audio_data_tx.meta.bad_data = bad_frame;

audio_data_rx.data = (void *)decoded_data_stereo;
audio_data_rx.data_size = PCM_NUM_BYTES_STEREO;
audio_data_rx.meta.data_coding = PCM;
audio_data_rx.meta.data_len_us = CONFIG_AUDIO_FRAME_DURATION_US;
audio_data_rx.meta.sample_rate_hz = m_config.decoder.sample_rate_hz;
audio_data_rx.meta.bits_per_sample = CONFIG_AUDIO_BIT_DEPTH_BITS;
audio_data_rx.meta.carried_bits_pr_sample = CONFIG_AUDIO_BIT_DEPTH_BITS;
audio_data_rx.meta.locations = audio_data_tx.meta.locations;

ret = audio_module_data_tx_rx(&handle[LC3_MODULE_ID_DECODER],
&handle[LC3_MODULE_ID_DECODER],
&audio_data_tx, &audio_data_rx, K_FOREVER);
if (ret) {
return ret;
}
break;
}
case SW_CODEC_STEREO: {
if (bad_frame && IS_ENABLED(CONFIG_SW_CODEC_OVERRIDE_PLC)) {
memset(decoded_data_mono[AUDIO_CH_L], 0, PCM_NUM_BYTES_MONO);
memset(decoded_data_mono[AUDIO_CH_R], 0, PCM_NUM_BYTES_MONO);
decoded_data_size = PCM_NUM_BYTES_MONO;
} else {
/* Decode left channel */
ret = sw_codec_lc3_dec_run(
encoded_data, encoded_size / 2, LC3_PCM_NUM_BYTES_MONO,
AUDIO_CH_L, decoded_data_mono[AUDIO_CH_L],
(uint16_t *)&decoded_data_size, bad_frame);
if (ret) {
return ret;
}

/* Decode right channel */
ret = sw_codec_lc3_dec_run(
(encoded_data + (encoded_size / 2)), encoded_size / 2,
LC3_PCM_NUM_BYTES_MONO, AUDIO_CH_R,
decoded_data_mono[AUDIO_CH_R],
(uint16_t *)&decoded_data_size, bad_frame);
if (ret) {
return ret;
}

for (int i = 0; i < m_config.decoder.channel_mode; ++i) {
ret = sw_codec_sample_rate_convert(
&decoder_converters[i],
m_config.decoder.sample_rate_hz,
CONFIG_AUDIO_SAMPLE_RATE_HZ, decoded_data_mono[i],
decoded_data_size,
decoded_data_mono_system_sample_rate[i],
&pcm_in_data_ptrs[i], &pcm_size_mono);
if (ret) {
LOG_ERR("Sample rate conversion failed for channel "
"%d : %d",
i, ret);
return ret;
}
}
}

ret = pscm_combine(pcm_in_data_ptrs[AUDIO_CH_L],
pcm_in_data_ptrs[AUDIO_CH_R], pcm_size_mono,
CONFIG_AUDIO_BIT_DEPTH_BITS, pcm_data_stereo,
&pcm_size_stereo);
dec_data = decoded_data_stereo;
dec_data_resampled = decoded_data_stereo_system_sample_rate;

for (int i = 0; i < m_config.decoder.num_ch; ++i) {
ret = sw_codec_sample_rate_convert(
&decoder_converters[i], m_config.decoder.sample_rate_hz,
CONFIG_AUDIO_SAMPLE_RATE_HZ, dec_data, PCM_NUM_BYTES_MONO,
dec_data_resampled, &dec_data, &pcm_size_mono);
if (ret) {
LOG_ERR("Sample rate conversion failed for channel "
"%d : %d",
i, ret);
return ret;
}
break;

dec_data += PCM_NUM_BYTES_MONO;
dec_data_resampled += PCM_NUM_BYTES_MONO;
}
default:
LOG_ERR("Unsupported channel mode for decoder: %d",
m_config.decoder.channel_mode);
return -ENODEV;

/* For now, i2s is only stereo, so in order to send
* just one channel, we need to insert 0 for the
* other channel.
*/
if (m_config.decoder.channel_mode == SW_CODEC_MONO) {
ret = pscm_zero_pad(decoded_data_stereo, pcm_size_mono,
m_config.decoder.audio_ch, CONFIG_AUDIO_BIT_DEPTH_BITS,
pcm_data_stereo, &pcm_size_stereo);
if (ret) {
return ret;
}
}

*decoded_size = pcm_size_stereo;
*decoded_data = pcm_data_stereo;
#endif /* (CONFIG_SW_CODEC_LC3) */
break;
}

default:
LOG_ERR("Unsupported codec: %d", m_config.sw_codec);
return -ENODEV;
Expand Down Expand Up @@ -347,10 +358,18 @@ int sw_codec_uninit(struct sw_codec_config sw_codec_cfg)
return -EALREADY;
}

ret = sw_codec_lc3_dec_uninit_all();
ret = audio_module_stop(&handle[LC3_MODULE_ID_DECODER]);
if (ret) {
LOG_WRN("Failed to stop decoder module: %d", ret);
return ret;
}

ret = audio_module_close(&handle[LC3_MODULE_ID_DECODER]);
if (ret) {
LOG_WRN("Failed to close decoder module: %d", ret);
return ret;
}

m_config.decoder.enabled = false;
}
#endif /* (CONFIG_SW_CODEC_LC3) */
Expand Down Expand Up @@ -385,6 +404,7 @@ int sw_codec_init(struct sw_codec_config sw_codec_cfg)
LOG_WRN("The LC3 encoder is already initialized");
return -EALREADY;
}

uint16_t pcm_bytes_req_enc;

LOG_DBG("Encode: %dHz %dbits %dus %dbps %d channel(s)",
Expand All @@ -396,30 +416,75 @@ int sw_codec_init(struct sw_codec_config sw_codec_cfg)
sw_codec_cfg.encoder.sample_rate_hz, CONFIG_AUDIO_BIT_DEPTH_BITS,
CONFIG_AUDIO_FRAME_DURATION_US, sw_codec_cfg.encoder.bitrate,
sw_codec_cfg.encoder.num_ch, &pcm_bytes_req_enc);

if (ret) {
return ret;
}
}

if (sw_codec_cfg.decoder.enabled) {
struct audio_module_parameters audio_decoder_param;
struct lc3_decoder_configuration decoder_config;

if (m_config.decoder.enabled) {
LOG_WRN("The LC3 decoder is already initialized");
return -EALREADY;
}

LOG_DBG("Decode: %dHz %dbits %dus %d channel(s)",
sw_codec_cfg.decoder.sample_rate_hz, CONFIG_AUDIO_BIT_DEPTH_BITS,
CONFIG_AUDIO_FRAME_DURATION_US, sw_codec_cfg.decoder.num_ch);
memset(&handle[LC3_MODULE_ID_DECODER], 0,
sizeof(struct audio_module_handle));

ret = sw_codec_lc3_dec_init(
sw_codec_cfg.decoder.sample_rate_hz, CONFIG_AUDIO_BIT_DEPTH_BITS,
CONFIG_AUDIO_FRAME_DURATION_US, sw_codec_cfg.decoder.num_ch);
decoder_config.sample_rate_hz = sw_codec_cfg.decoder.sample_rate_hz;
decoder_config.bits_per_sample = CONFIG_AUDIO_BIT_DEPTH_BITS;
decoder_config.carried_bits_per_sample = CONFIG_AUDIO_BIT_DEPTH_BITS;
decoder_config.data_len_us = CONFIG_AUDIO_FRAME_DURATION_US;
/* Once the resampler is either moved into the decoder/encoder or I2S this
* should be true.
*/
decoder_config.interleaved = false;
if (sw_codec_cfg.decoder.channel_mode == SW_CODEC_MONO) {
decoder_config.locations = BIT(sw_codec_cfg.decoder.audio_ch);
} else {
decoder_config.locations = BT_AUDIO_LOCATION_FRONT_LEFT +
BT_AUDIO_LOCATION_FRONT_RIGHT;
}
decoder_config.bitrate_bps_max = CONFIG_LC3_BITRATE;

data_fifo_init(&lc3_dec_fifo_rx);
data_fifo_init(&lc3_dec_fifo_tx);

ret = k_mem_slab_init(&audio_data_slab, &audio_data_memory[0],
PCM_NUM_BYTES_MONO * sw_codec_cfg.decoder.num_ch,
LC3_DATA_OBJECTS_NUM);
if (ret) {
LOG_ERR("Failed to allocate the data slab: %d", ret);
return ret;
}

AUDIO_MODULE_PARAMETERS(audio_decoder_param, lc3_decoder_description,
lc3_dec_thread_stack, CONFIG_LC3_DECODER_STACK_SIZE,
CONFIG_LC3_DECODER_THREAD_PRIO, lc3_dec_fifo_rx,
lc3_dec_fifo_tx, audio_data_slab,
PCM_NUM_BYTES_MONO * sw_codec_cfg.decoder.num_ch);

ret = audio_module_open(
&audio_decoder_param,
(struct audio_module_configuration *)&decoder_config, "LC3 Decoder",
(struct audio_module_context *)&decoder_ctx,
&handle[LC3_MODULE_ID_DECODER]);

ret = audio_module_connect(&handle[LC3_MODULE_ID_DECODER], NULL, true);
if (ret) {
LOG_ERR("Decoder connect failed, %d", ret);
return ret;
}

ret = audio_module_start(&handle[LC3_MODULE_ID_DECODER]);
if (ret) {
LOG_ERR("Decoder start failed, %d", ret);
return ret;
}
}

break;
#else
LOG_ERR("LC3 is not compiled in, please open menuconfig and select "
Expand Down
2 changes: 1 addition & 1 deletion applications/nrf5340_audio/src/audio/sw_codec_select.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
#define ENC_MAX_FRAME_SIZE MAX(LC3_ENC_MONO_FRAME_SIZE, 0)
#define ENC_TIME_US MAX(LC3_ENC_TIME_US, 0)
#define DEC_TIME_US MAX(LC3_DEC_TIME_US, 0)
#define PCM_NUM_BYTES_MONO MAX(LC3_PCM_NUM_BYTES_MONO, 0)
#define PCM_NUM_BYTES_MONO MAX(LC3_DECODER_PCM_NUM_BYTES_MONO, (MAX(LC3_PCM_NUM_BYTES_MONO, 0)))
#define PCM_NUM_BYTES_STEREO (PCM_NUM_BYTES_MONO * 2)

enum sw_codec_select {
Expand Down
8 changes: 8 additions & 0 deletions subsys/audio_modules/lc3/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#
# Copyright (c) 2018 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

add_subdirectory_ifdef(CONFIG_AUDIO_MODULE_LC3_DECODER_T2 t2/decoder)
add_subdirectory_ifdef(CONFIG_SW_LC3_ENCODER_T2 t2/encoder)
Loading

0 comments on commit 951a8d9

Please sign in to comment.