diff --git a/app/overlays/mtl/dax_overlay.conf b/app/overlays/mtl/dax_overlay.conf new file mode 100644 index 000000000000..f73f79addf32 --- /dev/null +++ b/app/overlays/mtl/dax_overlay.conf @@ -0,0 +1,11 @@ +CONFIG_COMP_MODULE_ADAPTER=y +CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING=y +CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING_MOCK=n +CONFIG_KCPS_DYNAMIC_CLOCK_CONTROL=n +CONFIG_SOF_STACK_SIZE=8192 + +# LLEXT +CONFIG_LLEXT_HEAP_SIZE=32 +# Disabled due to issues encountered on the MTL platform. +# See https://github.com/thesofproject/sof/issues/10370 +CONFIG_LLEXT_EXPERIMENTAL=n diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig index 339b2660e897..912b93768abc 100644 --- a/src/arch/host/configs/library_defconfig +++ b/src/arch/host/configs/library_defconfig @@ -2,6 +2,7 @@ CONFIG_COMP_ARIA=y CONFIG_COMP_ASRC=y CONFIG_COMP_CROSSOVER=y CONFIG_COMP_DCBLOCK=y +CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING=y CONFIG_COMP_DRC=y CONFIG_COMP_FIR=y CONFIG_COMP_GOOGLE_RTC_AUDIO_PROCESSING=y @@ -15,6 +16,7 @@ CONFIG_COMP_MULTIBAND_DRC=y CONFIG_COMP_MUX=y CONFIG_COMP_RTNR=y CONFIG_COMP_SEL=y +CONFIG_COMP_SOUND_DOSE=y CONFIG_COMP_SRC=y CONFIG_COMP_SRC_IPC4_FULL_MATRIX=y CONFIG_COMP_STUBS=y diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index c098b9267c61..d9de3c578d8e 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -80,6 +80,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_SMART_AMP) add_subdirectory(smart_amp) endif() + if(CONFIG_COMP_SOUND_DOSE) + add_subdirectory(sound_dose) + endif() if(CONFIG_COMP_SRC) add_subdirectory(src) endif() diff --git a/src/audio/Kconfig b/src/audio/Kconfig index ce3ba346b1e5..6bc1bd6ca012 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -149,6 +149,7 @@ rsource "nxp/Kconfig" rsource "rtnr/Kconfig" rsource "selector/Kconfig" rsource "smart_amp/Kconfig" +rsource "sound_dose/Kconfig" rsource "src/Kconfig" rsource "tdfb/Kconfig" rsource "template/Kconfig" diff --git a/src/audio/copier/copier.c b/src/audio/copier/copier.c index 9aab0858a305..0e1db840db67 100644 --- a/src/audio/copier/copier.c +++ b/src/audio/copier/copier.c @@ -540,7 +540,12 @@ static int do_conversion_copy(struct comp_dev *dev, comp_get_copy_limits(src, sink, processed_data); - i = IPC4_SINK_QUEUE_ID(buf_get_id(sink)); + /* + * Buffer ID is constructed as IPC4_COMP_ID(src_queue, dst_queue). + * From the buffer's perspective, copier's sink is the source, + * so we use IPC4_SRC_QUEUE_ID() to get the correct copier sink index. + */ + i = IPC4_SRC_QUEUE_ID(buf_get_id(sink)); if (i >= IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT) return -EINVAL; buffer_stream_invalidate(src, processed_data->source_bytes); @@ -617,7 +622,12 @@ static int copier_module_copy(struct processing_module *mod, uint32_t source_samples; int sink_queue_id; - sink_queue_id = IPC4_SINK_QUEUE_ID(buf_get_id(sink_c)); + /* + * Buffer ID is constructed as IPC4_COMP_ID(src_queue, dst_queue). + * From the buffer's perspective, copier's sink is the source, + * so we use IPC4_SRC_QUEUE_ID() to get the correct copier sink index. + */ + sink_queue_id = IPC4_SRC_QUEUE_ID(buf_get_id(sink_c)); if (sink_queue_id >= IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT) return -EINVAL; diff --git a/src/audio/eq_iir/tune/sof_eq_plot.m b/src/audio/eq_iir/tune/sof_eq_plot.m index 874bfe596c3e..4a11cf25c25f 100644 --- a/src/audio/eq_iir/tune/sof_eq_plot.m +++ b/src/audio/eq_iir/tune/sof_eq_plot.m @@ -75,14 +75,17 @@ function sof_eq_plot(eq, fn) eq.f, eq.iir_eq_db + offs_iir, '--',... eq.f, eq.fir_eq_db + offs_fir, '--'); legend('Target', 'Combined', 'IIR', 'FIR', 'Location', 'NorthWest'); + eq.diff_db = eq.tot_eq_db + offs_tot - eq.err_db_s; end if eq.enable_fir && eq.enable_iir == 0 semilogx(eq.f, eq.err_db_s, eq.f, eq.fir_eq_db + offs_fir); legend('Target', 'FIR', 'Location', 'NorthWest'); + eq.diff_db = eq.fir_eq_db + offs_fir - eq.err_db_s; end if eq.enable_fir == 0 && eq.enable_iir semilogx(eq.f, eq.err_db_s, eq.f, eq.iir_eq_db + offs_iir); legend('Target', 'IIR', 'Location', 'NorthWest'); + eq.diff_db = eq.iir_eq_db + offs_iir - eq.err_db_s; end grid on; ax=axis; axis([eq.p_fmin eq.p_fmax min(max(ax(3:4), -40), 40)]); @@ -91,6 +94,15 @@ function sof_eq_plot(eq, fn) tstr = sprintf('Filter target vs. achieved response: %s', eq.name); title(tstr); +fh=figure(fn); fn = fn+1; +semilogx(eq.f, eq.diff_db) +grid on; +ax=axis; axis([eq.p_fmin eq.p_fmax -5 5]); +xlabel('Frequency (Hz)'); +ylabel('Magnitude (dB)'); +tstr = sprintf('Response difference mean abs %.4f dB: %s', mean(abs(eq.diff_db)), eq.name); +title(tstr); + %% FIR filter if length(eq.b_fir) > 1 % Response diff --git a/src/audio/eq_iir/tune/sof_export_c_eq_uint32t.m b/src/audio/eq_iir/tune/sof_export_c_eq_uint32t.m index 7c861a4d8bbb..c7c26437b458 100644 --- a/src/audio/eq_iir/tune/sof_export_c_eq_uint32t.m +++ b/src/audio/eq_iir/tune/sof_export_c_eq_uint32t.m @@ -1,4 +1,4 @@ -function sof_export_c_eq_uint32t(fn, blob8, vn, justeq) +function sof_export_c_eq_uint32t(fn, blob8, vn, justeq, howto) % sof_export_c_eq_uint32t(fn, blob8, vn) % @@ -8,10 +8,11 @@ function sof_export_c_eq_uint32t(fn, blob8, vn, justeq) % blob8 - blob data from EQ design % vn - variable name % justeq - export just the EQ wihtout ABI headers for direct use in FW +% howto - add a comment how the header file was created % SPDX-License-Identifier: BSD-3-Clause % -% Copyright (c) 2021, Intel Corporation. All rights reserved. +% Copyright (c) 2021-2025, Intel Corporation. % Write blob fh = fopen(fn, 'w'); @@ -23,10 +24,20 @@ function sof_export_c_eq_uint32t(fn, blob8, vn, justeq) year = datestr(now, 'yyyy'); fprintf(fh, '/* SPDX-License-Identifier: BSD-3-Clause\n'); fprintf(fh, ' *\n'); - fprintf(fh, ' * Copyright(c) %s Intel Corporation. All rights reserved.\n', year); + fprintf(fh, ' * Copyright(c) %s Intel Corporation.\n', year); fprintf(fh, ' */\n'); fprintf(fh, '\n'); + if nargin == 5 + fprintf(fh, '/* Created %s with command:\n', datestr(now, 'yyyy-mm-dd')); + fprintf(fh, ' * %s\n */\n\n', howto); + end + + fprintf(fh, '#include \n\n'); + + % Drop 8 bytes TLV header + blob8 = blob8(9:end); + % Pad blob length to multiple of four bytes n_orig = length(blob8); n_new = ceil(n_orig/4); @@ -53,7 +64,7 @@ function sof_export_c_eq_uint32t(fn, blob8, vn, justeq) numbers_remain = n_new - numbers_in_line * full_lines; n = 1; - fprintf(fh, 'uint32_t %s[%d] = {\n', vn, n_new); + fprintf(fh, 'static const uint32_t %s[%d] = {\n', vn, n_new); for i = 1:full_lines fprintf(fh, '\t'); for j = 1:numbers_in_line diff --git a/src/audio/module_adapter/CMakeLists.txt b/src/audio/module_adapter/CMakeLists.txt index 505431e16613..45d1695561a7 100644 --- a/src/audio/module_adapter/CMakeLists.txt +++ b/src/audio/module_adapter/CMakeLists.txt @@ -25,6 +25,28 @@ if(zephyr) ### Zephyr ### zephyr_library_import(xa_mp3_enc ${CONFIG_CADENCE_CODEC_MP3_ENC_LIB}) endif() + if (CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING) + if(CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING STREQUAL "m" AND DEFINED CONFIG_LLEXT) + add_subdirectory(module/dolby/llext + ${PROJECT_BINARY_DIR}/dolby_dax_audio_processing_llext) + add_dependencies(app dolby_dax_audio_processing) + else() + zephyr_library_sources( + module/dolby/dax.c + ) + if (CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING_MOCK) + zephyr_library_sources( + module/dolby/dax_mock.c + ) + else() + target_link_libraries(SOF INTERFACE m) + target_link_libraries(SOF INTERFACE c) + zephyr_library_import(dax_effect + ${sof_top_dir}/third_party/lib/libdax.a) + endif() + endif() + endif() + zephyr_include_directories_ifdef(CONFIG_LIBRARY_MANAGER ${SOF_SRC_PATH}/include/sof/audio/module_adapter/iadk/ ${SOF_SRC_PATH}/include/sof/audio/module_adapter/library/ @@ -112,6 +134,15 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) endif() + if(CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING) + add_local_sources(sof module/dolby/dax.c) + if (CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING_MOCK) + add_local_sources(sof module/dolby/dax_mock.c) + else() + sof_add_static_library(dax_effect ${sof_top_dir}/third_party/lib/libdax.a) + endif() + endif() + if(CONFIG_PASSTHROUGH_CODEC) add_local_sources(sof module/passthrough.c) endif() diff --git a/src/audio/module_adapter/Kconfig b/src/audio/module_adapter/Kconfig index 7ed6b859cd64..a9f88dd1f2ad 100644 --- a/src/audio/module_adapter/Kconfig +++ b/src/audio/module_adapter/Kconfig @@ -175,6 +175,22 @@ if CADENCE_CODEC endif # Cadence + config COMP_DOLBY_DAX_AUDIO_PROCESSING + tristate "Dolby DAX audio processing component" + help + Select to include Dolby DAX component. Dolby DAX component implements DAX API. + API definition together with pre-compiled library is shared by Dolby. + If library is not provided, COMP_DOLBY_DAX_AUDIO_PROCESSING_MOCK must be true, + then the input will be copied to the output. + + config COMP_DOLBY_DAX_AUDIO_PROCESSING_MOCK + bool "Dolby DAX audio processing component mock" + default y if COMP_STUBS + depends on COMP_DOLBY_DAX_AUDIO_PROCESSING + help + Mock DAX audio processing. It allows for compilation check and basic audio + flow checking. + config PASSTHROUGH_CODEC bool "Passthrough codec" help diff --git a/src/audio/module_adapter/module/dolby/dax.c b/src/audio/module_adapter/module/dolby/dax.c new file mode 100644 index 000000000000..eaf6bfd9b03b --- /dev/null +++ b/src/audio/module_adapter/module/dolby/dax.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE +// +// Copyright(c) 2025 Dolby Laboratories. All rights reserved. +// +// Author: Jun Lai +// + +#include + +#include +#include +#include +#include +#include + +#include + +LOG_MODULE_REGISTER(dolby_dax_audio_processing, CONFIG_SOF_LOG_LEVEL); +SOF_DEFINE_REG_UUID(dolby_dax_audio_processing); +DECLARE_TR_CTX(dolby_dax_audio_processing_tr, SOF_UUID(dolby_dax_audio_processing_uuid), + LOG_LEVEL_INFO); + +#define MAX_PARAMS_STR_BUFFER_SIZE 1536 +#define DAX_ENABLE_MASK 0x1 +#define DAX_PROFILE_MASK 0x2 +#define DAX_DEVICE_MASK 0x4 +#define DAX_CP_MASK 0x8 +#define DAX_VOLUME_MASK 0x10 +#define DAX_CTC_MASK 0x20 + +#define DAX_SWITCH_ENABLE_CONTROL_ID 0 +#define DAX_SWITCH_CP_CONTROL_ID 1 +#define DAX_SWITCH_CTC_CONTROL_ID 2 +#define DAX_ENUM_PROFILE_CONTROL_ID 0 +#define DAX_ENUM_DEVICE_CONTROL_ID 1 + +static const char *get_params_str(const void *val, uint32_t val_sz) +{ + static char params_str[MAX_PARAMS_STR_BUFFER_SIZE + 16]; + const int32_t *param_val = (const int32_t *)val; + const uint32_t param_sz = val_sz >> 2; + uint32_t offset = 0; + + for (uint32_t i = 0; i < param_sz && offset < MAX_PARAMS_STR_BUFFER_SIZE; i++) + offset += sprintf(params_str + offset, "%d,", param_val[i]); + return ¶ms_str[0]; +} + +static int sof_to_dax_frame_fmt(enum sof_ipc_frame sof_frame_fmt) +{ + switch (sof_frame_fmt) { + case SOF_IPC_FRAME_S16_LE: + return DAX_FMT_SHORT_16; + case SOF_IPC_FRAME_S32_LE: + return DAX_FMT_INT; + case SOF_IPC_FRAME_FLOAT: + return DAX_FMT_FLOAT; + default: + return DAX_FMT_UNSUPPORTED; + } +} + +static int sof_to_dax_sample_rate(uint32_t rate) +{ + switch (rate) { + case 48000: + return rate; + default: + return DAX_RATE_UNSUPPORTED; + } +} + +static int sof_to_dax_channels(uint32_t channels) +{ + switch (channels) { + case 2: + return channels; + default: + return DAX_CHANNLES_UNSUPPORTED; + } +} + +static int sof_to_dax_buffer_layout(enum sof_ipc_buffer_format sof_buf_fmt) +{ + switch (sof_buf_fmt) { + case SOF_IPC_BUFFER_INTERLEAVED: + return DAX_BUFFER_LAYOUT_INTERLEAVED; + case SOF_IPC_BUFFER_NONINTERLEAVED: + return DAX_BUFFER_LAYOUT_NONINTERLEAVED; + default: + return DAX_BUFFER_LAYOUT_UNSUPPORTED; + } +} + +static void dax_buffer_release(struct dax_buffer *dax_buff) +{ + if (dax_buff->addr) { + rfree(dax_buff->addr); + dax_buff->addr = NULL; + } + dax_buff->size = 0; + dax_buff->avail = 0; + dax_buff->free = 0; +} + +static int dax_buffer_alloc(struct dax_buffer *dax_buff, uint32_t bytes) +{ + dax_buffer_release(dax_buff); + dax_buff->addr = rballoc(SOF_MEM_CAPS_RAM, bytes); + if (!dax_buff->addr) + dax_buff->addr = rzalloc(SOF_MEM_CAPS_RAM, bytes); + if (!dax_buff->addr) + return -ENOMEM; + + dax_buff->size = bytes; + dax_buff->avail = 0; + dax_buff->free = bytes; + return 0; +} + +/* After reading from buffer */ +static void dax_buffer_consume(struct dax_buffer *dax_buff, uint32_t bytes) +{ + bytes = MIN(bytes, dax_buff->avail); + memmove(dax_buff->addr, (uint8_t *)dax_buff->addr + bytes, dax_buff->avail - bytes); + dax_buff->avail = dax_buff->avail - bytes; + dax_buff->free = dax_buff->size - dax_buff->avail; +} + +/* After writing to buffer */ +static void dax_buffer_produce(struct dax_buffer *dax_buff, uint32_t bytes) +{ + dax_buff->avail += bytes; + dax_buff->avail = MIN(dax_buff->avail, dax_buff->size); + dax_buff->free = dax_buff->size - dax_buff->avail; +} + +static int set_tuning_file(struct processing_module *mod, void *value, uint32_t size) +{ + int ret = 0; + struct comp_dev *dev = mod->dev; + struct sof_dax *dax_ctx = module_get_private_data(mod); + + if (dax_buffer_alloc(&dax_ctx->tuning_file_buffer, size) != 0) { + comp_err(dev, "allocate %u bytes failed for tuning file", size); + ret = -ENOMEM; + } else { + memcpy_s(dax_ctx->tuning_file_buffer.addr, + dax_ctx->tuning_file_buffer.free, + value, + size); + } + + comp_info(dev, "allocated: tuning %u, ret %d", dax_ctx->tuning_file_buffer.size, ret); + return ret; +} + +static int set_enable(struct processing_module *mod, int32_t enable) +{ + int ret = 0; + struct sof_dax *dax_ctx = module_get_private_data(mod); + + if (enable) { + ret = dax_set_enable(1, dax_ctx); + dax_ctx->enable = (ret == 0 ? 1 : 0); + } else { + dax_ctx->enable = 0; + dax_set_enable(0, dax_ctx); + } + + comp_info(mod->dev, "set dax enable %d, ret %d", enable, ret); + return ret; +} + +static int set_volume(struct processing_module *mod, int32_t abs_volume) +{ + int ret; + struct sof_dax *dax_ctx = module_get_private_data(mod); + + dax_ctx->volume = abs_volume; + if (!dax_ctx->enable) + return 0; + + ret = dax_set_volume(abs_volume, dax_ctx); + comp_info(mod->dev, "set volume %d, ret %d", abs_volume, ret); + return ret; +} + +static int set_device(struct processing_module *mod, int32_t out_device) +{ + int ret; + struct sof_dax *dax_ctx = module_get_private_data(mod); + + dax_ctx->out_device = out_device; + ret = dax_set_device(out_device, dax_ctx); + + comp_info(mod->dev, "set device %d, ret %d", out_device, ret); + return ret; +} + +static int set_crosstalk_cancellation_enable(struct processing_module *mod, int32_t enable) +{ + int ret; + struct sof_dax *dax_ctx = module_get_private_data(mod); + + dax_ctx->ctc_enable = enable; + ret = dax_set_ctc_enable(enable, dax_ctx); + + comp_info(mod->dev, "set ctc enable %d, ret %d", enable, ret); + return ret; +} + +static int update_params_from_buffer(struct processing_module *mod, void *params, uint32_t size); + +static int set_profile(struct processing_module *mod, int32_t profile_id) +{ + int ret = -EINVAL; + struct comp_dev *dev = mod->dev; + struct sof_dax *dax_ctx = module_get_private_data(mod); + uint32_t params_sz = 0; + void *params; + + dax_ctx->profile = profile_id; + if (!dax_ctx->enable) + return 0; + + params = dax_find_params(DAX_PARAM_ID_PROFILE, profile_id, ¶ms_sz, dax_ctx); + if (params) + ret = update_params_from_buffer(mod, params, params_sz); + + comp_info(dev, "switched to profile %d, ret %d", profile_id, ret); + return ret; +} + +static int set_tuning_device(struct processing_module *mod, int32_t tuning_device) +{ + int ret = -EINVAL; + struct comp_dev *dev = mod->dev; + struct sof_dax *dax_ctx = module_get_private_data(mod); + uint32_t params_sz = 0; + void *params; + + dax_ctx->tuning_device = tuning_device; + if (!dax_ctx->enable) + return 0; + + params = dax_find_params(DAX_PARAM_ID_TUNING_DEVICE, tuning_device, ¶ms_sz, dax_ctx); + if (params) + ret = update_params_from_buffer(mod, params, params_sz); + + comp_info(dev, "switched to tuning device %d, ret %d", tuning_device, ret); + return ret; +} + +static int set_content_processing_enable(struct processing_module *mod, int32_t enable) +{ + int ret = -EINVAL; + struct comp_dev *dev = mod->dev; + struct sof_dax *dax_ctx = module_get_private_data(mod); + uint32_t params_sz = 0; + void *params; + + dax_ctx->content_processing_enable = enable; + if (!dax_ctx->enable) + return 0; + + params = dax_find_params(DAX_PARAM_ID_CP_ENABLE, enable, ¶ms_sz, dax_ctx); + if (params) + ret = update_params_from_buffer(mod, params, params_sz); + + comp_info(dev, "set content processing enable %d, ret %d", enable, ret); + return ret; +} + +static int dax_set_param_wrapper(struct processing_module *mod, + uint32_t id, void *value, uint32_t size) +{ + int ret = 0; + struct comp_dev *dev = mod->dev; + struct sof_dax *dax_ctx = module_get_private_data(mod); + int32_t tmp_val; + + switch (id) { + case DAX_PARAM_ID_TUNING_FILE: + set_tuning_file(mod, value, size); + break; + case DAX_PARAM_ID_ENABLE: + tmp_val = *((int32_t *)value); + tmp_val = !!tmp_val; + if (dax_ctx->enable != tmp_val) { + dax_ctx->enable = tmp_val; + dax_ctx->update_flags |= DAX_ENABLE_MASK; + } + break; + case DAX_PARAM_ID_ABSOLUTE_VOLUME: + dax_ctx->volume = *((int32_t *)value); + dax_ctx->update_flags |= DAX_VOLUME_MASK; + break; + case DAX_PARAM_ID_OUT_DEVICE: + tmp_val = *((int32_t *)value); + if (dax_ctx->out_device != tmp_val) { + dax_ctx->out_device = tmp_val; + dax_ctx->update_flags |= DAX_DEVICE_MASK; + } + break; + case DAX_PARAM_ID_PROFILE: + tmp_val = *((int32_t *)value); + if (dax_ctx->profile != tmp_val) { + dax_ctx->profile = tmp_val; + dax_ctx->update_flags |= DAX_PROFILE_MASK; + } + break; + case DAX_PARAM_ID_CP_ENABLE: + tmp_val = *((int32_t *)value); + tmp_val = !!tmp_val; + if (dax_ctx->content_processing_enable != tmp_val) { + dax_ctx->content_processing_enable = tmp_val; + dax_ctx->update_flags |= DAX_CP_MASK; + } + break; + case DAX_PARAM_ID_CTC_ENABLE: + tmp_val = *((int32_t *)value); + tmp_val = !!tmp_val; + if (dax_ctx->ctc_enable != tmp_val) { + dax_ctx->ctc_enable = tmp_val; + dax_ctx->update_flags |= DAX_CTC_MASK; + } + break; + case DAX_PARAM_ID_ENDPOINT: + if (dax_ctx->endpoint == *((int32_t *)value)) { + ret = update_params_from_buffer(mod, (uint8_t *)value + 4, size - 4); + comp_info(dev, "switched to endpoint %d, ret %d", dax_ctx->endpoint, ret); + } + break; + default: + ret = dax_set_param(id, (void *)(value), size, dax_ctx); + comp_info(dev, "dax_set_param: ret %d, id %#x, size %u, value %s", + ret, id, size >> 2, get_params_str(value, size)); + break; + } + + return ret; +} + +static int update_params_from_buffer(struct processing_module *mod, void *data, uint32_t data_size) +{ + struct comp_dev *dev = mod->dev; + struct module_param *param; + void *pos = data; + const uint32_t param_header_size = 8; + uint32_t param_data_size; + + for (uint32_t i = 0; i < data_size;) { + param = (struct module_param *)(pos); + if (param->size < param_header_size || + param->size > data_size - i || + (param->size & 0x03) != 0) { + comp_err(dev, "invalid param %#x, param size %u, pos %u", + param->id, param->size, i); + return -EINVAL; + } + + if (param->size > param_header_size) { + param_data_size = param->size - param_header_size; + dax_set_param_wrapper(mod, param->id, (void *)(param->data), + param_data_size); + } + + pos = (void *)((uint8_t *)(pos) + param->size); + i += param->size; + } + + return 0; +} + +static void check_and_update_settings(struct processing_module *mod) +{ + struct sof_dax *dax_ctx = module_get_private_data(mod); + + if (dax_ctx->update_flags & DAX_ENABLE_MASK) { + set_enable(mod, dax_ctx->enable); + if (dax_ctx->enable) { + dax_ctx->update_flags |= DAX_DEVICE_MASK; + dax_ctx->update_flags |= DAX_VOLUME_MASK; + } + dax_ctx->update_flags &= ~DAX_ENABLE_MASK; + return; + } + if (dax_ctx->update_flags & DAX_DEVICE_MASK) { + set_device(mod, dax_ctx->out_device); + set_tuning_device(mod, dax_ctx->tuning_device); + dax_ctx->update_flags |= DAX_PROFILE_MASK; + dax_ctx->update_flags &= ~DAX_DEVICE_MASK; + return; + } + if (dax_ctx->update_flags & DAX_CTC_MASK) { + set_crosstalk_cancellation_enable(mod, dax_ctx->ctc_enable); + dax_ctx->update_flags |= DAX_PROFILE_MASK; + dax_ctx->update_flags &= ~DAX_CTC_MASK; + return; + } + if (dax_ctx->update_flags & DAX_PROFILE_MASK) { + set_profile(mod, dax_ctx->profile); + if (!dax_ctx->content_processing_enable) + dax_ctx->update_flags |= DAX_CP_MASK; + dax_ctx->update_flags &= ~DAX_PROFILE_MASK; + return; + } + if (dax_ctx->update_flags & DAX_CP_MASK) { + set_content_processing_enable(mod, dax_ctx->content_processing_enable); + dax_ctx->update_flags &= ~DAX_CP_MASK; + return; + } + if (dax_ctx->update_flags & DAX_VOLUME_MASK) { + set_volume(mod, dax_ctx->volume); + dax_ctx->update_flags &= ~DAX_VOLUME_MASK; + } +} + +static int sof_dax_free(struct processing_module *mod) +{ + struct sof_dax *dax_ctx = module_get_private_data(mod); + + if (dax_ctx) { + dax_free(dax_ctx); + dax_buffer_release(&dax_ctx->persist_buffer); + dax_buffer_release(&dax_ctx->scratch_buffer); + dax_buffer_release(&dax_ctx->tuning_file_buffer); + dax_buffer_release(&dax_ctx->input_buffer); + dax_buffer_release(&dax_ctx->output_buffer); + comp_data_blob_handler_free(dax_ctx->blob_handler); + dax_ctx->blob_handler = NULL; + rfree(dax_ctx); + module_set_private_data(mod, NULL); + } + return 0; +} + +static int sof_dax_init(struct processing_module *mod) +{ + int ret; + struct comp_dev *dev = mod->dev; + struct module_data *md = &mod->priv; + struct sof_dax *dax_ctx; + uint32_t persist_sz; + uint32_t scratch_sz; + + md->private = rzalloc(SOF_MEM_CAPS_RAM, sizeof(struct sof_dax)); + if (!md->private) { + comp_err(dev, "failed to allocate %u bytes for initialization", + sizeof(struct sof_dax)); + return -ENOMEM; + } + dax_ctx = module_get_private_data(mod); + dax_ctx->enable = 0; + dax_ctx->profile = 0; + dax_ctx->out_device = 0; + dax_ctx->ctc_enable = 1; + dax_ctx->content_processing_enable = 1; + dax_ctx->volume = 1 << 23; + dax_ctx->update_flags = 0; + + dax_ctx->blob_handler = comp_data_blob_handler_new(dev); + if (!dax_ctx->blob_handler) { + comp_err(dev, "create blob handler failed"); + ret = -ENOMEM; + goto err; + } + + persist_sz = dax_query_persist_memory(dax_ctx); + if (dax_buffer_alloc(&dax_ctx->persist_buffer, persist_sz) != 0) { + comp_err(dev, "allocate %u bytes failed for persist", persist_sz); + ret = -ENOMEM; + goto err; + } + scratch_sz = dax_query_scratch_memory(dax_ctx); + if (dax_buffer_alloc(&dax_ctx->scratch_buffer, scratch_sz) != 0) { + comp_err(dev, "allocate %u bytes failed for scratch", scratch_sz); + ret = -ENOMEM; + goto err; + } + ret = dax_init(dax_ctx); + if (ret != 0) { + comp_err(dev, "dax instance initialization failed, ret %d", ret); + goto err; + } + + comp_info(dev, "allocated: persist %u, scratch %u. version: %s", + persist_sz, scratch_sz, dax_get_version()); + return 0; + +err: + sof_dax_free(mod); + return ret; +} + +static int check_media_format(struct processing_module *mod) +{ + int ret = 0; + struct comp_dev *dev = mod->dev; + struct comp_buffer *source = comp_dev_get_first_data_producer(dev); + struct comp_buffer *sink = comp_dev_get_first_data_consumer(dev); + const struct audio_stream *src_stream = &source->stream; + const struct audio_stream *sink_stream = &sink->stream; + struct sof_dax *dax_ctx = module_get_private_data(mod); + + if (audio_stream_get_frm_fmt(src_stream) != audio_stream_get_frm_fmt(sink_stream) || + sof_to_dax_frame_fmt(audio_stream_get_frm_fmt(src_stream)) == DAX_FMT_UNSUPPORTED) { + comp_err(dev, "unsupported format, source %d, sink %d", + audio_stream_get_frm_fmt(src_stream), + audio_stream_get_frm_fmt(sink_stream)); + ret = -EINVAL; + } + + if (audio_stream_get_rate(src_stream) != audio_stream_get_rate(sink_stream) || + sof_to_dax_sample_rate(audio_stream_get_rate(src_stream)) == DAX_RATE_UNSUPPORTED) { + comp_err(dev, "unsupported sample rate, source %d, sink %d", + audio_stream_get_rate(src_stream), audio_stream_get_rate(sink_stream)); + ret = -EINVAL; + } + + if (audio_stream_get_channels(sink_stream) != 2 || + sof_to_dax_channels(audio_stream_get_channels(src_stream)) == + DAX_CHANNLES_UNSUPPORTED) { + comp_err(dev, "unsupported number of channels, source %d, sink %d", + audio_stream_get_channels(src_stream), + audio_stream_get_channels(sink_stream)); + ret = -EINVAL; + } + + if (audio_stream_get_buffer_fmt(src_stream) != audio_stream_get_buffer_fmt(sink_stream) || + sof_to_dax_buffer_layout(audio_stream_get_buffer_fmt(src_stream)) == + DAX_BUFFER_LAYOUT_UNSUPPORTED) { + comp_err(dev, "unsupported buffer layout %d", + audio_stream_get_buffer_fmt(src_stream)); + ret = -EINVAL; + } + + if (ret != 0) + return ret; + + dax_ctx->input_media_format.data_format = + sof_to_dax_frame_fmt(audio_stream_get_frm_fmt(src_stream)); + dax_ctx->input_media_format.sampling_rate = + sof_to_dax_sample_rate(audio_stream_get_rate(src_stream)); + dax_ctx->input_media_format.num_channels = + sof_to_dax_channels(audio_stream_get_channels(src_stream)); + dax_ctx->input_media_format.layout = + sof_to_dax_buffer_layout(audio_stream_get_buffer_fmt(src_stream)); + dax_ctx->input_media_format.bytes_per_sample = audio_stream_sample_bytes(src_stream); + + dax_ctx->output_media_format.data_format = + sof_to_dax_frame_fmt(audio_stream_get_frm_fmt(sink_stream)); + dax_ctx->output_media_format.sampling_rate = + sof_to_dax_sample_rate(audio_stream_get_rate(sink_stream)); + dax_ctx->output_media_format.num_channels = + sof_to_dax_channels(audio_stream_get_channels(sink_stream)); + dax_ctx->output_media_format.layout = + sof_to_dax_buffer_layout(audio_stream_get_buffer_fmt(sink_stream)); + dax_ctx->output_media_format.bytes_per_sample = audio_stream_sample_bytes(sink_stream); + + comp_info(dev, "format %d, sample rate %d, channels %d, data format %d", + dax_ctx->input_media_format.data_format, + dax_ctx->input_media_format.sampling_rate, + dax_ctx->input_media_format.num_channels, + dax_ctx->input_media_format.data_format); + return 0; +} + +static int sof_dax_prepare(struct processing_module *mod, struct sof_source **sources, + int num_of_sources, struct sof_sink **sinks, int num_of_sinks) +{ + int ret; + struct comp_dev *dev = mod->dev; + struct sof_dax *dax_ctx = module_get_private_data(mod); + uint32_t ibs, obs; + + if (num_of_sources != 1 || num_of_sinks != 1) { + comp_err(dev, "unsupported number of buffers, in %d, out %d", + num_of_sources, num_of_sinks); + return -EINVAL; + } + + ret = check_media_format(mod); + if (ret != 0) + return ret; + + dax_ctx->sof_period_bytes = dev->frames * + dax_ctx->output_media_format.num_channels * + dax_ctx->output_media_format.bytes_per_sample; + dax_ctx->period_bytes = dax_query_period_frames(dax_ctx) * + dax_ctx->output_media_format.num_channels * + dax_ctx->output_media_format.bytes_per_sample; + dax_ctx->period_us = 1000000 * dax_ctx->period_bytes / + (dax_ctx->output_media_format.bytes_per_sample * + dax_ctx->output_media_format.num_channels * + dax_ctx->output_media_format.sampling_rate); + + ibs = (dax_query_period_frames(dax_ctx) + dev->frames) * + dax_ctx->input_media_format.num_channels * + dax_ctx->input_media_format.bytes_per_sample; + obs = dax_ctx->period_bytes + dax_ctx->sof_period_bytes; + if (dax_buffer_alloc(&dax_ctx->input_buffer, ibs) != 0) { + comp_err(dev, "allocate %u bytes failed for input", ibs); + ret = -ENOMEM; + goto err; + } + if (dax_buffer_alloc(&dax_ctx->output_buffer, obs) != 0) { + comp_err(dev, "allocate %u bytes failed for output", obs); + ret = -ENOMEM; + goto err; + } + memset(dax_ctx->output_buffer.addr, 0, dax_ctx->output_buffer.size); + dax_buffer_produce(&dax_ctx->output_buffer, dax_ctx->output_buffer.size); + comp_info(dev, "allocated: ibs %u, obs %u", ibs, obs); + + return 0; + +err: + dax_buffer_release(&dax_ctx->input_buffer); + dax_buffer_release(&dax_ctx->output_buffer); + return ret; +} + +static int sof_dax_process(struct processing_module *mod, struct sof_source **sources, + int num_of_sources, struct sof_sink **sinks, int num_of_sinks) +{ + struct sof_dax *dax_ctx = module_get_private_data(mod); + struct sof_source *source = sources[0]; + struct sof_sink *sink = sinks[0]; + uint8_t *buf, *bufstart, *bufend, *dax_buf; + size_t bufsz; + struct dax_buffer *dax_input_buffer = &dax_ctx->input_buffer; + struct dax_buffer *dax_output_buffer = &dax_ctx->output_buffer; + uint32_t consumed_bytes, processed_bytes, produced_bytes; + + /* source stream -> internal input buffer */ + consumed_bytes = MIN(source_get_data_available(source), dax_input_buffer->free); + source_get_data(source, consumed_bytes, (void *)&buf, (void *)&bufstart, &bufsz); + bufend = &bufstart[bufsz]; + dax_buf = (uint8_t *)(dax_input_buffer->addr); + cir_buf_copy(buf, bufstart, bufend, + dax_buf + dax_input_buffer->avail, + dax_buf, + dax_buf + dax_input_buffer->size, + consumed_bytes); + dax_buffer_produce(dax_input_buffer, consumed_bytes); + source_release_data(source, consumed_bytes); + + check_and_update_settings(mod); + + /* internal input buffer -> internal output buffer */ + processed_bytes = dax_process(dax_ctx); + dax_buffer_consume(dax_input_buffer, processed_bytes); + dax_buffer_produce(dax_output_buffer, processed_bytes); + + /* internal output buffer -> sink stream */ + produced_bytes = MIN(dax_output_buffer->avail, sink_get_free_size(sink)); + if (produced_bytes > 0) { + sink_get_buffer(sink, produced_bytes, (void *)&buf, (void *)&bufstart, &bufsz); + bufend = &bufstart[bufsz]; + dax_buf = (uint8_t *)(dax_output_buffer->addr); + cir_buf_copy(dax_buf, dax_buf, dax_buf + dax_output_buffer->size, + buf, bufstart, bufend, produced_bytes); + dax_buffer_consume(dax_output_buffer, produced_bytes); + sink_commit_buffer(sink, produced_bytes); + } + + return 0; +} + +static int sof_dax_set_configuration(struct processing_module *mod, uint32_t config_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) +{ + int ret; + struct comp_dev *dev = mod->dev; + struct sof_dax *dax_ctx = module_get_private_data(mod); + int32_t dax_param_id = 0; + int32_t val; + + if (fragment_size == 0) + return 0; + +#if CONFIG_IPC_MAJOR_4 + const struct sof_ipc4_control_msg_payload *ctl = NULL; + + switch (config_id) { + case 0: /* IPC4_VOLUME */ + /* ipc4_peak_volume_config::target_volume */ + val = ((const int32_t *)fragment)[1]; + val = sat_int32(Q_SHIFT_RND((int64_t)val, 31, 23)); + dax_param_id = DAX_PARAM_ID_ABSOLUTE_VOLUME; + break; + case SOF_IPC4_SWITCH_CONTROL_PARAM_ID: + ctl = (const struct sof_ipc4_control_msg_payload *)fragment; + if (ctl->num_elems != 1) + return -EINVAL; + + val = ctl->chanv[0].value; + switch (ctl->id) { + case DAX_SWITCH_ENABLE_CONTROL_ID: + dax_param_id = DAX_PARAM_ID_ENABLE; + break; + case DAX_SWITCH_CP_CONTROL_ID: + dax_param_id = DAX_PARAM_ID_CP_ENABLE; + break; + case DAX_SWITCH_CTC_CONTROL_ID: + dax_param_id = DAX_PARAM_ID_CTC_ENABLE; + break; + default: + comp_err(dev, "unknown switch control %d", ctl->id); + return -EINVAL; + } + break; + case SOF_IPC4_ENUM_CONTROL_PARAM_ID: + ctl = (const struct sof_ipc4_control_msg_payload *)fragment; + if (ctl->num_elems != 1) + return -EINVAL; + + val = ctl->chanv[0].value; + switch (ctl->id) { + case DAX_ENUM_PROFILE_CONTROL_ID: + dax_param_id = DAX_PARAM_ID_PROFILE; + break; + case DAX_ENUM_DEVICE_CONTROL_ID: + dax_param_id = DAX_PARAM_ID_OUT_DEVICE; + break; + default: + comp_err(dev, "unknown enum control %d", ctl->id); + return -EINVAL; + } + break; + default: + break; + } +#else + struct sof_ipc_ctrl_data *ctl = (struct sof_ipc_ctrl_data *)fragment; + + switch (ctl->cmd) { + case SOF_CTRL_CMD_VOLUME: + val = ctl->chanv[0].value; + dax_param_id = DAX_PARAM_ID_ABSOLUTE_VOLUME; + break; + case SOF_CTRL_CMD_SWITCH: + if (ctl->num_elems != 1) + return -EINVAL; + + val = ctl->chanv[0].value; + switch (ctl->index) { + case DAX_SWITCH_ENABLE_CONTROL_ID: + dax_param_id = DAX_PARAM_ID_ENABLE; + break; + case DAX_SWITCH_CP_CONTROL_ID: + dax_param_id = DAX_PARAM_ID_CP_ENABLE; + break; + case DAX_SWITCH_CTC_CONTROL_ID: + dax_param_id = DAX_PARAM_ID_CTC_ENABLE; + break; + default: + comp_err(dev, "unknown switch control %d", ctl->index); + return -EINVAL; + } + break; + case SOF_CTRL_CMD_ENUM: + if (ctl->num_elems != 1) + return -EINVAL; + + val = ctl->chanv[0].value; + switch (ctl->index) { + case DAX_ENUM_PROFILE_CONTROL_ID: + dax_param_id = DAX_PARAM_ID_PROFILE; + break; + case DAX_ENUM_DEVICE_CONTROL_ID: + dax_param_id = DAX_PARAM_ID_OUT_DEVICE; + break; + default: + comp_err(dev, "unknown enum control %d", ctl->index); + return -EINVAL; + } + break; + default: + break; + } +#endif + + if (dax_param_id == 0) { + ret = comp_data_blob_set(dax_ctx->blob_handler, pos, + data_offset_size, fragment, fragment_size); + if (ret == 0 && (pos == MODULE_CFG_FRAGMENT_LAST || + pos == MODULE_CFG_FRAGMENT_SINGLE)) { + void *data = NULL; + size_t data_size = 0; + + data = comp_get_data_blob(dax_ctx->blob_handler, &data_size, NULL); + if (data && data_size > 0) + update_params_from_buffer(mod, data, data_size); + } + } else { + ret = dax_set_param_wrapper(mod, dax_param_id, &val, sizeof(val)); + } + return ret; +} + +static const struct module_interface dolby_dax_audio_processing_interface = { + .init = sof_dax_init, + .prepare = sof_dax_prepare, + .process = sof_dax_process, + .set_configuration = sof_dax_set_configuration, + .free = sof_dax_free, +}; + +#if CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING_MODULE +/* modular: llext dynamic link */ + +#include +#include +#include + +static const struct sof_man_module_manifest main_manifest __section(".module") __used = { + .module = { + .name = "DAX", + .uuid = SOF_REG_UUID(dolby_dax_audio_processing), + .entry_point = (uint32_t)(&dolby_dax_audio_processing_interface), + .instance_max_count = 1, + .type = { + .load_type = SOF_MAN_MOD_TYPE_LLEXT, + .domain_dp = 1, + }, + .affinity_mask = 7, + } +}; + +SOF_LLEXT_BUILDINFO; + +#else + +DECLARE_MODULE_ADAPTER(dolby_dax_audio_processing_interface, dolby_dax_audio_processing_uuid, + dolby_dax_audio_processing_tr); +SOF_MODULE_INIT(dolby_dax_audio_processing, + sys_comp_module_dolby_dax_audio_processing_interface_init); + +#endif diff --git a/src/audio/module_adapter/module/dolby/dax.toml b/src/audio/module_adapter/module/dolby/dax.toml new file mode 100644 index 000000000000..c53911a3b33e --- /dev/null +++ b/src/audio/module_adapter/module/dolby/dax.toml @@ -0,0 +1,25 @@ +#ifndef LOAD_TYPE +#define LOAD_TYPE "0" +#endif + + REM # DAX module config + [[module.entry]] + name = "DAX" + uuid = "40F66C8B-5AA5-4345-8919-53EC431AAA98" + affinity_mask = "0x7" + instance_count = "1" + domain_types = "1" + load_type = LOAD_TYPE + module_type = "9" + auto_start = "0" + sched_caps = [1, 0x00008000] + + REM # see struct fw_pin_description + REM # pin = [dir, type, sample rate, size, container, channel-cfg] + pin = [0, 0, 0x400, 0x8, 0x8, 0x4404] + + REM # see struct sof_man_mod_config + 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, 135000000, 1024, 1024, 0, 675000, 0] + + index = __COUNTER__ diff --git a/src/audio/module_adapter/module/dolby/dax_mock.c b/src/audio/module_adapter/module/dolby/dax_mock.c new file mode 100644 index 000000000000..c8889699a7e8 --- /dev/null +++ b/src/audio/module_adapter/module/dolby/dax_mock.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE +// +// Copyright(c) 2025 Dolby Laboratories. All rights reserved. +// +// Author: Jun Lai +// + +#include +#include + +#define PLACEHOLDER_BUF_SZ 8 + +uint32_t dax_query_persist_memory(struct sof_dax *dax_ctx) +{ + return PLACEHOLDER_BUF_SZ; +} + +uint32_t dax_query_scratch_memory(struct sof_dax *dax_ctx) +{ + return PLACEHOLDER_BUF_SZ; +} + +uint32_t dax_query_period_frames(struct sof_dax *dax_ctx) +{ + return 256; +} + +int dax_free(struct sof_dax *dax_ctx) +{ + return 0; +} + +int dax_init(struct sof_dax *dax_ctx) +{ + return 0; +} + +int dax_process(struct sof_dax *dax_ctx) +{ + uint32_t peroid_bytes = dax_query_period_frames(dax_ctx) * + dax_ctx->input_media_format.num_channels * + dax_ctx->input_media_format.bytes_per_sample; + + if (dax_ctx->input_buffer.avail < peroid_bytes || + dax_ctx->output_buffer.free < peroid_bytes) { + return 0; + } + memcpy_s((uint8_t *)dax_ctx->output_buffer.addr + dax_ctx->output_buffer.avail, + dax_ctx->output_buffer.free, + dax_ctx->input_buffer.addr, + peroid_bytes); + return peroid_bytes; +} + +int dax_set_param(uint32_t id, const void *val, uint32_t val_sz, struct sof_dax *dax_ctx) +{ + return 0; +} + +int dax_set_enable(int32_t enable, struct sof_dax *dax_ctx) +{ + return 0; +} + +int dax_set_volume(int32_t pregain, struct sof_dax *dax_ctx) +{ + return 0; +} + +int dax_set_device(int32_t out_device, struct sof_dax *dax_ctx) +{ + return 0; +} + +int dax_set_ctc_enable(int32_t enable, struct sof_dax *dax_ctx) +{ + return 0; +} + +const char *dax_get_version(void) +{ + return ""; +} + +void *dax_find_params(uint32_t query_id, + int32_t query_val, + uint32_t *query_sz, + struct sof_dax *dax_ctx) +{ + return NULL; +} diff --git a/src/audio/module_adapter/module/dolby/llext-wrap.c b/src/audio/module_adapter/module/dolby/llext-wrap.c new file mode 100644 index 000000000000..30c7be24e22b --- /dev/null +++ b/src/audio/module_adapter/module/dolby/llext-wrap.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. All rights reserved. + +#include +#include +#include +#include + +/* + * Stubs that are needed for linkage of some applications or libraries + * that come from porting userspace code. Anyone porting should + * make sure that any code does not depend on working copies of these + * reentrant functions. We will fail for any caller. + */ + +struct stat; +struct _reent; + +size_t _read_r(struct _reent *ptr, int fd, char *buf, size_t cnt) +{ + errno = -ENOTSUP; + return -ENOTSUP; +} + +size_t _write_r(struct _reent *ptr, int fd, char *buf, size_t cnt) +{ + errno = -ENOTSUP; + return -ENOTSUP; +} + +void *_sbrk_r(struct _reent *ptr, ptrdiff_t incr) +{ + errno = -ENOTSUP; + return NULL; +} + +int _lseek_r(struct _reent *ptr, int fd, int pos, int whence) +{ + errno = -ENOTSUP; + return -ENOTSUP; +} + +int _kill_r(struct _reent *ptr, int pid, int sig) +{ + errno = -ENOTSUP; + return -ENOTSUP; +} + +int _getpid_r(struct _reent *ptr) +{ + errno = -ENOTSUP; + return -ENOTSUP; +} + +int _fstat_r(struct _reent *ptr, int fd, struct stat *pstat) +{ + errno = -ENOTSUP; + return -ENOTSUP; +} + +int _close_r(struct _reent *ptr, int fd) +{ + errno = -ENOTSUP; + return -ENOTSUP; +} + +void _exit(int status) +{ + assert(0); + while (1) { + /* spin forever */ + } + /* NOTREACHED */ +} diff --git a/src/audio/module_adapter/module/dolby/llext/CMakeLists.txt b/src/audio/module_adapter/module/dolby/llext/CMakeLists.txt new file mode 100644 index 000000000000..fe8a965d8c87 --- /dev/null +++ b/src/audio/module_adapter/module/dolby/llext/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright(c) 2025 Dolby Laboratories. +# SPDX-License-Identifier: Apache-2.0 + +if(CONFIG_COMP_DOLBY_DAX_AUDIO_PROCESSING_MOCK) + sof_llext_build("dolby_dax_audio_processing" + SOURCES ../dax.c + ../dax_mock.c + INCLUDES ${sof_top_dir}/third_party/include + ) +else() + sof_llext_build("dolby_dax_audio_processing" + SOURCES ../dax.c ../llext-wrap.c + INCLUDES ${sof_top_dir}/third_party/include + LIBS_PATH ${sof_top_dir}/third_party/lib/ + LIBS dax m c gcc + ) +endif() diff --git a/src/audio/module_adapter/module/dolby/llext/llext.toml.h b/src/audio/module_adapter/module/dolby/llext/llext.toml.h new file mode 100644 index 000000000000..74f92b85fb81 --- /dev/null +++ b/src/audio/module_adapter/module/dolby/llext/llext.toml.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2025 Dolby Laboratories. All rights reserved. + */ +#include +#define LOAD_TYPE "2" +#include "../dax.toml" + +[module] +count = __COUNTER__ diff --git a/src/audio/module_adapter/module/generic.c b/src/audio/module_adapter/module/generic.c index f95299f952d8..8d75fe62e2bc 100644 --- a/src/audio/module_adapter/module/generic.c +++ b/src/audio/module_adapter/module/generic.c @@ -71,10 +71,20 @@ int module_load_config(struct comp_dev *dev, const void *cfg, size_t size) return ret; } +static void mod_resource_init(struct processing_module *mod) +{ + struct module_data *md = &mod->priv; + /* Init memory list */ + list_init(&md->resources.res_list); + list_init(&md->resources.free_cont_list); + list_init(&md->resources.cont_chunk_list); + md->resources.heap_usage = 0; + md->resources.heap_high_water_mark = 0; +} + int module_init(struct processing_module *mod) { int ret; - struct module_data *md = &mod->priv; struct comp_dev *dev = mod->dev; const struct module_interface *const interface = dev->drv->adapter_ops; @@ -99,14 +109,9 @@ int module_init(struct processing_module *mod) return -EIO; } - /* Init memory list */ - list_init(&md->resources.res_list); - list_init(&md->resources.free_cont_list); - list_init(&md->resources.cont_chunk_list); - md->resources.heap_usage = 0; - md->resources.heap_high_water_mark = 0; + mod_resource_init(mod); #if CONFIG_MODULE_MEMORY_API_DEBUG && defined(__ZEPHYR__) - md->resources.rsrc_mngr = k_current_get(); + mod->priv.resources.rsrc_mngr = k_current_get(); #endif /* Now we can proceed with module specific initialization */ ret = interface->init(mod); @@ -117,7 +122,7 @@ int module_init(struct processing_module *mod) comp_dbg(dev, "done"); #if CONFIG_IPC_MAJOR_3 - md->state = MODULE_INITIALIZED; + mod->priv.state = MODULE_INITIALIZED; #endif return 0; @@ -572,6 +577,13 @@ void mod_free_all(struct processing_module *mod) list_item_del(&container->list); } + list_for_item_safe(list, _list, &res->free_cont_list) { + struct module_resource *container = + container_of(list, struct module_resource, list); + + list_item_del(&container->list); + } + list_for_item_safe(list, _list, &res->cont_chunk_list) { struct container_chunk *chunk = container_of(list, struct container_chunk, chunk_list); @@ -579,6 +591,9 @@ void mod_free_all(struct processing_module *mod) list_item_del(&chunk->chunk_list); rfree(chunk); } + + /* Make sure resource lists and accounting are reset */ + mod_resource_init(mod); } EXPORT_SYMBOL(mod_free_all); diff --git a/src/audio/sound_dose/CMakeLists.txt b/src/audio/sound_dose/CMakeLists.txt new file mode 100644 index 000000000000..d6cd9c10b798 --- /dev/null +++ b/src/audio/sound_dose/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: BSD-3-Clause + +if(CONFIG_COMP_SOUND_DOSE STREQUAL "m") + add_subdirectory(llext ${PROJECT_BINARY_DIR}/sound_dose_llext) + add_dependencies(app sound_dose) +else() + add_local_sources(sof sound_dose.c) + add_local_sources(sof sound_dose-generic.c) + add_local_sources(sof sound_dose-ipc4.c) +endif() diff --git a/src/audio/sound_dose/Kconfig b/src/audio/sound_dose/Kconfig new file mode 100644 index 000000000000..a15e9a2b724a --- /dev/null +++ b/src/audio/sound_dose/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause + +config COMP_SOUND_DOSE + tristate "Sound Dose module" + default m if LIBRARY_DEFAULT_MODULAR + depends on COMP_MODULE_ADAPTER + depends on IPC_MAJOR_4 + select MATH_IIR + select MATH_IIR_DF1 + select MATH_EXP + help + Select this for Sound Dose SOF module. The purpose is + to calculate for audio playback MEL values (momentary + sound exposure level) to provide to user space the data + to compute the sound dose CSD as defined in EN 50332-3. diff --git a/src/audio/sound_dose/README.md b/src/audio/sound_dose/README.md new file mode 100644 index 000000000000..2a25ad19d1ba --- /dev/null +++ b/src/audio/sound_dose/README.md @@ -0,0 +1,75 @@ +Sound Dose + +The purpose of this component is to calculate with DSP offload the +headphone audio playback MEL values (momentary sound exposure level). +The calculated MEL values are notified to user space to be available +every one second. The user space should respond to notification with +bytes control get to get the data. The MEL values are used to +calculate the CSD value (cumulative sound dose). Low enough CSD value +ensures the music listening is safe the user's hearing. + +The calculation of MEL values and CSD values is defined in EN 50332-3 +stadard. The implementation in the Android OS is described in +https://source.android.com/docs/core/audio/sound-dose . + +The SOF Sound Dose component should be placed in topology as last +component before playback dai-copier. + +Currently it can be tested with next test topologies: +- sof-hda-benchmark-sound_dose32.tplg +- sof-mtl-sdw-benchmark-sound_dose32-sdw0.tplg +- sof-mtl-sdw-benchmark-sound_dose32-simplejack.tplg + +E.g. in the sdw topologies the controls for setting it up can be +seen with command: + +$ amixer -c0 controls | grep "Jack Out Sound Dose" +numid=33,iface=MIXER,name='Jack Out Sound Dose data bytes' +numid=32,iface=MIXER,name='Jack Out Sound Dose gain bytes' +numid=30,iface=MIXER,name='Jack Out Sound Dose setup bytes' +numid=31,iface=MIXER,name='Jack Out Sound Dose volume bytes' + +The above topologies program the setup bytes for acoustical +sensitivity of acoustical 100 dBSPL for 0 dBFS digital level. +The gain is set to 0 dB and volume to 0 dB. + +To test the basics copy a compatible test topology over the +normal topology file. Or alternative use module options +tplg_filename and tplg_path for module snd_sof. + +Get kcontrol_events from https://github.com/ujfalusi/kcontrol_events +and build and install it. + +Start playback of some music or test signal. Note that there is no SOF +volume control component in the test topology, so it plays out at max +volume. + +Start in other console kcontrol_events. The control 'Jack Out Sound +Dose data bytes' should get updates every one second. The structures +are defined in src/include/user/audio_feature.h and +src/include/user/sound_dose.h. + +The next steps require build of blobs for sof-ctl. The blobs can be +rebuilt with command + +cd src/audio/sound_dose/tune; octave --quiet --no-window-system sof_sound_dose_blobs.m + +To simulate effect of initial sensitivity setup, try command + +sof-ctl -c name='Jack Out Sound Dose setup bytes' -s /sound_dose/setup_sens_0db.txt +sof-ctl -c name='Jack Out Sound Dose setup bytes' -s /sound_dose/setup_sens_100db.txt + +To simulate adjusting codec volume control down 10 dB and up again 10 dB + +sof-ctl -c name='Jack Out Sound Dose volume bytes' -s ctl4/sound_dose/setup_vol_-10db.txt +sof-ctl -c name='Jack Out Sound Dose volume bytes' -s ctl4/sound_dose/setup_vol_0db.txt + +To force user's listening level 10 dB down after observing too large dose per the MSD value + +sof-ctl -c name='Jack Out Sound Dose gain bytes' -s ctl4/sound_dose/setup_gain_-10db.txt + +To restore user's listening level to non-attenuated + +sof-ctl -c name='Jack Out Sound Dose gain bytes' -s ctl4/sound_dose/setup_gain_0db.txt + +The above commands have impact to data shown by kcontrol_events. diff --git a/src/audio/sound_dose/llext/CMakeLists.txt b/src/audio/sound_dose/llext/CMakeLists.txt new file mode 100644 index 000000000000..705280b08648 --- /dev/null +++ b/src/audio/sound_dose/llext/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Intel Corporation. +# SPDX-License-Identifier: Apache-2.0 + +sof_llext_build("sound_dose" + SOURCES ../sound_dose.c + ../sound_dose-generic.c + ../sound_dose-ipc4.c + LIB openmodules +) diff --git a/src/audio/sound_dose/llext/llext.toml.h b/src/audio/sound_dose/llext/llext.toml.h new file mode 100644 index 000000000000..b5bc25562bd2 --- /dev/null +++ b/src/audio/sound_dose/llext/llext.toml.h @@ -0,0 +1,6 @@ +#include +#define LOAD_TYPE "2" +#include "../sound_dose.toml" + +[module] +count = __COUNTER__ diff --git a/src/audio/sound_dose/sound_dose-generic.c b/src/audio/sound_dose/sound_dose-generic.c new file mode 100644 index 000000000000..fb5f63fc844a --- /dev/null +++ b/src/audio/sound_dose/sound_dose-generic.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sound_dose.h" + +LOG_MODULE_DECLARE(sound_dose, CONFIG_SOF_LOG_LEVEL); + +static void sound_dose_calculate_mel(const struct processing_module *mod, int frames) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + uint64_t energy_sum; + uint32_t log_arg; + int32_t tmp; + int ch; + + cd->frames_count += frames; + if (cd->frames_count < cd->report_count) + return; + + cd->frames_count = 0; + energy_sum = 0; + for (ch = 0; ch < cd->channels; ch++) { + energy_sum += cd->energy[ch]; + cd->energy[ch] = 0; + } + + /* Log2 argument is Q32.0 unsigned, log2 returns Q16.16 signed. + * Energy is Qx.30, so the argument 2^30 times scaled. Also to keep + * argument within uint32_t range, need to scale it down by 19. + * To compensate these, need to add 19 (Q16.16) and subtract 30 (Q16.16) + * from logarithm value. + */ + log_arg = (uint32_t)(energy_sum >> SOUND_DOSE_ENERGY_SHIFT); + log_arg = MAX(log_arg, 1); + tmp = base2_logarithm(log_arg); + tmp += SOUND_DOSE_LOG_FIXED_OFFSET; /* Compensate Q2.30 and energy shift */ + tmp += cd->log_offset_for_mean; /* logarithm subtract for mean */ + tmp = Q_MULTSR_32X32((int64_t)tmp, SOUND_DOSE_TEN_OVER_LOG2_10_Q29, + SOUND_DOSE_LOGOFFS_Q, SOUND_DOSE_LOGMULT_Q, SOUND_DOSE_LOGOFFS_Q); + cd->level_dbfs = tmp + SOUND_DOSE_WEIGHT_FILTERS_OFFS_Q16 + SOUND_DOSE_DFBS_OFFS_Q16; + + /* If stereo sum channel level values and subtract 3 dB, to generalize + * For stereo or more subtract -1.5 dB per channel. + */ + if (cd->channels > 1) + cd->level_dbfs += cd->channels * SOUND_DOSE_MEL_CHANNELS_SUM_FIX; + + sound_dose_report_mel(mod); +} + +#if CONFIG_FORMAT_S16LE + +/** + * sound_dose_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 in every frame are re-order to channels order defined in + * component data channel_map[]. + * + * Return: Value zero for success, otherwise an error code. + */ +static int sound_dose_s16(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + struct iir_state_df1 *iir; + int32_t weighted; + int16_t sample; + int16_t const *x0, *x, *x_start, *x_end; + int16_t *y0, *y, *y_start, *y_end; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int ch; + int i; + const int channels = cd->channels; + + /* Get pointer to source data in circular buffer, get buffer start and size to + * check for wrap. The size in bytes is converted to number of s16 samples to + * control the samples process loop. If the number of bytes requested is not + * possible, an error is returned. + */ + ret = source_get_data_s16(source, bytes, &x0, &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, &y0, &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 (samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - x0; + samples_without_wrap = y_end - y0; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, samples); + for (ch = 0; ch < cd->channels; ch++) { + iir = &cd->iir[ch]; + x = x0++; + y = y0++; + for (i = 0; i < samples_without_wrap; i += channels) { + sample = sat_int16(Q_MULTSR_32X32((int64_t)cd->gain, *x, + SOUND_DOSE_GAIN_Q, + SOUND_DOSE_S16_Q, + SOUND_DOSE_S16_Q)); + *y = sample; + x += channels; + y += channels; + weighted = iir_df1(iir, ((int32_t)sample) << 16) >> 16; + + /* Update sound dose, energy is Q1.15 * Q1.15 --> Q2.30 */ + cd->energy[ch] += weighted * weighted; + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x0 += samples_without_wrap; + y0 += samples_without_wrap; + x0 = (x0 >= x_end) ? x0 - x_size : x0; + y0 = (y0 >= y_end) ? y0 - y_size : y0; + + /* Update processed samples count for next loop iteration. */ + 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); + + sound_dose_calculate_mel(mod, frames); + return 0; +} +#endif /* CONFIG_FORMAT_S16LE */ + +#if CONFIG_FORMAT_S32LE || CONFIG_FORMAT_S24LE + +/** + * sound_dose_s32() - Process S32_LE or 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. + * + * Processing function for signed integer 32-bit PCM formats. The same + * function works for s24 and s32 formats since the samples values are + * not modified in computation. The audio samples in every frame are + * re-order to channels order defined in component data channel_map[]. + * + * Return: Value zero for success, otherwise an error code. + */ +static int sound_dose_s32(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + struct iir_state_df1 *iir; + int32_t sample; + int32_t const *x0, *x, *x_start, *x_end; + int32_t *y0, *y, *y_start, *y_end; + int32_t weighted; + int x_size, y_size; + int source_samples_without_wrap; + int samples_without_wrap; + int samples = frames * cd->channels; + int bytes = frames * cd->frame_bytes; + int ret; + int ch; + int i; + const int channels = cd->channels; + + /* Get pointer to source data in circular buffer, get buffer start and size to + * check for wrap. The size in bytes is converted to number of s32 samples to + * control the samples process loop. If the number of bytes requested is not + * possible, an error is returned. + */ + ret = source_get_data_s32(source, bytes, &x0, &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, &y0, &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 (samples) { + /* Find out samples to process before first wrap or end of data. */ + source_samples_without_wrap = x_end - x0; + samples_without_wrap = y_end - y0; + samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); + samples_without_wrap = MIN(samples_without_wrap, samples); + for (ch = 0; ch < cd->channels; ch++) { + iir = &cd->iir[ch]; + x = x0++; + y = y0++; + for (i = 0; i < samples_without_wrap; i += channels) { + sample = sat_int32(Q_MULTSR_32X32((int64_t)cd->gain, *x, + SOUND_DOSE_GAIN_Q, + SOUND_DOSE_S32_Q, + SOUND_DOSE_S32_Q)); + *y = sample; + x += channels; + y += channels; + weighted = iir_df1(iir, sample) >> 16; + + /* Update sound dose, energy is Q1.15 * Q1.15 --> Q2.30 */ + cd->energy[ch] += weighted * weighted; + } + } + + /* One of the buffers needs a wrap (or end of data), so check for wrap */ + x0 += samples_without_wrap; + y0 += samples_without_wrap; + x0 = (x0 >= x_end) ? x0 - x_size : x0; + y0 = (y0 >= y_end) ? y0 - y_size : y0; + + /* Update processed samples count for next loop iteration. */ + 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); + + sound_dose_calculate_mel(mod, frames); + return 0; +} +#endif /* CONFIG_FORMAT_S32LE || CONFIG_FORMAT_S24LE */ + +/* This struct array defines the used processing functions for + * the PCM formats + */ +const struct sound_dose_proc_fnmap sound_dose_proc_fnmap[] = { +#if CONFIG_FORMAT_S16LE + { SOF_IPC_FRAME_S16_LE, sound_dose_s16}, +#endif +#if CONFIG_FORMAT_S24LE + { SOF_IPC_FRAME_S24_4LE, sound_dose_s32}, +#endif +#if CONFIG_FORMAT_S32LE + { SOF_IPC_FRAME_S32_LE, sound_dose_s32}, +#endif +}; + +/** + * sound_dose_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. + */ +sound_dose_func sound_dose_find_proc_func(enum sof_ipc_frame src_fmt) +{ + int i; + + /* Find suitable processing function from map */ + for (i = 0; i < ARRAY_SIZE(sound_dose_proc_fnmap); i++) + if (src_fmt == sound_dose_proc_fnmap[i].frame_fmt) + return sound_dose_proc_fnmap[i].sound_dose_proc_func; + + return NULL; +} diff --git a/src/audio/sound_dose/sound_dose-ipc4.c b/src/audio/sound_dose/sound_dose-ipc4.c new file mode 100644 index 000000000000..fd3a8151e984 --- /dev/null +++ b/src/audio/sound_dose/sound_dose-ipc4.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#include +#include +#include +#include +#include +#include + +#include "sound_dose.h" + +LOG_MODULE_DECLARE(sound_dose, CONFIG_SOF_LOG_LEVEL); + +static struct ipc_msg *sound_dose_notification_init(struct processing_module *mod, + uint32_t control_type_param_id, + uint32_t control_id) +{ + struct ipc_msg msg_proto; + struct comp_dev *dev = mod->dev; + struct comp_ipc_config *ipc_config = &dev->ipc_config; + union ipc4_notification_header *primary = + (union ipc4_notification_header *)&msg_proto.header; + struct sof_ipc4_notify_module_data *msg_module_data; + struct sof_ipc4_control_msg_payload *msg_payload; + struct ipc_msg *msg; + + /* Clear header, extension, and other ipc_msg members */ + memset_s(&msg_proto, sizeof(msg_proto), 0, sizeof(msg_proto)); + primary->r.notif_type = SOF_IPC4_MODULE_NOTIFICATION; + primary->r.type = SOF_IPC4_GLB_NOTIFICATION; + primary->r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST; + primary->r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG; + msg = ipc_msg_w_ext_init(msg_proto.header, msg_proto.extension, + sizeof(struct sof_ipc4_notify_module_data) + + sizeof(struct sof_ipc4_control_msg_payload)); + if (!msg) + return NULL; + + msg_module_data = (struct sof_ipc4_notify_module_data *)msg->tx_data; + msg_module_data->instance_id = IPC4_INST_ID(ipc_config->id); + msg_module_data->module_id = IPC4_MOD_ID(ipc_config->id); + msg_module_data->event_id = SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL | + control_type_param_id; + msg_module_data->event_data_size = sizeof(struct sof_ipc4_control_msg_payload); + msg_payload = (struct sof_ipc4_control_msg_payload *)msg_module_data->event_data; + msg_payload->id = control_id; + msg_payload->num_elems = 0; + comp_dbg(dev, "instance_id = 0x%08x, module_id = 0x%08x", + msg_module_data->instance_id, msg_module_data->module_id); + return msg; +} + +int sound_dose_ipc_notification_init(struct processing_module *mod) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + + cd->msg = sound_dose_notification_init(mod, SOF_IPC4_BYTES_CONTROL_PARAM_ID, + SOF_SOUND_DOSE_PAYLOAD_PARAM_ID); + if (!cd->msg) { + comp_err(mod->dev, "Failed to initialize control notification."); + return -ENOMEM; + } + + return 0; +} + +void sound_dose_send_ipc_notification(const struct processing_module *mod) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + + ipc_msg_send(cd->msg, cd->msg->tx_data, false); +} + +/* This function handles the set_config() commands. The controls have the next purpose: + * - SOF_SOUND_DOSE_SETUP_PARAM_ID sets the acoustical sensitivity of the DAC, headphone + * amplifier and assumed worst-case loud headphones. E.g. 0 dBFS equals 100 dBSPL. The + * sensitivity is for max user volume. + * -SOF_SOUND_DOSE_VOLUME_PARAM_ID is set to new decibels value if volume is adjusted + * down from user maximum. + * - SOF_SOUND_DOSE_GAIN_PARAM_ID is normally set to 0 decibels value. If the user's + * listening is exceeding the safe MSD threshold the user's volume can be forced down + * with this gain. The bytes control is not visible in the mixer, so there is no simple + * way for user to force volume up with alsamixer or amixer utililities. The gain + * should be restored to 0 dB after the MSD value looks again safe. + * E.g. setting -10 dB value lowers user's listening volume with -10 dB and also lower + * the reported MEL values by -10 dB. + * + * These controls can be used as preferred by the MEL->MSD algorithm in user space. The + * controls offset the reported MEL values. The same offset can be also done in user + * space. And could use codec volume instead of SOF_SOUND_DOSE_GAIN_PARAM_ID if preferred. + */ +__cold static int +_sound_dose_set_config(struct processing_module *mod, uint32_t param_id, + uint32_t control_id, const void *data, uint32_t data_size) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + struct sound_dose_setup_config *new_setup; + struct sound_dose_volume_config *new_volume; + struct sound_dose_gain_config *new_gain; + struct comp_dev *dev = mod->dev; + uint8_t *dest; + size_t dest_size; + + assert_can_be_cold(); + + if (param_id != SOF_IPC4_BYTES_CONTROL_PARAM_ID) { + comp_warn(dev, "Not supported control type: %u", param_id); + return 0; + } + + comp_dbg(dev, "param_id = %u, control_id: %u", param_id, control_id); + + switch (control_id) { + case SOF_SOUND_DOSE_SETUP_PARAM_ID: + dest_size = sizeof(struct sound_dose_setup_config); + dest = (uint8_t *)&cd->setup; + break; + case SOF_SOUND_DOSE_VOLUME_PARAM_ID: + dest_size = sizeof(struct sound_dose_volume_config); + dest = (uint8_t *)&cd->vol; + break; + case SOF_SOUND_DOSE_GAIN_PARAM_ID: + dest_size = sizeof(struct sound_dose_gain_config); + dest = (uint8_t *)&cd->att; + break; + case SOF_SOUND_DOSE_PAYLOAD_PARAM_ID: + return 0; /* Just return, no need to set the audio feature data */ + default: + comp_warn(dev, "Ignored illegal control_id: %u", control_id); + return 0; + } + + if (data_size != dest_size) { + comp_err(dev, "Illegal fragment_size %u for %u:%u", + data_size, param_id, control_id); + return -EINVAL; + } + + switch (control_id) { + case SOF_SOUND_DOSE_SETUP_PARAM_ID: + new_setup = (struct sound_dose_setup_config *)data; + if (new_setup->sens_dbfs_dbspl < SOF_SOUND_DOSE_SENS_MIN_DB || + new_setup->sens_dbfs_dbspl > SOF_SOUND_DOSE_SENS_MAX_DB) { + comp_err(dev, "Illegal sensitivity = %d", new_setup->sens_dbfs_dbspl); + return -EINVAL; + } + break; + case SOF_SOUND_DOSE_VOLUME_PARAM_ID: + new_volume = (struct sound_dose_volume_config *)data; + if (new_volume->volume_offset < SOF_SOUND_DOSE_VOLUME_MIN_DB || + new_volume->volume_offset > SOF_SOUND_DOSE_VOLUME_MAX_DB) { + comp_err(dev, "Illegal volume = %d", new_volume->volume_offset); + return -EINVAL; + } + break; + case SOF_SOUND_DOSE_GAIN_PARAM_ID: + new_gain = (struct sound_dose_gain_config *)data; + if (new_gain->gain < SOF_SOUND_DOSE_GAIN_MIN_DB || + new_gain->gain > SOF_SOUND_DOSE_GAIN_MAX_DB) { + comp_err(dev, "Illegal gain = %d", new_gain->gain); + return -EINVAL; + } + cd->gain_update = true; + break; + } + + memcpy_s(dest, dest_size, data, data_size); + return 0; +} + +/* This function is the main set_config() handler. The two variants of bytes control + * are handled. The case where param_id is set to SOF_IPC4_BYTES_CONTROL_PARAM_ID + * with header sof_ipc4_control_msg_payload has the benefit of being able to pass + * the id of the control. It is useful when there are multiple instances of + * bytes control. The legacy case is where control instances are identified with + * param_id value. The first format with header must be used when a control supports + * a notification to user space. The legacy works only for simple set control usage. + */ +__cold int sound_dose_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) +{ + int ret; + + assert_can_be_cold(); + + if (!fragment_size) { + comp_warn(mod->dev, "Zero fragment size for param_id %d", param_id); + return 0; + } + + if (param_id == SOF_IPC4_BYTES_CONTROL_PARAM_ID) { + struct sof_ipc4_control_msg_payload *msg_payload; + + msg_payload = (struct sof_ipc4_control_msg_payload *)fragment; + + ret = _sound_dose_set_config(mod, param_id, msg_payload->id, + msg_payload->data, msg_payload->num_elems); + } else { + ret = _sound_dose_set_config(mod, SOF_IPC4_BYTES_CONTROL_PARAM_ID, + param_id, fragment, fragment_size); + } + + return ret; +} + +/* This function copies the data for get_config() request by the driver. Only + * the SOF_SOUND_DOSE_PAYLOAD_PARAM_ID as control_id is supported. The audio + * feature payload for sound_dose is copied to the recipient. + */ +__cold static int +_sound_dose_get_config(struct processing_module *mod, uint32_t param_id, + uint32_t control_id, uint32_t *size_out, + uint8_t *data, size_t data_size) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + size_t payload_size; + + assert_can_be_cold(); + + if (param_id != SOF_IPC4_BYTES_CONTROL_PARAM_ID) { + comp_warn(dev, "Not supported control type: %u", param_id); + return 0; + } + + comp_dbg(dev, "param_id = %u, control_id: %u", param_id, control_id); + + if (control_id != SOF_SOUND_DOSE_PAYLOAD_PARAM_ID) { + comp_warn(dev, "Ignored get config control_id: %u", control_id); + memset(data, 0, data_size); + *size_out = 0; + return 0; + } + + payload_size = cd->abi->size; + memcpy_s(data, data_size, cd->abi->data, payload_size); + *size_out = payload_size; + + return 0; +} + +/* This is the main get_config() handler. As in set_config() case, the no-header + * way is legacy. The response get_config() after notify must use the + * sof_ipc4_control_msg_payload header. + */ +__cold int sound_dose_get_config(struct processing_module *mod, + uint32_t param_id, uint32_t *data_offset_size, + uint8_t *fragment, size_t fragment_size) +{ + int ret; + + assert_can_be_cold(); + + if (!fragment_size) { + comp_warn(mod->dev, "Zero fragment size for param_id %d", param_id); + return 0; + } + + if (param_id == SOF_IPC4_BYTES_CONTROL_PARAM_ID) { + struct sof_ipc4_control_msg_payload *msg_payload; + + /* Note: msg_payload will get overwrite as fragment. */ + msg_payload = (struct sof_ipc4_control_msg_payload *)fragment; + ret = _sound_dose_get_config(mod, param_id, msg_payload->id, + data_offset_size, fragment, + fragment_size); + } else { + ret = _sound_dose_get_config(mod, SOF_IPC4_BYTES_CONTROL_PARAM_ID, + param_id, data_offset_size, + fragment, fragment_size); + } + + return ret; +} diff --git a/src/audio/sound_dose/sound_dose.c b/src/audio/sound_dose/sound_dose.c new file mode 100644 index 000000000000..dcbac5c643c7 --- /dev/null +++ b/src/audio/sound_dose/sound_dose.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2025 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sound_dose.h" +#include "sound_dose_iir_44k.h" +#include "sound_dose_iir_48k.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(sound_dose); + +/* Creates logging data for the component */ +LOG_MODULE_REGISTER(sound_dose, CONFIG_SOF_LOG_LEVEL); + +/* Creates the component trace. Traces show in trace console the component + * info, warning, and error messages. + */ +DECLARE_TR_CTX(sound_dose_tr, SOF_UUID(sound_dose_uuid), LOG_LEVEL_INFO); + +void sound_dose_report_mel(const struct processing_module *mod) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + uint64_t tmp_h, tmp_l; + + cd->dose->current_sens_dbfs_dbspl = cd->setup.sens_dbfs_dbspl; + cd->dose->current_volume_offset = cd->vol.volume_offset; + cd->dose->current_gain = cd->att.gain; + cd->dose->dbfs_value = (int32_t)(((int64_t)cd->level_dbfs * 100) >> 16); + cd->dose->mel_value = cd->dose->dbfs_value + + cd->setup.sens_dbfs_dbspl + + cd->vol.volume_offset; + /* Multiply in two 32x32 -> 64 bits parts and do sum of the parts + * including the shift from Q26 to Q0. The simplified 96 bits + * equation would be time = ((tmp_h << 32) + tmp_l) >> 26. + */ + tmp_l = (cd->total_frames_count & 0xffffffff) * cd->rate_to_us_coef; + tmp_h = (cd->total_frames_count >> 32) * cd->rate_to_us_coef; + cd->feature->stream_time_us = (tmp_l >> 26) + ((tmp_h & ((1LL << 32) - 1)) << 6); +#if SOUND_DOSE_DEBUG + comp_info(mod->dev, "Time %d dBFS %d MEL %d", + (int32_t)(cd->feature->stream_time_us / 1000000), + cd->dose->dbfs_value, cd->dose->mel_value); +#endif + + sound_dose_send_ipc_notification(mod); +} + +int sound_dose_filters_init(struct processing_module *mod) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + struct sof_eq_iir_config *iir_config; + struct sof_eq_iir_header *iir_coef; + struct comp_dev *dev = mod->dev; + struct sof_abi_hdr *blob; + size_t iir_size, alloc_size; + int32_t *data; + int i; + + /* Initialize FIR */ + switch (cd->rate) { + case 48000: + blob = (struct sof_abi_hdr *)sound_dose_iir_48k; + iir_config = (struct sof_eq_iir_config *)blob->data; + cd->log_offset_for_mean = SOUND_DOSE_LOG2_INV_48K_Q16; + cd->rate_to_us_coef = SOUND_DOSE_1M_OVER_48K_Q26; + break; + case 44100: + blob = (struct sof_abi_hdr *)sound_dose_iir_44k; + iir_config = (struct sof_eq_iir_config *)blob->data; + cd->log_offset_for_mean = SOUND_DOSE_LOG2_INV_44K_Q16; + cd->rate_to_us_coef = SOUND_DOSE_1M_OVER_44K_Q26; + break; + default: + /* TODO: 96 kHz and 192 kHz with integer decimation factor. The A-weight is not + * defined above 20 kHz, so high frequency energy is not needed. Also it will + * help keep the module load reasonable. + */ + comp_err(dev, "error: unsupported sample rate %d", cd->rate); + return -EINVAL; + } + + /* Apply the first responses in the blobs */ + iir_coef = (struct sof_eq_iir_header *)&iir_config->data[iir_config->channels_in_config]; + iir_size = iir_delay_size_df1(iir_coef); + alloc_size = cd->channels * iir_size; + cd->delay_lines = rzalloc(SOF_MEM_FLAG_USER, alloc_size); + if (!cd->delay_lines) { + comp_err(dev, "Failed to allocate memory for weighting filters."); + return -ENOMEM; + } + + data = cd->delay_lines; + for (i = 0; i < cd->channels; i++) { + iir_init_coef_df1(&cd->iir[i], iir_coef); + iir_init_delay_df1(&cd->iir[i], &data); + cd->energy[i] = 0; + } + + cd->report_count = cd->rate; /* report every 1s, for 48k frames */ + return 0; +} + +void sound_dose_filters_free(struct sound_dose_comp_data *cd) +{ + rfree(cd->delay_lines); +} + +__cold static void sound_dose_setup_init(struct sound_dose_comp_data *cd) +{ + cd->setup.sens_dbfs_dbspl = 0; /* 0 dbFS is 100 dB */ + cd->vol.volume_offset = 0; /* Assume max vol */ + cd->att.gain = 0; /* No attenuation, 0 dB */ + cd->gain = SOUND_DOSE_GAIN_ONE_Q30; + cd->new_gain = SOUND_DOSE_GAIN_ONE_Q30; +} + +static int sound_dose_audio_feature_init(struct processing_module *mod) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + size_t alloc_size = sizeof(struct sof_abi_hdr) + + sizeof(struct sof_audio_feature) + + sizeof(struct sof_sound_dose); + + cd->abi = rzalloc(SOF_MEM_FLAG_USER, alloc_size); + if (!cd->abi) { + comp_err(mod->dev, "Failed to allocate audio feature data."); + return -ENOMEM; + } + + cd->abi->magic = SOF_IPC4_ABI_MAGIC; + cd->abi->abi = SOF_ABI_VERSION; + cd->abi->size = sizeof(struct sof_audio_feature) + sizeof(struct sof_sound_dose); + cd->feature = (struct sof_audio_feature *)cd->abi->data; + cd->feature->data_size = sizeof(struct sof_sound_dose); + cd->feature->type = SOF_AUDIO_FEATURE_SOUND_DOSE_MEL; + cd->feature->num_audio_features = 1; /* Single MEL value in audio feature data */ + cd->dose = (struct sof_sound_dose *)cd->feature->data; + return 0; +} + +/** + * sound_dose_init() - Initialize the 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 sound_dose_init(struct processing_module *mod) +{ + struct module_data *md = &mod->priv; + struct comp_dev *dev = mod->dev; + struct sound_dose_comp_data *cd; + int ret; + + comp_info(dev, "Initialize"); + + cd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*cd)); + if (!cd) { + comp_err(dev, "Failed to allocate component data."); + return -ENOMEM; + } + + md->private = cd; + + sound_dose_setup_init(cd); + ret = sound_dose_audio_feature_init(mod); + if (ret) { + rfree(cd); + return ret; + } + + ret = sound_dose_ipc_notification_init(mod); + if (ret) { + rfree(cd->abi); + rfree(cd); + } + + return ret; +} + +/** + * sound_dose_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 sound_dose_process(struct processing_module *mod, + struct sof_source **sources, + int num_of_sources, + struct sof_sink **sinks, + int num_of_sinks) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + struct sof_source *source = sources[0]; /* One input in this example */ + struct sof_sink *sink = sinks[0]; /* One output in this example */ + struct comp_dev *dev = mod->dev; + int32_t arg; + int frames = source_get_data_frames_available(source); + int sink_frames = sink_get_free_frames(sink); + + comp_dbg(dev, "sound_dose_process()"); + + if (cd->gain_update) { + /* Convert dB * 100 to Q8.24 */ + arg = (int32_t)cd->att.gain * SOUND_DOSE_ONE_OVER_100_Q24; + + /* Calculate linear value as Q12.20 and convert to Q2.30 */ + cd->new_gain = Q_SHIFT_LEFT(sofm_db2lin_fixed(arg), 20, 30); + cd->gain_update = false; + } + + if (cd->new_gain < cd->gain) { + cd->gain = Q_MULTSR_32X32((int64_t)cd->gain, SOUND_DOSE_GAIN_DOWN_Q30, 30, 30, 30); + cd->gain = MAX(cd->gain, cd->new_gain); + } else if (cd->new_gain > cd->gain) { + cd->gain = Q_MULTSR_32X32((int64_t)cd->gain, SOUND_DOSE_GAIN_UP_Q30, 30, 30, 30); + cd->gain = MIN(cd->gain, SOUND_DOSE_GAIN_ONE_Q30); + } + + frames = MIN(frames, sink_frames); + cd->total_frames_count += frames; + return cd->sound_dose_func(mod, source, sink, frames); +} + +/** + * sound_dose_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 sound_dose_prepare(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + enum sof_ipc_frame source_format; + + comp_dbg(dev, "sound_dose_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->rate = source_get_rate(sources[0]); + + cd->sound_dose_func = sound_dose_find_proc_func(source_format); + if (!cd->sound_dose_func) { + comp_err(dev, "No processing function found for format %d.", + source_format); + return -EINVAL; + } + + return sound_dose_filters_init(mod); +} + +/** + * sound_dose_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 sound_dose_reset(struct processing_module *mod) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + + comp_dbg(mod->dev, "sound_dose_reset()"); + + sound_dose_setup_init(cd); + return 0; +} + +/** + * sound_dose_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 sound_dose_free(struct processing_module *mod) +{ + struct sound_dose_comp_data *cd = module_get_private_data(mod); + + assert_can_be_cold(); + comp_dbg(mod->dev, "sound_dose_free()"); + + sound_dose_filters_free(cd); + if (cd->msg) { + rfree(cd->msg->tx_data); + rfree(cd->msg); + } + rfree(cd->abi); + rfree(cd); + return 0; +} + +/* This defines the module operations */ +static const struct module_interface sound_dose_interface = { + .init = sound_dose_init, + .prepare = sound_dose_prepare, + .process = sound_dose_process, + .set_configuration = sound_dose_set_config, + .get_configuration = sound_dose_get_config, + .reset = sound_dose_reset, + .free = sound_dose_free +}; + +/* This controls build of the module. If COMP_MODULE is selected in kconfig + * this is build as dynamically loadable module. + */ +#if CONFIG_COMP_SOUND_DOSE_MODULE + +#include +#include +#include + +static const struct sof_man_module_manifest mod_manifest __section(".module") __used = + SOF_LLEXT_MODULE_MANIFEST("SNDDOSE", &sound_dose_interface, 1, + SOF_REG_UUID(sound_dose), 4); + +SOF_LLEXT_BUILDINFO; + +#else + +DECLARE_MODULE_ADAPTER(sound_dose_interface, sound_dose_uuid, sound_dose_tr); +SOF_MODULE_INIT(sound_dose, sys_comp_module_sound_dose_interface_init); + +#endif diff --git a/src/audio/sound_dose/sound_dose.h b/src/audio/sound_dose/sound_dose.h new file mode 100644 index 000000000000..0366d1dc9b35 --- /dev/null +++ b/src/audio/sound_dose/sound_dose.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2025 Intel Corporation. + * + */ +#ifndef __SOF_AUDIO_SOUND_DOSE_H__ +#define __SOF_AUDIO_SOUND_DOSE_H__ + +#include +#include +#include +#include +#include + +#define SOUND_DOSE_DEBUG 0 + +#define SOUND_DOSE_1M_OVER_44K_Q26 1521742948 /* int32(1000/44.1 * 2^26) */ +#define SOUND_DOSE_1M_OVER_48K_Q26 1398101333 /* int32(1000/48 * 2^26) */ +#define SOUND_DOSE_ONE_OVER_100_Q24 167772 /* int32(0.01*2^24) */ +#define SOUND_DOSE_GAIN_ONE_Q30 1073741824 /* int32(2^30) */ +#define SOUND_DOSE_GAIN_UP_Q30 1079940603 /* int32(10^(+0.05/20)*2^30) */ +#define SOUND_DOSE_GAIN_DOWN_Q30 1067578625 /* int32(10^(-0.05/20)*2^30) */ +#define SOUND_DOSE_LOG2_INV_44K_Q16 -1011122 /* int32(log2(1/44.1e3) * 2^16) */ +#define SOUND_DOSE_LOG2_INV_48K_Q16 -1019134 /* int32(log2(1/48e3) * 2^16) */ +#define SOUND_DOSE_TEN_OVER_LOG2_10_Q29 1616142483 /* int32(10 / log2(10) * 2^29) */ +#define SOUND_DOSE_WEIGHT_FILTERS_OFFS_Q16 196608 /* int32(3 * 2^16) */ +#define SOUND_DOSE_DFBS_OFFS_Q16 197263 /* int32(3.01 * 2^16) */ +#define SOUND_DOSE_MEL_CHANNELS_SUM_FIX -98304 /* int32(-1.5 * 2^16) */ +#define SOUND_DOSE_ENERGY_SHIFT 19 /* Scale shift for 1s energy */ +#define SOUND_DOSE_LOG_FIXED_OFFSET (65536 * (SOUND_DOSE_ENERGY_SHIFT - 30)) + +#define SOUND_DOSE_S16_Q 15 /* Q1.15 samples */ +#define SOUND_DOSE_S32_Q 31 /* Q1.31 samples */ +#define SOUND_DOSE_GAIN_Q 30 /* Q2.30 gain */ +#define SOUND_DOSE_LOGOFFS_Q 16 /* see SOUND_DOSE_LOG2_INV_48K_Q16 */ +#define SOUND_DOSE_LOGMULT_Q 29 /* see SOUND_DOSE_TEN_OVER_LOG2_10_Q29 */ + +/** + * struct sound_dose_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 (*sound_dose_func)(const struct processing_module *mod, + struct sof_source *source, + struct sof_sink *sink, + uint32_t frames); + +/* Sound Dose component private data */ + +/** + * struct sound_dose_comp_data + * @sound_dose_func: Pointer to used processing function. + * @channels_order[]: Vector with desired sink channels order. + * @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 sound_dose_comp_data { + struct iir_state_df1 iir[PLATFORM_MAX_CHANNELS]; + struct sound_dose_setup_config setup; + struct sound_dose_volume_config vol; + struct sound_dose_gain_config att; + struct sof_abi_hdr *abi; + struct sof_audio_feature *feature; + struct sof_sound_dose *dose; + struct ipc_msg *msg; + sound_dose_func sound_dose_func; + int64_t energy[PLATFORM_MAX_CHANNELS]; + int64_t total_frames_count; + int32_t log_offset_for_mean; + int32_t rate_to_us_coef; + int32_t *delay_lines; + int32_t level_dbfs; + int32_t new_gain; + int32_t gain; + bool gain_update; + int report_count; + int frames_count; + int frame_bytes; + int channels; + int rate; +}; + +/** + * struct sound_dose_proc_fnmap - processing functions for frame formats + * @frame_fmt: Current frame format + * @sound_dose_proc_func: Function pointer for the suitable processing function + */ +struct sound_dose_proc_fnmap { + enum sof_ipc_frame frame_fmt; + sound_dose_func sound_dose_proc_func; +}; + +/** + * sound_dose_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. + */ +sound_dose_func sound_dose_find_proc_func(enum sof_ipc_frame src_fmt); + +/** + * sound_dose_set_config() - Handle controls set request + * @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. + */ +int sound_dose_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); + +/** + * sound_dose_get_config() - Handle controls get request + * @mod: Pointer to module data. + * @config_id: Configuration ID. + * @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. + * + * This function is used for controls get. + * + * Return: Zero if success, otherwise error code. + */ +int sound_dose_get_config(struct processing_module *mod, + uint32_t config_id, uint32_t *data_offset_size, + uint8_t *fragment, size_t fragment_size); + +void sound_dose_filters_free(struct sound_dose_comp_data *cd); +int sound_dose_filters_init(struct processing_module *mod); +void sound_dose_report_mel(const struct processing_module *mod); +int sound_dose_ipc_notification_init(struct processing_module *mod); +void sound_dose_send_ipc_notification(const struct processing_module *mod); + +#endif /* __SOF_AUDIO_SOUND_DOSE_H__ */ diff --git a/src/audio/sound_dose/sound_dose.toml b/src/audio/sound_dose/sound_dose.toml new file mode 100644 index 000000000000..95f58ae27e05 --- /dev/null +++ b/src/audio/sound_dose/sound_dose.toml @@ -0,0 +1,21 @@ +#ifndef LOAD_TYPE +#define LOAD_TYPE "0" +#endif + + REM # Sound Dose component module config + [[module.entry]] + name = "SNDDOSE" + uuid = UUIDREG_STR_SOUND_DOSE + 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/sound_dose/sound_dose_iir_44k.h b/src/audio/sound_dose/sound_dose_iir_44k.h new file mode 100644 index 000000000000..d6fbf665f4eb --- /dev/null +++ b/src/audio/sound_dose/sound_dose_iir_44k.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2025 Intel Corporation. + */ + +/* Created 2025-08-15 with command: + * cd src/audio/sound_dose/tune + * octave --quiet --no-window-system sof_sound_dose_time_domain_filters.m + */ + +#include + +static const uint32_t sound_dose_iir_44k[51] = { + 0x34464f53, 0x00000000, 0x000000ac, 0x0301d001, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x000000ac, 0x00000002, 0x00000001, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000004, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xc04a8747, + 0x7fb56455, 0x2000c3eb, 0xbffe782a, 0x2000c3eb, + 0x00000000, 0x00004000, 0xc5d5ada3, 0x7a174530, + 0x1e9ac221, 0xc2ca7bbd, 0x1e9ac221, 0x00000000, + 0x00004000, 0xc382c10f, 0x7c63e3c7, 0x1e4cf2c0, + 0xc1d9d144, 0x1fe2eeaa, 0x00000000, 0x00004000, + 0xfe0cb792, 0xe92a46fc, 0x02d2f1b4, 0x10d57d25, + 0x18d70df1, 0xfffffffd, 0x000067d8 +}; diff --git a/src/audio/sound_dose/sound_dose_iir_48k.h b/src/audio/sound_dose/sound_dose_iir_48k.h new file mode 100644 index 000000000000..6dca9af5e4b8 --- /dev/null +++ b/src/audio/sound_dose/sound_dose_iir_48k.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2025 Intel Corporation. + */ + +/* Created 2025-08-15 with command: + * cd src/audio/sound_dose/tune + * octave --quiet --no-window-system sof_sound_dose_time_domain_filters.m + */ + +#include + +static const uint32_t sound_dose_iir_48k[51] = { + 0x34464f53, 0x00000000, 0x000000ac, 0x0301d001, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x000000ac, 0x00000002, 0x00000001, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000004, 0x00000004, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xc0447c54, + 0x7fbb7275, 0x200247f9, 0xbffb700e, 0x200247f9, + 0x00000000, 0x00004000, 0xc56175ce, 0x7a8e6600, + 0x1eb83f18, 0xc28f81d0, 0x1eb83f18, 0x00000000, + 0x00004000, 0xc33b9ba4, 0x7caef0cf, 0x1e6ee6bd, + 0xc1b48ddf, 0x1fe4bfc4, 0x00000000, 0x00004000, + 0xff729f84, 0xf3612432, 0x01acb380, 0x0cd1120d, + 0x182fcd24, 0xfffffffd, 0x000067e0 +}; diff --git a/src/audio/sound_dose/tune/sof_sound_dose_blobs.m b/src/audio/sound_dose/tune/sof_sound_dose_blobs.m new file mode 100644 index 000000000000..7670303c2770 --- /dev/null +++ b/src/audio/sound_dose/tune/sof_sound_dose_blobs.m @@ -0,0 +1,95 @@ +% Export configuration blobs for Sound Dose + +% SPDX-License-Identifier: BSD-3-Clause +% +% Copyright (c) 2025, Intel Corporation. + +function sof_sound_dose_blobs() + + % Common definitions + sof_tools = '../../../../tools'; + sof_tplg = fullfile(sof_tools, 'topology'); + fn.tpath = fullfile(sof_tplg, 'topology2/include/components/sound_dose'); + str_comp = "sound_dose_config"; + str_comment = "Exported with script sof_sound_dose_blobs.m"; + str_howto = "cd src/audio/sound_dose/tune; octave sof_sound_dose_blobs.m"; + bytes_control_param_id = 202; + + % Set the parameters here + sof_tools = '../../../../tools'; + sof_tplg = fullfile(sof_tools, 'topology'); + sof_tplg_sound_dose = fullfile(sof_tplg, 'topology2/include/components/sound_dose'); + sof_ctl_sound_dose = fullfile(sof_tools, 'ctl/ipc4/sound_dose'); + + sof_sound_dose_paths(true); + + blob8 = sof_sound_dose_build_blob([10000 0], 0); + tplg2_fn = sprintf("%s/setup_sens_100db.conf", sof_tplg_sound_dose); + sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto); + sof_alsactl_write([sof_ctl_sound_dose "/setup_sens_100db.txt"], blob8); + + blob8 = sof_sound_dose_build_blob([0 0], 0); + tplg2_fn = sprintf("%s/setup_sens_0db.conf", sof_tplg_sound_dose); + sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto); + sof_alsactl_write([sof_ctl_sound_dose "/setup_sens_0db.txt"], blob8); + + blob8 = sof_sound_dose_build_blob([0 0], 1); + tplg2_fn = sprintf("%s/setup_vol_0db.conf", sof_tplg_sound_dose); + sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto); + sof_alsactl_write([sof_ctl_sound_dose "/setup_vol_0db.txt"], blob8); + + blob8 = sof_sound_dose_build_blob([-1000 0], 1); + tplg2_fn = sprintf("%s/setup_vol_-10db.conf", sof_tplg_sound_dose); + sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto); + sof_alsactl_write([sof_ctl_sound_dose "/setup_vol_-10db.txt"], blob8); + + blob8 = sof_sound_dose_build_blob([-1000 0], 2); + tplg2_fn = sprintf("%s/setup_gain_-10db.conf", sof_tplg_sound_dose); + sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto); + sof_alsactl_write([sof_ctl_sound_dose "/setup_gain_-10db.txt"], blob8); + + blob8 = sof_sound_dose_build_blob([0 0], 2); + tplg2_fn = sprintf("%s/setup_gain_0db.conf", sof_tplg_sound_dose); + sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto); + sof_alsactl_write([sof_ctl_sound_dose "/setup_gain_0db.txt"], blob8); + + blob8 = sof_sound_dose_build_blob([], bytes_control_param_id); + tplg2_fn = sprintf("%s/setup_data_init.conf", sof_tplg_sound_dose); + sof_tplg2_write(tplg2_fn, blob8, str_comp, str_comment, str_howto); + + sof_sound_dose_paths(false); +end + +function sof_sound_dose_paths(enable) + + common = '../../../../tools/tune/common'; + if enable + addpath(common); + else + rmpath(common); + end +end + +function blob8 = sof_sound_dose_build_blob(param_values, blob_param_id) + + blob_type = 0; + data_length = length(param_values); + data_size = 2 * 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+1) = short2byte(int16(param_values(i))); + j=j+2; + end +end + +function bytes = short2byte(word) + sh = [0 -8]; + bytes = uint8(zeros(1,2)); + bytes(1) = bitand(bitshift(word, sh(1)), 255); + bytes(2) = bitand(bitshift(word, sh(2)), 255); +end diff --git a/src/audio/sound_dose/tune/sof_sound_dose_ref.m b/src/audio/sound_dose/tune/sof_sound_dose_ref.m new file mode 100644 index 000000000000..dc15ab9f3567 --- /dev/null +++ b/src/audio/sound_dose/tune/sof_sound_dose_ref.m @@ -0,0 +1,69 @@ +% Compute MEL every 1s +tc = 1.0; + +% Load sound clip +sound_clip = "/usr/share/sounds/alsa/Front_Right.wav"; +[x1, fs] = audioread(sound_clip); +if size(x1, 2) == 1 + x1 = repmat(x1, 1, 2); +end +sx = size(x1); +num_frames = sx(1); +num_channels = sx(2); + +% load A-weight filters +load sof_sound_dose_time_domain_filters.mat + +% Filter with IIR, FIR +x2 = filter(b_iir, a_iir, x1); +x3 = filter(b_fir, 1, x2); + +%figure +%plot(x1) + +%figure +%plot(x3) + +% compute RMS level in 1s chunks +np = 1; +nc = tc * fs; +num_chunks = floor(num_frames/nc); +l_dbfs_all = zeros(num_chunks, num_channels); +mel_all = zeros(num_chunks, 1); + +dbfs_offs = 3.01; +dbfs_offs_weight_filters = 3; +i1 = 1; +for n = 1:num_chunks + sum = 0; + i2 = i1 + nc - 1; + for ch = 1:num_channels + y = x3(i1:i2, ch); + sum = sum + mean(y.^2); + l_dbfs = 20*log10(sqrt(mean(y.^2))) + dbfs_offs + dbfs_offs_weight_filters; + l_dbfs_all(np, ch) = l_dbfs; + end + mel = 10*log10(sum) + dbfs_offs_weight_filters; + if num_channels > 1 + mel = mel - 1.5 * num_channels; + end + mel_all(np) = mel; + i1 = i1 + nc; + np = np + 1; +end + +figure(1) +plot(l_dbfs_all) +grid on +xlabel('Time (s)'); +ylabel('Level (dBSFS)'); + +figure(2) +plot(mel_all) +grid on +xlabel('Time (s)'); +ylabel('MEL (dB)'); + +mel_all + +mel_all_rnd = round(mel_all) diff --git a/src/audio/sound_dose/tune/sof_sound_dose_time_domain_filters.m b/src/audio/sound_dose/tune/sof_sound_dose_time_domain_filters.m new file mode 100644 index 000000000000..26e2b2e00fa5 --- /dev/null +++ b/src/audio/sound_dose/tune/sof_sound_dose_time_domain_filters.m @@ -0,0 +1,163 @@ +% Export time domain IIR and FIR filter to apply A-weight for sound dose. +% +% Usage: +% sof_sound_dose_time_domain_filters() + +% SPDX-License-Identifier: BSD-3-Clause +% +% Copyright (c) 2025, Intel Corporation. + +function sof_sound_dose_time_domain_filters(fs) + + if exist('OCTAVE_VERSION', 'builtin') + pkg load signal; + end + + sof_sound_dose_time_domain_filters_for_rate(48e3); + sof_sound_dose_time_domain_filters_for_rate(44.1e3); + +end + +function sof_sound_dose_time_domain_filters_for_rate(fs) + + sof_sound_dose = '../../sound_dose'; + sof_ctl = '../../../../tools/ctl'; + sound_dose_paths(true); + + + fs_str = sprintf('%dk', round(fs/1000)); + fir_str = sprintf('sound_dose_fir_%s', fs_str); + iir_str = sprintf('sound_dose_iir_%s', fs_str); + prm.fir_coef_fn = fullfile(sof_sound_dose, [fir_str '.h']); + prm.iir_coef_fn = fullfile(sof_sound_dose, [iir_str '.h']); + prm.fir_blob_fn = fullfile(sof_ctl, ['ipc4/eq_fir/' fir_str '.txt']); + prm.iir_blob_fn = fullfile(sof_ctl, ['ipc4/eq_iir/' iir_str '.txt']); + prm.fir_blob_vn = fir_str; + prm.iir_blob_vn = iir_str; + prm.ipc_ver = 4; + eq = sound_dose_filters(fs); + export_filters(eq, prm); + + sound_dose_paths(false); +end + +function export_filters(eq, prm) + + b_fir = 1; + b_iir = 1; + a_iir = 1; + howto = 'cd src/audio/sound_dose/tune; octave --quiet --no-window-system sof_sound_dose_time_domain_filters.m'; + + % Export FIR + if eq.enable_fir + bq = sof_eq_fir_blob_quant(eq.b_fir); + channels_in_config = 2; % Setup max 2 channels EQ + assign_response = [0 0]; % Same response for L and R + num_responses = 1; % One response + bm = sof_eq_fir_blob_merge(channels_in_config, ... + num_responses, ... + assign_response, ... + bq); + bp = sof_eq_fir_blob_pack(bm, prm.ipc_ver); + sof_export_c_eq_uint32t(prm.fir_coef_fn, bp, prm.fir_blob_vn, 0, howto); + sof_alsactl_write(prm.fir_blob_fn, bp); + b_fir = eq.b_fir; + end + + % Export IIR + if eq.enable_iir + coefq = sof_eq_iir_blob_quant(eq.p_z, eq.p_p, eq.p_k); + channels_in_config = 2; % Setup max 2 channels EQ + assign_response = [0 0]; % Same response for L and R + num_responses = 1; % One response + coefm = sof_eq_iir_blob_merge(channels_in_config, ... + num_responses, ... + assign_response, ... + coefq); + coefp = sof_eq_iir_blob_pack(coefm, prm.ipc_ver); + sof_export_c_eq_uint32t(prm.iir_coef_fn, coefp, prm.iir_blob_vn, 0, howto); + sof_alsactl_write(prm.iir_blob_fn, coefp); + [b_iir, a_iir] = zp2tf( eq.p_z, eq.p_p, eq.p_k); + end + + % Export mat file + save sof_sound_dose_time_domain_filters.mat b_fir b_iir a_iir; + +end + +function eq = sound_dose_filters(fs) + + np = 200; + f_hi = 0.95 * fs/2; + fv = logspace(log10(10), log10(f_hi), np); + ref_a_weight = func_a_weight_db(fv); + + eq = sof_eq_defaults(); + eq.fs = fs; + eq.enable_fir = 0; + eq.enable_iir = 1; + eq.iir_norm_type = '1k'; % At 1 kHz -3 dB + eq.iir_norm_offs_db = -3; + eq.fir_norm_type = '1k'; % At 1 kHz 0 dB, total -3 dB gain to avoid clip + eq.fir_norm_offs_db = 0; + eq.p_fmin = 10; + eq.p_fmax = 30e3; + + eq.fir_minph = 1; + eq.fir_beta = 4; + eq.fir_length = 31; + eq.fir_autoband = 0; + eq.fmin_fir = 300; + eq.fmax_fir = f_hi; + + eq.raw_f = fv; + eq.raw_m_db = zeros(1, length(fv)); + eq.target_f = fv; + eq.target_m_db = ref_a_weight; + + eq.peq = [ ... + eq.PEQ_HP1 12 0 0; ... + eq.PEQ_HP1 20 0 0; ... + eq.PEQ_HP2 280 0 0; ... + eq.PEQ_PN2 245 -5.45 0.5; ... + eq.PEQ_HS1 13000 -3 0; ... + eq.PEQ_HS1 14000 -3 0; ... + ]; + + eq = sof_eq_compute(eq); + sof_eq_plot(eq, 1); + figure(3); + axis([10 20e3 -60 10]); + + +end + +% See https://en.wikipedia.org/wiki/A-weighting +% IEC 61672-1:2013 Electroacoustics - Sound level meters, +% Part 1: Specifications. IEC. 2013. + +function a_weight = func_a_weight_db(fv) + a_weight = 20*log10(func_ra(fv)) - 20*log10(func_ra(1000)); +end + +function y = func_ra(f) + f2 = f.^2; + f4 = f.^4; + c1 = 12194^2; + c2 = 20.6^2; + c3 = 107.7^2; + c4 = 737.9^2; + c5 = 12194^2; + y = c1 * f4 ./ ((f2 + c2).*sqrt((f2 + c3).*(f2 + c4).*(f2 + c5))); +end + +function sound_dose_paths(enable) + switch enable + case true + addpath('../../eq_iir/tune'); + addpath('../../../../tools/tune/common'); + case false + rmpath('../../eq_iir/tune'); + rmpath('../../../../tools/tune/common'); + end +end diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index 4b360ab4eabe..e0644ef493cb 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -919,6 +919,7 @@ void sys_comp_module_copier_interface_init(void); void sys_comp_module_crossover_interface_init(void); void sys_comp_module_dcblock_interface_init(void); void sys_comp_module_demux_interface_init(void); +void sys_comp_module_dolby_dax_audio_processing_interface_init(void); void sys_comp_module_drc_interface_init(void); void sys_comp_module_dts_interface_init(void); void sys_comp_module_eq_fir_interface_init(void); @@ -937,6 +938,7 @@ void sys_comp_module_mux_interface_init(void); void sys_comp_module_nxp_eap_interface_init(void); void sys_comp_module_rtnr_interface_init(void); void sys_comp_module_selector_interface_init(void); +void sys_comp_module_sound_dose_interface_init(void); void sys_comp_module_src_interface_init(void); void sys_comp_module_src_lite_interface_init(void); void sys_comp_module_tdfb_interface_init(void); diff --git a/src/include/user/audio_feature.h b/src/include/user/audio_feature.h new file mode 100644 index 000000000000..8e7791c0f3db --- /dev/null +++ b/src/include/user/audio_feature.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2025 Intel Corporation. + * + */ + +#ifndef __USER_AUDIO_FEATURE_H__ +#define __USER_AUDIO_FEATURE_H__ + +/** \brief Audio feature data types. */ +enum sof_audio_feature_type { + SOF_AUDIO_FEATURE_MFCC, /**< For Mel Frequency Cepstral Coefficients */ + SOF_AUDIO_FEATURE_SOUND_DOSE_MEL, /**< For Sound Dose MEL (loudness) values */ +}; + +/** \brief Header for audio features data. */ +struct sof_audio_feature { + uint64_t stream_time_us; /**< Timestamp, relative time in microseconds */ + enum sof_audio_feature_type type; /**< Type of audio feature, as above*/ + uint32_t num_audio_features; /**< Number of audio feature structs in data */ + size_t data_size; /**< Size of data without this header */ + uint32_t reserved[4]; /**< Reserved for future use */ + int32_t data[]; /**< Start of data */ +} __attribute__((packed)); + +#endif /* __USER_AUDIO_FEATURE_H__ */ diff --git a/src/include/user/sound_dose.h b/src/include/user/sound_dose.h new file mode 100644 index 000000000000..a2479eb79951 --- /dev/null +++ b/src/include/user/sound_dose.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2025 Intel Corporation. + * + */ + +#include +#include +#include + +#ifndef __USER_SOUND_DOSE_H__ +#define __USER_SOUND_DOSE_H__ + +#define SOF_SOUND_DOSE_SETUP_PARAM_ID 0 +#define SOF_SOUND_DOSE_VOLUME_PARAM_ID 1 +#define SOF_SOUND_DOSE_GAIN_PARAM_ID 2 +#define SOF_SOUND_DOSE_PAYLOAD_PARAM_ID 3 + +#define SOF_SOUND_DOSE_SENS_MIN_DB (-10 * 100) /* -10 to +130 dB */ +#define SOF_SOUND_DOSE_SENS_MAX_DB (130 * 100) +#define SOF_SOUND_DOSE_VOLUME_MIN_DB (-100 * 100) /* -100 to +40 dB */ +#define SOF_SOUND_DOSE_VOLUME_MAX_DB (40 * 100) +#define SOF_SOUND_DOSE_GAIN_MIN_DB (-100 * 100) /* -100 to 0 dB */ +#define SOF_SOUND_DOSE_GAIN_MAX_DB (0 * 100) + +struct sof_sound_dose { + int16_t mel_value; /* Decibels x100, e.g. 85 dB is 8500 */ + int16_t dbfs_value; /* Decibels x100 */ + int16_t current_sens_dbfs_dbspl; /* Decibels x100 */ + int16_t current_volume_offset; /* Decibels x100 */ + int16_t current_gain; /* Decibels x100 */ + uint16_t reserved16; /**< reserved for future use */ + uint32_t reserved32[4]; /**< reserved for future use */ +} __attribute__((packed)); + +struct sound_dose_setup_config { + int16_t sens_dbfs_dbspl; /* Decibels x100 */ + int16_t reserved; +} __attribute__((packed)); + +struct sound_dose_volume_config { + int16_t volume_offset; /* Decibels x100 */ + int16_t reserved; +} __attribute__((packed)); + +struct sound_dose_gain_config { + int16_t gain; /* Decibels x100 */ + int16_t reserved; +} __attribute__((packed)); + +#endif /* __USER_SOUND_DOSE_H__ */ diff --git a/src/library_manager/lib_manager.c b/src/library_manager/lib_manager.c index d45df1493f5e..8cd5c5d38a1c 100644 --- a/src/library_manager/lib_manager.c +++ b/src/library_manager/lib_manager.c @@ -937,6 +937,9 @@ static int lib_manager_store_library(struct lib_manager_dma_ext *dma_ext, return ret; } + /* Writeback entire library to ensure it's visible to other cores */ + dcache_writeback_region((__sparse_force void *)library_base_address, preload_size); + #if CONFIG_LIBRARY_AUTH_SUPPORT /* AUTH_PHASE_LAST - do final library authentication checks */ ret = lib_manager_auth_proc((__sparse_force void *)library_base_address, diff --git a/src/library_manager/llext_manager_dram.c b/src/library_manager/llext_manager_dram.c index 9c134a3fcd81..25c00f47b4e1 100644 --- a/src/library_manager/llext_manager_dram.c +++ b/src/library_manager/llext_manager_dram.c @@ -23,6 +23,7 @@ struct lib_manager_dram_storage { struct llext_elf_sect_map *sect; struct llext_symbol *sym; unsigned int n_llext; + unsigned int n_mod; }; /* @@ -147,6 +148,8 @@ int llext_manager_store_to_dram(void) } lib_manager_dram.n_llext = n_llext; + lib_manager_dram.n_mod = n_mod; + /* Make sure, that the data is actually in the DRAM, not just in data cache */ dcache_writeback_region((__sparse_force void __sparse_cache *)&lib_manager_dram, sizeof(lib_manager_dram)); @@ -162,26 +165,33 @@ int llext_manager_restore_from_dram(void) struct ext_library *_ext_lib = ext_lib_get(); unsigned int i, j, k, n_mod, n_llext, n_sect, n_sym; + struct llext_loader **ldr; + struct llext **llext; - if (!lib_manager_dram.n_llext || !lib_manager_dram.ctx) { + if (!lib_manager_dram.n_mod || !lib_manager_dram.ctx) { tr_dbg(&lib_manager_tr, "No modules saved"); dcache_writeback_region((__sparse_force void __sparse_cache *)&lib_manager_dram, sizeof(lib_manager_dram)); return 0; } - /* arrays of pointers for llext_restore() */ - void **ptr_array = rmalloc(SOF_MEM_FLAG_KERNEL, - sizeof(*ptr_array) * lib_manager_dram.n_llext * 2); - - if (!ptr_array) - return -ENOMEM; - - struct llext_loader **ldr = (struct llext_loader **)ptr_array; - struct llext **llext = (struct llext **)(ptr_array + lib_manager_dram.n_llext); - *_ext_lib = lib_manager_dram.ext_lib; + if (lib_manager_dram.n_llext) { + /* arrays of pointers for llext_restore() */ + void **ptr_array = rmalloc(SOF_MEM_FLAG_KERNEL, + sizeof(*ptr_array) * lib_manager_dram.n_llext * 2); + + if (!ptr_array) + return -ENOMEM; + + ldr = (struct llext_loader **)ptr_array; + llext = (struct llext **)(ptr_array + lib_manager_dram.n_llext); + } else { + ldr = NULL; + llext = NULL; + } + /* The external loop walks all the libraries */ for (i = 0, j = 0, n_mod = 0, n_llext = 0, n_sect = 0, n_sym = 0; i < ARRAY_SIZE(_ext_lib->desc); i++) { @@ -262,26 +272,28 @@ int llext_manager_restore_from_dram(void) _ext_lib->desc[i] = ctx; } - /* Let Zephyr restore extensions and its own internal bookkeeping */ - int ret = llext_restore(llext, ldr, lib_manager_dram.n_llext); + if (lib_manager_dram.n_llext) { + /* Let Zephyr restore extensions and its own internal bookkeeping */ + int ret = llext_restore(llext, ldr, lib_manager_dram.n_llext); - if (ret < 0) { - tr_err(&lib_manager_tr, "Zephyr failed to restore: %d", ret); - goto nomem; - } + if (ret < 0) { + tr_err(&lib_manager_tr, "Zephyr failed to restore: %d", ret); + goto nomem; + } - /* Rewrite to correct LLEXT pointers, created by Zephyr */ - for (i = 0, n_llext = 0; i < ARRAY_SIZE(_ext_lib->desc); i++) { - struct lib_manager_mod_ctx *ctx = _ext_lib->desc[i]; + /* Rewrite to correct LLEXT pointers, created by Zephyr */ + for (i = 0, n_llext = 0; i < ARRAY_SIZE(_ext_lib->desc); i++) { + struct lib_manager_mod_ctx *ctx = _ext_lib->desc[i]; - if (!ctx) - continue; + if (!ctx) + continue; - struct lib_manager_module *mod = ctx->mod; + struct lib_manager_module *mod = ctx->mod; - for (k = 0; k < ctx->n_mod; k++) { - if (mod[k].llext) - mod[k].llext = llext[n_llext++]; + for (k = 0; k < ctx->n_mod; k++) { + if (mod[k].llext) + mod[k].llext = llext[n_llext++]; + } } } diff --git a/third_party/include/dax_inf.h b/third_party/include/dax_inf.h new file mode 100644 index 000000000000..54464f7590b6 --- /dev/null +++ b/third_party/include/dax_inf.h @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE + * + * Copyright(c) 2025 Dolby Laboratories. All rights reserved. + * + * Author: Jun Lai + */ + +#ifndef DAX_INF_H +#define DAX_INF_H + +#include +#include +#include + +enum dax_frame_fmt { + DAX_FMT_UNSUPPORTED = -1, + DAX_FMT_SHORT_16 = 4, + DAX_FMT_INT = 5, + DAX_FMT_FLOAT = 7, +}; + +enum dax_sample_rate { + DAX_RATE_UNSUPPORTED = -1, +}; + +enum dax_channels { + DAX_CHANNLES_UNSUPPORTED = -1, +}; + +enum dax_buffer_fmt { + DAX_BUFFER_LAYOUT_UNSUPPORTED = -1, + DAX_BUFFER_LAYOUT_INTERLEAVED, + DAX_BUFFER_LAYOUT_NONINTERLEAVED, +}; + +enum dax_param_id { + DAX_PARAM_ID_ENABLE = 0x08001026, + DAX_PARAM_ID_TUNING_FILE = 0x08001027, + DAX_PARAM_ID_PROFILE = 0x08001028, + DAX_PARAM_ID_ENDPOINT = 0x08001029, + DAX_PARAM_ID_TUNING_DEVICE = 0x08001030, + DAX_PARAM_ID_CP_ENABLE = 0x08001031, + DAX_PARAM_ID_OUT_DEVICE = 0x08001032, + DAX_PARAM_ID_ABSOLUTE_VOLUME = 0x08001033, + DAX_PARAM_ID_CTC_ENABLE = 0x08001034, +}; + +struct dax_media_fmt { + enum dax_frame_fmt data_format; + uint32_t sampling_rate; + uint32_t num_channels; + enum dax_buffer_fmt layout; + uint32_t bytes_per_sample; +}; + +struct dax_buffer { + void *addr; + uint32_t size; /* Total buffer size in bytes */ + uint32_t avail; /* Available bytes for reading */ + uint32_t free; /* Free bytes for writing */ +}; + +struct sof_dax { + /* SOF module parameters */ + uint32_t sof_period_bytes; + + /* DAX state parameters */ + uint32_t period_bytes; + uint32_t period_us; + int32_t endpoint; + int32_t tuning_device; + void *blob_handler; + void *p_dax; + struct dax_media_fmt input_media_format; + struct dax_media_fmt output_media_format; + + /* DAX control parameters */ + int32_t enable; + int32_t profile; + int32_t out_device; + int32_t ctc_enable; + int32_t content_processing_enable; + int32_t volume; + uint32_t update_flags; + + /* DAX buffers */ + struct dax_buffer persist_buffer; /* Used for dax instance */ + struct dax_buffer scratch_buffer; /* Used for dax process */ + struct dax_buffer input_buffer; + struct dax_buffer output_buffer; + struct dax_buffer tuning_file_buffer; +}; + +/** + * @brief Query the persistent memory requirements for the DAX module + * + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return Size of required persistent memory in bytes + */ +uint32_t dax_query_persist_memory(struct sof_dax *dax_ctx); + +/** + * @brief Query the scratch memory requirements for the DAX module + * + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return Size of required scratch memory in bytes + */ +uint32_t dax_query_scratch_memory(struct sof_dax *dax_ctx); + +/** + * @brief Query the number of frames in a processing period + * + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return Number of frames per period + */ +uint32_t dax_query_period_frames(struct sof_dax *dax_ctx); + +/** + * @brief Free the DAX module + * + * @param[in] dax_ctx Pointer to the DAX context structure + * + * This function free all resources built on persistent buffer. + * DO NOT USE THE INSTANCE AFTER CALLING FREE. + * + * @return 0 on success, negative error code on failure + */ +int dax_free(struct sof_dax *dax_ctx); + +/** + * @brief Initialize the DAX module + * + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return 0 on success, negative error code on failure + */ +int dax_init(struct sof_dax *dax_ctx); + +/** + * @brief Process audio data through the DAX module + * + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return Bytes of processed. negative error code on failure + */ +int dax_process(struct sof_dax *dax_ctx); + +/** + * @brief Set a parameter value for the DAX module + * + * @param[in] id Parameter identifier + * @param[in] val Pointer to parameter value + * @param[in] val_sz Size of parameter value in bytes + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return 0 on success, negative error code on failure + */ +int dax_set_param(uint32_t id, const void *val, uint32_t val_sz, struct sof_dax *dax_ctx); + +/** + * @brief Enable/Disable the DAX module + * + * @param[in] enable 0:disable, 1:enable. + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return 0 on success, negative error code on failure + */ +int dax_set_enable(int32_t enable, struct sof_dax *dax_ctx); + +/** + * @brief Set the volume for the DAX module + * + * @param[in] volume Value to apply + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return 0 or positive code on success, negative error code on failure + */ +int dax_set_volume(int32_t volume, struct sof_dax *dax_ctx); + +/** + * @brief Update the output device configuration + * + * @param[in] out_device Output device identifier. Supported devices: + * 0: speaker + * 1: headphone + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return 0 on success, negative error code on failure + */ +int dax_set_device(int32_t out_device, struct sof_dax *dax_ctx); + +/** + * @brief Enable/Disable crosstalk cancellation feature + * + * @param[in] enable 0:disable, 1:enable. + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return 0 on success, negative error code on failure + */ +int dax_set_ctc_enable(int32_t enable, struct sof_dax *dax_ctx); + +/** + * @brief Get the DAX module version string + * + * @return Pointer to null-terminated version string + */ +const char *dax_get_version(void); + +/** + * @brief Find parameters in a buffer based on query criteria + * + * @param[in] query_id ID of the parameter to search for. Supported query IDs: + * - DAX_PARAM_ID_PROFILE + * - DAX_PARAM_ID_TUNING_DEVICE + * - DAX_PARAM_ID_CP_ENABLE + * @param[in] query_val Value to match when searching + * @param[out] query_sz Pointer to store the size of the found parameters + * @param[in] dax_ctx Pointer to the DAX context structure + * + * @return Pointer to the found parameters, or NULL if not found + */ +void *dax_find_params(uint32_t query_id, + int32_t query_val, + uint32_t *query_sz, + struct sof_dax *dax_ctx); + +#endif /* DAX_INF_H */ diff --git a/tools/rimage/config/lnl.toml.h b/tools/rimage/config/lnl.toml.h index 0667548e1773..5fc7008bfbe8 100644 --- a/tools/rimage/config/lnl.toml.h +++ b/tools/rimage/config/lnl.toml.h @@ -146,5 +146,9 @@ #include