Skip to content

Commit e5ac2e1

Browse files
committed
Audio: MFCC: Add VAD switch control notification to user space
Add IPC4 notification that sends the VAD state to user space via a switch control whenever the VAD decision changes between speech and silence. The notification is initialized during prepare and sent from the audio processing path on VAD state transitions. The implementation follows the TDFB/sound_dose notification pattern: mfcc_ipc4.c contains the IPC4-specific notification init and send functions, while mfcc.c provides weak stubs so IPC3 builds link without the IPC4 dependencies. Add handling for SOF_IPC4_SWITCH_CONTROL_PARAM_ID in mfcc_get_config and mfcc_set_config so the kernel driver can read back the current VAD state after receiving a notification. The switch control is read-only from the DSP side. Both the notification init and the VAD state change detection are gated on the update_controls flag in the configuration blob struct. Add a switch control (mixer) to the MFCC topology2 widget definition for the VAD notification. Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
1 parent 3191027 commit e5ac2e1

7 files changed

Lines changed: 251 additions & 28 deletions

File tree

src/audio/mfcc/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@ if(CONFIG_COMP_MFCC STREQUAL "m" AND DEFINED CONFIG_LLEXT)
55
add_dependencies(app mfcc)
66
else()
77
add_local_sources(sof mfcc.c mfcc_setup.c mfcc_common.c mfcc_generic.c mfcc_hifi4.c mfcc_hifi3.c mfcc_vad.c)
8+
if(CONFIG_IPC_MAJOR_4)
9+
add_local_sources(sof mfcc_ipc4.c)
10+
endif()
811
endif()

src/audio/mfcc/mfcc.c

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,36 +97,13 @@ static int mfcc_free(struct processing_module *mod)
9797
struct mfcc_comp_data *cd = module_get_private_data(mod);
9898

9999
comp_info(mod->dev, "entry");
100+
ipc_msg_free(cd->msg);
100101
mod_data_blob_handler_free(mod, cd->model_handler);
101102
mfcc_free_buffers(mod);
102103
mod_free(mod, cd);
103104
return 0;
104105
}
105106

106-
static int mfcc_get_config(struct processing_module *mod,
107-
uint32_t config_id, uint32_t *data_offset_size,
108-
uint8_t *fragment, size_t fragment_size)
109-
{
110-
struct sof_ipc_ctrl_data *cdata = (struct sof_ipc_ctrl_data *)fragment;
111-
struct mfcc_comp_data *cd = module_get_private_data(mod);
112-
113-
comp_info(mod->dev, "entry");
114-
115-
return comp_data_blob_get_cmd(cd->model_handler, cdata, fragment_size);
116-
}
117-
118-
static int mfcc_set_config(struct processing_module *mod, uint32_t config_id,
119-
enum module_cfg_fragment_position pos, uint32_t data_offset_size,
120-
const uint8_t *fragment, size_t fragment_size, uint8_t *response,
121-
size_t response_size)
122-
{
123-
struct mfcc_comp_data *cd = module_get_private_data(mod);
124-
125-
comp_info(mod->dev, "entry");
126-
127-
return comp_data_blob_set(cd->model_handler, pos, data_offset_size,
128-
fragment, fragment_size);
129-
}
130107

