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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/boards/intel_adsp_ace15_mtpm.conf
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ CONFIG_DAI_DMIC_HAS_MULTIPLE_LINE_SYNC=y
CONFIG_DAI_DMIC_HW_IOCLK=38400000
CONFIG_DAI_INIT_PRIORITY=70
CONFIG_DMA_DW_LLI_POOL_SIZE=50
CONFIG_DMA_DW_FIFO_PARTITION=y
CONFIG_DMA_INTEL_ADSP_GPDMA=y
CONFIG_MEMORY_WIN_2_SIZE=12288
CONFIG_MM_DRV_INTEL_ADSP_TLB_REMAP_UNUSED_RAM=y
Expand Down
1 change: 1 addition & 0 deletions app/boards/intel_adsp_ace30_ptl.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ CONFIG_FORMAT_CONVERT_HIFI3=n
CONFIG_COMP_GOOGLE_RTC_AUDIO_PROCESSING=m
CONFIG_GOOGLE_RTC_AUDIO_PROCESSING_MOCK=y
CONFIG_COMP_STFT_PROCESS=y
CONFIG_COMP_BT_CODEC=y

# SOF / infrastructure
CONFIG_KCPS_DYNAMIC_CLOCK_CONTROL=n
Expand Down
1 change: 1 addition & 0 deletions app/boards/intel_adsp_ace30_wcl.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ CONFIG_FORMAT_CONVERT_HIFI3=n
CONFIG_COMP_GOOGLE_RTC_AUDIO_PROCESSING=m
CONFIG_GOOGLE_RTC_AUDIO_PROCESSING_MOCK=y
CONFIG_COMP_STFT_PROCESS=y
CONFIG_COMP_BT_CODEC=y

# SOF / infrastructure
CONFIG_KCPS_DYNAMIC_CLOCK_CONTROL=n
Expand Down
4 changes: 4 additions & 0 deletions src/audio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,7 @@ else() ### library, e.g. testbench ###
)
endif()
endif()

if(CONFIG_COMP_BT_CODEC)
add_subdirectory(bt_codec)
endif()
2 changes: 2 additions & 0 deletions src/audio/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -451,4 +451,6 @@ config DAI_VERBOSE_GLITCH_WARNINGS
always emit a warning. This option useful to debug issues with
DAI DMA operation and issues with low-latency scheduling.

rsource "bt_codec/Kconfig"

endmenu
45 changes: 45 additions & 0 deletions src/audio/bt_codec/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: BSD-3-Clause

if(DEFINED ZEPHYR_LIBLC3_MODULE_DIR AND EXISTS "${ZEPHYR_LIBLC3_MODULE_DIR}/include/lc3.h")
set(LIBLC3_DIR "${ZEPHYR_LIBLC3_MODULE_DIR}")
else()
cmake_path(SET LIBLC3_DIR NORMALIZE "${CMAKE_CURRENT_LIST_DIR}/../../../../modules/lib/liblc3")
endif()

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

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

zephyr_compile_definitions(
LC3_PLUS=0
LC3_PLUS_HR=0
)

add_local_sources(sof
bt_codec.c
${LIBLC3_SOURCES}
)

set_source_files_properties(
${CMAKE_CURRENT_LIST_DIR}/bt_codec.c
${LIBLC3_SOURCES}
TARGET_DIRECTORY modules_sof
PROPERTIES COMPILE_OPTIONS
"-O3;-ffast-math;-Wno-array-bounds;-Wno-double-promotion;-include;${CMAKE_CURRENT_LIST_DIR}/lc3_math_shim.h"
)
11 changes: 11 additions & 0 deletions src/audio/bt_codec/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: BSD-3-Clause

config COMP_BT_CODEC
bool "Bluetooth Transparent Mode DSP Codec components (LC3, mSBC, G.722, aptX)"
depends on COMP_MODULE_ADAPTER && IPC_MAJOR_4
help
Select to include Bluetooth Transparent Mode (0x03) DSP audio codec
offload components for SSP2-BT playback and capture pipelines.
Provides bt_lc3 (Zephyr liblc3 7.5 ms codec), bt_msbc (HFP H2-framed
mSBC), bt_g722 (ITU-T G.722 64 kbps SB-ADPCM), and bt_aptx (Bluetooth
aptX 4-band QMF ADPCM).
288 changes: 288 additions & 0 deletions src/audio/bt_codec/bt_codec.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2026 Intel Corporation. All rights reserved.
//
// SOF Bluetooth Transparent Mode (0x03) DSP Codec Offload Components:
// - bt_lc3 (Zephyr liblc3)
// - bt_msbc (Bluetooth HFP H2-framed mSBC)
// - bt_g722 (ITU-T G.722 64 kbps SB-ADPCM)
// - bt_aptx (Bluetooth aptX 4-band QMF ADPCM)

