diff --git a/scripts/sof-rebuild-processing-comp-blobs.sh b/scripts/sof-rebuild-processing-comp-blobs.sh index 97f988ee8523..617669c10646 100755 --- a/scripts/sof-rebuild-processing-comp-blobs.sh +++ b/scripts/sof-rebuild-processing-comp-blobs.sh @@ -41,3 +41,4 @@ cd "$SOF_WORKSPACE"/sof/src/audio/multiband_drc/tune; $OCTAVE sof_example_multib cd "$SOF_WORKSPACE"/sof/src/audio/tdfb/tune; ./sof_example_all.sh cd "$SOF_WORKSPACE"/sof/src/audio/selector/tune; $OCTAVE ./sof_selector_blobs.m cd "$SOF_WORKSPACE"/sof/tools/tune/mfcc; $OCTAVE setup_mfcc.m +cd "$SOF_WORKSPACE"/sof/src/audio/level_multiplier/tune; $OCTAVE sof_level_multiplier_blobs.m diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig index 1b9483ffe858..339b2660e897 100644 --- a/src/arch/host/configs/library_defconfig +++ b/src/arch/host/configs/library_defconfig @@ -8,6 +8,7 @@ CONFIG_COMP_GOOGLE_RTC_AUDIO_PROCESSING=y CONFIG_COMP_GOOGLE_CTC_AUDIO_PROCESSING=y CONFIG_COMP_IIR=y CONFIG_COMP_IGO_NR=y +CONFIG_COMP_LEVEL_MULTIPLIER=y CONFIG_COMP_MFCC=y CONFIG_COMP_MODULE_ADAPTER=y CONFIG_COMP_MULTIBAND_DRC=y diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index c7f45ca88bc8..76625e9796b8 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -123,6 +123,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_TEMPLATE_COMP) add_subdirectory(template_comp) endif() + if(CONFIG_COMP_LEVEL_MULTIPLIER) + add_subdirectory(level_multiplier) + endif() endif() ### Common files (also used in shared library build) diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 005c618cf9a5..5b6d5b061198 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -180,6 +180,8 @@ rsource "codec/Kconfig" rsource "template_comp/Kconfig" +rsource "level_multiplier/Kconfig" + endmenu # "Audio components" menu "Data formats" diff --git a/src/audio/level_multiplier/CMakeLists.txt b/src/audio/level_multiplier/CMakeLists.txt new file mode 100644 index 000000000000..aa3f19a02b66 --- /dev/null +++ b/src/audio/level_multiplier/CMakeLists.txt @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause + +if(CONFIG_COMP_LEVEL_MULTIPLIER STREQUAL "m") + add_subdirectory(llext ${PROJECT_BINARY_DIR}/level_multiplier_llext) + add_dependencies(app level_multiplier) +else() + add_local_sources(sof level_multiplier.c) + add_local_sources(sof level_multiplier-generic.c) + add_local_sources(sof level_multiplier-hifi3.c) + add_local_sources(sof level_multiplier-hifi5.c) + + if(CONFIG_IPC_MAJOR_4) + add_local_sources(sof level_multiplier-ipc4.c) + endif() +endif() diff --git a/src/audio/level_multiplier/Kconfig b/src/audio/level_multiplier/Kconfig new file mode 100644 index 000000000000..cc8a79632cd7 --- /dev/null +++ b/src/audio/level_multiplier/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause + +config COMP_LEVEL_MULTIPLIER + tristate "Level multiplier component" + default y + help + Select for Level multiplier component. This component + applies a fixed gain to audio. The amount of gain + is configured in the bytes control that is typically + set in boot time from topology. It can e.g. increase + capture sensitivity of voice applications by 20 dB + compared to media capture. diff --git a/src/audio/level_multiplier/level_multiplier-generic.c b/src/audio/level_multiplier/level_multiplier-generic.c new file mode 100644 index 000000000000..554a5f7f49c6 --- /dev/null +++ b/src/audio/level_multiplier/level_multiplier-generic.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include "level_multiplier.h" + +#define LEVEL_MULTIPLIER_S16_SHIFT Q_SHIFT_BITS_32(15, LEVEL_MULTIPLIER_QXY_Y, 15) +#define LEVEL_MULTIPLIER_S24_SHIFT Q_SHIFT_BITS_64(23, LEVEL_MULTIPLIER_QXY_Y, 23) +#define LEVEL_MULTIPLIER_S32_SHIFT Q_SHIFT_BITS_64(31, LEVEL_MULTIPLIER_QXY_Y, 31) + +#if SOF_USE_HIFI(NONE, VOLUME) + +#if CONFIG_FORMAT_S16LE +/** + * level_multiplier_s16() - Process S16_LE format. + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * This is the processing function for 16-bit signed integer PCM formats. The + * audio samples are copied from source to sink with gain defined in cd->gain. + * + * Return: Value zero for success, otherwise an error code. + */ +static int level_multiplier_s16(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + const int32_t gain = cd->gain; + int16_t const *x, *x_start, *x_end; + int16_t *y, *y_start, *y_end; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int remaining_samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int i; + + ret = source_get_data_s16(source, bytes, &x, &x_start, &x_size); + if (ret) + return ret; + + /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s16(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + y_end = y_start + y_size; + while (remaining_samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - x; + samples_without_wrap = y_end - y; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + for (i = 0; i < samples_without_wrap; i++) { + *y = q_multsr_sat_32x32_16(*x, gain, LEVEL_MULTIPLIER_S16_SHIFT); + x++; + y++; + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x = (x >= x_end) ? x - x_size : x; + y = (y >= y_end) ? y - y_size : y; + + remaining_samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + source_release_data(source, bytes); + sink_commit_buffer(sink, bytes); + return 0; +} +#endif /* CONFIG_FORMAT_S16LE */ + +#if CONFIG_FORMAT_S24LE +/** + * level_multiplier_s24() - Process S24_4LE format. + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * This is the processing function for 24-bit signed integer PCM formats. The + * audio samples are copied from source to sink with gain defined in cd->gain. + * + * Return: Value zero for success, otherwise an error code. + */ +static int level_multiplier_s24(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + const int32_t gain = cd->gain; + int32_t const *x, *x_start, *x_end; + int32_t *y, *y_start, *y_end; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int remaining_samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int i; + + ret = source_get_data_s32(source, bytes, &x, &x_start, &x_size); + if (ret) + return ret; + + /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s32(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + y_end = y_start + y_size; + while (remaining_samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - x; + samples_without_wrap = y_end - y; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + for (i = 0; i < samples_without_wrap; i++) { + *y = q_multsr_sat_32x32_24(sign_extend_s24(*x), gain, + LEVEL_MULTIPLIER_S24_SHIFT); + x++; + y++; + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x = (x >= x_end) ? x - x_size : x; + y = (y >= y_end) ? y - y_size : y; + + remaining_samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + source_release_data(source, bytes); + sink_commit_buffer(sink, bytes); + return 0; +} +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S32LE +/** + * level_multiplier_s32() - Process S32_LE format. + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * This is the processing function for 32-bit signed integer PCM formats. The + * audio samples are copied from source to sink with gain defined in cd->gain. + * + * Return: Value zero for success, otherwise an error code. + */ +static int level_multiplier_s32(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + const int32_t gain = cd->gain; + int32_t const *x, *x_start, *x_end; + int32_t *y, *y_start, *y_end; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int remaining_samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int i; + + ret = source_get_data_s32(source, bytes, &x, &x_start, &x_size); + if (ret) + return ret; + + /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s32(sink, bytes, &y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + y_end = y_start + y_size; + while (remaining_samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - x; + samples_without_wrap = y_end - y; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + for (i = 0; i < samples_without_wrap; i++) { + *y = q_multsr_sat_32x32(*x, gain, LEVEL_MULTIPLIER_S32_SHIFT); + x++; + y++; + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x = (x >= x_end) ? x - x_size : x; + y = (y >= y_end) ? y - y_size : y; + + remaining_samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + source_release_data(source, bytes); + sink_commit_buffer(sink, bytes); + return 0; +} +#endif /* CONFIG_FORMAT_S32LE */ + +/* This struct array defines the used processing functions for + * the PCM formats + */ +const struct level_multiplier_proc_fnmap level_multiplier_proc_fnmap[] = { +#if CONFIG_FORMAT_S16LE + { SOF_IPC_FRAME_S16_LE, level_multiplier_s16 }, +#endif +#if CONFIG_FORMAT_S24LE + { SOF_IPC_FRAME_S24_4LE, level_multiplier_s24 }, +#endif +#if CONFIG_FORMAT_S32LE + { SOF_IPC_FRAME_S32_LE, level_multiplier_s32 }, +#endif +}; + +/** + * level_multiplier_find_proc_func() - Find suitable processing function. + * @src_fmt: Enum value for PCM format. + * + * This function finds the suitable processing function to use for + * the used PCM format. If not found, return NULL. + * + * Return: Pointer to processing function for the requested PCM format. + */ +level_multiplier_func level_multiplier_find_proc_func(enum sof_ipc_frame src_fmt) +{ + int i; + + /* Find suitable processing function from map */ + for (i = 0; i < ARRAY_SIZE(level_multiplier_proc_fnmap); i++) + if (src_fmt == level_multiplier_proc_fnmap[i].frame_fmt) + return level_multiplier_proc_fnmap[i].level_multiplier_proc_func; + + return NULL; +} + +#endif /* SOF_USE_HIFI(NONE, VOLUME) */ diff --git a/src/audio/level_multiplier/level_multiplier-hifi3.c b/src/audio/level_multiplier/level_multiplier-hifi3.c new file mode 100644 index 000000000000..077476254c0e --- /dev/null +++ b/src/audio/level_multiplier/level_multiplier-hifi3.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include "level_multiplier.h" + +#define LEVEL_MULTIPLIER_S32_SHIFT 8 /* See explanation from level_multiplier_s32() */ + +#if SOF_USE_HIFI(3, VOLUME) || SOF_USE_HIFI(4, VOLUME) + +#include + +#if CONFIG_FORMAT_S16LE +/** + * level_multiplier_s16() - Process S16_LE format. + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * This is the processing function for 16-bit signed integer PCM formats. The + * audio samples are copied from source to sink with gain defined in cd->gain. + * + * Return: Value zero for success, otherwise an error code. + */ +static int level_multiplier_s16(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + ae_valign x_align; + ae_valign y_align = AE_ZALIGN64(); + ae_f32x2 samples0; + ae_f32x2 samples1; + const ae_f32x2 gain = cd->gain; + ae_f16x4 samples; + ae_f16x4 const *x; + ae_f16x4 *y; + int16_t const *x_start, *x_end; + int16_t *y_start, *y_end; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int remaining_samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int n, i; + + ret = source_get_data_s16(source, bytes, (const int16_t **)&x, &x_start, &x_size); + if (ret) + return ret; + + /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s16(sink, bytes, (int16_t **)&y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + y_end = y_start + y_size; + while (remaining_samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - (int16_t *)x; + samples_without_wrap = y_end - (int16_t *)y; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + x_align = AE_LA64_PP(x); + + /* Process with 64 bit loads and stores */ + n = samples_without_wrap >> 2; + for (i = 0; i < n; i++) { + AE_LA16X4_IP(samples, x_align, x); + + /* Multiply the input sample */ + samples0 = AE_MULFP32X16X2RS_H(gain, samples); + samples1 = AE_MULFP32X16X2RS_L(gain, samples); + + /* Q9.23 to Q1.31 */ + samples0 = AE_SLAI32S(samples0, 8); + samples1 = AE_SLAI32S(samples1, 8); + + /* To Q1.15 */ + samples = AE_ROUND16X4F32SSYM(samples0, samples1); + AE_SA16X4_IP(samples, y_align, y); + } + + AE_SA64POS_FP(y_align, y); + n = samples_without_wrap - (n << 2); + for (i = 0; i < n; i++) { + AE_L16_IP(samples, (ae_f16 *)x, sizeof(ae_f16)); + samples0 = AE_MULFP32X16X2RS_H(gain, samples); + samples0 = AE_SLAI32S(samples0, 8); + samples = AE_ROUND16X4F32SSYM(samples0, samples0); + AE_S16_0_IP(samples, (ae_f16 *)y, sizeof(ae_f16)); + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x = (x >= (ae_f16x4 *)x_end) ? x - x_size : x; + y = (y >= (ae_f16x4 *)y_end) ? y - y_size : y; + remaining_samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + source_release_data(source, bytes); + sink_commit_buffer(sink, bytes); + return 0; +} +#endif /* CONFIG_FORMAT_S16LE */ + +#if CONFIG_FORMAT_S24LE +/** + * level_multiplier_s24() - Process S24_4LE format. + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * This is the processing function for 24-bit signed integer PCM formats. The + * audio samples are copied from source to sink with gain defined in cd->gain. + * + * Return: Value zero for success, otherwise an error code. + */ +static int level_multiplier_s24(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + ae_valign x_align; + ae_valign y_align = AE_ZALIGN64(); + const ae_f32x2 gain = cd->gain; + ae_f32x2 samples; + ae_f32x2 const *x; + ae_f32x2 *y; + int32_t const *x_start, *x_end; + int32_t *y_start, *y_end; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int remaining_samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int n, i; + + ret = source_get_data_s32(source, bytes, (const int32_t **)&x, &x_start, &x_size); + if (ret) + return ret; + + /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s32(sink, bytes, (int32_t **)&y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + y_end = y_start + y_size; + while (remaining_samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - (int32_t *)x; + samples_without_wrap = y_end - (int32_t *)y; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + x_align = AE_LA64_PP(x); + + /* Process with 64 bit loads and stores */ + n = samples_without_wrap >> 1; + for (i = 0; i < n; i++) { + AE_LA32X2_IP(samples, x_align, x); + samples = AE_MULFP32X2RS(gain, AE_SLAI32(samples, 8)); + samples = AE_SLAI32S(samples, 8); + samples = AE_SRAI32(samples, 8); + AE_SA32X2_IP(samples, y_align, y); + } + + AE_SA64POS_FP(y_align, y); + if (samples_without_wrap - (n << 1)) { + AE_L32_IP(samples, (ae_f32 *)x, sizeof(ae_f32)); + samples = AE_MULFP32X2RS(gain, AE_SLAI32(samples, 8)); + samples = AE_SLAI32S(samples, 8); + samples = AE_SRAI32(samples, 8); + AE_S32_L_IP(samples, (ae_f32 *)y, sizeof(ae_f32)); + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x = (x >= (ae_f32x2 *)x_end) ? x - x_size : x; + y = (y >= (ae_f32x2 *)y_end) ? y - y_size : y; + remaining_samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + source_release_data(source, bytes); + sink_commit_buffer(sink, bytes); + return 0; +} +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S32LE +/** + * level_multiplier_s32() - Process S32_LE format. + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * This is the processing function for 32-bit signed integer PCM formats. The + * audio samples are copied from source to sink with gain defined in cd->gain. + * + * Return: Value zero for success, otherwise an error code. + */ +static int level_multiplier_s32(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + ae_valign x_align; + ae_valign y_align = AE_ZALIGN64(); + ae_f64 mult0; + ae_f64 mult1; + const ae_f32x2 gain = cd->gain; + ae_f32x2 samples; + ae_f32x2 const *x; + ae_f32x2 *y; + int32_t const *x_start, *x_end; + int32_t *y_start, *y_end; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int remaining_samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int n, i; + + ret = source_get_data_s32(source, bytes, (const int32_t **)&x, &x_start, &x_size); + if (ret) + return ret; + + /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s32(sink, bytes, (int32_t **)&y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + y_end = y_start + y_size; + while (remaining_samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - (int32_t *)x; + samples_without_wrap = y_end - (int32_t *)y; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + x_align = AE_LA64_PP(x); + + /* Process with 64 bit loads and stores */ + n = samples_without_wrap >> 1; + for (i = 0; i < n; i++) { + AE_LA32X2_IP(samples, x_align, x); + /* Q31 gain would give Q47, then Q23 gain gives Q39, need to shift + * the product left by 8 to get Q47 for round instruction. + */ + mult0 = AE_MULF32R_HH(gain, samples); + mult1 = AE_MULF32R_LL(gain, samples); + mult0 = AE_SLAI64(mult0, LEVEL_MULTIPLIER_S32_SHIFT); + mult1 = AE_SLAI64(mult1, LEVEL_MULTIPLIER_S32_SHIFT); + samples = AE_ROUND32X2F48SSYM(mult0, mult1); /* Q2.47 -> Q1.31 */ + AE_SA32X2_IP(samples, y_align, y); + } + + AE_SA64POS_FP(y_align, y); + if (samples_without_wrap - (n << 1)) { + AE_L32_IP(samples, (ae_f32 *)x, sizeof(ae_f32)); + mult0 = AE_MULF32R_HH(gain, samples); + mult0 = AE_SLAI64(mult0, LEVEL_MULTIPLIER_S32_SHIFT); + samples = AE_ROUND32F48SSYM(mult0); + AE_S32_L_IP(samples, (ae_f32 *)y, sizeof(ae_f32)); + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x = (x >= (ae_f32x2 *)x_end) ? x - x_size : x; + y = (y >= (ae_f32x2 *)y_end) ? y - y_size : y; + remaining_samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + source_release_data(source, bytes); + sink_commit_buffer(sink, bytes); + return 0; +} +#endif /* CONFIG_FORMAT_S32LE */ + +/* This struct array defines the used processing functions for + * the PCM formats + */ +const struct level_multiplier_proc_fnmap level_multiplier_proc_fnmap[] = { +#if CONFIG_FORMAT_S16LE + { SOF_IPC_FRAME_S16_LE, level_multiplier_s16 }, +#endif +#if CONFIG_FORMAT_S24LE + { SOF_IPC_FRAME_S24_4LE, level_multiplier_s24 }, +#endif +#if CONFIG_FORMAT_S32LE + { SOF_IPC_FRAME_S32_LE, level_multiplier_s32 }, +#endif +}; + +/** + * level_multiplier_find_proc_func() - Find suitable processing function. + * @src_fmt: Enum value for PCM format. + * + * This function finds the suitable processing function to use for + * the used PCM format. If not found, return NULL. + * + * Return: Pointer to processing function for the requested PCM format. + */ +level_multiplier_func level_multiplier_find_proc_func(enum sof_ipc_frame src_fmt) +{ + int i; + + /* Find suitable processing function from map */ + for (i = 0; i < ARRAY_SIZE(level_multiplier_proc_fnmap); i++) + if (src_fmt == level_multiplier_proc_fnmap[i].frame_fmt) + return level_multiplier_proc_fnmap[i].level_multiplier_proc_func; + + return NULL; +} + +#endif /* SOF_USE_HIFI(3, VOLUME) || SOF_USE_HIFI(4, VOLUME) */ diff --git a/src/audio/level_multiplier/level_multiplier-hifi5.c b/src/audio/level_multiplier/level_multiplier-hifi5.c new file mode 100644 index 000000000000..91f52ade4767 --- /dev/null +++ b/src/audio/level_multiplier/level_multiplier-hifi5.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include "level_multiplier.h" + +#define LEVEL_MULTIPLIER_S32_SHIFT 8 /* See explanation from level_multiplier_s32() */ + +#if SOF_USE_MIN_HIFI(5, VOLUME) + +#include + +#if CONFIG_FORMAT_S16LE +/** + * level_multiplier_s16() - Process S16_LE format. + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * This is the processing function for 16-bit signed integer PCM formats. The + * audio samples are copied from source to sink with gain defined in cd->gain. + * + * Return: Value zero for success, otherwise an error code. + */ +static int level_multiplier_s16(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + ae_valignx2 x_align; + ae_valignx2 y_align = AE_ZALIGN128(); + ae_f32x2 tmp0; + ae_f32x2 tmp1; + const ae_f32x2 gain = cd->gain; + ae_f16x4 samples0; + ae_f16x4 samples1; + ae_int16x8 const *x; + ae_int16x8 *y; + int16_t const *x_start, *x_end; + int16_t *y_start, *y_end; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int remaining_samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int n, i; + + ret = source_get_data_s16(source, bytes, (const int16_t **)&x, &x_start, &x_size); + if (ret) + return ret; + + /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s16(sink, bytes, (int16_t **)&y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + y_end = y_start + y_size; + while (remaining_samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - (int16_t *)x; + samples_without_wrap = y_end - (int16_t *)y; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + x_align = AE_LA128_PP(x); + + /* Process with 128 bit loads and stores */ + n = samples_without_wrap >> 3; + for (i = 0; i < n; i++) { + AE_LA16X4X2_IP(samples0, samples1, x_align, x); + + AE_MULF2P32X16X4RS(tmp0, tmp1, gain, gain, samples0); + /* Q9.23 to Q1.31 */ + tmp0 = AE_SLAI32S(tmp0, 8); + tmp1 = AE_SLAI32S(tmp1, 8); + samples0 = AE_ROUND16X4F32SSYM(tmp0, tmp1); + + AE_MULF2P32X16X4RS(tmp0, tmp1, gain, gain, samples1); + /* Q9.23 to Q1.31 */ + tmp0 = AE_SLAI32S(tmp0, 8); + tmp1 = AE_SLAI32S(tmp1, 8); + samples1 = AE_ROUND16X4F32SSYM(tmp0, tmp1); + + AE_SA16X4X2_IP(samples0, samples1, y_align, y); + } + + AE_SA128POS_FP(y_align, y); + n = samples_without_wrap - (n << 3); + for (i = 0; i < n; i++) { + AE_L16_IP(samples0, (ae_f16 *)x, sizeof(ae_f16)); + tmp0 = AE_MULFP32X16X2RS_H(gain, samples0); + tmp0 = AE_SLAI32S(tmp0, 8); + samples0 = AE_ROUND16X4F32SSYM(tmp0, tmp0); + AE_S16_0_IP(samples0, (ae_f16 *)y, sizeof(ae_f16)); + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x = (x >= (ae_int16x8 *)x_end) ? x - x_size : x; + y = (y >= (ae_int16x8 *)y_end) ? y - y_size : y; + remaining_samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + source_release_data(source, bytes); + sink_commit_buffer(sink, bytes); + return 0; +} +#endif /* CONFIG_FORMAT_S16LE */ + +#if CONFIG_FORMAT_S24LE +/** + * level_multiplier_s24() - Process S24_4LE format. + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * This is the processing function for 24-bit signed integer PCM formats. The + * audio samples are copied from source to sink with gain defined in cd->gain. + * + * Return: Value zero for success, otherwise an error code. + */ +static int level_multiplier_s24(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + ae_valignx2 x_align; + ae_valignx2 y_align = AE_ZALIGN128(); + const ae_f32x2 gain = cd->gain; + ae_f32x2 samples0; + ae_f32x2 samples1; + ae_f32x2 tmp0; + ae_f32x2 tmp1; + ae_int32x4 const *x; + ae_int32x4 *y; + int32_t const *x_start, *x_end; + int32_t *y_start, *y_end; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int remaining_samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int n, i; + + ret = source_get_data_s32(source, bytes, (const int32_t **)&x, &x_start, &x_size); + if (ret) + return ret; + + /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s32(sink, bytes, (int32_t **)&y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + y_end = y_start + y_size; + while (remaining_samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - (int32_t *)x; + samples_without_wrap = y_end - (int32_t *)y; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + x_align = AE_LA128_PP(x); + + /* Process with 64 bit loads and stores */ + n = samples_without_wrap >> 2; + for (i = 0; i < n; i++) { + AE_LA32X2X2_IP(samples0, samples1, x_align, x); + AE_MULF2P32X4RS(tmp0, tmp1, gain, gain, + AE_SLAI32(samples0, 8), + AE_SLAI32(samples1, 8)); + samples0 = AE_SRAI32(AE_SLAI32S(tmp0, 8), 8); + samples1 = AE_SRAI32(AE_SLAI32S(tmp1, 8), 8); + AE_SA32X2X2_IP(samples0, samples1, y_align, y); + } + + AE_SA128POS_FP(y_align, y); + n = samples_without_wrap - (n << 2); + for (i = 0; i < n; i++) { + AE_L32_IP(samples0, (ae_f32 *)x, sizeof(ae_f32)); + samples0 = AE_MULFP32X2RS(gain, AE_SLAI32(samples0, 8)); + samples0 = AE_SRAI32(AE_SLAI32S(samples0, 8), 8); + AE_S32_L_IP(samples0, (ae_f32 *)y, sizeof(ae_f32)); + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x = (x >= (ae_int32x4 *)x_end) ? x - x_size : x; + y = (y >= (ae_int32x4 *)y_end) ? y - y_size : y; + remaining_samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + source_release_data(source, bytes); + sink_commit_buffer(sink, bytes); + return 0; +} +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S32LE +/** + * level_multiplier_s32() - Process S32_LE format. + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * This is the processing function for 32-bit signed integer PCM formats. The + * audio samples are copied from source to sink with gain defined in cd->gain. + * + * Return: Value zero for success, otherwise an error code. + */ +static int level_multiplier_s32(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + ae_valignx2 x_align; + ae_valignx2 y_align = AE_ZALIGN128(); + ae_f64 mult0; + ae_f64 mult1; + const ae_f32x2 gain = cd->gain; + ae_f32x2 samples0; + ae_f32x2 samples1; + ae_int32x4 const *x; + ae_int32x4 *y; + int32_t const *x_start, *x_end; + int32_t *y_start, *y_end; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int remaining_samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int n, i; + + ret = source_get_data_s32(source, bytes, (const int32_t **)&x, &x_start, &x_size); + if (ret) + return ret; + + /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ + ret = sink_get_buffer_s32(sink, bytes, (int32_t **)&y, &y_start, &y_size); + if (ret) + return ret; + + /* Set helper pointers to buffer end for wrap check. Then loop until all + * samples are processed. + */ + x_end = x_start + x_size; + y_end = y_start + y_size; + while (remaining_samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - (int32_t *)x; + samples_without_wrap = y_end - (int32_t *)y; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + x_align = AE_LA128_PP(x); + + /* Process with 64 bit loads and stores */ + n = samples_without_wrap >> 2; + for (i = 0; i < n; i++) { + AE_LA32X2X2_IP(samples0, samples1, x_align, x); + + AE_MULF32X2R_HH_LL(mult0, mult1, gain, samples0); + mult0 = AE_SLAI64(mult0, LEVEL_MULTIPLIER_S32_SHIFT); + mult1 = AE_SLAI64(mult1, LEVEL_MULTIPLIER_S32_SHIFT); + samples0 = AE_ROUND32X2F48SSYM(mult0, mult1); /* Q2.47 -> Q1.31 */ + + AE_MULF32X2R_HH_LL(mult0, mult1, gain, samples1); + mult0 = AE_SLAI64(mult0, LEVEL_MULTIPLIER_S32_SHIFT); + mult1 = AE_SLAI64(mult1, LEVEL_MULTIPLIER_S32_SHIFT); + samples1 = AE_ROUND32X2F48SSYM(mult0, mult1); /* Q2.47 -> Q1.31 */ + + AE_SA32X2X2_IP(samples0, samples1, y_align, y); + } + + AE_SA128POS_FP(y_align, y); + n = samples_without_wrap - (n << 2); + for (i = 0; i < n; i++) { + AE_L32_IP(samples0, (ae_f32 *)x, sizeof(ae_f32)); + mult0 = AE_MULF32R_HH(gain, samples0); + mult0 = AE_SLAI64(mult0, LEVEL_MULTIPLIER_S32_SHIFT); + samples0 = AE_ROUND32F48SSYM(mult0); + AE_S32_L_IP(samples0, (ae_f32 *)y, sizeof(ae_f32)); + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x = (x >= (ae_int32x4 *)x_end) ? x - x_size : x; + y = (y >= (ae_int32x4 *)y_end) ? y - y_size : y; + remaining_samples -= samples_without_wrap; + } + + /* Update the source and sink for bytes consumed and produced. Return success. */ + source_release_data(source, bytes); + sink_commit_buffer(sink, bytes); + return 0; +} +#endif /* CONFIG_FORMAT_S32LE */ + +/* This struct array defines the used processing functions for + * the PCM formats + */ +const struct level_multiplier_proc_fnmap level_multiplier_proc_fnmap[] = { +#if CONFIG_FORMAT_S16LE + { SOF_IPC_FRAME_S16_LE, level_multiplier_s16 }, +#endif +#if CONFIG_FORMAT_S24LE + { SOF_IPC_FRAME_S24_4LE, level_multiplier_s24 }, +#endif +#if CONFIG_FORMAT_S32LE + { SOF_IPC_FRAME_S32_LE, level_multiplier_s32 }, +#endif +}; + +/** + * level_multiplier_find_proc_func() - Find suitable processing function. + * @src_fmt: Enum value for PCM format. + * + * This function finds the suitable processing function to use for + * the used PCM format. If not found, return NULL. + * + * Return: Pointer to processing function for the requested PCM format. + */ +level_multiplier_func level_multiplier_find_proc_func(enum sof_ipc_frame src_fmt) +{ + int i; + + /* Find suitable processing function from map */ + for (i = 0; i < ARRAY_SIZE(level_multiplier_proc_fnmap); i++) + if (src_fmt == level_multiplier_proc_fnmap[i].frame_fmt) + return level_multiplier_proc_fnmap[i].level_multiplier_proc_func; + + return NULL; +} + +#endif /* SOF_USE_MIN_HIFI(5, VOLUME) */ diff --git a/src/audio/level_multiplier/level_multiplier-ipc4.c b/src/audio/level_multiplier/level_multiplier-ipc4.c new file mode 100644 index 000000000000..02289d844f63 --- /dev/null +++ b/src/audio/level_multiplier/level_multiplier-ipc4.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#include +#include +#include "level_multiplier.h" + +LOG_MODULE_DECLARE(level_multiplier, CONFIG_SOF_LOG_LEVEL); + +/* IPC4 controls handler */ +__cold int level_multiplier_set_config(struct processing_module *mod, + uint32_t param_id, + enum module_cfg_fragment_position pos, + uint32_t data_offset_size, + const uint8_t *fragment, + size_t fragment_size, + uint8_t *response, + size_t response_size) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + + assert_can_be_cold(); + + switch (param_id) { + case SOF_IPC4_SWITCH_CONTROL_PARAM_ID: + case SOF_IPC4_ENUM_CONTROL_PARAM_ID: + comp_err(dev, "Illegal control param_id %d.", param_id); + return -EINVAL; + } + + if (fragment_size != sizeof(int32_t)) { + comp_err(dev, "Illegal fragment size %d.", fragment_size); + return -EINVAL; + } + + memcpy_s(&cd->gain, sizeof(int32_t), fragment, sizeof(int32_t)); + comp_dbg(mod->dev, "Gain set to %d", cd->gain); + return 0; +} diff --git a/src/audio/level_multiplier/level_multiplier.c b/src/audio/level_multiplier/level_multiplier.c new file mode 100644 index 000000000000..68edff07a96e --- /dev/null +++ b/src/audio/level_multiplier/level_multiplier.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#include +#include +#include +#include +#include +#include "level_multiplier.h" + +/* UUID identifies the components. Use e.g. command uuidgen from package + * uuid-runtime, add it to uuid-registry.txt in SOF top level. + */ +SOF_DEFINE_REG_UUID(level_multiplier); + +/* Creates logging data for the component */ +LOG_MODULE_REGISTER(level_multiplier, CONFIG_SOF_LOG_LEVEL); + +/* Creates the component trace. Traces show in trace console the component + * info, warning, and error messages. + */ +DECLARE_TR_CTX(level_multiplier_tr, SOF_UUID(level_multiplier_uuid), LOG_LEVEL_INFO); + +/** + * level_multiplier_init() - Initialize the level_multiplier component. + * @mod: Pointer to module data. + * + * This function is called when the instance is created. The + * macro __cold informs that the code that is non-critical + * is loaded to slower but large DRAM. + * + * Return: Zero if success, otherwise error code. + */ +__cold static int level_multiplier_init(struct processing_module *mod) +{ + struct module_data *md = &mod->priv; + struct comp_dev *dev = mod->dev; + struct level_multiplier_comp_data *cd; + + comp_info(dev, "level_multiplier_init()"); + + cd = mod_alloc(mod, sizeof(*cd)); + if (!cd) + return -ENOMEM; + + md->private = cd; + cd->gain = LEVEL_MULTIPLIER_GAIN_ONE; + return 0; +} + +/** + * level_multiplier_process() - The audio data processing function. + * @mod: Pointer to module data. + * @sources: Pointer to audio samples data sources array. + * @num_of_sources: Number of sources in the array. + * @sinks: Pointer to audio samples data sinks array. + * @num_of_sinks: Number of sinks in the array. + * + * This is the processing function that is called for scheduled + * pipelines. The processing is controlled by the enable switch. + * + * Return: Zero if success, otherwise error code. + */ +static int level_multiplier_process(struct processing_module *mod, + struct sof_source **sources, + int num_of_sources, + struct sof_sink **sinks, + int num_of_sinks) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + struct sof_source *source = sources[0]; /* One input */ + struct sof_sink *sink = sinks[0]; /* One output */ + int frames = source_get_data_frames_available(source); + int sink_frames = sink_get_free_frames(sink); + + comp_dbg(dev, "level_multiplier_process()"); + + frames = MIN(frames, sink_frames); + frames = MIN(frames, dev->frames); + if (cd->gain != LEVEL_MULTIPLIER_GAIN_ONE) + /* Process the data with the requested gain. */ + return cd->level_multiplier_func(mod, source, sink, frames); + + /* Just copy from source to sink. */ + source_to_sink_copy(source, sink, true, frames * cd->frame_bytes); + return 0; +} + +/** + * level_multiplier_prepare() - Prepare the component for processing. + * @mod: Pointer to module data. + * @sources: Pointer to audio samples data sources array. + * @num_of_sources: Number of sources in the array. + * @sinks: Pointer to audio samples data sinks array. + * @num_of_sinks: Number of sinks in the array. + * + * Function prepare is called just before the pipeline is started. In + * this case the audio format parameters are for better code performance + * saved to component data to avoid to find out them in process. The + * processing function pointer is set to process the current audio format. + * + * Return: Value zero if success, otherwise error code. + */ +static int level_multiplier_prepare(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + enum sof_ipc_frame source_format; + + comp_dbg(dev, "level_multiplier_prepare()"); + + /* The processing example in this component supports one input and one + * output. Generally there can be more. + */ + if (num_of_sources != 1 || num_of_sinks != 1) + return -EINVAL; + + /* get source data format */ + cd->frame_bytes = source_get_frame_bytes(sources[0]); + cd->channels = source_get_channels(sources[0]); + source_format = source_get_frm_fmt(sources[0]); + + cd->level_multiplier_func = level_multiplier_find_proc_func(source_format); + if (!cd->level_multiplier_func) { + comp_err(dev, "No processing function found for format %d.", + source_format); + return -EINVAL; + } + + return 0; +} + +/** + * level_multiplier_reset() - Reset the component. + * @mod: Pointer to module data. + * + * The component reset is called when pipeline is stopped. The reset + * should return the component to same state as init. + * + * Return: Value zero, always success. + */ +static int level_multiplier_reset(struct processing_module *mod) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + + comp_dbg(mod->dev, "level_multiplier_reset()"); + + memset(cd, 0, sizeof(*cd)); + cd->gain = LEVEL_MULTIPLIER_GAIN_ONE; + return 0; +} + +/** + * level_multiplier_free() - Free dynamic allocations. + * @mod: Pointer to module data. + * + * Component free is called when the pipelines are deleted. All + * dynamic allocations need to be freed here. The macro __cold + * instructs the build to locate this performance wise non-critical + * function to large and slower DRAM. + * + * Return: Value zero, always success. + */ +__cold static int level_multiplier_free(struct processing_module *mod) +{ + struct level_multiplier_comp_data *cd = module_get_private_data(mod); + + assert_can_be_cold(); + + comp_dbg(mod->dev, "level_multiplier_free()"); + mod_free(mod, cd); + return 0; +} + +/* This defines the module operations */ +static const struct module_interface level_multiplier_interface = { + .init = level_multiplier_init, + .prepare = level_multiplier_prepare, + .process = level_multiplier_process, + .set_configuration = level_multiplier_set_config, + .reset = level_multiplier_reset, + .free = level_multiplier_free +}; + +/* This controls build of the module. If COMP_MODULE is selected in kconfig + * this is build as dynamically loadable module. + */ +#if CONFIG_COMP_LEVEL_MULTIPLIER_MODULE + +#include +#include +#include + +SOF_LLEXT_MOD_ENTRY(level_multiplier, &level_multiplier_interface); + +static const struct sof_man_module_manifest mod_manifest __section(".module") __used = + SOF_LLEXT_MODULE_MANIFEST("LEVEL_MULTIPLIER", level_multiplier_llext_entry, 1, + SOF_REG_UUID(level_multiplier), 40); + +SOF_LLEXT_BUILDINFO; + +#else + +DECLARE_MODULE_ADAPTER(level_multiplier_interface, level_multiplier_uuid, level_multiplier_tr); +SOF_MODULE_INIT(level_multiplier, sys_comp_module_level_multiplier_interface_init); + +#endif diff --git a/src/audio/level_multiplier/level_multiplier.h b/src/audio/level_multiplier/level_multiplier.h new file mode 100644 index 000000000000..e6df98c68c61 --- /dev/null +++ b/src/audio/level_multiplier/level_multiplier.h @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2025 Intel Corporation. + * + */ +#ifndef __SOF_AUDIO_LEVEL_MULTIPLIER_H__ +#define __SOF_AUDIO_LEVEL_MULTIPLIER_H__ + +#include +#include +#include + +/** \brief Level multiplier gain Qx.y integer x number of bits including sign bit. + * With Q8.23 format the gain range is -138.47 to +48.17 dB. + */ +#define LEVEL_MULTIPLIER_QXY_X 9 + +/** \brief Level multiplier gain Qx.y fractional y number of bits. */ +#define LEVEL_MULTIPLIER_QXY_Y 23 + +/** \brief Level multiplier unity gain */ +#define LEVEL_MULTIPLIER_GAIN_ONE (1 << LEVEL_MULTIPLIER_QXY_Y) + +/** + * struct level_multiplier_func - Function call pointer for process function + * @mod: Pointer to module data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + */ +typedef int (*level_multiplier_func)(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames); + +/* Level_Multiplier component private data */ + +/** + * struct level_multiplier_comp_data + * @level_multiplier_func: Pointer to used processing function. + * @gain: Applied gain in linear Q9.23 format + * @source_format: Source samples format. + * @frame_bytes: Number of bytes in an audio frame. + * @channels: Channels count. + * @enable: Control processing on/off, on - reorder channels + */ +struct level_multiplier_comp_data { + level_multiplier_func level_multiplier_func; + int32_t gain; + int source_format; + int frame_bytes; + int channels; +}; + +/** + * struct level_multiplier_proc_fnmap - processing functions for frame formats + * @frame_fmt: Current frame format + * @level_multiplier_proc_func: Function pointer for the suitable processing function + */ +struct level_multiplier_proc_fnmap { + enum sof_ipc_frame frame_fmt; + level_multiplier_func level_multiplier_proc_func; +}; + +/** + * level_multiplier_find_proc_func() - Find suitable processing function. + * @src_fmt: Enum value for PCM format. + * + * This function finds the suitable processing function to use for + * the used PCM format. If not found, return NULL. + * + * Return: Pointer to processing function for the requested PCM format. + */ +level_multiplier_func level_multiplier_find_proc_func(enum sof_ipc_frame src_fmt); + +/** + * level_multiplier_set_config() - Handle controls set + * @mod: Pointer to module data. + * @param_id: Id to know control type, used to know ALSA control type. + * @pos: Position of the fragment in the large message. + * @data_offset_size: Size of the whole configuration if it is the first or only + * fragment. Otherwise it is offset of the fragment. + * @fragment: Message payload data. + * @fragment_size: Size of this fragment. + * @response_size: Size of response. + * + * This function handles the real-time controls. The ALSA controls have the + * param_id set to indicate the control type. The control ID, from topology, + * is used to separate the controls instances of same type. In control payload + * the num_elems defines to how many channels the control is applied to. + * + * Return: Zero if success, otherwise error code. + */ + +#if CONFIG_IPC_MAJOR_3 +static inline int level_multiplier_set_config(struct processing_module *mod, + uint32_t param_id, + enum module_cfg_fragment_position pos, + uint32_t data_offset_size, + const uint8_t *fragment, + size_t fragment_size, + uint8_t *response, + size_t response_size) +{ + /* No controls implementation for IPC3, add level_multiplier-ipc3.c + * handler if need. + */ + return 0; +} +#else +int level_multiplier_set_config(struct processing_module *mod, + uint32_t param_id, + enum module_cfg_fragment_position pos, + uint32_t data_offset_size, + const uint8_t *fragment, + size_t fragment_size, + uint8_t *response, + size_t response_size); +#endif + +#endif // __SOF_AUDIO_LEVEL_MULTIPLIER_H__ diff --git a/src/audio/level_multiplier/level_multiplier.toml b/src/audio/level_multiplier/level_multiplier.toml new file mode 100644 index 000000000000..6eafd4efa8d7 --- /dev/null +++ b/src/audio/level_multiplier/level_multiplier.toml @@ -0,0 +1,21 @@ +#ifndef LOAD_TYPE +#define LOAD_TYPE "0" +#endif + + REM # Level Multiplier module config + [[module.entry]] + name = "LVLMULT" + uuid = UUIDREG_STR_LEVEL_MULTIPLIER + affinity_mask = "0x1" + instance_count = "40" + domain_types = "0" + load_type = LOAD_TYPE + module_type = "9" + auto_start = "0" + sched_caps = [1, 0x00008000] + REM # pin = [dir, type, sample rate, size, container, channel-cfg] + pin = [0, 0, 0xfeef, 0xf, 0xf, 0x45ff, 1, 0, 0xfeef, 0xf, 0xf, 0x1ff] + REM # mod_cfg [PAR_0 PAR_1 PAR_2 PAR_3 IS_BYTES CPS IBS OBS MOD_FLAGS CPC OBLS] + mod_cfg = [0, 0, 0, 0, 4096, 1000000, 128, 128, 0, 0, 0] + + index = __COUNTER__ diff --git a/src/audio/level_multiplier/llext/CMakeLists.txt b/src/audio/level_multiplier/llext/CMakeLists.txt new file mode 100644 index 000000000000..49d91bc6a7ce --- /dev/null +++ b/src/audio/level_multiplier/llext/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2025 Intel Corporation. +# SPDX-License-Identifier: Apache-2.0 + +sof_llext_build("level_multiplier" + SOURCES ../level_multiplier.c + ../level_multiplier-generic.c + ../level_multiplier-hifi3.c + ../level_multiplier-hifi5.c + ../level_multiplier-ipc4.c + LIB openmodules +) diff --git a/src/audio/level_multiplier/llext/llext.toml.h b/src/audio/level_multiplier/llext/llext.toml.h new file mode 100644 index 000000000000..019909d20697 --- /dev/null +++ b/src/audio/level_multiplier/llext/llext.toml.h @@ -0,0 +1,6 @@ +#include +#define LOAD_TYPE "2" +#include "../level_multiplier.toml" + +[module] +count = __COUNTER__ diff --git a/src/audio/level_multiplier/tune/sof_level_multiplier_blobs.m b/src/audio/level_multiplier/tune/sof_level_multiplier_blobs.m new file mode 100644 index 000000000000..7312bae25444 --- /dev/null +++ b/src/audio/level_multiplier/tune/sof_level_multiplier_blobs.m @@ -0,0 +1,83 @@ +% Export configuration blobs for Level Multiplier +% +% This script creates configuration blobs for the Level Multiplier +% component to apply gains -40, -30, -20, -10, 0, 10, 20, 30, 40 dB. +% Run the script interactively from Octave shell with just command: +% +% sof_level_multiplier_blobs +% +% There are no arguments for the function. Or from SOF level directory +% with command: +% +% cd src/audio/level_multiplier/tune; octave sof_level_multiplier_blobs.m +% + +% SPDX-License-Identifier: BSD-3-Clause +% +% Copyright (c) 2025, Intel Corporation. + +function sof_level_multiplier_blobs() + + % Set the parameters here + sof_tools = '../../../../tools'; + sof_tplg = fullfile(sof_tools, 'topology/topology2'); + sof_tplg_level_multiplier = fullfile(sof_tplg, 'include/components/level_multiplier'); + sof_ctl_level_multiplier = fullfile(sof_tools, 'ctl/ipc4/level_multiplier'); + + sof_level_multiplier_paths(true); + + for param = -40:10:40 + gain_value = sof_level_multiplier_db2lin(param); + blob8 = sof_level_multiplier_build_blob(gain_value); + tplg2_fn = sprintf("%s/gain_%d_db.conf", sof_tplg_level_multiplier, param); + sof_tplg2_write(tplg2_fn, blob8, "level_multiplier_config", ... + "Exported with script sof_level_multiplier_blobs.m" , ... + "cd tools/tune/level_multiplier; octave sof_level_multiplier_blobs.m"); + ctl_fn = sprintf("%s/gain_%d_db.txt", sof_ctl_level_multiplier, param); + sof_alsactl_write(ctl_fn, blob8); + end + + sof_level_multiplier_paths(false); +end + +function lin_value = sof_level_multiplier_db2lin(db) + scale = 2^23; + lin_value = int32(10^(db/20) * scale); +end + +function sof_level_multiplier_paths(enable) + + common = '../../../../tools/tune/common'; + if enable + addpath(common); + else + rmpath(common); + end +end + +function blob8 = sof_level_multiplier_build_blob(param_values) + + blob_type = 0; + blob_param_id = 1; + data_length = length(param_values); + data_size = 4 * data_length; + ipc_ver = 4; + [abi_bytes, abi_size] = sof_get_abi(data_size, ipc_ver, blob_type, blob_param_id); + blob_size = data_size + abi_size; + blob8 = uint8(zeros(1, blob_size)); + blob8(1:abi_size) = abi_bytes; + j = abi_size + 1; + for i = 1:data_length + blob8(j:j+3) = word2byte(param_values(i)); + j=j+4; + end +end + +function bytes = word2byte(word) + sh = [0 -8 -16 -24]; + bytes = uint8(zeros(1,4)); + bytes(1) = bitand(bitshift(word, sh(1)), 255); + bytes(2) = bitand(bitshift(word, sh(2)), 255); + bytes(3) = bitand(bitshift(word, sh(3)), 255); + bytes(4) = bitand(bitshift(word, sh(4)), 255); +end diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index 1440a775b400..37515576e32c 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -926,6 +926,7 @@ void sys_comp_module_gain_interface_init(void); void sys_comp_module_google_rtc_audio_processing_interface_init(void); void sys_comp_module_google_ctc_audio_processing_interface_init(void); void sys_comp_module_igo_nr_interface_init(void); +void sys_comp_module_level_multiplier_interface_init(void); void sys_comp_module_mfcc_interface_init(void); void sys_comp_module_mixer_interface_init(void); void sys_comp_module_mixin_interface_init(void); diff --git a/tools/rimage/config/lnl.toml.h b/tools/rimage/config/lnl.toml.h index f369df356274..030683b6ef77 100644 --- a/tools/rimage/config/lnl.toml.h +++ b/tools/rimage/config/lnl.toml.h @@ -142,5 +142,9 @@ #include