Skip to content

kpb: add multi-KPB WOV arbiter for 3-keyword DMIC capture with VAD - #11022

Draft
lgirdwood wants to merge 9 commits into
thesofproject:mainfrom
lgirdwood:feature/wov-multi-kpb
Draft

kpb: add multi-KPB WOV arbiter for 3-keyword DMIC capture with VAD#11022
lgirdwood wants to merge 9 commits into
thesofproject:mainfrom
lgirdwood:feature/wov-multi-kpb

Conversation

@lgirdwood

@lgirdwoodlgirdwood commented Jul 22, 2026

Copy link
Copy Markdown
Member

EDIT now added test detection in each WoV module to wake on voice frequencies for male, female and children voice frequencies. i.e. each WoV will trigger on a different speaker type.

Adds a new WOV arbiter component and multi-KPB topology to support three simultaneous wake-word detectors sharing a single DMIC, with exclusive host PCM drain arbitration when any keyword fires.

New component: src/audio/wov_arbiter/

  • 3 input pins (one per KPB host-sink), 1 output pin (to host copier)
  • AMS subscriber for AMS_WOV_DETECT_MSG_UUID: activates the triggering slot and broadcasts WOV_CTRL PAUSE to sibling detectors
  • On trigger(STOP): deactivates and broadcasts WOV_CTRL RESUME so all detectors return to listening
  • Idle inputs are silently drained to prevent buffer stalls
  • UUID 4a5b6c7d-8e9f-4a1b-2c3d-4e5f60718293 registered in uuid-registry

AMS additions (ams_msg.h):

  • AMS_WOV_DETECT_MSG_UUID: detector -> arbiter on keyword detect
  • AMS_WOV_CTRL_MSG_UUID: arbiter -> detectors for pause/resume control

detect_test extensions:

  • IPC4_DETECT_TEST_SET_WOV_SLOT large-config param assigns a slot id (0-2)
  • On detection: also sends AMS_WOV_DETECT_MSG_UUID with slot_id to arbiter
  • AMS consumer for WOV_CTRL: sets paused flag; copy() drains buffer without running the detector when paused
  • The existing energy-threshold detector fires on a hand clap, making detect_test a ready-to-use lab test stub for this topology

Topology: tools/topology/topology2/platform/intel/dmic-wov-multi.conf

  • Pipeline 100 (Core 0): DMIC Copier -> Mixin (fan-out to 3 Mixouts)
  • Pipelines 101-102 (Core 0): Mixout -> KPB -> detect_test (slots 0-1)
  • Pipeline 103 (Core 1): Mixout -> KPB -> detect_test (slot 2)
  • Pipeline 104 (Core 0): WOV Arbiter -> Host Copier (single PCM stream)
  • KPB output_pin_binding: pin 0 -> detector (sel_sink), pin 1 -> arbiter (host_sink)

Requires: CONFIG_COMP_WOV_ARBITER, CONFIG_COMP_KPB, CONFIG_COMP_MIXIN_MIXOUT,
CONFIG_AMS, CONFIG_COMP_KWD_DETECT

@lgirdwoodlgirdwood left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some opens.

Comment threadsrc/audio/wov_arbiter/wov_arbiter.c Outdated
Comment threadsrc/audio/wov_arbiter/wov_arbiter.c
Comment threadsrc/audio/CMakeLists.txt Outdated
Comment threadsrc/audio/Kconfig Outdated
Comment threadsrc/include/sof/audio/wov_arbiter.h Outdated
Comment threaduuid-registry.txt Outdated
@lgirdwood
lgirdwoodforce-pushed the feature/wov-multi-kpb branch from 49f3dc6 to c3c7161CompareJuly 22, 2026 13:13
@lgirdwoodlgirdwood changed the title WIP: audio/topology: add multi-KPB WOV arbiter for 3-keyword DMIC captureWIP: audio/topology: add multi-KPB WOV arbiter for 3-keyword DMIC capture with VADJul 23, 2026
@lgirdwood
lgirdwoodforce-pushed the feature/wov-multi-kpb branch 2 times, most recently from 0f6f889 to 3af37c0CompareJuly 29, 2026 15:22
@lgirdwood
lgirdwood marked this pull request as ready for review July 29, 2026 15:23
@lgirdwoodlgirdwood changed the title WIP: audio/topology: add multi-KPB WOV arbiter for 3-keyword DMIC capture with VADkpb: add multi-KPB WOV arbiter for 3-keyword DMIC capture with VADJul 29, 2026

@lgirdwoodlgirdwood left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs more inline comments around code blocks and important statements can also do with more context in each commit message. I would put the README.md patch first to give reviewer context first before further review.

Comment threadsrc/audio/kpb.c
Comment threadsrc/audio/kpb.c Outdated
}
#endif /* CONFIG_IPC_MAJOR_4 */

