From 91d324bac714494e98f6745e9005fde666124e27 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Fri, 13 Jun 2025 13:46:18 +0300 Subject: [PATCH 1/6] Audio: EQIIR: Tune: Add missing include to generated C header The exported header file is missing the include for stdint.h for the used uint32_t type. Without it some builds fail to warning. Also the copyright text is updated. This patch also adds static const to the array declaration. Signed-off-by: Seppo Ingalsuo --- src/audio/eq_iir/tune/sof_export_c_eq_uint32t.m | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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..be7d8c6de040 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 @@ -11,7 +11,7 @@ function sof_export_c_eq_uint32t(fn, blob8, vn, justeq) % 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,9 +23,10 @@ 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'); + fprintf(fh, '#include \n\n'); % Pad blob length to multiple of four bytes n_orig = length(blob8); @@ -53,7 +54,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 From fdea2f707e4d2225225768f94e16098352984e92 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Wed, 11 Jun 2025 21:34:07 +0300 Subject: [PATCH 2/6] Audio: EQIIR: Tune: Drop TLV header from exported C header Need to drop the TLV header (used for Linux kernel bytes control) from data to keep the existing format after the header was added to sof-ctl generated data header. This header is not passed to firmware, only the kernel that this usage of filter coefficients passes, is using it. Signed-off-by: Seppo Ingalsuo --- src/audio/eq_iir/tune/sof_export_c_eq_uint32t.m | 3 +++ 1 file changed, 3 insertions(+) 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 be7d8c6de040..a52c4caee43d 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 @@ -28,6 +28,9 @@ function sof_export_c_eq_uint32t(fn, blob8, vn, justeq) fprintf(fh, '\n'); 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); From f82fac301f76ed07e7609001d40a43730078d7e6 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Mon, 9 Jun 2025 20:58:44 +0300 Subject: [PATCH 3/6] Audio: Sound Dose: Add Octave script to export A-weight filter This patch adds script sof_sound_dose_time_domain_filters.m that exports IIR and FIR coefficients to approximate A-weight function. The current choice is IIR only for lower MCPS load. The script sof_sound_dose_blobs.m creates a few control blobs to test the sound_dose component. A simple script sof_sound_dose_ref.m to compute dBFS and MEL for a wav file is added to compare with firmware reported values. Signed-off-by: Seppo Ingalsuo --- .../eq_iir/tune/sof_export_c_eq_uint32t.m | 9 +- .../sound_dose/tune/sof_sound_dose_blobs.m | 95 ++++++++++ .../sound_dose/tune/sof_sound_dose_ref.m | 69 ++++++++ .../tune/sof_sound_dose_time_domain_filters.m | 163 ++++++++++++++++++ 4 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 src/audio/sound_dose/tune/sof_sound_dose_blobs.m create mode 100644 src/audio/sound_dose/tune/sof_sound_dose_ref.m create mode 100644 src/audio/sound_dose/tune/sof_sound_dose_time_domain_filters.m 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 a52c4caee43d..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,6 +8,7 @@ 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 % @@ -26,6 +27,12 @@ function sof_export_c_eq_uint32t(fn, blob8, vn, justeq) 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 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 From 732105c8bee53f774fde84e6f86e4b36a7e4cabf Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Mon, 16 Jun 2025 17:49:32 +0300 Subject: [PATCH 4/6] Audio: EQIIR: Tune: Add plot for equalizer response error The added plot is useful especially with parametric IIR equalizer tuning to see achieved response error vs. target. A numerical mean(abs()) value of error is printed to help see filter parameters change impact. Signed-off-by: Seppo Ingalsuo --- src/audio/eq_iir/tune/sof_eq_plot.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 From cb2c0c7516edbc22f994192e511879f69729ab7b Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Wed, 11 Jun 2025 21:39:43 +0300 Subject: [PATCH 5/6] Tools: Topology: Add test topology for sound dose component The build topologies are - sof-hda-benchmark-sound_dose32.tplg - sof-mtl-sdw-benchmark-sound_dose32-sdw0.tplg - sof-mtl-sdw-benchmark-sound_dose32-simplejack.tplg The controls are for example initialized to - sensitivity 100 dB, 0 dBFS equals 100 dBSPL (a worst case loudness) - volume 0 dB, assumes codec gain for headphones is set to max - gain 0 dB, user's music playback is not attenuated Signed-off-by: Seppo Ingalsuo --- .../topology2/cavs-benchmark-hda.conf | 17 +++++ .../topology2/cavs-benchmark-sdw.conf | 17 +++++ .../development/tplg-targets-bench.cmake | 2 + .../bench/sound_dose_controls_capture.conf | 33 +++++++++ .../bench/sound_dose_controls_playback.conf | 33 +++++++++ .../include/bench/sound_dose_route.conf | 19 ++++++ .../include/bench/sound_dose_s16.conf | 13 ++++ .../include/bench/sound_dose_s24.conf | 13 ++++ .../include/bench/sound_dose_s32.conf | 13 ++++ .../include/components/sound_dose.conf | 67 +++++++++++++++++++ .../sound_dose/setup_data_init.conf | 9 +++ .../components/sound_dose/setup_gain_0db.conf | 10 +++ .../components/sound_dose/setup_sens_0db.conf | 10 +++ .../sound_dose/setup_sens_100db.conf | 10 +++ .../components/sound_dose/setup_vol_0db.conf | 10 +++ 15 files changed, 276 insertions(+) create mode 100644 tools/topology/topology2/include/bench/sound_dose_controls_capture.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_controls_playback.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_route.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_s16.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_s24.conf create mode 100644 tools/topology/topology2/include/bench/sound_dose_s32.conf create mode 100644 tools/topology/topology2/include/components/sound_dose.conf create mode 100644 tools/topology/topology2/include/components/sound_dose/setup_data_init.conf create mode 100644 tools/topology/topology2/include/components/sound_dose/setup_gain_0db.conf create mode 100644 tools/topology/topology2/include/components/sound_dose/setup_sens_0db.conf create mode 100644 tools/topology/topology2/include/components/sound_dose/setup_sens_100db.conf create mode 100644 tools/topology/topology2/include/components/sound_dose/setup_vol_0db.conf diff --git a/tools/topology/topology2/cavs-benchmark-hda.conf b/tools/topology/topology2/cavs-benchmark-hda.conf index cefcddd0ec59..49e610ed1db9 100644 --- a/tools/topology/topology2/cavs-benchmark-hda.conf +++ b/tools/topology/topology2/cavs-benchmark-hda.conf @@ -44,6 +44,7 @@ + @@ -834,6 +835,22 @@ IncludeByKey.BENCH_CONFIG { } + # + # Sound Dose component + # + + "sound_dose16" { + + } + + "sound_dose24" { + + } + + "sound_dose32" { + + } + # # SRC component # diff --git a/tools/topology/topology2/cavs-benchmark-sdw.conf b/tools/topology/topology2/cavs-benchmark-sdw.conf index e8d8e6d07584..900d897eb7a4 100644 --- a/tools/topology/topology2/cavs-benchmark-sdw.conf +++ b/tools/topology/topology2/cavs-benchmark-sdw.conf @@ -42,6 +42,7 @@ + @@ -474,6 +475,22 @@ IncludeByKey.BENCH_CONFIG { } + # + # Sound Dose component + # + + "sound_dose16" { + + } + + "sound_dose24" { + + } + + "sound_dose32" { + + } + # # SRC component # diff --git a/tools/topology/topology2/development/tplg-targets-bench.cmake b/tools/topology/topology2/development/tplg-targets-bench.cmake index a9b11736e2fd..4508fbb47d8f 100644 --- a/tools/topology/topology2/development/tplg-targets-bench.cmake +++ b/tools/topology/topology2/development/tplg-targets-bench.cmake @@ -19,6 +19,7 @@ set(components "level_multiplier" "micsel" "rtnr" + "sound_dose" "src" "src_lite" "tdfb" @@ -37,6 +38,7 @@ set(component_parameters "BENCH_LEVEL_MULTIPLIER_PARAMS=default" "BENCH_MICSEL_PARAMS=passthrough" "BENCH_RTNR_PARAMS=default" + "BENCH_SOUND_DOSE_PARAMS=default" "BENCH_SRC_PARAMS=default" "BENCH_SRC_LITE_PARAMS=default" "BENCH_TDFB_PARAMS=default" diff --git a/tools/topology/topology2/include/bench/sound_dose_controls_capture.conf b/tools/topology/topology2/include/bench/sound_dose_controls_capture.conf new file mode 100644 index 000000000000..38cbbc70b615 --- /dev/null +++ b/tools/topology/topology2/include/bench/sound_dose_controls_capture.conf @@ -0,0 +1,33 @@ + # Created initially with script "./bench_comp_generate.sh sound_dose" + # may need edits to modify controls + Object.Control { + # Un-comment the supported controls in SOUND_DOSE + bytes."1" { + name '$ANALOG_CAPTURE_PCM Sound Dose setup bytes' + max 44 + IncludeByKey.BENCH_SOUND_DOSE_PARAMS { + "default" "include/components/sound_dose/setup_sens_100db.conf" + } + } + bytes."2" { + name '$ANALOG_CAPTURE_PCM Sound Dose volume bytes' + max 44 + + } + bytes."3" { + name '$ANALOG_CAPTURE_PCM Sound Dose gain bytes' + max 44 + + } + bytes."4" { + name '$ANALOG_CAPTURE_PCM Sound Dose data bytes' + max 256 + + } + #mixer."1" { + # name '$ANALOG_CAPTURE_PCM SOUND_DOSE switch or volume' + #} + #enum."1" { + # name '$ANALOG_CAPTURE_PCM SOUND_DOSE enum' + #} + } diff --git a/tools/topology/topology2/include/bench/sound_dose_controls_playback.conf b/tools/topology/topology2/include/bench/sound_dose_controls_playback.conf new file mode 100644 index 000000000000..e0d83ffc0c2b --- /dev/null +++ b/tools/topology/topology2/include/bench/sound_dose_controls_playback.conf @@ -0,0 +1,33 @@ + # Created initially with script "./bench_comp_generate.sh sound_dose" + # may need edits to modify controls + Object.Control { + # Un-comment the supported controls in SOUND_DOSE + bytes."1" { + name '$ANALOG_PLAYBACK_PCM Sound Dose setup bytes' + max 44 + IncludeByKey.BENCH_SOUND_DOSE_PARAMS { + "default" "include/components/sound_dose/setup_sens_100db.conf" + } + } + bytes."2" { + name '$ANALOG_PLAYBACK_PCM Sound Dose volume bytes' + max 44 + + } + bytes."3" { + name '$ANALOG_PLAYBACK_PCM Sound Dose gain bytes' + max 44 + + } + bytes."4" { + name '$ANALOG_PLAYBACK_PCM Sound Dose data bytes' + max 256 + + } + #mixer."1" { + # name '$ANALOG_PLAYBACK_PCM SOUND_DOSE switch or volume' + #} + #enum."1" { + # name '$ANALOG_PLAYBACK_PCM SOUND_DOSE enum' + #} + } diff --git a/tools/topology/topology2/include/bench/sound_dose_route.conf b/tools/topology/topology2/include/bench/sound_dose_route.conf new file mode 100644 index 000000000000..20f4b164e14d --- /dev/null +++ b/tools/topology/topology2/include/bench/sound_dose_route.conf @@ -0,0 +1,19 @@ + # Created with script "./bench_comp_generate.sh sound_dose" + Object.Base.route [ + { + sink '$BENCH_PLAYBACK_DAI_COPIER' + source 'sound_dose.$BENCH_PLAYBACK_HOST_PIPELINE.1' + } + { + sink 'sound_dose.$BENCH_PLAYBACK_HOST_PIPELINE.1' + source 'host-copier.0.playback' + } + { + source '$BENCH_CAPTURE_DAI_COPIER' + sink 'sound_dose.$BENCH_CAPTURE_HOST_PIPELINE.2' + } + { + source 'sound_dose.$BENCH_CAPTURE_HOST_PIPELINE.2' + sink 'host-copier.0.capture' + } + ] diff --git a/tools/topology/topology2/include/bench/sound_dose_s16.conf b/tools/topology/topology2/include/bench/sound_dose_s16.conf new file mode 100644 index 000000000000..3aec94858e08 --- /dev/null +++ b/tools/topology/topology2/include/bench/sound_dose_s16.conf @@ -0,0 +1,13 @@ + # Created with script "./bench_comp_generate.sh sound_dose" + Object.Widget.sound_dose.1 { + index $BENCH_PLAYBACK_HOST_PIPELINE + + + } + Object.Widget.sound_dose.2 { + index $BENCH_CAPTURE_HOST_PIPELINE + + + } + + diff --git a/tools/topology/topology2/include/bench/sound_dose_s24.conf b/tools/topology/topology2/include/bench/sound_dose_s24.conf new file mode 100644 index 000000000000..fe59e8e2fb4c --- /dev/null +++ b/tools/topology/topology2/include/bench/sound_dose_s24.conf @@ -0,0 +1,13 @@ + # Created with script "./bench_comp_generate.sh sound_dose" + Object.Widget.sound_dose.1 { + index $BENCH_PLAYBACK_HOST_PIPELINE + + + } + Object.Widget.sound_dose.2 { + index $BENCH_CAPTURE_HOST_PIPELINE + + + } + + diff --git a/tools/topology/topology2/include/bench/sound_dose_s32.conf b/tools/topology/topology2/include/bench/sound_dose_s32.conf new file mode 100644 index 000000000000..5e3146a8076f --- /dev/null +++ b/tools/topology/topology2/include/bench/sound_dose_s32.conf @@ -0,0 +1,13 @@ + # Created with script "./bench_comp_generate.sh sound_dose" + Object.Widget.sound_dose.1 { + index $BENCH_PLAYBACK_HOST_PIPELINE + + + } + Object.Widget.sound_dose.2 { + index $BENCH_CAPTURE_HOST_PIPELINE + + + } + + diff --git a/tools/topology/topology2/include/components/sound_dose.conf b/tools/topology/topology2/include/components/sound_dose.conf new file mode 100644 index 000000000000..264da305e35d --- /dev/null +++ b/tools/topology/topology2/include/components/sound_dose.conf @@ -0,0 +1,67 @@ +# +# +# A Sound Dose component. All attributes defined herein are namespaced +# by alsatplg to "Object.Widget.sound_dose.attribute_name" +# +# Usage: this component can be used by declaring in the parent object. i.e. +# +# Object.Widget.sound_dose."N" { +# index 1 +# } +# } + +# +# Where M is pipeline ID and N is a unique integer in the parent object. + +Class.Widget."sound_dose" { + # + # Pipeline ID + # + DefineAttribute."index" { + type "integer" + } + + # + # Unique instance for Sound Dose widget + # + DefineAttribute."instance" { + type "integer" + } + + # Include common widget attributes definition + + + attributes { + !constructor [ + "index" + "instance" + ] + !mandatory [ + "num_input_pins" + "num_output_pins" + "num_input_audio_formats" + "num_output_audio_formats" + ] + + !immutable [ + "uuid" + "type" + ] + !deprecated [ + "preload_count" + ] + unique "instance" + } + + # + # Default attributes for sound_dose + # + + # uuid a43f9d7c-ea75-44d5-942d967991a33809 + + uuid "7c:9d:3f:a4:75:ea:d5:44:94:2d:96:79:91:a3:38:09" + type "effect" + no_pm "true" + num_input_pins 1 + num_output_pins 1 +} diff --git a/tools/topology/topology2/include/components/sound_dose/setup_data_init.conf b/tools/topology/topology2/include/components/sound_dose/setup_data_init.conf new file mode 100644 index 000000000000..c75e8ed60392 --- /dev/null +++ b/tools/topology/topology2/include/components/sound_dose/setup_data_init.conf @@ -0,0 +1,9 @@ +# Exported with script sof_sound_dose_blobs.m 17-Sep-2025 +# cd src/audio/sound_dose/tune; octave sof_sound_dose_blobs.m +Object.Base.data."sound_dose_config" { + bytes " + 0x53,0x4f,0x46,0x34,0xca,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00" +} diff --git a/tools/topology/topology2/include/components/sound_dose/setup_gain_0db.conf b/tools/topology/topology2/include/components/sound_dose/setup_gain_0db.conf new file mode 100644 index 000000000000..4d64fab9a11c --- /dev/null +++ b/tools/topology/topology2/include/components/sound_dose/setup_gain_0db.conf @@ -0,0 +1,10 @@ +# Exported with script sof_sound_dose_blobs.m 17-Sep-2025 +# cd src/audio/sound_dose/tune; octave sof_sound_dose_blobs.m +Object.Base.data."sound_dose_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x02,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00" +} diff --git a/tools/topology/topology2/include/components/sound_dose/setup_sens_0db.conf b/tools/topology/topology2/include/components/sound_dose/setup_sens_0db.conf new file mode 100644 index 000000000000..ff32c00a6714 --- /dev/null +++ b/tools/topology/topology2/include/components/sound_dose/setup_sens_0db.conf @@ -0,0 +1,10 @@ +# Exported with script sof_sound_dose_blobs.m 17-Sep-2025 +# cd src/audio/sound_dose/tune; octave sof_sound_dose_blobs.m +Object.Base.data."sound_dose_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x00,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00" +} diff --git a/tools/topology/topology2/include/components/sound_dose/setup_sens_100db.conf b/tools/topology/topology2/include/components/sound_dose/setup_sens_100db.conf new file mode 100644 index 000000000000..5c54f455a25c --- /dev/null +++ b/tools/topology/topology2/include/components/sound_dose/setup_sens_100db.conf @@ -0,0 +1,10 @@ +# Exported with script sof_sound_dose_blobs.m 17-Sep-2025 +# cd src/audio/sound_dose/tune; octave sof_sound_dose_blobs.m +Object.Base.data."sound_dose_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x00,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x10,0x27,0x00,0x00" +} diff --git a/tools/topology/topology2/include/components/sound_dose/setup_vol_0db.conf b/tools/topology/topology2/include/components/sound_dose/setup_vol_0db.conf new file mode 100644 index 000000000000..d75c1503a44b --- /dev/null +++ b/tools/topology/topology2/include/components/sound_dose/setup_vol_0db.conf @@ -0,0 +1,10 @@ +# Exported with script sof_sound_dose_blobs.m 17-Sep-2025 +# cd src/audio/sound_dose/tune; octave sof_sound_dose_blobs.m +Object.Base.data."sound_dose_config" { + bytes " + 0x53,0x4f,0x46,0x34,0x01,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x01,0xd0,0x01,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00" +} From b9407761d6fc622d7d039346bf0b473b3c85fd33 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 10 Jun 2025 15:10:52 +0300 Subject: [PATCH 6/6] Audio: Sound Dose: Add new component This patch adds a new SOF component Sound Dose. 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. Signed-off-by: Peter Ujfalusi Signed-off-by: Seppo Ingalsuo --- src/arch/host/configs/library_defconfig | 1 + src/audio/CMakeLists.txt | 3 + src/audio/Kconfig | 1 + src/audio/sound_dose/CMakeLists.txt | 10 + src/audio/sound_dose/Kconfig | 15 + src/audio/sound_dose/README.md | 75 +++++ src/audio/sound_dose/llext/CMakeLists.txt | 9 + src/audio/sound_dose/llext/llext.toml.h | 6 + src/audio/sound_dose/sound_dose-generic.c | 296 +++++++++++++++++ src/audio/sound_dose/sound_dose-ipc4.c | 282 ++++++++++++++++ src/audio/sound_dose/sound_dose.c | 375 ++++++++++++++++++++++ src/audio/sound_dose/sound_dose.h | 158 +++++++++ src/audio/sound_dose/sound_dose.toml | 21 ++ src/audio/sound_dose/sound_dose_iir_44k.h | 27 ++ src/audio/sound_dose/sound_dose_iir_48k.h | 27 ++ src/include/sof/audio/component.h | 1 + src/include/user/audio_feature.h | 26 ++ src/include/user/sound_dose.h | 51 +++ tools/rimage/config/lnl.toml.h | 4 + tools/rimage/config/mtl.toml.h | 4 + tools/rimage/config/nvl.toml.h | 4 + tools/rimage/config/ptl.toml.h | 4 + tools/rimage/config/tgl-h.toml | 18 +- tools/rimage/config/tgl.toml | 18 +- tools/rimage/config/wcl.toml.h | 4 + tools/testbench/utils_ipc4.c | 1 + uuid-registry.txt | 1 + 27 files changed, 1440 insertions(+), 2 deletions(-) create mode 100644 src/audio/sound_dose/CMakeLists.txt create mode 100644 src/audio/sound_dose/Kconfig create mode 100644 src/audio/sound_dose/README.md create mode 100644 src/audio/sound_dose/llext/CMakeLists.txt create mode 100644 src/audio/sound_dose/llext/llext.toml.h create mode 100644 src/audio/sound_dose/sound_dose-generic.c create mode 100644 src/audio/sound_dose/sound_dose-ipc4.c create mode 100644 src/audio/sound_dose/sound_dose.c create mode 100644 src/audio/sound_dose/sound_dose.h create mode 100644 src/audio/sound_dose/sound_dose.toml create mode 100644 src/audio/sound_dose/sound_dose_iir_44k.h create mode 100644 src/audio/sound_dose/sound_dose_iir_48k.h create mode 100644 src/include/user/audio_feature.h create mode 100644 src/include/user/sound_dose.h diff --git a/src/arch/host/configs/library_defconfig b/src/arch/host/configs/library_defconfig index 339b2660e897..5c1ffe029cd6 100644 --- a/src/arch/host/configs/library_defconfig +++ b/src/arch/host/configs/library_defconfig @@ -15,6 +15,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/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/include/sof/audio/component.h b/src/include/sof/audio/component.h index 4b360ab4eabe..61486e0e2a36 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -937,6 +937,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/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