#include <sof/audio/module_adapter/module/generic.h>
#include <sof/audio/buffer.h>
#include <sof/audio/component.h>
#include <sof/audio/format.h>
#include <sof/audio/pipeline.h>
#include <sof/audio/sink_api.h>
#include <sof/audio/source_api.h>
#include <sof/audio/sink_source_utils.h>
#include <sof/audio/ipc-config.h>
#include <module/ipc4/base-config.h>
#include <sof/common.h>
#include <rtos/alloc.h>
#include <rtos/init.h>
#include <sof/lib/uuid.h>
#include <sof/trace/trace.h>
#include <ipc/stream.h>
#include <ipc/topology.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/toolchain.h>

#include "lc3_math_shim.h"
#include "bt_codec_algos.h"

#undef fabsf
#undef fminf
#undef fmaxf
#undef floorf
#undef ceilf
#undef ldexpf
#undef frexpf
#undef sqrtf
#undef log10f

__weak float fabsf(float x) { return sof_lc3_fabsf(x); }
__weak float fminf(float a, float b) { return sof_lc3_fminf(a, b); }
__weak float fmaxf(float a, float b) { return sof_lc3_fmaxf(a, b); }
__weak float floorf(float x) { return sof_lc3_floorf(x); }
__weak float ceilf(float x) { return sof_lc3_ceilf(x); }
__weak float ldexpf(float x, int exp) { return sof_lc3_ldexpf(x, exp); }
__weak float frexpf(float x, int *exp) { return sof_lc3_frexpf(x, exp); }
__weak float sqrtf(float x) { return sof_lc3_sqrtf(x); }
__weak float log10f(float x) { return sof_lc3_log10f(x); }

LOG_MODULE_REGISTER(bt_codec, CONFIG_SOF_LOG_LEVEL);

SOF_DEFINE_REG_UUID(bt_lc3);
SOF_DEFINE_REG_UUID(bt_msbc);
SOF_DEFINE_REG_UUID(bt_g722);
SOF_DEFINE_REG_UUID(bt_aptx);

struct bt_codec_comp_data {
enum bt_codec_type codec_type;
enum sof_ipc_frame source_format;
uint32_t channels;
uint32_t rate;
uint32_t frame_bytes;
struct bt_codec_state state;
};

static int bt_codec_init_common(struct processing_module *mod, enum bt_codec_type type)
{
struct module_data *md = &mod->priv;
struct comp_dev *dev = mod->dev;
struct bt_codec_comp_data *cd;

cd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*cd));
if (!cd)
return -ENOMEM;

cd->codec_type = type;
md->private = cd;
comp_info(dev, "bt_codec init type=%d", (int)type);
return 0;
}

static int bt_lc3_init(struct processing_module *mod)
{
return bt_codec_init_common(mod, BT_CODEC_LC3);
}

static int bt_msbc_init(struct processing_module *mod)
{
return bt_codec_init_common(mod, BT_CODEC_MSBC);
}

static int bt_g722_init(struct processing_module *mod)
{
return bt_codec_init_common(mod, BT_CODEC_G722);
}

static int bt_aptx_init(struct processing_module *mod)
{
return bt_codec_init_common(mod, BT_CODEC_APTX);
}

static int bt_codec_free(struct processing_module *mod)
{
struct bt_codec_comp_data *cd = module_get_private_data(mod);

rfree(cd);
return 0;
}

static void bt_codec_params(struct processing_module *mod)
{
struct sof_ipc_stream_params *params = mod->stream_params;
struct comp_buffer *sinkb, *sourceb;
struct comp_dev *dev = mod->dev;

ipc4_base_module_cfg_to_stream_params(&mod->priv.cfg.base_cfg, params);
component_set_nearest_period_frames(dev, params->rate);

sinkb = comp_dev_get_first_data_consumer(dev);
if (sinkb)
ipc4_update_buffer_format(sinkb, &mod->priv.cfg.base_cfg.audio_fmt);

sourceb = comp_dev_get_first_data_producer(dev);
if (sourceb)
ipc4_update_buffer_format(sourceb, &mod->priv.cfg.base_cfg.audio_fmt);
}

static int bt_codec_prepare(struct processing_module *mod,
struct sof_source **sources, int num_of_sources,
struct sof_sink **sinks, int num_of_sinks)
{
struct bt_codec_comp_data *cd = module_get_private_data(mod);
struct comp_dev *dev = mod->dev;
bool is_enc;

if (num_of_sources != 1 || num_of_sinks != 1)
return -EINVAL;

bt_codec_params(mod);

cd->source_format = source_get_frm_fmt(sources[0]);
cd->channels = source_get_channels(sources[0]);
cd->rate = source_get_rate(sources[0]);
cd->frame_bytes = source_get_frame_bytes(sources[0]);

is_enc = (dev->direction == SOF_IPC_STREAM_PLAYBACK);
bt_codec_state_init(&cd->state, cd->codec_type, is_enc);

comp_info(dev, "bt_codec prepare: type=%d dir=%d (enc=%d) rate=%u ch=%u fmt=%d",
(int)cd->codec_type, (int)dev->direction, (int)is_enc,
cd->rate, cd->channels, (int)cd->source_format);
return 0;
}

