Skip to content

Commit 6f32897

Browse files
committed
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: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
1 parent 9e22312 commit 6f32897

19 files changed

Lines changed: 891 additions & 2 deletions

src/audio/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD)
120120
list(APPEND base_files host-legacy.c)
121121
sof_list_append_ifdef(CONFIG_COMP_DAI base_files dai-legacy.c)
122122
endif()
123+
if(CONFIG_COMP_SOUND_DOSE)
124+
add_subdirectory(sound_dose)
125+
endif()
123126
if(CONFIG_COMP_TEMPLATE_COMP)
124127
add_subdirectory(template_comp)
125128
endif()

src/audio/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ rsource "mfcc/Kconfig"
168168

169169
rsource "codec/Kconfig"
170170

171+
rsource "sound_dose/Kconfig"
172+
171173
rsource "template_comp/Kconfig"
172174

173175
endmenu # "Audio components"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
3+
if(CONFIG_COMP_TEMPLATE_COMP STREQUAL "m")
4+
add_subdirectory(llext ${PROJECT_BINARY_DIR}/sound_dose_llext)
5+
add_dependencies(app sound_dose)
6+
else()
7+
add_local_sources(sof sound_dose.c)
8+
add_local_sources(sof sound_dose-generic.c)
9+
10+
if(CONFIG_IPC_MAJOR_3)
11+
add_local_sources(sof sound_dose-ipc3.c)
12+
elseif(CONFIG_IPC_MAJOR_4)
13+
add_local_sources(sof sound_dose-ipc4.c)
14+
endif()
15+
endif()