131108
static int mfcc_process(struct processing_module *mod,
132109
struct input_stream_buffer *input_buffers, int num_input_buffers,
@@ -198,6 +175,15 @@ static int mfcc_prepare(struct processing_module *mod,
198175
goto err;
199176
}
200177

178+
/* Initialize VAD switch control notification if enabled */
179+
if (cd->config && cd->config->update_controls) {
180+
ret = mfcc_ipc_notification_init(mod);
181+
if (ret < 0)
182+
goto err;
183+
184+
cd->vad_prev = false;
185+
}
186+
201187
return 0;
202188

203189
err:

src/audio/mfcc/mfcc_common.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ LOG_MODULE_REGISTER(mfcc_common, CONFIG_SOF_LOG_LEVEL);
2929
* The main processing function for MFCC
3030
*/
3131

32-
static int mfcc_stft_process(const struct comp_dev *dev, struct mfcc_comp_data *cd)
32+
static int mfcc_stft_process(struct processing_module *mod, struct mfcc_comp_data *cd)
3333
{
34+
const struct comp_dev *dev = mod->dev;
3435
struct sof_mfcc_config *config = cd->config;
3536
struct mfcc_state *state = &cd->state;
3637
struct mfcc_buffer *buf = &state->buf;
@@ -186,6 +187,16 @@ static int mfcc_stft_process(const struct comp_dev *dev, struct mfcc_comp_data *
186187

187188
/* Increment hop counter at end of hop processing */
188189
state->hop_count++;
190+
191+
/* Send notification when VAD state changes */
192+
if (config->update_controls) {
193+
bool vad_now = cd->vad.is_speech;
194+
195+
if (vad_now != cd->vad_prev) {
196+
mfcc_send_vad_notification(mod, vad_now ? 1 : 0);
197+
cd->vad_prev = vad_now;
198+
}
199+
}
189200
}
190201

191202
return cc_count;
@@ -294,7 +305,7 @@ void mfcc_s16_default(struct processing_module *mod, struct input_stream_buffer
294305
mfcc_source_copy_s16(bsource, buf, &state->emph, frames, state->source_channel);
295306

296307
/* Run STFT and processing after FFT: Mel auditory filter and DCT. */
297-
num_ceps = mfcc_stft_process(mod->dev, cd);
308+
num_ceps = mfcc_stft_process(mod, cd);
298309

299310
/* If new output produced, set up pointer into scratch data and mark header pending */
300311
if (num_ceps > 0) {
@@ -393,7 +404,7 @@ void mfcc_s24_default(struct processing_module *mod, struct input_stream_buffer
393404
mfcc_source_copy_s24(bsource, buf, &state->emph, frames, state->source_channel);
394405

395406
/* Run STFT and processing after FFT */
396-
num_ceps = mfcc_stft_process(mod->dev, cd);
407+
num_ceps = mfcc_stft_process(mod, cd);
397408

398409
/* If new output produced, set up pointer into scratch data */
399410
if (num_ceps > 0) {
@@ -472,7 +483,7 @@ void mfcc_s32_default(struct processing_module *mod, struct input_stream_buffer
472483
mfcc_source_copy_s32(bsource, buf, &state->emph, frames, state->source_channel);
473484

474485
/* Run STFT and processing after FFT */
475-
num_ceps = mfcc_stft_process(mod->dev, cd);
486+
num_ceps = mfcc_stft_process(mod, cd);
476487

477488
/* If new output produced, set up pointer into scratch data */
478489
if (num_ceps > 0) {

src/audio/mfcc/mfcc_ipc4.c

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
//
3+
// Copyright(c) 2026 Intel Corporation.
4+
//
5+
// Author: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
6+
7+
/**
8+
* \file mfcc_ipc4.c
9+
* \brief IPC4-specific functions for MFCC component.
10+
*
11+
* Provides VAD switch control notification to user space via the
12+
* IPC4 module notification mechanism.
13+
*/
14+
15+
#include <sof/audio/mfcc/mfcc_comp.h>
16+
#include <sof/audio/module_adapter/module/generic.h>
17+
#include <sof/audio/component.h>
18+
#include <sof/ipc/msg.h>
19+
#include <sof/trace/trace.h>
20+
#include <ipc4/base-config.h>
21+
#include <ipc4/header.h>
22+
#include <ipc4/module.h>
23+
#include <ipc4/notification.h>
24+
#include <rtos/string.h>
25+
#include <errno.h>
26+
#include <stdint.h>
27+
28+
LOG_MODULE_DECLARE(mfcc, CONFIG_SOF_LOG_LEVEL);
29+
30+
/**
31+
* \brief Initialize IPC notification message for VAD switch control.
32+
*
33+
* Allocates and configures the IPC message used to send VAD state
34+
* change notifications to user space via a switch control.
35+
*/
36+
int mfcc_ipc_notification_init(struct processing_module *mod)
37+
{
38+
struct mfcc_comp_data *cd = module_get_private_data(mod);
39+
struct ipc_msg msg_proto;
40+
struct comp_dev *dev = mod->dev;
41+
struct comp_ipc_config *ipc_config = &dev->ipc_config;
42+
union ipc4_notification_header *primary =
43+
(union ipc4_notification_header *)&msg_proto.header;
44+
struct sof_ipc4_notify_module_data *msg_module_data;
45+
struct sof_ipc4_control_msg_payload *msg_payload;
46+
47+
memset_s(&msg_proto, sizeof(msg_proto), 0, sizeof(msg_proto));
48+
primary->r.notif_type = SOF_IPC4_MODULE_NOTIFICATION;
49+
primary->r.type = SOF_IPC4_GLB_NOTIFICATION;
50+
primary->r.rsp = SOF_IPC4_MESSAGE_DIR_MSG_REQUEST;
51+
primary->r.msg_tgt = SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG;
52+
cd->msg = ipc_msg_w_ext_init(msg_proto.header, msg_proto.extension,
53+
sizeof(struct sof_ipc4_notify_module_data) +
54+
sizeof(struct sof_ipc4_control_msg_payload) +
55+
sizeof(struct sof_ipc4_ctrl_value_chan));
56+
if (!cd->msg) {
57+
comp_err(dev, "Failed to initialize VAD notification");
58+
return -ENOMEM;
59+
}
60+
61+
msg_module_data = (struct sof_ipc4_notify_module_data *)cd->msg->tx_data;
62+
msg_module_data->instance_id = IPC4_INST_ID(ipc_config->id);
63+
msg_module_data->module_id = IPC4_MOD_ID(ipc_config->id);
64+
msg_module_data->event_id = SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL |
65+
SOF_IPC4_SWITCH_CONTROL_PARAM_ID;
66+
msg_module_data->event_data_size = sizeof(struct sof_ipc4_control_msg_payload) +
67+
sizeof(struct sof_ipc4_ctrl_value_chan);
68+
69+
msg_payload = (struct sof_ipc4_control_msg_payload *)msg_module_data->event_data;
70+
msg_payload->id = MFCC_CTRL_INDEX_VAD;
71+
msg_payload->num_elems = 1;
72+
msg_payload->chanv[0].channel = 0;
73+
74+
comp_dbg(dev, "VAD notification init: instance_id = 0x%08x, module_id = 0x%08x",
75+
msg_module_data->instance_id, msg_module_data->module_id);
76+
return 0;
77+
}
78+
79+
/**
80+
* \brief Send VAD switch control notification to user space.
81+
* \param mod Processing module.
82+
* \param val VAD value: 1 = speech, 0 = silence.
83+
*/
84+
void mfcc_send_vad_notification(struct processing_module *mod, uint32_t val)
85+
{
86+
struct mfcc_comp_data *cd = module_get_private_data(mod);
87+
struct sof_ipc4_notify_module_data *msg_module_data;
88+
struct sof_ipc4_control_msg_payload *msg_payload;
89+
90+
if (!cd->msg)
91+
return;
92+
93+
msg_module_data = (struct sof_ipc4_notify_module_data *)cd->msg->tx_data;
94+
msg_payload = (struct sof_ipc4_control_msg_payload *)msg_module_data->event_data;
95+
msg_payload->chanv[0].value = val;
96+
ipc_msg_send(cd->msg, NULL, false);
97+
}
98+
99+
int mfcc_get_config(struct processing_module *mod,
100+
uint32_t config_id, uint32_t *data_offset_size,
101+
uint8_t *fragment, size_t fragment_size)
102+
{
103+
struct sof_ipc_ctrl_data *cdata = (struct sof_ipc_ctrl_data *)fragment;
104+
struct mfcc_comp_data *cd = module_get_private_data(mod);
105+
struct sof_ipc4_control_msg_payload *ctl;
106+
107+
comp_info(mod->dev, "entry");
108+
109+
switch (config_id) {
110+
case SOF_IPC4_SWITCH_CONTROL_PARAM_ID:
111+
ctl = (struct sof_ipc4_control_msg_payload *)fragment;
112+
if (ctl->id == MFCC_CTRL_INDEX_VAD && ctl->num_elems == 1) {
113+
ctl->chanv[0].value = cd->vad_prev ? 1 : 0;
114+
*data_offset_size = sizeof(*ctl) + sizeof(ctl->chanv[0]);
115+
return 0;
116+
}
117+
return -EINVAL;
118+
default:
119+
return comp_data_blob_get_cmd(cd->model_handler, cdata, fragment_size);
120+
}
121+
}
122+
123+
int mfcc_set_config(struct processing_module *mod, uint32_t config_id,
124+
enum module_cfg_fragment_position pos, uint32_t data_offset_size,
125+
const uint8_t *fragment, size_t fragment_size, uint8_t *response,
126+
size_t response_size)
127+
{
128+
struct mfcc_comp_data *cd = module_get_private_data(mod);
129+
130+
comp_info(mod->dev, "entry");
131+
132+
switch (config_id) {
133+
case SOF_IPC4_SWITCH_CONTROL_PARAM_ID:
134+
/* VAD switch is read-only, ignore set requests */
135+
return 0;
136+
default:
137+
return comp_data_blob_set(cd->model_handler, pos, data_offset_size,
138+
fragment, fragment_size);
139+
}
140+
}

src/include/sof/audio/mfcc/mfcc_comp.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
#define __SOF_AUDIO_MFCC_MFCC_COMP_H__
1010

1111
#include <sof/audio/module_adapter/module/generic.h>
12+
#include <sof/audio/data_blob.h>
1213
#include <sof/math/auditory.h>
1314
#include <sof/math/dct.h>
1415
#include <sof/math/fft.h>
1516
#include <sof/audio/mfcc/mfcc_vad.h>
17+
#include <sof/ipc/msg.h>
1618
#include <stddef.h>
1719
#include <stdint.h>
1820

@@ -34,6 +36,9 @@
3436
#define MFCC_FFT_BITS 32
3537
#define MFCC_MAX_SAMPLE_RATE 64000 /* Max sample rate in Hz, limited by int16_t Mel scale */
3638

39+
/** \brief Switch control index for VAD notification to user space */
40+
#define MFCC_CTRL_INDEX_VAD 0
41+
3742
/**
3843
* \brief Data header prepended to every MFCC output frame.
3944
*
@@ -137,7 +142,9 @@ struct mfcc_comp_data {
137142
struct mfcc_vad_state vad;
138143
struct comp_data_blob_handler *model_handler;
139144
struct sof_mfcc_config *config;
145+
struct ipc_msg *msg; /**< IPC notification for VAD switch control */
140146
int max_frames;
147+
bool vad_prev; /**< Previous VAD state for edge detection */
141148
mfcc_func mfcc_func; /**< processing function */
142149
};
143150

@@ -192,6 +199,52 @@ void mfcc_s32_default(struct processing_module *mod, struct input_stream_buffer
192199
struct output_stream_buffer *bsink, int frames);
193200
#endif
194201

202+
#if CONFIG_IPC_MAJOR_4
203+
int mfcc_ipc_notification_init(struct processing_module *mod);
204+
205+
void mfcc_send_vad_notification(struct processing_module *mod, uint32_t val);
206+
207+
int mfcc_get_config(struct processing_module *mod,
208+
uint32_t config_id, uint32_t *data_offset_size,
209+
uint8_t *fragment, size_t fragment_size);
210+
211+
int mfcc_set_config(struct processing_module *mod, uint32_t config_id,
212+
enum module_cfg_fragment_position pos, uint32_t data_offset_size,
213+
const uint8_t *fragment, size_t fragment_size, uint8_t *response,
214+
size_t response_size);
215+
216+
#else
217+
static inline int mfcc_ipc_notification_init(struct processing_module *mod)
218+
{
219+
return 0;
220+
}
221+
222+
static inline void mfcc_send_vad_notification(struct processing_module *mod, uint32_t val)
223+
{
224+
}
225+
226+
static inline int mfcc_get_config(struct processing_module *mod,
227+
uint32_t config_id, uint32_t *data_offset_size,
228+
uint8_t *fragment, size_t fragment_size)
229+
{
230+
struct sof_ipc_ctrl_data *cdata = (struct sof_ipc_ctrl_data *)fragment;
231+
struct mfcc_comp_data *cd = module_get_private_data(mod);
232+
233+
return comp_data_blob_get_cmd(cd->model_handler, cdata, fragment_size);
234+
}
235+
236+
static inline int mfcc_set_config(struct processing_module *mod, uint32_t config_id,
237+
enum module_cfg_fragment_position pos, uint32_t data_offset_size,
238+
const uint8_t *fragment, size_t fragment_size, uint8_t *response,
239+
size_t response_size)
240+
{
241+
struct mfcc_comp_data *cd = module_get_private_data(mod);
242+
243+
return comp_data_blob_set(cd->model_handler, pos, data_offset_size,
244+
fragment, fragment_size);
245+
}
246+
#endif
247+
195248
#ifdef UNIT_TEST
196249
void sys_comp_module_mfcc_interface_init(void);
197250
#endif

tools/topology/topology2/platform/intel/sdw-dmic-audio-feature.conf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ Object.Pipeline.host-gateway-src-mfcc-capture [
2121
name "$SDW_DMIC_AUDIO_FEATURE_CAPTURE_PCM_NAME MFCC bytes"
2222
<include/components/mfcc/mel80.conf>
2323
}
24+
mixer."1" {
25+
name "$SDW_DMIC_AUDIO_FEATURE_CAPTURE_PCM_NAME MFCC VAD"
26+
Object.Base.channel.1 {
27+
name "fc"
28+
shift 0
29+
}
30+
Object.Base.ops.1 {
31+
name "ctl"
32+
info "volsw"
33+
#259 binds the mixer control to switch get/put handlers
34+
get 259
35+
put 259
36+
}
37+
max 1
38+
}
2439
}
2540
}
2641
}

tools/topology/topology2/platform/intel/sdw-jack-audio-feature.conf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ Object.Pipeline.host-gateway-src-mfcc-capture [
2121
name "$SDW_JACK_AUDIO_FEATURE_CAPTURE_PCM_NAME MFCC bytes"
2222
<include/components/mfcc/mel80.conf>
2323
}
24+
mixer."1" {
25+
name "$SDW_JACK_AUDIO_FEATURE_CAPTURE_PCM_NAME MFCC VAD"
26+
Object.Base.channel.1 {
27+
name "fc"
28+
shift 0
29+
}
30+
Object.Base.ops.1 {
31+
name "ctl"
32+
info "volsw"
33+
#259 binds the mixer control to switch get/put handlers
34+
get 259
35+
put 259
36+
}
37+
max 1
38+
}
2439
}
2540
}
2641
}

0 commit comments

Comments
 (0)