if (!kpb->sel_sink || !kpb->host_sink) {

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets comment what this block is doing and why.

Comment threadsrc/audio/kpb.c
comp_err(dev, "could not find sink: sel_sink %p",
kpb->sel_sink);
ret = -EIO;
} else {

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto re comments.

#UUID: D8218443-5FF3-4A4C-B388-6CFE07B9562E
uuid "43:84:21:d8:f3:5f:4c:4a:b3:88:6c:fe:07:b9:56:2e"
#UUID: a8a0cb32-4a77-4db1-85c753d7ee07bce6 (kpb4 static module)
uuid "32:cb:a0:a8:77:4a:b1:4d:85:c7:53:d7:ee:07:bc:e6"

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need a new UUID

if (frames_to_copy + start_frame > mixout_data->mixed_frames)
mixout_data->mixed_frames = frames_to_copy + start_frame;

if (mixout_mod && mixout_mod->dev)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need comment for what we are checking and why.

Comment threadsrc/audio/wov_arbiter/wov_arbiter.c Outdated
@@ -0,0 +1,455 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2024 Intel Corporation. All rights reserved.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plus the all rights reserved bit should be removed...

Comment threadsrc/audio/wov_arbiter/wov_arbiter.c Outdated
*/
uint8_t active_slot;

#if CONFIG_AMS

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should actually depend on AMS in teh Kconfig and then we can remove all these ifdefs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, but I'm wondering whether we should/need to depend on AMS. It is currently disabled in many Intel configs.

Comment on lines +107 to +109
if(CONFIG_COMP_VAD_GATE)
add_subdirectory(vad_gate)
endif()

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be in the vad gate patch.

Comment threaduuid-registry.txt
e93326d8-0d14-4bf0-bcb9e063d3d80136 twb_sched
42f8060c-832f-4dbf-b24751e961997b34 up_down_mixer
6f6b6f4b-6f73-7466-20e1e62b9779f003 userspace_proxy
8c7d6e5f-4a3b-2c1d-0e9f8a7b6c5d4e3f vad_gate

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be in vad gate patch

Comment on lines +43 to +45
dai_type "DMIC"
copier_type "DMIC"
stream_name $DMIC1_NAME

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isnt this passed in via build for the target ?

Comment threadsrc/audio/kpb.c Outdated

if (sink_buf_id == buf_id) {
struct comp_dev *sc = comp_buffer_get_sink_component(sink);
comp_err(dev, "kpb_bind: buf_id=%d sink_comp=0x%x -> %s",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this an error condition?

Comment threadsrc/audio/kpb.c Outdated
comp_dev_for_each_consumer(dev, sink) {
struct comp_dev *sc = comp_buffer_get_sink_component(sink);
if (sc) {
comp_err(dev, "kpb consumer in bsink_list: comp_id=0x%x type=%d sink_buf=%p",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace comp_err() everywhere in this commit / PR

Comment threadsrc/audio/kpb.c Outdated
if (sc) {
comp_err(dev, "kpb consumer in bsink_list: comp_id=0x%x type=%d sink_buf=%p",
dev_comp_id(sc), sc->drv ? sc->drv->type : -1, sink);
if (dev_comp_id(sc) != 0x10)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is 0x10?

Comment threadsrc/audio/kpb.c Outdated
kpb->sel_sink = sink;
else
kpb->host_sink = sink;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the loop should run exactly twice to only set each of these pointers once? Should we check or at least add a comment?

Comment threadsrc/audio/kpb.c
if (!kpb->sel_sink)
kpb->sel_sink = sink;
else if (!kpb->host_sink)
kpb->host_sink = sink;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here - shouldn't be overwriting, right?

Comment threadsrc/samples/audio/detect_test.c Outdated
}

LOG_MODULE_REGISTER(kd_test, CONFIG_SOF_LOG_LEVEL);
LOG_MODULE_REGISTER(kd_test, LOG_LEVEL_INF);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switch to CONFIG_SOF_LOG_LEVEL for consistency ?

Comment threadsrc/samples/audio/detect_test.c Outdated
struct comp_data *cd = comp_get_drvdata(dev);

comp_info(dev, "entry");
comp_err(dev, "notify_host: WOV module_id=0x%x instance_id=0x%x slot_id=%u detected",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comp_err() wrong

uint8_t slot_id = cd->wov_slot_id;
if (slot_id == WOV_SLOT_INVALID) {
if (dev->pipeline && dev->pipeline->pipeline_id >= 101 && dev->pipeline->pipeline_id <= 103)
slot_id = dev->pipeline->pipeline_id - 101;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these magic numbers?

freq_match = (freq_hz >= 80 && freq_hz <= 170);
voice_type = "MALE";
break;
case 1: /* Female Voice Range: 175 - 270 Hz */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so 172Hz will be "nobody?" Or are frequencies detected with 5Hz granularity?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typical human frequency bands from internet, so yes - nobody.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lgirdwood sorry, I cannot imagine this being right. Typical - yes. But I cannot imagine no man / woman / child being able to intersect frequencies. So, if someone can speak at 170Hz and somebody else can speak at 175Hz, I'm pretty sure it's humanly possible to speak at 172Hz too.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its taken from some internet references, I'm not an expert on the subject so will leave it as is. Its not that important for the test.

else if (ppl_id == 102 || ppl_id == 3)
cd->wov_slot_id = 1;
else if (ppl_id == 103 || ppl_id == 4)
cd->wov_slot_id = 2;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these numbers look familiar

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a multi-slot Wake-on-Voice (WoV) capture design that allows three concurrent detectors to share a single capture source, with a new firmware “arbiter” component selecting which KPB drain feeds a single host PCM stream. It also adds a VAD gate component intended to suppress downstream processing during silence, and extends the detect_test sample to coordinate with the arbiter via AMS.

Changes:

  • Add new firmware components: wov_arbiter (multi-input drain routing + AMS control) and vad_gate (energy-based gating).
  • Add/adjust Topology2 definitions for multi-slot WoV, plus wiring/includes updates.
  • Extend detect_test for multi-slot behavior and AMS messaging; adjust rimage/module TOMLs for additional instances.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 12 comments.

Show a summary per file
FileDescription
uuid-registry.txtRegisters new UUIDs for vad_gate and wov_arbiter.
tools/topology/topology2/sof-hda-generic.confAdds includes for WoV/VAD/KPB-related topology snippets.
tools/topology/topology2/sof-hda-generic-wov-manifest.confNew manifest-style topology source including WoV/VAD/KPB assets.
tools/topology/topology2/sof-hda-generic-manifest.confNew manifest-style topology source for HDA generic.
tools/topology/topology2/sof-hda-generic-4ch.confNew manifest-style topology source variant.
tools/topology/topology2/platform/intel/dmic-wov-multi.confNew multi-detector WoV topology definition (DMIC multi-slot).
tools/topology/topology2/platform/intel/dmic-generic.confSwitches/extends DMIC includes to pull in the new multi-WoV topology.
tools/topology/topology2/include/pipelines/cavs/wov-detect.confAdjusts pipeline class formatting metadata.
tools/topology/topology2/include/pipelines/cavs/dai-kpb-be.confModifies the DAI→KPB pipeline class defaults and routing source.
tools/topology/topology2/include/components/wov.confChanges WoV control naming to be index-qualified.
tools/topology/topology2/include/components/wov-arbiter.confNew Topology2 widget class for the arbiter component.
tools/topology/topology2/include/components/vad-gate.confNew Topology2 widget class for the VAD gate component.
tools/topology/topology2/include/components/kpb.confSwitches KPB widget UUID to the kpb4 static module UUID.
tools/topology/topology2/dmic-wov-multi-manifest.confNew manifest-style topology for the multi-slot WoV design.
tools/rimage/config/tgl.toml.hUpdates rimage TOML include set for new modules.
src/samples/audio/detect_test.tomlIncreases instance count for the detector sample module.
src/samples/audio/detect_test.cExtends detect_test with slot awareness, AMS integration, and new detection behavior.
src/include/sof/lib/ams_msg.hAdds new AMS UUIDs and payload structs for arbiter↔detector control.
src/include/sof/audio/wov_arbiter.hNew public header for arbiter constants/IPC param IDs.
src/include/sof/audio/vad_gate.hNew public header for VAD gate runtime config/IPC param IDs.
src/include/sof/audio/kpb.hAdds a new detector lookup prototype.
src/include/ipc4/detect_test.hAdds IPC4 large-config param for setting WoV slot ID.
src/audio/wov_arbiter/wov_arbiter.tomlNew module metadata for rimage/LLEXT.
src/audio/wov_arbiter/wov_arbiter.cNew arbiter component implementation.
src/audio/wov_arbiter/README.mdNew architecture and testing documentation for multi-slot WoV.
src/audio/wov_arbiter/CMakeLists.txtBuilds the new arbiter component.
src/audio/vad_gate/vad_gate.tomlNew module metadata for rimage/LLEXT.
src/audio/vad_gate/vad_gate.cNew VAD gate component implementation.
src/audio/vad_gate/KconfigKconfig option for building the VAD gate component.
src/audio/vad_gate/CMakeLists.txtBuilds the new VAD gate component.
src/audio/mixin_mixout/mixin_mixout.cModifies scheduling/state/copy behavior in mixin/mixout processing.
src/audio/kpb.tomlIncreases instance count for KPB module.
src/audio/kpb.cModifies KPB binding/prepare/copy behavior and downstream triggering.
src/audio/KconfigAdds Kconfig entry for the new arbiter component and sources VAD gate Kconfig.
src/audio/CMakeLists.txtAdds build integration for vad_gate and wov_arbiter.
app/prj.confEnables samples / sample keyphrase in the app config.
app/debug_overlay.confEnables fatal breadcrumb recording in debug overlay.
app/boards/intel_adsp_cavs25.confEnables new components and sample config on the cavs25 board.
Suppressed comments (2)

src/audio/mixin_mixout/mixin_mixout.c:506

  • Calling comp_copy() on other components from inside mixin_process() is likely to cause re-entrancy/recursion and breaks the normal scheduler-driven copy order. This should be removed and left to the pipeline scheduler.
 if (mixout_mod && mixout_mod->dev)
comp_copy(mixout_mod->dev);

src/audio/mixin_mixout/mixin_mixout.c:607

  • mixout_process() manually triggers comp_copy() on the downstream sink component (e.g., KPB). This can cause recursive copy calls and violates the scheduler’s ownership of graph execution order.
 comp_err(dev, "mixout_process: sink_buf=0x%x produced=%u bytes, triggering kpb=0x%x",
sink_buf, bytes_to_produce, dev_comp_id(sink_comp));
comp_copy(sink_comp);

Comment threaduuid-registry.txt
e93326d8-0d14-4bf0-bcb9e063d3d80136 twb_sched
42f8060c-832f-4dbf-b24751e961997b34 up_down_mixer
6f6b6f4b-6f73-7466-20e1e62b9779f003 userspace_proxy
8c7d6e5f-4a3b-2c1d-0e9f8a7b6c5d4e3f vad_gate
Comment threaduuid-registry.txt
1028070e-04e8-46ab-8d8110a0116ce738 wait
d944281a-afe9-4695-a043d7f62b89538e waves
13c8bc59-c4fa-4ad1-b93ace97cd30acc7 wdt
4a5b6c7d-8e9f-4a1b-2c3d4e5f60718293 wov_arbiter
#endif

#ifdef CONFIG_SAMPLE_KEYPHRASE
#include <samples/audio/detect_test.toml>
<vendor-token.conf>
<tokens.conf>
<virtual.conf>
<vad-gate.conf>
Comment on lines +49 to +50
KWD_TEST_UUID "1f:d5:a8:eb:27:78:b5:47:ee:82:de:6e:77:43:af:67"
WOV_UUID "1f:d5:a8:eb:27:78:b5:47:ee:82:de:6e:77:43:af:67"
Comment on lines 348 to 350
if (mixout->state != COMP_STATE_ACTIVE)
mixout->state = COMP_STATE_ACTIVE;

Comment threadsrc/audio/kpb.c
Comment on lines +1378 to +1385
struct comp_dev *wov_comp = sink ? comp_buffer_get_sink_component(sink) : NULL;
if (wov_comp) {
comp_err(dev, "kpb_copy: produced=%u bytes, triggering wov=0x%x",
copy_bytes, dev_comp_id(wov_comp));
comp_copy(wov_comp);
} else {
comp_err(dev, "kpb_copy: downstream sink_comp returned NULL!");
}
Comment threadsrc/samples/audio/detect_test.c Outdated
}

LOG_MODULE_REGISTER(kd_test, CONFIG_SOF_LOG_LEVEL);
LOG_MODULE_REGISTER(kd_test, LOG_LEVEL_INF);
Comment threadsrc/samples/audio/detect_test.c Outdated
Comment on lines +170 to +171
comp_err(dev, "notify_host: WOV module_id=0x%x instance_id=0x%x slot_id=%u detected",
dev_comp_id(dev) >> 16, dev_comp_id(dev) & 0xffff, cd->wov_slot_id);
Comment on lines 71 to +79
static const struct comp_driver comp_keyword;
static struct comp_dev *wov_detect_devs[256];

struct comp_dev *get_wov_detector_comp(uint32_t ppl_id)
{
if (ppl_id < 256)
return wov_detect_devs[ppl_id];
return NULL;
}

@kv2019ikv2019i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments inline

continue;
}
if (mixout->state != COMP_STATE_ACTIVE)
mixout->state = COMP_STATE_ACTIVE;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to revert existing functionality that on purpose skipped non-active mixouts. The removed comment states non-active mixouts might not be configured, making this even more unsafe.

sink = comp_dev_get_first_data_consumer(dev);

frame_bytes = audio_stream_frame_bytes(&source->stream);
frames = audio_stream_get_avail_bytes(&source->stream) / frame_bytes;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to se the new pipeline2.0 style source/sink interface from the start (like the flurry of recent PRs to convert existing components).

Comment threadsrc/audio/vad_gate/vad_gate.c Outdated
@@ -0,0 +1,344 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2024 Intel Corporation. All rights reserved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copyright statement needs update

const int32_t *src = audio_stream_read_frag_s32(s, i);
int32_t diff = abs(*src) - abs(cd->energy);
cd->energy += diff >> cd->config.energy_shift;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite simple (albeit very cheap to compute). We do have a bit more logic in MFCC VAD with noise floor tracking already (#10782 ).

Comment threadsrc/audio/wov_arbiter/wov_arbiter.c Outdated
@@ -0,0 +1,455 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright(c) 2024 Intel Corporation. All rights reserved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plus the all rights reserved bit should be removed...

Comment threadsrc/audio/wov_arbiter/wov_arbiter.c Outdated
*/
uint8_t active_slot;

#if CONFIG_AMS

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, but I'm wondering whether we should/need to depend on AMS. It is currently disabled in many Intel configs.

@lgirdwood
lgirdwoodforce-pushed the feature/wov-multi-kpb branch from 3af37c0 to a79115bCompareAugust 9, 2026 11:12
Comment threadsrc/audio/kpb.c
}
}

kpb_change_state(kpb, KPB_STATE_RUN);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kpb enters run too early ? another run L1012
should we check after all sink prepare & its check completed ?

Comment threadsrc/samples/audio/detect_test.c Outdated
}

LOG_MODULE_REGISTER(kd_test, CONFIG_SOF_LOG_LEVEL);
LOG_MODULE_REGISTER(kd_test, LOG_LEVEL_INF);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switch to CONFIG_SOF_LOG_LEVEL for consistency ?

@lgirdwood
lgirdwoodforce-pushed the feature/wov-multi-kpb branch from a79115b to f45334fCompareAugust 11, 2026 13:04

@lgirdwoodlgirdwood left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please comment every new code block

Comment threadsrc/audio/kpb.c
Comment on lines +997 to +999
sink_params.sample_container_bytes = 4;
sink_params.sample_valid_bytes = 4;
sink_params.frame_fmt = SOF_IPC_FRAME_S32_LE;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should come from the topology for kpb object

Comment threadsrc/audio/kpb.c Outdated
Comment on lines +911 to +920
if (sink_comp && sink_comp->state == COMP_STATE_INIT) {
struct sof_ipc_stream_params sink_params;
memset_s(&sink_params, sizeof(sink_params), 0, sizeof(sink_params));
sink_params.channels = kpb->config.channels ? kpb->config.channels : 2;
sink_params.rate = kpb->config.sampling_freq ? kpb->config.sampling_freq : 16000;
sink_params.sample_container_bytes = 4;
sink_params.sample_valid_bytes = 4;
sink_params.frame_fmt = SOF_IPC_FRAME_S32_LE;
comp_params(sink_comp, &sink_params);
comp_prepare(sink_comp);

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like teh same code block as line 992, we should make this a statice line if its doing teh same thing as 992, cooment it and also get config data from topology rather than hard code.

Comment threadsrc/audio/kpb.c
ret = -EBUSY;
if (kpb->host_sink) {
kpb_change_state(kpb, KPB_STATE_RESETTING);
ret = -EBUSY;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we inline comment when reset will be done since we break here.

Comment threadsrc/audio/kpb.c
Comment on lines -1243 to -1249
/* Discard data if sink is not active */
if (comp_buffer_get_sink_component(sink)->state != COMP_STATE_ACTIVE) {
copy_bytes = audio_stream_get_avail_bytes(&source->stream);
comp_update_buffer_consume(source, copy_bytes);
comp_dbg(dev, "KD not active, dropping %zu bytes...", copy_bytes);
break;
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to drop this, if downstream is not active then this seems like the right thing todo.

Comment threadsrc/audio/kpb.c
Comment on lines -2371 to +2485
kpb_clear_history_buffer(buff);

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we no longer need to clear ?

Comment on lines +29 to +35
#define AMS_WOV_DETECT_MSG_UUID { 0xc3, 0xd7, 0xe8, 0x41, 0x12, 0xf0, 0x4e, \
0x8a, 0xb9, 0x01, 0x5a, 0x6b, 0x7c, 0x8d, \
0x9e, 0x0f }

#define AMS_WOV_CTRL_MSG_UUID { 0xf1, 0xe2, 0xd3, 0xc4, 0xb5, 0xa6, 0x47, \
0x89, 0x8a, 0xce, 0x12, 0x34, 0x56, 0x78, \
0x90, 0xab }

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are the Zephyr options here instead of AMS ? i.e. what maps at feature level to AMS and also works well in userspace context

Comment on lines +50 to +52
#if CONFIG_IPC_MAJOR_4
struct ipc4_base_module_cfg base_cfg;
#endif

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets make this IPC4 only via Kconfig and remove the ifdefs

Comment on lines +5 to +11
# to three KPB instances each fronted by a detect_test WOV detector.
# When any detector fires, the WOV arbiter drains that KPB to the host PCM
# capture stream and pauses the remaining two via AMS.
#
# Pipeline IDs
# 100 DAI pipeline : HDA copier → VAD Gate → Mixin
# 101 KPB pipeline : Mixout_0 → KPB_0 → WOV_0 (Core 0)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to be "feature" topologies and they should be able to use any capture endpoint e.g. SDW DMIC, PCM, DMIC, I2S and HDA Mics

Comment on lines +890 to +894

After `arecord` opens `hw:0,11` (pipeline prepare):

```
kd_test.test_keyword_prepare: comp:4 0x2000d kd_dp thread started for slot 2 (pinned to core 1)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we include example command to generate test tones that can be used to test the WOV.

Comment on lines +6 to +7
running on the DSP. Each detector has its own Keyphrase Buffer (KPB) that continuously records
a pre-roll window (6 seconds on TigerLake, 2.1 seconds on other platforms). When any detector

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we just have one KPB now ?

@lgirdwood
lgirdwood marked this pull request as draft August 11, 2026 14:31
@lgirdwood

Copy link
Copy Markdown
MemberAuthor

Please also add a enum kcontrol that will be RO and volatile and will reflect the ID of the WOV that triggered the wake word.

@lgirdwood
lgirdwoodforce-pushed the feature/wov-multi-kpb branch from f45334f to 9458dbdCompareAugust 11, 2026 17:34
Allow multiple downstream WOV detector pipelines to share one KPB
instance by extending the KPB to support a sel_sink drain path alongside
the existing dedicated host_sink path.
Key changes:
- Add sel_sink field to kpb_data to track the downstream WOV detector
sink; kpb_set_sink() assigns it from the component bind call.
- kpb_init_draining: when host_sink is NULL (multi-KPB WOV topology
with no dedicated PCM capture), redirect the pre-roll drain through
sel_sink so that history reaches the wov_arbiter and onward to the
host copier. Initialise host_period_size from sel_sink stream geometry
when not already set. Skip pausing the selector component when
sel_sink is the active drain path.
- kpb_init_draining: cap drain_req to the actual buffered amount instead
of aborting when less history is available than requested (partial
pre-roll is better than none).
- kpb_reset: add immediate-reset path when host_sink==NULL in
BUFFERING/DRAINING state; the LL scheduler is gone after STOP so
the async-EBUSY path can never complete.
- kpb_init_draining: guard against NULL host_sink to prevent NULL deref
on WOV-only instances.
- Fix fallback sink assignment in kpb_copy: use && instead of || so the
fallback only fires when BOTH sel_sink and host_sink are NULL.
- Raise KPB_MAX_BUFF_TIME to 6000 ms; clear HOST_WAKEUP_TIME (no extra
delay needed with direct sel_sink routing).
- kpb.conf: expose host_sink_index parameter for topology binding.
- Reduce log noise: demote two comp_err to comp_dbg in the RUN copy path.
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add a lightweight VAD gate component that sits between the DAI copier
and the mixin fan-out in the WOV capture pipeline. It measures per-frame
signal energy and suppresses propagation of near-silence frames to the
downstream KPB/detector branches, preventing unnecessary wakeups and
reducing idle CPU load.
The gate passes frames when the mean absolute sample value exceeds a
configurable threshold (VAD_THRESHOLD, default 0 = bypass for testing).
Suppressed frames are replaced with PPL_STATUS_PATH_STOP so the LL
scheduler can idle the downstream pipelines.
Kconfig: CONFIG_COMP_VAD_GATE (depends on IPC_MAJOR_4).
Topology: vad-gate.conf widget class for topology2.
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add two AMS message UUIDs and their payload structures for the multi-slot
WOV arbitration protocol:
AMS_WOV_DETECT_MSG_UUID — sent from a keyword detector to the
wov_arbiter when a keyword is confirmed. Payload: wov_detect_payload
carrying the slot_id (0..N-1) of the detector that fired.
AMS_WOV_CTRL_MSG_UUID — sent from the wov_arbiter to all registered
keyword detectors. Payload: wov_ctrl_payload carrying a cmd field
(WOV_CTRL_CMD_PAUSE or WOV_CTRL_CMD_RESUME) and the active_slot
(WOV_ARB_NO_ACTIVE=0xff on RESUME to address all slots).
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add the wov_arbiter audio component. It receives audio from up to
WOV_ARB_MAX_SLOTS (3) KPB host-sink pins and routes exactly one slot's
audio to the single host-copier downstream of it. Before any keyword
fires the arbiter fills the host sink with silence (memset zero). When
any detector sends an AMS_WOV_DETECT message the arbiter activates that
slot (first-wins), broadcasts AMS_WOV_CTRL PAUSE to all other detectors,
and starts forwarding KPB pre-roll + live audio to the host. On stream
STOP or PAUSE the arbiter resets to NO_ACTIVE and broadcasts RESUME so
all detectors restart listening.
IPC4 debug parameter param_id=1 (IPC4_WOV_ARB_SET_ACTIVE_SLOT) allows
forcing a slot active without AMS, useful for lab testing.
The IBS/OBS is set to 32 bytes (matching the 1 ms DMIC LL period).
The arbiter fills exactly sink_free bytes on every copy to avoid host
copier underruns during the pre-detection silence phase.
Kconfig: CONFIG_COMP_WOV_ARBITER (auto-selects CONFIG_AMS).
UUID: 4a5b6c7d-8e9f-4a1b-2c3d-4e5f60718293.
Topology: wov-arbiter.conf widget class for topology2.
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Enable CONFIG_COMP_WOV_ARBITER, CONFIG_COMP_VAD_GATE, CONFIG_COMP_KPB,
CONFIG_SAMPLES, and CONFIG_SAMPLE_KEYPHRASE on the intel_adsp_cavs25
board (TigerLake, CAVS2.5) to include the multi-slot WOV pipeline in
default firmware builds for that platform.
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Extend detect_test (the sample keyword detector) to participate in the
multi-slot WOV arbitration system coordinated by wov_arbiter.
Slot assignment:
A static wov_slot_id is derived from the pipeline ID at component
creation time (pipeline 101/1 → slot 0, 102/3 → slot 1, 103/4 → slot 2).
The slot ID can be overridden via IPC4 LARGE_CONFIG_SET param_id=4
(IPC4_DETECT_TEST_SET_WOV_SLOT).
DP thread batching:
Each slot runs its own Zephyr k_thread at K_PRIO_PREEMPT(12) with a
4096-byte stack. The LL copy path accumulates 320-frame (20 ms at
16 kHz) S16_LE samples into a double-buffer and gives a semaphore when
a batch is ready. The DP thread wakes, runs the detection algorithm
on the completed buffer, and signals the LL thread to switch sides.
Slot 2 is additionally pinned to DSP Core 1 via k_thread_cpu_pin().
Threads are started in prepare() and stopped in reset() + free().
AMS integration:
On detection, detect_test_notify() sends:
1. SOF_IPC4_NOTIFY_PHRASE_DETECTED IPC4 notification to the host
(word_id = wov_slot_id).
2. AMS_KPD_MSG to the KPB to initiate pre-roll drain on host_sink.
3. AMS_WOV_DETECT_MSG to the wov_arbiter.
The component registers as a consumer of AMS_WOV_CTRL_MSG and responds
to PAUSE (stops detecting) and RESUME (resets cd->detected, resumes).
Rotating auto-trigger (test mode):
When CONFIG_AMS is enabled a per-slot auto-trigger fires after the
first 320-frame DP batch if this slot matches the current session target
(kd_test_session % KD_MAX_SLOTS). kd_test_session is incremented in
reset() when slot 0 resets, so each arecord session cycles through
slots 0 → 1 → 2 → 0 without a firmware reload.
Audio pass-through:
detect_test.copy() forwards source audio to the downstream
wov-arbiter sink so the real-time data path remains live both before
and after detection.
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Add a standalone three-slot WOV topology (dmic-wov-multi.conf /
dmic-wov-multi-manifest.conf) driven by a single DMIC input with:
Pipeline 100 (Core 0): DAI Copier → vad_gate → mixin (1→3 fan-out)
Pipeline 101 (Core 0): mixout → kpb → detect_test (slot 0, Male)
Pipeline 102 (Core 0): mixout → kpb → detect_test (slot 1, Female)
Pipeline 103 (Core 1): mixout → kpb → detect_test (slot 2, Child)
Pipeline 104 (Core 0): wov_arbiter → host-copier (PCM 11, hw:0,11)
Each KPB has two output pins: sel_sink (to the detector) and host_sink
(to the arbiter). When a detector fires, it switches KPB to drain via
host_sink; the arbiter routes that slot's audio to the host copier.
Also included:
sof-hda-generic-wov-manifest.conf — build entry point for the WOV
topology variant (includes dmic-wov-multi.conf via INCLUDE_WOV).
Bug fixes to shared topology2 infrastructure:
wov.conf: rename wov_init → wov_init_$index so multiple wov widget
instances do not share a control name.
dai-kpb-be.conf: add dai_type/copier_type/stream_name fields to the
dai-copier widget; fix channel config for 4-channel DMIC; source
route now uses $DMIC_WOV_DAI_PIPELINE_SRC for flexibility.
wov-detect.conf: add num_input/output_audio_formats=1 to the micsel
widget (mandatory attribute missing from the original class).
sof-hda-generic.conf: add WOV widget class includes (dai-kpb-be,
wov-detect, vad-gate, kpb, wov, pin-binding helpers) so the generic
HDA topology can instantiate WOV pipelines when INCLUDE_WOV is set;
remove duplicate vad-gate.conf include.
tgl.toml.h: restore CONFIG_SAMPLE_KEYPHRASE guard on detect_test.toml;
add CONFIG_COMP_VAD_GATE and CONFIG_COMP_WOV_ARBITER guards on the
new vad_gate/wov_arbiter toml includes.
Compile with:
alsatplg -I tools/topology/topology2 -p \
-c tools/topology/topology2/dmic-wov-multi-manifest.conf \
-o sof-hda-generic-wov.tplg
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Document the multi-slot WOV subsystem comprehensively:
Architecture:
- Component graph mermaid diagram showing all 5 pipelines (100-104),
their component chains, and the AMS message flows.
- Key design points table (format, pre-roll duration, slot count,
arbitration policy, core affinity, PCM device).
Signal processing flow:
- LL thread flowchart: HW DMA → DAI → VAD gate → mixin → mixout →
KPB → detect_test accumulation → k_sem_give.
- DP thread flowchart: semaphore wake → algorithm → notify or
auto-trigger (test mode).
- KPB drain sequence: AMS → sel_sink switch → ring drain → live audio.
Arbiter state machine:
- stateDiagram-v2 with Idle/Active transitions and first-wins guard.
- Routing logic table (silence vs active slot vs idle slot output).
AMS inter-module messaging:
- Message catalog with UUID constants, directions, payload structs.
- Full detect-to-drain sequenceDiagram from DMIC capture to host close.
Firmware API reference:
- IPC4 LARGE_CONFIG_SET parameter tables for detect_test and wov_arbiter.
- AMS producer/consumer registration code examples.
- detect_test_notify() three-step contract.
- DP thread ping-pong buffer field semantics.
Linux host API:
- ALSA PCM capture (hw:0,11) via arecord and libasound.
- snd_ctl voice detection notification polling in userspace.
- Capture timing model (silence → pre-roll → live audio transition).
- IPC4 slot override via sof-ctl.
Adding a new WOV algorithm:
- Approach A: modify detect_test.c directly (prototype path).
- Approach B: new native SOF module with full skeleton (AMS reg,
IPC4 new/prepare/copy/notify, Kconfig, CMakeLists, UUID registration).
- Approach C: IADK/LLEXT loadable binary for third-party IP.
Topology: build and deploy guide:
- Source layout, alsatplg compile command, generic deploy instructions.
- Firmware build and deploy (generic build dir, generic DUT SCP).
- Topology config reference table. Adding a fourth slot.
Build system: Kconfig minimum set, module UUID catalog.
Testing and verification:
- Three-slot rotating auto-trigger test with Python RMS verification.
- Real-audio frequency sweep test.
- Firmware trace (mtrace) reading procedure and expected log events.
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
Expose the new dmic-wov-multi topology through two non-invasive hooks in
dmic-generic.conf, without disturbing existing single-slot WOV users:
INCLUDE_WOV "multi": includes dmic-wov-multi.conf, selecting the
3-slot KPB/arbiter topology. The existing "true" value continues
to select the original dmic-wov.conf (single-slot, unchanged).
DMIC1_ENABLE "wov-multi": includes dmic-wov-multi.conf as an
alternative DMIC1 pipeline mode, alongside the existing
"passthrough" and "mfcc" values.
Build the multi-slot WOV topology variant with:
alsatplg ... -DINCLUDE_WOV=multi
Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
@lgirdwood
lgirdwoodforce-pushed the feature/wov-multi-kpb branch from 9458dbd to a59bf45CompareAugust 12, 2026 17:05

@lgirdwoodlgirdwood left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs all review comments to be addressed, also plan AMS alternative.

Comment threadsrc/audio/kpb.c
Comment on lines +898 to +909
/* WOV topology has exactly 2 output pins:
* pin 0 (buf_id=0) -> sel_sink (WOV detector)
* pin 1 (buf_id=IPC4_COMP_ID(1,0)=0x10) -> host_sink (arbiter drain) */
comp_dev_for_each_consumer(dev, sink) {
if (buf_get_id(sink) == 0)
kpb->sel_sink = sink;
else if (!kpb->host_sink)
kpb->host_sink = sink;
else
comp_warn(dev, "kpb_prepare: unexpected 3rd consumer pin, buf_id=0x%x",
buf_get_id(sink));
}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these are always hard coded, we should really use enums for the buf_id and I expect the host and sel sinks should be set from topology via IPC4 bind calls ?

Comment threadsrc/audio/kpb.c
Comment on lines +924 to +926
sink_params.sample_container_bytes = 4;
sink_params.sample_valid_bytes = 4;
sink_params.frame_fmt = SOF_IPC_FRAME_S32_LE;

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these 3 are hard coded, they should come from topology ?

Comment on lines +26 to +29
#define VAD_DEFAULT_THRESHOLD 0 /* bypass — tone-friendly for lab testing */
#define VAD_DEFAULT_ONSET_FRAMES 3 /* frames above threshold before SPEECH */
#define VAD_DEFAULT_HANGOVER 30 /* frames below threshold before SILENCE */
#define VAD_DEFAULT_ENERGY_SHIFT 6 /* IIR alpha = 1/2^6 */

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should come from topology.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lgirdwood@lyakh@naveen-manohar@kv2019i@lrgirdwo