static int bt_codec_reset(struct processing_module *mod)
{
struct bt_codec_comp_data *cd = module_get_private_data(mod);
bool is_enc = (mod->dev->direction == SOF_IPC_STREAM_PLAYBACK);

bt_codec_state_init(&cd->state, cd->codec_type, is_enc);
return 0;
}

static int bt_codec_process(struct processing_module *mod,
struct sof_source **sources, int num_of_sources,
struct sof_sink **sinks, int num_of_sinks)
{
struct bt_codec_comp_data *cd = module_get_private_data(mod);
struct sof_source *source = sources[0];
struct sof_sink *sink = sinks[0];
uint32_t frames = source_get_data_frames_available(source);
uint32_t sink_frames = sink_get_free_frames(sink);
const int16_t *x, *x_start, *x_end;
int16_t *y, *y_start, *y_end;
size_t x_size, y_size;
int remaining_samples;
size_t bytes;
int ret;

(void)num_of_sources;
(void)num_of_sinks;

frames = MIN(frames, sink_frames);
if (!frames)
return 0;

bytes = frames * cd->frame_bytes;

/* Passthrough if not 8 kHz mono S16_LE Transparent stream */
if (cd->source_format != SOF_IPC_FRAME_S16_LE || cd->channels != 1 || cd->rate != 8000) {
source_to_sink_copy(source, sink, true, bytes);
return 0;
}

ret = source_get_data_s16(source, bytes, &x, &x_start, &x_size);
if (ret)
return ret;

ret = sink_get_buffer_s16(sink, bytes, &y, &y_start, &y_size);
if (ret) {
source_release_data(source, 0);
return ret;
}

x_end = x_start + x_size;
y_end = y_start + y_size;
remaining_samples = (int)frames;

while (remaining_samples > 0) {
int src_avail = (int)(x_end - x);
int dst_avail = (int)(y_end - y);
int chunk = MIN(src_avail, dst_avail);
int i;

chunk = MIN(chunk, remaining_samples);
if (cd->state.is_encoder) {
for (i = 0; i < chunk; i++)
*y++ = bt_codec_process_enc_sample(&cd->state, *x++);
} else {
for (i = 0; i < chunk; i++)
*y++ = bt_codec_process_dec_sample(&cd->state, *x++);
}

if (x >= x_end)
x -= x_size;
if (y >= y_end)
y -= y_size;
remaining_samples -= chunk;
}

source_release_data(source, bytes);
sink_commit_buffer(sink, bytes);
return 0;
}

static const struct module_interface bt_lc3_interface = {
.init = bt_lc3_init,
.prepare = bt_codec_prepare,
.process = bt_codec_process,
.reset = bt_codec_reset,
.free = bt_codec_free,
};

static const struct module_interface bt_msbc_interface = {
.init = bt_msbc_init,
.prepare = bt_codec_prepare,
.process = bt_codec_process,
.reset = bt_codec_reset,
.free = bt_codec_free,
};

static const struct module_interface bt_g722_interface = {
.init = bt_g722_init,
.prepare = bt_codec_prepare,
.process = bt_codec_process,
.reset = bt_codec_reset,
.free = bt_codec_free,
};

static const struct module_interface bt_aptx_interface = {
.init = bt_aptx_init,
.prepare = bt_codec_prepare,
.process = bt_codec_process,
.reset = bt_codec_reset,
.free = bt_codec_free,
};

DECLARE_TR_CTX(bt_lc3_tr, SOF_UUID(bt_lc3_uuid), LOG_LEVEL_INFO);
DECLARE_MODULE_ADAPTER(bt_lc3_interface, bt_lc3_uuid, bt_lc3_tr);
SOF_MODULE_INIT(bt_lc3, sys_comp_module_bt_lc3_interface_init);

DECLARE_TR_CTX(bt_msbc_tr, SOF_UUID(bt_msbc_uuid), LOG_LEVEL_INFO);
DECLARE_MODULE_ADAPTER(bt_msbc_interface, bt_msbc_uuid, bt_msbc_tr);
SOF_MODULE_INIT(bt_msbc, sys_comp_module_bt_msbc_interface_init);

DECLARE_TR_CTX(bt_g722_tr, SOF_UUID(bt_g722_uuid), LOG_LEVEL_INFO);
DECLARE_MODULE_ADAPTER(bt_g722_interface, bt_g722_uuid, bt_g722_tr);
SOF_MODULE_INIT(bt_g722, sys_comp_module_bt_g722_interface_init);

DECLARE_TR_CTX(bt_aptx_tr, SOF_UUID(bt_aptx_uuid), LOG_LEVEL_INFO);
DECLARE_MODULE_ADAPTER(bt_aptx_interface, bt_aptx_uuid, bt_aptx_tr);
SOF_MODULE_INIT(bt_aptx, sys_comp_module_bt_aptx_interface_init);
Loading
Loading