Skip to content

Commit 0a30470

Browse files
lrgirdwoclaude
andcommitted
audio: add VAD gate component for multi-WOV WOV pipeline
Adds a new lightweight vad_gate module that inserts between the DMIC copier and the Mixin fan-out in the multi-WOV capture pipeline. The gate runs a first-order IIR energy estimator on each audio frame (same pattern as detect_test.c's activation tracker) and manages two states: SPEECH — voice onset detected (onset_frames consecutive frames above threshold); audio is passed through to the Mixin → KPB → WOV chain which begins buffering. SILENCE — energy drops for hangover_frames consecutive frames; the gate drains its DMIC input to prevent DAI DMA stall but returns PPL_STATUS_PATH_STOP so the downstream Mixin and all three KPB/WOV pipelines idle without consuming CPU. Tunable at runtime via IPC4 SET_LARGE_CONFIG (threshold, onset_frames, hangover_frames, energy_shift). Changes: - src/audio/vad_gate/vad_gate.c — new component - src/audio/vad_gate/CMakeLists.txt - src/audio/vad_gate/Kconfig - src/include/sof/audio/vad_gate.h — config struct + defaults - src/audio/Kconfig — CONFIG_COMP_VAD_GATE rsource - src/audio/CMakeLists.txt — conditional subdirectory - uuid-registry.txt — vad_gate UUID - include/components/vad-gate.conf — topology2 widget class - dmic-wov-multi.conf — pipeline 100: copier → vad-gate → mixin Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c3c7161 commit 0a30470

9 files changed

Lines changed: 489 additions & 3 deletions

File tree

src/audio/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD)
104104
if(CONFIG_COMP_UP_DOWN_MIXER)
105105
add_subdirectory(up_down_mixer)
106106
endif()
107+
if(CONFIG_COMP_VAD_GATE)
108+
add_subdirectory(vad_gate)
109+
endif()
107110
if(CONFIG_COMP_VOLUME)
108111
add_subdirectory(volume)
109112
endif()

src/audio/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ rsource "template/Kconfig"
172172
rsource "tensorflow/Kconfig"
173173
rsource "tone/Kconfig"
174174
rsource "up_down_mixer/Kconfig"
175+
rsource "vad_gate/Kconfig"
175176
rsource "volume/Kconfig"
176177
# --- End Kconfig Sources (alphabetical order) ---
177178

src/audio/vad_gate/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
add_local_sources(sof vad_gate.c)

src/audio/vad_gate/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
3+
config COMP_VAD_GATE
4+
bool "VAD gate component"
5+
depends on IPC_MAJOR_4
6+
help
7+
Select to build the VAD (Voice Activity Detection) gate component.
8+
The gate sits between the DMIC copier and the downstream Mixin in
9+
a WOV capture pipeline. During silence it drains the DMIC input
10+
and returns PPL_STATUS_PATH_STOP so KPB and WOV detectors idle.
11+
When voice onset is detected audio flows through and the downstream
12+
chain wakes and begins buffering.

