Skip to content

Commit d041aa0

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 530712a commit d041aa0

18 files changed

Lines changed: 852 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: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
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/eq.h>
13+
#include <stdbool.h>
14+
#include <stdint.h>
15+
16+
#include "sound_dose.h"
17+
#include "sound_dose_iir_48k.h"
18+
19+
LOG_MODULE_DECLARE(sound_dose, CONFIG_SOF_LOG_LEVEL);
20+
21+
int sound_dose_filters_init(struct processing_module *mod)
22+
{
23+
struct sound_dose_comp_data *cd = module_get_private_data(mod);
24+
struct comp_dev *dev = mod->dev;
25+
struct sof_abi_hdr *blob;
26+
struct sof_eq_iir_config *iir_config;
27+
struct sof_eq_iir_header *iir_coef;
28+
size_t iir_size, alloc_size;
29+
int32_t *data;
30+
int i;
31+
32+
/* Initialize FIR */
33+
switch (cd->rate) {
34+
case 48000:
35+
blob = (struct sof_abi_hdr *)sound_dose_iir_48k;
36+
iir_config = (struct sof_eq_iir_config *)blob->data;
37+
cd->log_offset_for_mean = SOUND_DOSE_LOG2_INV_48K_Q16;
38+
break;
39+
default:
40+
/* TODO: Add 44100 rate handling and integer ratio decimation code from
41+
* e.g. 96 kHz to 48 kHz. The A-weight is not defined above 20 kHz, so
42+
* high frequency energy is not needed. Also it will help keep the
43+
* module load reasonable.
44+
*/
45+
comp_err(dev, "error: unsupported sample rate %d", cd->rate);
46+
return -EINVAL;
47+
}
48+
49+
/* Apply the first responses in the blobs */
50+
iir_coef = (struct sof_eq_iir_header *)&iir_config->data[iir_config->channels_in_config];
51+
iir_size = iir_delay_size_df1(iir_coef);
52+
alloc_size = cd->channels * iir_size;
53+
cd->delay_lines = rzalloc(SOF_MEM_ZONE_RUNTIME, 0, SOF_MEM_CAPS_RAM, alloc_size);
54+
if (!cd->delay_lines) {
55+
comp_err(dev, "Failed to allocate memory for weighting filters.");
56+
return -ENOMEM;
57+
}
58+
59+
data = cd->delay_lines;
60+
for (i = 0; i < cd->channels; i++) {
61+
iir_init_coef_df1(&cd->iir[i], iir_coef);
62+
iir_init_delay_df1(&cd->iir[i], &data);
63+
cd->energy[i] = 0;
64+
}
65+
66+
cd->frames_count = 0;
67+
cd->report_count = cd->rate; /* report every 1s, for 48k frames */
68+
return 0;
69+
}
70+
71+
void sound_dose_filters_free(struct sound_dose_comp_data *cd)
72+
{
73+
rfree(cd->delay_lines);
74+
}
75+
76+
#if CONFIG_FORMAT_S16LE
77+
78+
/**
79+
* sound_dose_s16() - Process S16_LE format.
80+
* @mod: Pointer to module data.
81+
* @source: Source for PCM samples data.
82+
* @sink: Sink for PCM samples data.
83+
* @frames: Number of audio data frames to process.
84+
*
85+
* This is the processing function for 16-bit signed integer PCM formats. The
86+
* audio samples in every frame are re-order to channels order defined in
87+
* component data channel_map[].
88+
*
89+
* Return: Value zero for success, otherwise an error code.
90+
*/
91+
static int sound_dose_s16(const struct processing_module *mod,
92+
struct sof_source *source,
93+
struct sof_sink *sink,
94+
uint32_t frames)
95+
{
96+
struct sound_dose_comp_data *cd = module_get_private_data(mod);
97+
int16_t const *x, *x_start, *x_end;
98+
int16_t *y, *y_start, *y_end;
99+
int x_size, y_size;
100+
int source_samples_without_wrap;
101+
int samples_without_wrap;
102+
int samples = frames * cd->channels;
103+
int bytes = frames * cd->frame_bytes;
104+
int ret;
105+
int ch;
106+
int i;
107+
108+
/* Get pointer to source data in circular buffer, get buffer start and size to
109+
* check for wrap. The size in bytes is converted to number of s16 samples to
110+
* control the samples process loop. If the number of bytes requested is not
111+
* possible, an error is returned.
112+
*/
113+
ret = source_get_data_s16(source, bytes, &x, &x_start, &x_size);
114+
if (ret)
115+
return ret;
116+
117+
/* Similarly get pointer to sink data in circular buffer, buffer start and size. */
118+
ret = sink_get_buffer_s16(sink, bytes, &y, &y_start, &y_size);
119+
if (ret)
120+
return ret;
121+
122+
/* Set helper pointers to buffer end for wrap check. Then loop until all
123+
* samples are processed.
124+
*/
125+
x_end = x_start + x_size;
126+
y_end = y_start + y_size;
127+
while (samples) {
128+
/* Find out samples to process before first wrap or end of data. */
129+
source_samples_without_wrap = x_end - x;
130+
samples_without_wrap = y_end - y;
131+
samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap);
132+
samples_without_wrap = MIN(samples_without_wrap, samples);
133+
134+
/* Since the example processing is for frames of audio channels, process
135+
* with step of channels count.
136+
*/
137+
for (i = 0; i < samples_without_wrap; i += cd->channels) {
138+
/* In inner loop process the frame. As example re-arrange the channels
139+
* as defined in array channel_map[].
140+
*/
141+
for (ch = 0; ch < cd->channels; ch++) {
142+
*y = x[ch];
143+
y++;
144+
}
145+
x += cd->channels;
146+
}
147+
148+
/* One of the buffers needs a wrap (or end of data), so check for wrap */
149+
x = (x >= x_end) ? x - x_size : x;
150+
y = (y >= y_end) ? y - y_size : y;
151+
152+
/* Update processed samples count for next loop iteration. */
153+
samples -= samples_without_wrap;
154+
}
155+
156+
/* Update the source and sink for bytes consumed and produced. Return success. */
157+
source_release_data(source, bytes);
158+
sink_commit_buffer(sink, bytes);
159+
return 0;
160+
}
161+
#endif /* CONFIG_FORMAT_S16LE */
162+
163+
#if CONFIG_FORMAT_S32LE || CONFIG_FORMAT_S32LE
164+
165+
/**
166+
* sound_dose_s32() - Process S32_LE or S24_4LE format.
167+
* @mod: Pointer to module data.
168+
* @source: Source for PCM samples data.
169+
* @sink: Sink for PCM samples data.
170+
* @frames: Number of audio data frames to process.
171+
*
172+
* Processing function for signed integer 32-bit PCM formats. The same
173+
* function works for s24 and s32 formats since the samples values are
174+
* not modified in computation. The audio samples in every frame are
175+
* re-order to channels order defined in component data channel_map[].
176+
*
177+
* Return: Value zero for success, otherwise an error code.
178+
*/
179+
static int sound_dose_s32(const struct processing_module *mod,
180+
struct sof_source *source,
181+
struct sof_sink *sink,
182+
uint32_t frames)
183+
{
184+
struct sound_dose_comp_data *cd = module_get_private_data(mod);
185+
struct iir_state_df1 *iir;
186+
uint64_t energy_sum;
187+
uint32_t log_arg;
188+
int32_t mel;
189+
int32_t tmp;
190+
int32_t sample;
191+
int32_t const *x0, *x, *x_start, *x_end;
192+
int32_t *y0, *y, *y_start, *y_end;
193+
int32_t weighted;
194+
int x_size, y_size;
195+
int source_samples_without_wrap;
196+
int samples_without_wrap;
197+
int samples = frames * cd->channels;
198+
int bytes = frames * cd->frame_bytes;
199+
int ret;
200+
int ch;
201+
int i;
202+
const int channels = cd->channels;
203+
204+
/* Get pointer to source data in circular buffer, get buffer start and size to
205+
* check for wrap. The size in bytes is converted to number of s16 samples to
206+
* control the samples process loop. If the number of bytes requested is not
207+
* possible, an error is returned.
208+
*/
209+
ret = source_get_data_s32(source, bytes, &x0, &x_start, &x_size);
210+
if (ret)
211+
return ret;
212+
213+
/* Similarly get pointer to sink data in circular buffer, buffer start and size. */
214+
ret = sink_get_buffer_s32(sink, bytes, &y0, &y_start, &y_size);
215+
if (ret)
216+
return ret;
217+
218+
/* Set helper pointers to buffer end for wrap check. Then loop until all
219+
* samples are processed.
220+
*/
221+
x_end = x_start + x_size;
222+
y_end = y_start + y_size;
223+
while (samples) {
224+
/* Find out samples to process before first wrap or end of data. */
225+
source_samples_without_wrap = x_end - x0;
226+
samples_without_wrap = y_end - y0;
227+
samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap);
228+
samples_without_wrap = MIN(samples_without_wrap, samples);
229+
for (ch = 0; ch < cd->channels; ch++) {
230+
iir = &cd->iir[ch];
231+
x = x0++;
232+
y = y0++;
233+
for (i = 0; i < samples_without_wrap; i += channels) {
234+
sample = *x;
235+
*y = sample;
236+
x += channels;
237+
y += channels;
238+
weighted = iir_df1(iir, sample) >> 16;
239+
240+
/* Update sound dose, energy is Q1.15 * Q1.15 --> Q2.30 */
241+
cd->energy[ch] += weighted * weighted;
242+
}
243+
}
244+
245+
/* One of the buffers needs a wrap (or end of data), so check for wrap */
246+
x0 += samples_without_wrap;
247+
y0 += samples_without_wrap;
248+
x0 = (x0 >= x_end) ? x0 - x_size : x0;
249+
y0 = (y0 >= y_end) ? y0 - y_size : y0;
250+
251+
/* Update processed samples count for next loop iteration. */
252+
samples -= samples_without_wrap;
253+
}
254+
255+
cd->frames_count += frames;
256+
if (cd->frames_count >= cd->report_count) {
257+
energy_sum = 0;
258+
for (ch = 0; ch < cd->channels; ch++)
259+
energy_sum += cd->energy[ch];
260+
261+
/* Log2 argument is Q32.0 unsigned, log2 returns Q16.16 signed.
262+
* Energy is Qx.30, so the argument 2^30 times scaled. Also to keep
263+
* argument within uint32_t range, need to scale it down by 19.
264+
* To compensate these, need to add 19 (Q16.16) and subtract 30 (Q16.16)
265+
* from logarithm value.
266+
*/
267+
log_arg = (uint32_t)(energy_sum >> SOUND_DOSE_ENERGY_SHIFT);
268+
log_arg = MAX(log_arg, 1);
269+
tmp = base2_logarithm(log_arg);
270+
tmp += SOUND_DOSE_LOG_FIXED_OFFSET; /* Compensate Q2.30 and energy shift */
271+
tmp += cd->log_offset_for_mean; /* logarithm subtract for mean */
272+
tmp = Q_MULTSR_32X32((int64_t)tmp, SOUND_DOSE_TEN_OVER_LOG2_10_Q29, 16, 29, 16);
273+
mel = tmp + SOUND_DOSE_WEIGHT_FILTERS_OFFS_Q16;
274+
275+
/* If stereo sum channel level values and subtract 3 dB, to generalize
276+
* For stereo or more subtract -1.5 dB per channel.
277+
*/
278+
if (cd->channels > 1)
279+
mel += cd->channels * SOUND_DOSE_MEL_CHANNELS_SUM_FIX;
280+
281+
comp_info(mod->dev, "MEL %d %d", mel, ((mel >> 15) + 1) >> 1);
282+
283+
/* Prepare for next MEL value */
284+
cd->frames_count = 0;
285+
for (ch = 0; ch < cd->channels; ch++)
286+
cd->energy[ch] = 0;
287+
}
288+
289+
/* Update the source and sink for bytes consumed and produced. Return success. */
290+
source_release_data(source, bytes);
291+
sink_commit_buffer(sink, bytes);
292+
return 0;
293+
}
294+
#endif /* CONFIG_FORMAT_S32LE || CONFIG_FORMAT_S24LE */
295+
296+
/* This struct array defines the used processing functions for
297+
* the PCM formats
298+
*/
299+
const struct sound_dose_proc_fnmap sound_dose_proc_fnmap[] = {
300+
#if CONFIG_FORMAT_S16LE
301+
{ SOF_IPC_FRAME_S16_LE, sound_dose_s16},
302+
#endif
303+
#if CONFIG_FORMAT_S24LE
304+
{ SOF_IPC_FRAME_S24_4LE, sound_dose_s32},
305+
#endif
306+
#if CONFIG_FORMAT_S32LE
307+
{ SOF_IPC_FRAME_S32_LE, sound_dose_s32},
308+
#endif
309+
};
310+
311+
/**
312+
* sound_dose_find_proc_func() - Find suitable processing function.
313+
* @src_fmt: Enum value for PCM format.
314+
*
315+
* This function finds the suitable processing function to use for
316+
* the used PCM format. If not found, return NULL.
317+
*
318+
* Return: Pointer to processing function for the requested PCM format.
319+
*/
320+
sound_dose_func sound_dose_find_proc_func(enum sof_ipc_frame src_fmt)
321+
{
322+
int i;
323+
324+
/* Find suitable processing function from map */
325+
for (i = 0; i < ARRAY_SIZE(sound_dose_proc_fnmap); i++)
326+
if (src_fmt == sound_dose_proc_fnmap[i].frame_fmt)
327+
return sound_dose_proc_fnmap[i].sound_dose_proc_func;
328+
329+
return NULL;
330+
}

0 commit comments

Comments
 (0)