Skip to content

Commit 0da8107

Browse files
committed
audio: vad_gate: add Voice Activity Detection gate component
Add VAD gate audio component to pre-filter background silence and gate downstream WOV keyphrase detector pipelines when no voice energy is present. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
1 parent 9d4e279 commit 0da8107

6 files changed

Lines changed: 471 additions & 0 deletions

File tree

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

src/audio/vad_gate/vad_gate.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[[module.entry]]
2+
name = "VAD_GATE"
3+
uuid = UUIDREG_STR_VAD_GATE
4+
affinity_mask = "0x1"
5+
instance_count = "1"
6+
domain_types = "0"
7+
load_type = "0"
8+
module_type = "0xB"
9+
auto_start = "0"
10+
sched_caps = [1, 0x00008000]
11+
12+
REM # pin = [dir, type, sample rate, size, container, channel-cfg]
13+
pin = [0, 0, 0xfeef, 0xf, 0xf, 0x45ff,
14+
1, 0, 0xfeef, 0xf, 0xa, 0x45ff]
15+
16+
REM # mod_cfg [PAR_0 PAR_1 PAR_2 PAR_3 IS_BYTES CPS IBS OBS MOD_FLAGS CPC OBLS]
17+
mod_cfg = [0, 0, 0, 0, 14400, 1114000, 16, 16, 0, 0, 0]
18+
19+
index = __COUNTER__

0 commit comments

Comments
 (0)