src/audio/vad_gate/vad_gate.c

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
//
3+
// Copyright(c) 2024 Intel Corporation. All rights reserved.
4+
//
5+
// VAD Gate — lightweight voice-activity detector placed between the DMIC
6+
// copier and the downstream Mixin in a WOV pipeline.
7+
//
8+
// When the detected energy stays below the threshold for hangover_frames
9+
// consecutive frames (silence), the gate drains its input but returns
10+
// PPL_STATUS_PATH_STOP so the Mixin/KPB/WOV pipelines downstream do not
11+
// run. When voice is detected (onset_frames consecutive frames above the
12+
// threshold) the gate passes audio through and the downstream chain wakes.
13+
//
14+
// Energy estimator: first-order IIR on the peak |sample| amplitude per
15+
// processing period; same pattern as detect_test.c's activation tracker.
16+
17+
#include <sof/audio/buffer.h>
18+
#include <sof/audio/component.h>
19+
#include <sof/audio/format.h>
20+
#include <sof/audio/ipc-config.h>
21+
#include <sof/audio/pipeline.h>
22+
#include <sof/audio/vad_gate.h>
23+
#include <sof/common.h>
24+
#include <rtos/alloc.h>
25+
#include <rtos/init.h>
26+
#include <sof/lib/uuid.h>
27+
#include <sof/list.h>
28+
#include <sof/trace/trace.h>
29+
#include <sof/ut.h>
30+
#include <ipc/stream.h>
31+
#include <ipc/topology.h>
32+
#if CONFIG_IPC_MAJOR_4
33+
#include <ipc4/base-config.h>
34+
#endif
35+
#include <errno.h>
36+
#include <stdbool.h>
37+
#include <stdint.h>
38+
#include <string.h>
39+
40+
LOG_MODULE_REGISTER(vad_gate, CONFIG_SOF_LOG_LEVEL);
41+
42+
SOF_DEFINE_REG_UUID(vad_gate);
43+
DECLARE_TR_CTX(vad_gate_tr, SOF_UUID(vad_gate_uuid), LOG_LEVEL_INFO);
44+
45+
/* Private runtime data. */
46+
struct vad_gate_data {
47+
#if CONFIG_IPC_MAJOR_4
48+
struct ipc4_base_module_cfg base_cfg;
49+
#endif
50+
struct ipc4_vad_gate_config config;
51+
52+
/* IIR energy accumulator (same units as S32LE sample amplitude). */
53+
int32_t energy;
54+
55+
/* Debounce counters. */
56+
uint16_t speech_cnt;
57+
uint16_t silence_cnt;
58+
59+
bool vad_active;
60+
};
61+
62+
/* -------------------------------------------------------------------------
63+
* Energy estimation and VAD state machine
64+
* ---------------------------------------------------------------------- */
65+
66+
/* Per-period energy update; assumes single-channel S32LE (mono DMIC capture). */
67+
static void vad_update_energy(struct comp_dev *dev,
68+
struct audio_stream *s, uint32_t frames)
69+
{
70+
struct vad_gate_data *cd = comp_get_drvdata(dev);
71+
uint32_t i;
72+
bool above;
73+
74+
for (i = 0; i < frames; i++) {
75+
const int32_t *src = audio_stream_read_frag_s32(s, i);
76+
int32_t diff = abs(*src) - abs(cd->energy);
77+
cd->energy += diff >> cd->config.energy_shift;
78+
}
79+
80+
above = (cd->energy >= cd->config.threshold);
81+
if (above) {
82+
cd->silence_cnt = 0;
83+
if (++cd->speech_cnt >= cd->config.onset_frames && !cd->vad_active) {
84+
cd->vad_active = true;
85+
comp_info(dev, "vad_gate: SPEECH onset");
86+
}
87+
} else {
88+
cd->speech_cnt = 0;
89+
if (++cd->silence_cnt >= cd->config.hangover_frames && cd->vad_active) {
90+
cd->vad_active = false;
91+
comp_info(dev, "vad_gate: SILENCE hangover expired");
92+
}
93+
}
94+
}
95+
96+
/* -------------------------------------------------------------------------
97+
* Component lifecycle
98+
* ---------------------------------------------------------------------- */
99+
100+
static struct comp_dev *vad_gate_new(const struct comp_driver *drv,
101+
const struct comp_ipc_config *config,
102+
const void *spec)
103+
{
104+
struct comp_dev *dev;
105+
struct vad_gate_data *cd;
106+
107+
comp_cl_info(&drv->tctx, "vad_gate_new");
108+
109+
dev = comp_alloc(drv, sizeof(*dev));
110+
if (!dev)
111+
return NULL;
112+
dev->ipc_config = *config;
113+
114+
cd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*cd));
115+
if (!cd) {
116+
comp_free_device(dev);
117+
return NULL;
118+
}
119+
120+
#if CONFIG_IPC_MAJOR_4
121+
const struct ipc4_base_module_cfg *base_cfg = spec;
122+
memcpy_s(&cd->base_cfg, sizeof(cd->base_cfg), base_cfg, sizeof(*base_cfg));
123+
#endif
124+
125+
cd->config.threshold = VAD_DEFAULT_THRESHOLD;
126+
cd->config.onset_frames = VAD_DEFAULT_ONSET_FRAMES;
127+
cd->config.hangover_frames = VAD_DEFAULT_HANGOVER;
128+
cd->config.energy_shift = VAD_DEFAULT_ENERGY_SHIFT;
129+
130+
comp_set_drvdata(dev, cd);
131+
dev->direction = SOF_IPC_STREAM_CAPTURE;
132+
dev->direction_set = true;
133+
dev->state = COMP_STATE_READY;
134+
135+
return dev;
136+
}
137+
138+
static void vad_gate_free(struct comp_dev *dev)
139+
{
140+
comp_info(dev, "vad_gate_free");
141+
rfree(comp_get_drvdata(dev));
142+
comp_free_device(dev);
143+
}
144+
145+
static int vad_gate_prepare(struct comp_dev *dev)
146+
{
147+
struct vad_gate_data *cd = comp_get_drvdata(dev);
148+
149+
comp_info(dev, "vad_gate_prepare threshold=%d onset=%u hangover=%u",
150+
cd->config.threshold,
151+
cd->config.onset_frames,
152+
cd->config.hangover_frames);
153+
154+
cd->energy = 0;
155+
cd->speech_cnt = 0;
156+
cd->silence_cnt = 0;
157+
cd->vad_active = false;
158+
159+
return comp_set_state(dev, COMP_TRIGGER_PREPARE);
160+
}
161+
162+
static int vad_gate_reset(struct comp_dev *dev)
163+
{
164+
struct vad_gate_data *cd = comp_get_drvdata(dev);
165+
166+
comp_info(dev, "vad_gate_reset");
167+
168+
cd->energy = 0;
169+
cd->speech_cnt = 0;
170+
cd->silence_cnt = 0;
171+
cd->vad_active = false;
172+
173+
return comp_set_state(dev, COMP_TRIGGER_RESET);
174+
}
175+
176+
static int vad_gate_trigger(struct comp_dev *dev, int cmd)
177+
{
178+
comp_info(dev, "vad_gate_trigger cmd %d", cmd);
179+
return comp_set_state(dev, cmd);
180+
}
181+
182+
static int vad_gate_params(struct comp_dev *dev,
183+
struct sof_ipc_stream_params *params)
184+
{
185+
#if CONFIG_IPC_MAJOR_4
186+
struct vad_gate_data *cd = comp_get_drvdata(dev);
187+
188+
memset(params, 0, sizeof(*params));
189+
params->channels = cd->base_cfg.audio_fmt.channels_count;
190+
params->rate = cd->base_cfg.audio_fmt.sampling_frequency;
191+
params->sample_container_bytes = cd->base_cfg.audio_fmt.depth / 8;
192+
params->sample_valid_bytes =
193+
cd->base_cfg.audio_fmt.valid_bit_depth / 8;
194+
params->buffer_fmt = cd->base_cfg.audio_fmt.interleaving_style;
195+
params->buffer.size = cd->base_cfg.ibs;
196+
#endif
197+
return comp_verify_params(dev, 0, params);
198+
}
199+
200+
/* -------------------------------------------------------------------------
201+
* IPC4 large-config — runtime tuning of threshold, onset, hangover.
202+
* ---------------------------------------------------------------------- */
203+
204+
#if CONFIG_IPC_MAJOR_4
205+
static int vad_gate_set_large_config(struct comp_dev *dev,
206+
uint32_t param_id,
207+
bool first_block,
208+
bool last_block,
209+
uint32_t data_offset,
210+
const char *data)
211+
{
212+
struct vad_gate_data *cd = comp_get_drvdata(dev);
213+
214+
if (param_id != IPC4_VAD_GATE_SET_CONFIG)
215+
return -EINVAL;
216+
217+
if (data_offset < sizeof(struct ipc4_vad_gate_config))
218+
return -EINVAL;
219+
220+
const struct ipc4_vad_gate_config *cfg =
221+
(const struct ipc4_vad_gate_config *)data;
222+
223+
memcpy_s(&cd->config, sizeof(cd->config), cfg, sizeof(*cfg));
224+
225+
comp_info(dev, "vad_gate: config updated threshold=%d onset=%u hangover=%u shift=%u",
226+
cd->config.threshold,
227+
cd->config.onset_frames,
228+
cd->config.hangover_frames,
229+
cd->config.energy_shift);
230+
231+
return 0;
232+
}
233+
234+
static int vad_gate_get_attribute(struct comp_dev *dev,
235+
uint32_t type, void *value)
236+
{
237+
struct vad_gate_data *cd = comp_get_drvdata(dev);
238+
239+
if (type == COMP_ATTR_BASE_CONFIG) {
240+
*(struct ipc4_base_module_cfg *)value = cd->base_cfg;
241+
return 0;
242+
}
243+
return -EINVAL;
244+
}
245+
#endif /* CONFIG_IPC_MAJOR_4 */
246+
247+
/* -------------------------------------------------------------------------
248+
* copy() — main audio processing
249+
*
250+
* Always drains the source buffer to prevent DMIC DMA back-pressure.
251+
* Only forwards data to the sink (and returns 0) when VAD is active.
252+
* Returns PPL_STATUS_PATH_STOP during silence so downstream components idle.
253+
*
254+
* Assumes single-channel S32LE (mono DMIC capture at 16 kHz).
255+
* ---------------------------------------------------------------------- */
256+
static int vad_gate_copy(struct comp_dev *dev)
257+
{
258+
struct vad_gate_data *cd = comp_get_drvdata(dev);
259+
struct comp_buffer *source, *sink;
260+
uint32_t frame_bytes, frames, src_bytes;
261+
262+
source = comp_dev_get_first_data_producer(dev);
263+
sink = comp_dev_get_first_data_consumer(dev);
264+
265+
frame_bytes = audio_stream_frame_bytes(&source->stream);
266+
frames = audio_stream_get_avail_bytes(&source->stream) / frame_bytes;
267+
if (!frames)
268+
return PPL_STATUS_PATH_STOP;
269+
src_bytes = frames * frame_bytes;
270+
271+
buffer_stream_invalidate(source, src_bytes);
272+
vad_update_energy(dev, &source->stream, frames);
273+
274+
if (!cd->vad_active) {
275+
/* Drain input to keep DMIC DMA running during silence. */
276+
comp_update_buffer_consume(source, src_bytes);
277+
return PPL_STATUS_PATH_STOP;
278+
}
279+
280+
/* VAD active: limit frames to what sink can accept. */
281+
uint32_t sink_frame_bytes = audio_stream_frame_bytes(&sink->stream);
282+
uint32_t sink_frames = audio_stream_get_free_bytes(&sink->stream) / sink_frame_bytes;
283+
284+
if (sink_frames < frames)
285+
frames = sink_frames;
286+
if (!frames)
287+
return 0;
288+
289+
src_bytes = frames * frame_bytes;
290+
uint32_t sink_bytes = frames * sink_frame_bytes;
291+
292+
audio_stream_copy(&source->stream, 0, &sink->stream, 0,
293+
frames * audio_stream_channels(&source->stream));
294+
buffer_stream_writeback(sink, sink_bytes);
295+
comp_update_buffer_produce(sink, sink_bytes);
296+
comp_update_buffer_consume(source, src_bytes);
297+
298+
return 0;
299+
}
300+
301+
/* -------------------------------------------------------------------------
302+
* Component driver registration
303+
* ---------------------------------------------------------------------- */
304+
305+
static const struct comp_driver vad_gate_drv = {
306+
.type = SOF_COMP_NONE,
307+
.uid = SOF_RT_UUID(vad_gate_uuid),
308+
.tctx = &vad_gate_tr,
309+
.ops = {
310+
.create = vad_gate_new,
311+
.free = vad_gate_free,
312+
.params = vad_gate_params,
313+
.trigger = vad_gate_trigger,
314+
.copy = vad_gate_copy,
315+
.prepare = vad_gate_prepare,
316+
.reset = vad_gate_reset,
317+
#if CONFIG_IPC_MAJOR_4
318+
.set_large_config = vad_gate_set_large_config,
319+
.get_attribute = vad_gate_get_attribute,
320+
#endif
321+
},
322+
};
323+
324+
static SHARED_DATA struct comp_driver_info vad_gate_info = {
325+
.drv = &vad_gate_drv,
326+
};
327+
328+
UT_STATIC void sys_comp_vad_gate_init(void)
329+
{
330+
comp_register(platform_shared_get(&vad_gate_info,
331+
sizeof(vad_gate_info)));
332+
}
333+
334+
DECLARE_MODULE(sys_comp_vad_gate_init);
335+
SOF_MODULE_INIT(vad_gate, sys_comp_vad_gate_init);

0 commit comments

Comments
 (0)