src/audio/sound_dose/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
3+
config COMP_SOUND_DOSE
4+
tristate "Sound Dose module"
5+
default y
6+
help
7+
Select this for Sound Dose SOF module. The purpose is
8+
to calculate for audio playback MEL values (momentary
9+
sound exposure level) to provide to user space the data
10+
to compute the sound dose CSD as defined in EN 50332-3.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright (c) 2025 Intel Corporation.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
sof_llext_build("template_comp"
5+
SOURCES ../sound_dose.c
6+
LIB openmodules
7+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include <tools/rimage/config/platform.toml>
2+
#define LOAD_TYPE "2"
3+
#include "../sound_dose.toml"
4+
5+
[module]
6+
count = __COUNTER__
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
//
3+
// Copyright(c) 2025 Intel Corporation.
4+
5+
#include <sof/audio/module_adapter/module/generic.h>
6+
#include <sof/audio/component.h>
7+
#include <sof/audio/sink_api.h>
8+
#include <sof/audio/sink_source_utils.h>
9+
#include <sof/audio/source_api.h>
10+
#include <sof/math/log.h>
11+
#include <sof/math/iir_df1.h>
12+
#include <user/fir.h>
13+
#include <stdbool.h>
14+
#include <stdint.h>
15+
16+
#include "sound_dose.h"
17+
18+
#if SOF_USE_MIN_HIFI(3, FILTER)
19+
#include <sof/math/fir_hifi3.h>
20+
#else
21+
#include <sof/math/fir_generic.h>
22+
#endif
23+
24+
LOG_MODULE_DECLARE(sound_dose, CONFIG_SOF_LOG_LEVEL);
25+
26+
#if CONFIG_FORMAT_S16LE
27+
28+
/**
29+
* sound_dose_s16() - Process S16_LE format.
30+
* @mod: Pointer to module data.
31+
* @source: Source for PCM samples data.
32+
* @sink: Sink for PCM samples data.
33+
* @frames: Number of audio data frames to process.
34+
*
35+
* This is the processing function for 16-bit signed integer PCM formats. The
36+
* audio samples in every frame are re-order to channels order defined in
37+
* component data channel_map[].
38+
*
39+
* Return: Value zero for success, otherwise an error code.
40+
*/
41+
static int sound_dose_s16(const struct processing_module *mod,
42+
struct sof_source *source,
43+
struct sof_sink *sink,
44+
uint32_t frames)
45+
{
46+
struct sound_dose_comp_data *cd = module_get_private_data(mod);
47+
int16_t const *x, *x_start, *x_end;
48+
int16_t *y, *y_start, *y_end;
49+
int x_size, y_size;
50+
int source_samples_without_wrap;
51+
int samples_without_wrap;
52+
int samples = frames * cd->channels;
53+
int bytes = frames * cd->frame_bytes;
54+
int ret;
55+
int ch;
56+
int i;
57+
58+
/* Get pointer to source data in circular buffer, get buffer start and size to
59+
* check for wrap. The size in bytes is converted to number of s16 samples to
60+
* control the samples process loop. If the number of bytes requested is not
61+
* possible, an error is returned.
62+
*/
63+
ret = source_get_data_s16(source, bytes, &x, &x_start, &x_size);
64+
if (ret)
65+
return ret;
66+
67+
/* Similarly get pointer to sink data in circular buffer, buffer start and size. */
68+
ret = sink_get_buffer_s16(sink, bytes, &y, &y_start, &y_size);
69+
if (ret)
70+
return ret;
71+
72+
/* Set helper pointers to buffer end for wrap check. Then loop until all
73+
* samples are processed.
74+
*/
75+
x_end = x_start + x_size;
76+
y_end = y_start + y_size;
77+
while (samples) {
78+
/* Find out samples to process before first wrap or end of data. */
79+
source_samples_without_wrap = x_end - x;
80+
samples_without_wrap = y_end - y;
81+
samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap);
82+
samples_without_wrap = MIN(samples_without_wrap, samples);
83+
84+
/* Since the example processing is for frames of audio channels, process
85+
* with step of channels count.
86+
*/
87+
for (i = 0; i < samples_without_wrap; i += cd->channels) {
88+
/* In inner loop process the frame. As example re-arrange the channels
89+
* as defined in array channel_map[].
90+
*/
91+
for (ch = 0; ch < cd->channels; ch++) {
92+
*y = x[ch];
93+
y++;
94+
}
95+
x += cd->channels;
96+
}
97+
98+
/* One of the buffers needs a wrap (or end of data), so check for wrap */
99+
x = (x >= x_end) ? x - x_size : x;
100+
y = (y >= y_end) ? y - y_size : y;
101+
102+
/* Update processed samples count for next loop iteration. */
103+
samples -= samples_without_wrap;
104+
}
105+
106+
/* Update the source and sink for bytes consumed and produced. Return success. */
107+
source_release_data(source, bytes);
108+
sink_commit_buffer(sink, bytes);
109+
return 0;
110+
}
111+
#endif /* CONFIG_FORMAT_S16LE */
112+
113+
#if CONFIG_FORMAT_S32LE || CONFIG_FORMAT_S32LE
114+
115+
/**
116+
* sound_dose_s32() - Process S32_LE or S24_4LE format.
117+
* @mod: Pointer to module data.
118+
* @source: Source for PCM samples data.
119+
* @sink: Sink for PCM samples data.
120+
* @frames: Number of audio data frames to process.
121+
*
122+
* Processing function for signed integer 32-bit PCM formats. The same
123+
* function works for s24 and s32 formats since the samples values are
124+
* not modified in computation. The audio samples in every frame are
125+
* re-order to channels order defined in component data channel_map[].
126+
*
127+
* Return: Value zero for success, otherwise an error code.
128+
*/
129+
static int sound_dose_s32(const struct processing_module *mod,
130+
struct sof_source *source,
131+
struct sof_sink *sink,
132+
uint32_t frames)
133+
{
134+
struct sound_dose_comp_data *cd = module_get_private_data(mod);
135+
struct fir_state_32x16 *fir;
136+
struct iir_state_df1 *iir;
137+
uint64_t energy_sum;
138+
uint32_t log_arg;
139+
int32_t mel;
140+
int32_t tmp;
141+
int32_t sample;
142+
int32_t const *x0, *x, *x_start, *x_end;
143+
int32_t *y0, *y, *y_start, *y_end;
144+
int32_t weighted;
145+
int x_size, y_size;
146+
int source_samples_without_wrap;
147+
int samples_without_wrap;
148+
int samples = frames * cd->channels;
149+
int bytes = frames * cd->frame_bytes;
150+
int ret;
151+
int ch;
152+
int i;
153+
const int channels = cd->channels;
154+
155+
#if SOF_USE_MIN_HIFI(3, FILTER)
156+
int shift;
157+
#endif
158+
159+
/* Get pointer to source data in circular buffer, get buffer start and size to
160+
* check for wrap. The size in bytes is converted to number of s16 samples to
161+
* control the samples process loop. If the number of bytes requested is not
162+
* possible, an error is returned.
163+
*/
164+
ret = source_get_data_s32(source, bytes, &x0, &x_start, &x_size);
165+
if (ret)
166+
return ret;
167+
168+
/* Similarly get pointer to sink data in circular buffer, buffer start and size. */
169+
ret = sink_get_buffer_s32(sink, bytes, &y0, &y_start, &y_size);
170+
if (ret)
171+
return ret;
172+
173+
/* Set helper pointers to buffer end for wrap check. Then loop until all
174+
* samples are processed.
175+
*/
176+
x_end = x_start + x_size;
177+
y_end = y_start + y_size;
178+
while (samples) {
179+
/* Find out samples to process before first wrap or end of data. */
180+
source_samples_without_wrap = x_end - x0;
181+
samples_without_wrap = y_end - y0;
182+
samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap);
183+
samples_without_wrap = MIN(samples_without_wrap, samples);
184+
for (ch = 0; ch < cd->channels; ch++) {
185+
iir = &cd->iir[ch];
186+
fir = &cd->fir[ch];
187+
#if SOF_USE_MIN_HIFI(3, FILTER)
188+
shift = -fir->out_shift;
189+
fir_core_setup_circular(fir);
190+
#endif
191+
x = x0++;
192+
y = y0++;
193+
for (i = 0; i < samples_without_wrap; i += channels) {
194+
sample = *x;
195+
*y = sample;
196+
x += channels;
197+
y += channels;
198+
199+
sample = iir_df1(iir, sample);
200+
#if SOF_USE_MIN_HIFI(3, FILTER)
201+
fir_32x16(fir, (ae_int32)sample, (ae_int32 *)&weighted, shift);
202+
#else
203+
weighted = fir_32x16(fir, sample);
204+
#endif
205+
206+
/* Update sound dose, energy is Q1.15 * Q1.15 --> Q2.30 */
207+
weighted = weighted >> 16;
208+
cd->energy[ch] += weighted * weighted;
209+
}
210+
}
211+
212+
/* One of the buffers needs a wrap (or end of data), so check for wrap */
213+
x0 += samples_without_wrap;
214+
y0 += samples_without_wrap;
215+
x0 = (x0 >= x_end) ? x0 - x_size : x0;
216+
y0 = (y0 >= y_end) ? y0 - y_size : y0;
217+
218+
/* Update processed samples count for next loop iteration. */
219+
samples -= samples_without_wrap;
220+
}
221+
222+
cd->frames_count += frames;
223+
if (cd->frames_count >= cd->report_count) {
224+
energy_sum = 0;
225+
for (ch = 0; ch < cd->channels; ch++)
226+
energy_sum += cd->energy[ch];
227+
228+
/* Log2 argument is Q32.0 unsigned, log2 returns Q16.16 signed.
229+
* Energy is Qx.30, so the argument 2^30 times scaled. Also to keep
230+
* argument within uint32_t range, need to scale it down by 19.
231+
* To compensate these, need to add 19 (Q16.16) and subtract 30 (Q16.16)
232+
* from logarithm value.
233+
*/
234+
log_arg = (uint32_t)(energy_sum >> SOUND_DOSE_ENERGY_SHIFT);
235+
log_arg = MAX(log_arg, 1);
236+
tmp = base2_logarithm(log_arg);
237+
tmp += SOUND_DOSE_LOG_FIXED_OFFSET; /* Compensate Q2.30 and energy shift */
238+
tmp += cd->log_offset_for_mean; /* logarithm subtract for mean */
239+
tmp = Q_MULTSR_32X32((int64_t)tmp, SOUND_DOSE_TEN_OVER_LOG2_10_Q29, 16, 29, 16);
240+
mel = tmp + SOUND_DOSE_WEIGHT_FILTERS_OFFS_Q16;
241+
242+
/* If stereo sum channel level values and subtract 3 dB, to generalize
243+
* For stereo or more subtract -1.5 dB per channel.
244+
*/
245+
if (cd->channels > 1)
246+
mel += cd->channels * SOUND_DOSE_MEL_CHANNELS_SUM_FIX;
247+
248+
comp_info(mod->dev, "MEL %d %d", mel, ((mel >> 15) + 1) >> 1);
249+
250+
/* Prepare for next MEL value */
251+
cd->frames_count = 0;
252+
for (ch = 0; ch < cd->channels; ch++)
253+
cd->energy[ch] = 0;
254+
}
255+
256+
/* Update the source and sink for bytes consumed and produced. Return success. */
257+
source_release_data(source, bytes);
258+
sink_commit_buffer(sink, bytes);
259+
return 0;
260+
}
261+
#endif /* CONFIG_FORMAT_S32LE || CONFIG_FORMAT_S24LE */
262+
263+
/* This struct array defines the used processing functions for
264+
* the PCM formats
265+
*/
266+
const struct sound_dose_proc_fnmap sound_dose_proc_fnmap[] = {
267+
#if CONFIG_FORMAT_S16LE
268+
{ SOF_IPC_FRAME_S16_LE, sound_dose_s16},
269+
#endif
270+
#if CONFIG_FORMAT_S24LE
271+
{ SOF_IPC_FRAME_S24_4LE, sound_dose_s32},
272+
#endif
273+
#if CONFIG_FORMAT_S32LE
274+
{ SOF_IPC_FRAME_S32_LE, sound_dose_s32},
275+
#endif
276+
};
277+
278+
/**
279+
* sound_dose_find_proc_func() - Find suitable processing function.
280+
* @src_fmt: Enum value for PCM format.
281+
*
282+
* This function finds the suitable processing function to use for
283+
* the used PCM format. If not found, return NULL.
284+
*
285+
* Return: Pointer to processing function for the requested PCM format.
286+
*/
287+
sound_dose_func sound_dose_find_proc_func(enum sof_ipc_frame src_fmt)
288+
{
289+
int i;
290+
291+
/* Find suitable processing function from map */
292+
for (i = 0; i < ARRAY_SIZE(sound_dose_proc_fnmap); i++)
293+
if (src_fmt == sound_dose_proc_fnmap[i].frame_fmt)
294+
return sound_dose_proc_fnmap[i].sound_dose_proc_func;
295+
296+
return NULL;
297+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
//
3+
// Copyright(c) 2025 Intel Corporation.
4+
5+
#include <sof/audio/module_adapter/module/generic.h>
6+
#include <sof/audio/component.h>
7+
#include "sound_dose.h"
8+
9+
LOG_MODULE_DECLARE(sound_dose, CONFIG_SOF_LOG_LEVEL);
10+
11+
/* This function handles the real-time controls. The ALSA controls have the
12+
* param_id set to indicate the control type. The control ID, from topology,
13+
* is used to separate the controls instances of same type. In control payload
14+
* the num_elems defines to how many channels the control is applied to.
15+
*/
16+
__cold int sound_dose_set_config(struct processing_module *mod, uint32_t param_id,
17+
enum module_cfg_fragment_position pos,
18+
uint32_t data_offset_size, const uint8_t *fragment,
19+
size_t fragment_size, uint8_t *response,
20+
size_t response_size)
21+
{
22+
assert_can_be_cold();
23+
return 0;
24+
}
25+
26+
__cold int sound_dose_get_config(struct processing_module *mod,
27+
uint32_t config_id, uint32_t *data_offset_size,
28+
uint8_t *fragment, size_t fragment_size)
29+
{
30+
assert_can_be_cold();
31+
return 0;
32+
}

0 commit comments

Comments
 (0)