From c3c716144e8f11a4e6679a1e2851cedd18423793 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Wed, 22 Jul 2026 13:05:21 +0100 Subject: [PATCH 1/3] audio/topology: add multi-KPB WOV arbiter for 3-keyword DMIC capture 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 Co-Authored-By: Claude Sonnet 4.6 --- src/audio/CMakeLists.txt | 3 + src/audio/Kconfig | 12 + src/audio/wov_arbiter/CMakeLists.txt | 3 + src/audio/wov_arbiter/wov_arbiter.c | 440 +++++++++++++ src/include/ipc4/detect_test.h | 9 +- src/include/sof/audio/wov_arbiter.h | 25 + src/include/sof/lib/ams_msg.h | 36 ++ src/samples/audio/detect_test.c | 120 +++- .../include/components/wov-arbiter.conf | 58 ++ .../platform/intel/dmic-wov-multi.conf | 587 ++++++++++++++++++ uuid-registry.txt | 1 + 11 files changed, 1291 insertions(+), 3 deletions(-) create mode 100644 src/audio/wov_arbiter/CMakeLists.txt create mode 100644 src/audio/wov_arbiter/wov_arbiter.c create mode 100644 src/include/sof/audio/wov_arbiter.h create mode 100644 tools/topology/topology2/include/components/wov-arbiter.conf create mode 100644 tools/topology/topology2/platform/intel/dmic-wov-multi.conf diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 92002c8b7c1c..d7a2e60aab60 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -107,6 +107,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_VOLUME) add_subdirectory(volume) endif() + if(CONFIG_COMP_WOV_ARBITER) + add_subdirectory(wov_arbiter) + endif() if(CONFIG_DTS_CODEC) add_subdirectory(codec) endif() diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 8accb25738a2..e3b5ec03ed7e 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -102,6 +102,18 @@ config COMP_STUBS Select to force all 3P blocks to link against stubs rather than their libraries. This should only be used in testing environments like fuzzers or CI. +config COMP_WOV_ARBITER + bool "WOV arbiter component" + depends on COMP_KPB + depends on IPC_MAJOR_4 + select AMS + help + Select to build the WOV (Wake-on-Voice) arbiter. The arbiter sits + between multiple KPB host-drain outputs and a single host PCM copier. + When a keyword is detected by one of the WOV detectors the arbiter + routes that KPB's drain stream to the host and instructs the remaining + detectors to pause. + config COMP_KPB bool "KPB component" default y diff --git a/src/audio/wov_arbiter/CMakeLists.txt b/src/audio/wov_arbiter/CMakeLists.txt new file mode 100644 index 000000000000..fd15150321c6 --- /dev/null +++ b/src/audio/wov_arbiter/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: BSD-3-Clause + +add_local_sources(sof wov_arbiter.c) diff --git a/src/audio/wov_arbiter/wov_arbiter.c b/src/audio/wov_arbiter/wov_arbiter.c new file mode 100644 index 000000000000..c8307765b8be --- /dev/null +++ b/src/audio/wov_arbiter/wov_arbiter.c @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2024 Intel Corporation. All rights reserved. +// +// WOV Arbiter — routes the drain output of one-of-N KPB host sinks to the +// single host PCM copier, while silencing the idle inputs and coordinating +// pause/resume of sibling WOV detectors via AMS. +// +// Topology connectivity (per KPB slot i): +// KPB_i host_sink (output pin 1) ──► wov_arbiter input pin i +// wov_arbiter output pin 0 ──► host copier +// +// AMS: +// Subscribes to AMS_WOV_DETECT_MSG_UUID (detector → arbiter on keyword) +// Publishes AMS_WOV_CTRL_MSG_UUID (arbiter → detectors: pause/resume) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if CONFIG_IPC_MAJOR_4 +#include +#endif +#if CONFIG_AMS +#include +#include +#include +#endif +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(wov_arbiter, CONFIG_SOF_LOG_LEVEL); + +SOF_DEFINE_REG_UUID(wov_arbiter); +DECLARE_TR_CTX(wov_arbiter_tr, SOF_UUID(wov_arbiter_uuid), LOG_LEVEL_INFO); + +/* Private runtime data. */ +struct wov_arb_data { +#if CONFIG_IPC_MAJOR_4 + struct ipc4_base_module_cfg base_cfg; +#endif + /* + * Index of the currently active KPB slot (0..WOV_ARB_MAX_SLOTS-1). + * WOV_ARB_NO_ACTIVE when no drain is in progress. + * Protected by the scheduler; no extra lock needed. + */ + uint8_t active_slot; + +#if CONFIG_AMS + uint32_t detect_uuid_id; /* AMS id for AMS_WOV_DETECT_MSG_UUID */ + uint32_t ctrl_uuid_id; /* AMS id for AMS_WOV_CTRL_MSG_UUID */ +#endif +}; + +/* ------------------------------------------------------------------------- + * AMS helpers + * ---------------------------------------------------------------------- */ + +#if CONFIG_AMS + +static const ams_uuid_t ams_wov_detect_uuid = AMS_WOV_DETECT_MSG_UUID; +static const ams_uuid_t ams_wov_ctrl_uuid = AMS_WOV_CTRL_MSG_UUID; + +/* Send a WOV_CTRL AMS message to all registered detectors. */ +static void arb_send_ctrl(const struct comp_dev *dev, uint8_t cmd, + uint8_t active_slot) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + struct ams_message_payload payload; + struct wov_ctrl_payload ctrl = { .cmd = cmd, .active_slot = active_slot }; + + if (cd->ctrl_uuid_id == AMS_INVALID_MSG_TYPE) + return; + + ams_helper_prepare_payload(dev, &payload, cd->ctrl_uuid_id, + (uint8_t *)&ctrl, sizeof(ctrl)); + if (ams_send(&payload)) + comp_warn(dev, "wov_arb: ctrl AMS send failed"); +} + +/* AMS callback: a WOV detector has fired. */ +static void arb_on_detect(const uint32_t msg_type_id, + const void *message, + const uint32_t message_size, + void *ctx) +{ + struct comp_dev *dev = ctx; + struct wov_arb_data *cd = comp_get_drvdata(dev); + const struct ams_message_payload *p = message; + + if (message_size < sizeof(*p) || + p->payload_size < sizeof(struct wov_detect_payload)) + return; + + const struct wov_detect_payload *det = + (const struct wov_detect_payload *)p->payload; + + if (det->slot_id >= WOV_ARB_MAX_SLOTS) { + comp_err(dev, "wov_arb: bad slot_id %u", det->slot_id); + return; + } + + /* First-wins: ignore if another slot is already draining. */ + if (cd->active_slot != WOV_ARB_NO_ACTIVE) { + comp_warn(dev, "wov_arb: slot %u detected but slot %u active, ignoring", + det->slot_id, cd->active_slot); + return; + } + + comp_info(dev, "wov_arb: activating slot %u", det->slot_id); + cd->active_slot = det->slot_id; + + /* Tell all detectors to pause; each checks active_slot to decide. */ + arb_send_ctrl(dev, WOV_CTRL_CMD_PAUSE, det->slot_id); +} + +#endif /* CONFIG_AMS */ + +/* ------------------------------------------------------------------------- + * Component lifecycle + * ---------------------------------------------------------------------- */ + +static struct comp_dev *wov_arb_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec) +{ + struct comp_dev *dev; + struct wov_arb_data *cd; + + comp_cl_info(&drv->tctx, "wov_arb_new"); + + dev = comp_alloc(drv, sizeof(*dev)); + if (!dev) + return NULL; + dev->ipc_config = *config; + + cd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*cd)); + if (!cd) { + comp_free_device(dev); + return NULL; + } + +#if CONFIG_IPC_MAJOR_4 + const struct ipc4_base_module_cfg *base_cfg = spec; + memcpy_s(&cd->base_cfg, sizeof(cd->base_cfg), base_cfg, sizeof(*base_cfg)); +#endif + + cd->active_slot = WOV_ARB_NO_ACTIVE; +#if CONFIG_AMS + cd->detect_uuid_id = AMS_INVALID_MSG_TYPE; + cd->ctrl_uuid_id = AMS_INVALID_MSG_TYPE; +#endif + + comp_set_drvdata(dev, cd); + dev->direction = SOF_IPC_STREAM_CAPTURE; + dev->direction_set = true; + dev->state = COMP_STATE_READY; + + return dev; +} + +static void wov_arb_free(struct comp_dev *dev) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + + comp_info(dev, "wov_arb_free"); + +#if CONFIG_AMS + if (cd->detect_uuid_id != AMS_INVALID_MSG_TYPE) + ams_helper_unregister_consumer(dev, cd->detect_uuid_id, + arb_on_detect); + if (cd->ctrl_uuid_id != AMS_INVALID_MSG_TYPE) + ams_helper_unregister_producer(dev, cd->ctrl_uuid_id); +#endif + + rfree(cd); + comp_free_device(dev); +} + +static int wov_arb_prepare(struct comp_dev *dev) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + int ret; + + comp_info(dev, "wov_arb_prepare"); + + cd->active_slot = WOV_ARB_NO_ACTIVE; + +#if CONFIG_AMS + /* Register as consumer of WOV_DETECT messages. */ + ret = ams_helper_register_consumer(dev, &cd->detect_uuid_id, + (const uint8_t *)&ams_wov_detect_uuid, + arb_on_detect); + if (ret) { + comp_err(dev, "wov_arb: detect consumer register failed %d", ret); + return ret; + } + + /* Register as producer of WOV_CTRL messages. */ + ret = ams_helper_register_producer(dev, &cd->ctrl_uuid_id, + (const uint8_t *)&ams_wov_ctrl_uuid); + if (ret) { + comp_err(dev, "wov_arb: ctrl producer register failed %d", ret); + ams_helper_unregister_consumer(dev, cd->detect_uuid_id, + arb_on_detect); + cd->detect_uuid_id = AMS_INVALID_MSG_TYPE; + return ret; + } +#endif + + return comp_set_state(dev, COMP_TRIGGER_PREPARE); +} + +static int wov_arb_reset(struct comp_dev *dev) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + + comp_info(dev, "wov_arb_reset"); + + cd->active_slot = WOV_ARB_NO_ACTIVE; + +#if CONFIG_AMS + if (cd->detect_uuid_id != AMS_INVALID_MSG_TYPE) { + ams_helper_unregister_consumer(dev, cd->detect_uuid_id, + arb_on_detect); + cd->detect_uuid_id = AMS_INVALID_MSG_TYPE; + } + if (cd->ctrl_uuid_id != AMS_INVALID_MSG_TYPE) { + ams_helper_unregister_producer(dev, cd->ctrl_uuid_id); + cd->ctrl_uuid_id = AMS_INVALID_MSG_TYPE; + } +#endif + + return comp_set_state(dev, COMP_TRIGGER_RESET); +} + +static int wov_arb_trigger(struct comp_dev *dev, int cmd) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + int ret; + + comp_info(dev, "wov_arb_trigger cmd %d", cmd); + + ret = comp_set_state(dev, cmd); + if (ret) + return ret; + + /* + * Host closed the PCM stream: deactivate and resume all detectors so + * they return to listening mode. + */ + if (cmd == COMP_TRIGGER_STOP || cmd == COMP_TRIGGER_PAUSE) { + if (cd->active_slot != WOV_ARB_NO_ACTIVE) { + comp_info(dev, "wov_arb: stream stopped, resuming all slots"); + cd->active_slot = WOV_ARB_NO_ACTIVE; +#if CONFIG_AMS + arb_send_ctrl(dev, WOV_CTRL_CMD_RESUME, WOV_ARB_NO_ACTIVE); +#endif + } + } + + return 0; +} + +static int wov_arb_params(struct comp_dev *dev, + struct sof_ipc_stream_params *params) +{ +#if CONFIG_IPC_MAJOR_4 + struct wov_arb_data *cd = comp_get_drvdata(dev); + + memset(params, 0, sizeof(*params)); + params->channels = cd->base_cfg.audio_fmt.channels_count; + params->rate = cd->base_cfg.audio_fmt.sampling_frequency; + params->sample_container_bytes = cd->base_cfg.audio_fmt.depth / 8; + params->sample_valid_bytes = + cd->base_cfg.audio_fmt.valid_bit_depth / 8; + params->buffer_fmt = cd->base_cfg.audio_fmt.interleaving_style; + params->buffer.size = cd->base_cfg.ibs; +#endif + return comp_verify_params(dev, 0, params); +} + +/* ------------------------------------------------------------------------- + * IPC4 large-config: allow host to force-select a slot (debug/test use). + * ---------------------------------------------------------------------- */ + +#if CONFIG_IPC_MAJOR_4 +static int wov_arb_set_large_config(struct comp_dev *dev, + uint32_t param_id, + bool first_block, + bool last_block, + uint32_t data_offset, + const char *data) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + + if (param_id == IPC4_WOV_ARB_SET_ACTIVE_SLOT) { + if (data_offset < sizeof(uint8_t)) + return -EINVAL; + cd->active_slot = *(const uint8_t *)data; + comp_info(dev, "wov_arb: force active_slot=%u", cd->active_slot); + return 0; + } + + return -EINVAL; +} + +static int wov_arb_get_attribute(struct comp_dev *dev, + uint32_t type, void *value) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + + if (type == COMP_ATTR_BASE_CONFIG) { + *(struct ipc4_base_module_cfg *)value = cd->base_cfg; + return 0; + } + return -EINVAL; +} +#endif /* CONFIG_IPC_MAJOR_4 */ + +/* ------------------------------------------------------------------------- + * copy() — main audio processing + * + * For the active input pin: forward frames to the output. + * For all other input pins: consume and discard to prevent buffer stalls. + * + * Input buffers are ordered by connection order in bsource_list (sink_list + * is the per-buffer link field). Slot 0 = first connected source, etc. + * ---------------------------------------------------------------------- */ +static int wov_arb_copy(struct comp_dev *dev) +{ + struct wov_arb_data *cd = comp_get_drvdata(dev); + struct comp_buffer *sink; + struct comp_buffer *source; + struct list_item *src_item; + uint32_t slot; + uint32_t sink_free; + uint32_t active_avail = 0; + uint32_t copy_bytes; + + comp_dbg(dev, "wov_arb_copy active=%u", cd->active_slot); + + sink = comp_dev_get_first_data_consumer(dev); + if (!sink) + return 0; + + sink_free = audio_stream_get_free_bytes(&sink->stream); + + /* First pass: find how many bytes the active source has available. */ + slot = 0; + list_for_item(src_item, &dev->bsource_list) { + source = list_item(src_item, struct comp_buffer, sink_list); + if (slot == cd->active_slot) { + active_avail = audio_stream_get_avail_bytes(&source->stream); + break; + } + if (++slot >= WOV_ARB_MAX_SLOTS) + break; + } + + copy_bytes = MIN(active_avail, sink_free); + + /* Second pass: copy active slot, silently drain idle slots. */ + slot = 0; + list_for_item(src_item, &dev->bsource_list) { + source = list_item(src_item, struct comp_buffer, sink_list); + uint32_t avail = audio_stream_get_avail_bytes(&source->stream); + + if (slot == cd->active_slot && copy_bytes > 0) { + uint32_t frame_bytes = audio_stream_frame_bytes(&source->stream); + uint32_t aligned = (copy_bytes / frame_bytes) * frame_bytes; + + if (aligned > 0) { + buffer_stream_invalidate(source, aligned); + audio_stream_copy(&source->stream, 0, + &sink->stream, 0, + aligned / audio_stream_sample_bytes(&source->stream)); + comp_update_buffer_consume(source, aligned); + buffer_stream_writeback(sink, aligned); + comp_update_buffer_produce(sink, aligned); + } + } else if (avail > 0) { + comp_update_buffer_consume(source, avail); + } + + if (++slot >= WOV_ARB_MAX_SLOTS) + break; + } + + return 0; +} + +/* ------------------------------------------------------------------------- + * Component driver registration + * ---------------------------------------------------------------------- */ + +static const struct comp_driver wov_arbiter_drv = { + .type = SOF_COMP_NONE, + .uid = SOF_RT_UUID(wov_arbiter_uuid), + .tctx = &wov_arbiter_tr, + .ops = { + .create = wov_arb_new, + .free = wov_arb_free, + .params = wov_arb_params, + .trigger = wov_arb_trigger, + .copy = wov_arb_copy, + .prepare = wov_arb_prepare, + .reset = wov_arb_reset, +#if CONFIG_IPC_MAJOR_4 + .set_large_config = wov_arb_set_large_config, + .get_attribute = wov_arb_get_attribute, +#endif + }, +}; + +static SHARED_DATA struct comp_driver_info wov_arbiter_info = { + .drv = &wov_arbiter_drv, +}; + +UT_STATIC void sys_comp_wov_arbiter_init(void) +{ + comp_register(platform_shared_get(&wov_arbiter_info, + sizeof(wov_arbiter_info))); +} + +DECLARE_MODULE(sys_comp_wov_arbiter_init); +SOF_MODULE_INIT(wov_arbiter, sys_comp_wov_arbiter_init); diff --git a/src/include/ipc4/detect_test.h b/src/include/ipc4/detect_test.h index 603bf1741b2c..4e5396d21cc9 100644 --- a/src/include/ipc4/detect_test.h +++ b/src/include/ipc4/detect_test.h @@ -30,6 +30,13 @@ enum ipc4_detect_test_module_config_params { * Ipc mailbox must contain properly built sof_detect_test_config * struct. */ - IPC4_DETECT_TEST_GET_CONFIG = 3 + IPC4_DETECT_TEST_GET_CONFIG = 3, + + /* + * Use LARGE_CONFIG_SET to assign this detector's slot index in the + * WOV arbiter (0, 1, or 2). Payload is a single uint8_t. + * Set to WOV_SLOT_INVALID (0xff) to disable arbiter integration. + */ + IPC4_DETECT_TEST_SET_WOV_SLOT = 4 }; #endif diff --git a/src/include/sof/audio/wov_arbiter.h b/src/include/sof/audio/wov_arbiter.h new file mode 100644 index 000000000000..13285ddf1a8e --- /dev/null +++ b/src/include/sof/audio/wov_arbiter.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2024 Intel Corporation. All rights reserved. + */ + +#ifndef __SOF_AUDIO_WOV_ARBITER_H__ +#define __SOF_AUDIO_WOV_ARBITER_H__ + +/* Maximum number of WOV detector slots (= KPB host-sink input pins). */ +#define WOV_ARB_MAX_SLOTS 8 + +/* Sentinel: no slot is currently draining. */ +#define WOV_ARB_NO_ACTIVE 0xff + +/* + * IPC4 SET_LARGE_CONFIG param ID used to set the arbiter's active input + * explicitly from the host (testing / override). + */ +#define IPC4_WOV_ARB_SET_ACTIVE_SLOT 1 + +#ifdef UNIT_TEST +void sys_comp_wov_arbiter_init(void); +#endif + +#endif /* __SOF_AUDIO_WOV_ARBITER_H__ */ diff --git a/src/include/sof/lib/ams_msg.h b/src/include/sof/lib/ams_msg.h index 9c1ad5ce6efb..7feb8f4943cf 100644 --- a/src/include/sof/lib/ams_msg.h +++ b/src/include/sof/lib/ams_msg.h @@ -15,4 +15,40 @@ typedef uint8_t ams_uuid_t[16]; #define AMS_KPD_MSG_UUID { 0x80, 0xa1, 0x11, 0x22, 0xb3, 0x6c, 0x11, 0xed, \ 0xaf, 0xa1, 0x02, 0x42, 0xac, 0x12, 0x00, 0x02 } +/* + * WOV arbiter AMS message UUIDs. + * + * AMS_WOV_DETECT_MSG_UUID: c3d7e841-12f0-4e8a-b901-5a6b7c8d9e0f + * Sent by a WOV detector (detect_test) to the WOV arbiter when a keyword + * is detected. Payload: struct wov_detect_payload. + * + * AMS_WOV_CTRL_MSG_UUID: f1e2d3c4-b5a6-4789-8ace-1234567890ab + * Sent by the WOV arbiter to all WOV detectors to pause or resume + * detection. Payload: struct wov_ctrl_payload. + */ +#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 } + +#define WOV_SLOT_INVALID 0xff + +/* Payload for AMS_WOV_DETECT_MSG_UUID (detector → arbiter) */ +struct wov_detect_payload { + uint8_t slot_id; /* detector slot: 0, 1, or 2 */ +}; + +/* Command codes for AMS_WOV_CTRL_MSG_UUID (arbiter → detectors) */ +#define WOV_CTRL_CMD_PAUSE 0 +#define WOV_CTRL_CMD_RESUME 1 + +/* Payload for AMS_WOV_CTRL_MSG_UUID (arbiter → detectors) */ +struct wov_ctrl_payload { + uint8_t cmd; /* WOV_CTRL_CMD_PAUSE or WOV_CTRL_CMD_RESUME */ + uint8_t active_slot; /* slot being activated (valid for PAUSE) */ +}; + #endif /* __SOF_LIB_AMS_MSG_H__ */ diff --git a/src/samples/audio/detect_test.c b/src/samples/audio/detect_test.c index 7979d7db3d6c..f5bbb8fad9e0 100644 --- a/src/samples/audio/detect_test.c +++ b/src/samples/audio/detect_test.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -103,6 +104,17 @@ struct comp_data { #if CONFIG_AMS uint32_t kpd_uuid_id; + /* + * WOV arbiter integration. + * wov_slot_id: slot index reported to the arbiter (0-2). + * WOV_SLOT_INVALID means arbiter is not used. + * paused: set by AMS_WOV_CTRL_MSG_UUID PAUSE command; cleared on + * RESUME or COMP_TRIGGER_START/RELEASE. + */ + uint8_t wov_slot_id; + bool paused; + uint32_t wov_detect_uuid_id; /* AMS producer id for WOV_DETECT */ + uint32_t wov_ctrl_uuid_id; /* AMS consumer id for WOV_CTRL */ #else struct kpb_event_data event_data; #endif /* CONFIG_AMS */ @@ -153,6 +165,10 @@ static void notify_host(const struct comp_dev *dev) /* Key-phrase detected message*/ static const ams_uuid_t ams_kpd_msg_uuid = AMS_KPD_MSG_UUID; +/* WOV arbiter AMS UUIDs */ +static const ams_uuid_t ams_wov_detect_uuid = AMS_WOV_DETECT_MSG_UUID; +static const ams_uuid_t ams_wov_ctrl_uuid = AMS_WOV_CTRL_MSG_UUID; + static int ams_notify_kpb(const struct comp_dev *dev) { struct comp_data *cd = comp_get_drvdata(dev); @@ -172,6 +188,55 @@ static int ams_notify_kpb(const struct comp_dev *dev) return ams_send(&ams_payload); } + +/* Notify the WOV arbiter which slot detected a keyword. */ +static void ams_notify_arb(const struct comp_dev *dev) +{ + struct comp_data *cd = comp_get_drvdata(dev); + struct ams_message_payload ams_payload; + struct wov_detect_payload det = { .slot_id = cd->wov_slot_id }; + + if (cd->wov_slot_id == WOV_SLOT_INVALID || + cd->wov_detect_uuid_id == AMS_INVALID_MSG_TYPE) + return; + + ams_helper_prepare_payload(dev, &ams_payload, cd->wov_detect_uuid_id, + (uint8_t *)&det, sizeof(det)); + if (ams_send(&ams_payload)) + comp_warn(dev, "wov_arb: detect AMS send failed"); +} + +/* AMS callback: arbiter is broadcasting a PAUSE or RESUME command. */ +static void on_wov_ctrl(const uint32_t msg_type_id, + const void *message, + const uint32_t message_size, + void *ctx) +{ + struct comp_dev *dev = ctx; + struct comp_data *cd = comp_get_drvdata(dev); + const struct ams_message_payload *p = message; + + if (message_size < sizeof(*p) || + p->payload_size < sizeof(struct wov_ctrl_payload)) + return; + + const struct wov_ctrl_payload *ctrl = + (const struct wov_ctrl_payload *)p->payload; + + if (ctrl->cmd == WOV_CTRL_CMD_PAUSE) { + if (cd->wov_slot_id != ctrl->active_slot) { + comp_info(dev, "kd: paused (slot %u active)", ctrl->active_slot); + cd->paused = true; + } + } else if (ctrl->cmd == WOV_CTRL_CMD_RESUME) { + comp_info(dev, "kd: resumed"); + cd->paused = false; + cd->detected = 0; + cd->activation = 0; + cd->detect_preamble = 0; + } +} + #else static void notify_kpb(const struct comp_dev *dev) { @@ -200,6 +265,7 @@ void detect_test_notify(const struct comp_dev *dev) notify_host(dev); #if CONFIG_AMS ams_notify_kpb(dev); + ams_notify_arb(dev); #else notify_kpb(dev); #endif @@ -417,6 +483,14 @@ static int test_keyword_set_large_config(struct comp_dev *dev, data); case IPC4_DETECT_TEST_SET_CONFIG: return test_keyword_set_config(dev, data, data_offset); +#if CONFIG_AMS + case IPC4_DETECT_TEST_SET_WOV_SLOT: + if (data_offset < sizeof(uint8_t)) + return -EINVAL; + cd->wov_slot_id = *(const uint8_t *)data; + comp_info(dev, "kd: wov_slot_id set to %u", cd->wov_slot_id); + return 0; +#endif default: return -EINVAL; } @@ -787,6 +861,11 @@ static void test_keyword_free(struct comp_dev *dev) ret = ams_helper_unregister_producer(dev, cd->kpd_uuid_id); if (ret) comp_err(dev, "unregister ams error %d", ret); + + if (cd->wov_ctrl_uuid_id != AMS_INVALID_MSG_TYPE) + ams_helper_unregister_consumer(dev, cd->wov_ctrl_uuid_id, on_wov_ctrl); + if (cd->wov_detect_uuid_id != AMS_INVALID_MSG_TYPE) + ams_helper_unregister_producer(dev, cd->wov_detect_uuid_id); #endif ipc_msg_free(cd->msg); @@ -873,7 +952,11 @@ static int test_keyword_params(struct comp_dev *dev, } #if CONFIG_AMS - cd->kpd_uuid_id = AMS_INVALID_MSG_TYPE; + cd->kpd_uuid_id = AMS_INVALID_MSG_TYPE; + cd->wov_detect_uuid_id = AMS_INVALID_MSG_TYPE; + cd->wov_ctrl_uuid_id = AMS_INVALID_MSG_TYPE; + cd->wov_slot_id = WOV_SLOT_INVALID; + cd->paused = false; #endif /* CONFIG_AMS */ return 0; @@ -895,6 +978,9 @@ static int test_keyword_trigger(struct comp_dev *dev, int cmd) cd->detect_preamble = 0; cd->detected = 0; cd->activation = 0; +#if CONFIG_AMS + cd->paused = false; +#endif } return 0; @@ -919,7 +1005,13 @@ static int test_keyword_copy(struct comp_dev *dev) /* copy and perform detection */ buffer_stream_invalidate(source, audio_stream_get_avail_bytes(&source->stream)); +#if CONFIG_AMS + /* When paused by the arbiter, drain the buffer without running detection. */ + if (!cd->paused) + cd->detect_func(dev, &source->stream, frames); +#else cd->detect_func(dev, &source->stream, frames); +#endif /* calc new available */ comp_update_buffer_consume(source, audio_stream_get_avail_bytes(&source->stream)); @@ -986,11 +1078,35 @@ static int test_keyword_prepare(struct comp_dev *dev) &cd->data_blob_crc); #if CONFIG_AMS - /* Register KD as AMS producer */ + /* Register KD as AMS producer for the existing KPB drain path. */ ret = ams_helper_register_producer(dev, &cd->kpd_uuid_id, ams_kpd_msg_uuid); if (ret) return ret; + + /* If a WOV arbiter slot is configured, register the additional paths. */ + if (cd->wov_slot_id != WOV_SLOT_INVALID) { + ret = ams_helper_register_producer(dev, &cd->wov_detect_uuid_id, + (const uint8_t *)&ams_wov_detect_uuid); + if (ret) { + comp_err(dev, "wov_detect producer register failed %d", ret); + ams_helper_unregister_producer(dev, cd->kpd_uuid_id); + cd->kpd_uuid_id = AMS_INVALID_MSG_TYPE; + return ret; + } + + ret = ams_helper_register_consumer(dev, &cd->wov_ctrl_uuid_id, + (const uint8_t *)&ams_wov_ctrl_uuid, + on_wov_ctrl); + if (ret) { + comp_err(dev, "wov_ctrl consumer register failed %d", ret); + ams_helper_unregister_producer(dev, cd->wov_detect_uuid_id); + cd->wov_detect_uuid_id = AMS_INVALID_MSG_TYPE; + ams_helper_unregister_producer(dev, cd->kpd_uuid_id); + cd->kpd_uuid_id = AMS_INVALID_MSG_TYPE; + return ret; + } + } #endif return comp_set_state(dev, COMP_TRIGGER_PREPARE); diff --git a/tools/topology/topology2/include/components/wov-arbiter.conf b/tools/topology/topology2/include/components/wov-arbiter.conf new file mode 100644 index 000000000000..a4d2f3196c0c --- /dev/null +++ b/tools/topology/topology2/include/components/wov-arbiter.conf @@ -0,0 +1,58 @@ +# +# wov-arbiter widget +# +# Routes the drain output of one of N KPB host sinks to the single host PCM +# copier. Idle inputs are consumed (discarded) so their buffers never stall. +# Detection and pause/resume coordination is done via AMS in firmware. +# +# Usage: +# +# Object.Widget.wov-arbiter."N" { +# uuid "4a5b6c7d-8e9f-4a1b-2c3d-4e5f60718293" +# num_input_audio_formats 1 +# num_output_audio_formats 1 +# } +# +# Where N is the unique instance number within the same alsaconf node. + +Class.Widget."wov-arbiter" { + + DefineAttribute."index" {} + DefineAttribute."instance" {} + + DefineAttribute."cpc" { + token_ref "comp.word" + } + + + + DefineAttribute."is_pages" { + token_ref "comp.word" + } + + attributes { + !constructor [ + "index" + "instance" + ] + + !mandatory [ + "no_pm" + "uuid" + "num_input_audio_formats" + "num_output_audio_formats" + ] + + !immutable [ + "type" + ] + + unique "instance" + } + + # 3 KPB host-sink inputs, 1 host-copier output + type "effect" + no_pm "true" + num_input_pins 3 + num_output_pins 1 +} diff --git a/tools/topology/topology2/platform/intel/dmic-wov-multi.conf b/tools/topology/topology2/platform/intel/dmic-wov-multi.conf new file mode 100644 index 000000000000..e0e7c57887b3 --- /dev/null +++ b/tools/topology/topology2/platform/intel/dmic-wov-multi.conf @@ -0,0 +1,587 @@ +# +# Multi-KPB Wake-on-Voice topology +# +# Captures from one DMIC, fans audio out through a Mixin to three KPB +# instances each fronted by a detect_test WOV detector (energy-based, +# sensitive enough to trigger on a hand-clap for lab testing). When any +# detector fires its keyword its KPB drains through the WOV arbiter to the +# single host PCM capture stream. The remaining two detectors are paused +# via AMS until the host closes the stream. +# +# Core assignments +# Core 0 : DMIC copier + Mixin + KPB_0 + KPB_1 + WOV detectors 0 & 1 +# Core 1 : KPB_2 + WOV detector 2 +# Core 0 : WOV Arbiter + Host copier +# +# Pipeline IDs used +# 100 DAI pipeline : DMIC DAI copier → Mixin +# 101 KPB pipeline : Mixout_0 → KPB_0 → WOV_0 (Core 0) +# 102 KPB pipeline : Mixout_1 → KPB_1 → WOV_1 (Core 0) +# 103 KPB pipeline : Mixout_2 → KPB_2 → WOV_2 (Core 1) +# 104 Host pipeline : WOV Arbiter → Host copier (Core 0) +# +# Requires +# CONFIG_COMP_KPB=y +# CONFIG_COMP_WOV_ARBITER=y +# CONFIG_COMP_MIXIN_MIXOUT=y +# CONFIG_AMS=y +# CONFIG_COMP_KWD_DETECT (detect_test sample) + + + + + + + + + + + + +# +# detect_test UUID (keyword energy detector — fires on a hand clap in the lab) +# This matches the 'keyword' UUID registered in uuid-registry.txt. +# +Define { + KWD_TEST_UUID "eba8d51f-7827-47b5-82ee-de6e7743af67" + + # WOV arbiter UUID (registered in uuid-registry.txt as 'wov_arbiter') + WOV_ARB_UUID "4a5b6c7d-8e9f-4a1b-2c3d-4e5f60718293" + + # DMIC DAI configuration — adjust for target hardware + DMIC_DAI_INDEX 1 + DMIC_PCM_ID 11 + DMIC_NAME "DMIC01 Pin" + DMIC_PCM_CAPS "DMIC01 16k Capture" + FORMAT "s32le" + + # Detector cycle budget — clap test requires very little compute + KWD_CPC 100000 + WOV_ARB_CPC 20000 +} + +# --------------------------------------------------------------------------- +# Pipeline 100 — DAI (Core 0) +# DMIC DAI copier → Mixin (fan-out to 3 Mixouts in pipelines 101/102/103) +# --------------------------------------------------------------------------- +Object.Pipeline.dai-kpb-be [ + { + direction "capture" + format $FORMAT + rate 16000 + rate_min 16000 + rate_max 16000 + index 100 + + Object.Widget.dai-copier.1 { + dai_index $DMIC_DAI_INDEX + type "dai_out" + dai_type "DMIC" + copier_type "DMIC" + stream_name $DMIC_NAME + node_type $DMIC_LINK_INPUT_CLASS + + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_ch_cfg $CHANNEL_CONFIG_MONO + in_ch_map $CHANNEL_MAP_MONO + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_ch_cfg $CHANNEL_CONFIG_MONO + out_ch_map $CHANNEL_MAP_MONO + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + + Object.Widget.mixin.1 { + type "mixer" + mix_type "mix_in" + + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + + Object.Widget.pipeline.1 { + priority 0 + lp_mode 0 + core_id 0 + } + } +] + +# --------------------------------------------------------------------------- +# Pipeline 101 — KPB slot 0 (Core 0) +# Mixout_0 → KPB_0 → WOV_0 (detect_test slot 0) +# └→ WOV Arbiter input 0 +# --------------------------------------------------------------------------- +Object.Pipeline.dai-kpb-be [ + { + direction "capture" + format $FORMAT + rate 16000 + rate_min 16000 + rate_max 16000 + index 101 + + Object.Widget.mixout.1 { + type "mixer" + mix_type "mix_out" + + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + + Object.Widget.kpb.1 { + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + + # pin 0 (sel_sink) → WOV detector + # pin 1 (host_sink) → WOV arbiter input 0 + Object.Base.output_pin_binding.1 { + output_pin_binding_name "wov.101.1" + } + Object.Base.output_pin_binding.2 { + output_pin_binding_name "wov-arbiter.104.1" + } + } + + Object.Widget.wov.1 { + uuid $KWD_TEST_UUID + cpc $KWD_CPC + + num_input_audio_formats 1 + num_output_audio_formats 0 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + } + + Object.Widget.pipeline.1 { + priority 0 + lp_mode 0 + core_id 0 + } + } +] + +# --------------------------------------------------------------------------- +# Pipeline 102 — KPB slot 1 (Core 0) +# Mixout_1 → KPB_1 → WOV_1 (detect_test slot 1) +# └→ WOV Arbiter input 1 +# --------------------------------------------------------------------------- +Object.Pipeline.dai-kpb-be [ + { + direction "capture" + format $FORMAT + rate 16000 + rate_min 16000 + rate_max 16000 + index 102 + + Object.Widget.mixout.1 { + type "mixer" + mix_type "mix_out" + + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + + Object.Widget.kpb.1 { + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + + Object.Base.output_pin_binding.1 { + output_pin_binding_name "wov.102.1" + } + Object.Base.output_pin_binding.2 { + output_pin_binding_name "wov-arbiter.104.1" + } + } + + Object.Widget.wov.1 { + uuid $KWD_TEST_UUID + cpc $KWD_CPC + + num_input_audio_formats 1 + num_output_audio_formats 0 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + } + + Object.Widget.pipeline.1 { + priority 0 + lp_mode 0 + core_id 0 + } + } +] + +# --------------------------------------------------------------------------- +# Pipeline 103 — KPB slot 2 (Core 1) +# Mixout_2 → KPB_2 → WOV_2 (detect_test slot 2) +# └→ WOV Arbiter input 2 +# --------------------------------------------------------------------------- +Object.Pipeline.dai-kpb-be [ + { + direction "capture" + format $FORMAT + rate 16000 + rate_min 16000 + rate_max 16000 + index 103 + + Object.Widget.mixout.1 { + type "mixer" + mix_type "mix_out" + + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + + Object.Widget.kpb.1 { + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + + Object.Base.output_pin_binding.1 { + output_pin_binding_name "wov.103.1" + } + Object.Base.output_pin_binding.2 { + output_pin_binding_name "wov-arbiter.104.1" + } + } + + Object.Widget.wov.1 { + uuid $KWD_TEST_UUID + cpc $KWD_CPC + + num_input_audio_formats 1 + num_output_audio_formats 0 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + } + + Object.Widget.pipeline.1 { + priority 0 + lp_mode 0 + # This pipeline runs on Core 1 + core_id 1 + } + } +] + +# --------------------------------------------------------------------------- +# Pipeline 104 — Host PCM (Core 0) +# WOV Arbiter (3 inputs from KPB host sinks) → Host copier → PCM to host +# --------------------------------------------------------------------------- +Object.Pipeline.host-gateway-capture [ + { + format $FORMAT + rate 16000 + rate_min 16000 + rate_max 16000 + index 104 + + Object.Widget.host-copier.1 { + stream_name $DMIC_PCM_CAPS + pcm_id $DMIC_PCM_ID + + num_output_audio_formats 1 + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + + Object.Widget."wov-arbiter".1 { + uuid $WOV_ARB_UUID + cpc $WOV_ARB_CPC + + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + + Object.Widget.pipeline.1 { + priority 0 + lp_mode 0 + core_id 0 + } + } +] + +# --------------------------------------------------------------------------- +# Routes +# --------------------------------------------------------------------------- +Object.Base.route [ + + # DAI copier → Mixin (pipeline 100 internal) + { + source "dai-copier.DMIC.$DMIC_NAME.capture" + sink "mixin.100.1" + } + + # Mixin fan-out → three Mixouts (one per KPB pipeline) + { + source "mixin.100.1" + sink "mixout.101.1" + } + { + source "mixin.100.1" + sink "mixout.102.1" + } + { + source "mixin.100.1" + sink "mixout.103.1" + } + + # Mixout → KPB (slot 0) + { + source "mixout.101.1" + sink "kpb.101.1" + } + + # KPB_0 sel_sink → WOV_0 detector (pin 0, declared via output_pin_binding) + { + source "kpb.101.1" + sink "wov.101.1" + } + + # KPB_0 host_sink → WOV Arbiter input 0 (pin 1, output_pin_binding) + { + source "kpb.101.1" + sink "wov-arbiter.104.1" + } + + # Mixout → KPB (slot 1) + { + source "mixout.102.1" + sink "kpb.102.1" + } + + # KPB_1 sel_sink → WOV_1 detector + { + source "kpb.102.1" + sink "wov.102.1" + } + + # KPB_1 host_sink → WOV Arbiter input 1 + { + source "kpb.102.1" + sink "wov-arbiter.104.1" + } + + # Mixout → KPB (slot 2) + { + source "mixout.103.1" + sink "kpb.103.1" + } + + # KPB_2 sel_sink → WOV_2 detector + { + source "kpb.103.1" + sink "wov.103.1" + } + + # KPB_2 host_sink → WOV Arbiter input 2 + { + source "kpb.103.1" + sink "wov-arbiter.104.1" + } + + # WOV Arbiter → Host copier (single PCM output) + { + source "wov-arbiter.104.1" + sink "host-copier.$DMIC_PCM_ID.capture" + } +] + +# --------------------------------------------------------------------------- +# PCM (ALSA card definition for the single WOV capture stream) +# --------------------------------------------------------------------------- +Object.PCM.pcm."$DMIC_PCM_ID" { + name "DMIC Multi-WOV" + id $DMIC_PCM_ID + direction "capture" + + Object.Base.fe_dai."$DMIC_PCM_CAPS" {} + + Object.PCM.pcm_caps."capture" { + name $DMIC_PCM_CAPS + formats "S32_LE" + rates "16000" + channels_min 1 + channels_max 1 + } +} diff --git a/uuid-registry.txt b/uuid-registry.txt index e9e8f8e77876..c87bf913e324 100644 --- a/uuid-registry.txt +++ b/uuid-registry.txt @@ -180,6 +180,7 @@ b77e677e-5ff4-4188-af14fba8bdbf8682 volume 1028070e-04e8-46ab-8d8110a0116ce738 wait d944281a-afe9-4695-a043d7f62b89538e waves 13c8bc59-c4fa-4ad1-b93ace97cd30acc7 wdt +4a5b6c7d-8e9f-4a1b-2c3d4e5f60718293 wov_arbiter 300aaad4-45d2-8313-25d05e1d6086cdd1 zephyr 5f1ec3f8-faaf-4099-903ccee98351f169 zephyr_idc 8fa1d42f-bc6f-464b-867f547af08834da zipc_task From 0a3047030c735962cbac333ea31a77b18d1003ad Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Wed, 22 Jul 2026 15:26:38 +0100 Subject: [PATCH 2/3] audio: add VAD gate component for multi-WOV WOV pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/audio/CMakeLists.txt | 3 + src/audio/Kconfig | 1 + src/audio/vad_gate/CMakeLists.txt | 2 + src/audio/vad_gate/Kconfig | 12 + src/audio/vad_gate/vad_gate.c | 335 ++++++++++++++++++ src/include/sof/audio/vad_gate.h | 36 ++ .../include/components/vad-gate.conf | 58 +++ .../platform/intel/dmic-wov-multi.conf | 44 ++- uuid-registry.txt | 1 + 9 files changed, 489 insertions(+), 3 deletions(-) create mode 100644 src/audio/vad_gate/CMakeLists.txt create mode 100644 src/audio/vad_gate/Kconfig create mode 100644 src/audio/vad_gate/vad_gate.c create mode 100644 src/include/sof/audio/vad_gate.h create mode 100644 tools/topology/topology2/include/components/vad-gate.conf diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index d7a2e60aab60..9e63311f9eaf 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -104,6 +104,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) if(CONFIG_COMP_UP_DOWN_MIXER) add_subdirectory(up_down_mixer) endif() + if(CONFIG_COMP_VAD_GATE) + add_subdirectory(vad_gate) + endif() if(CONFIG_COMP_VOLUME) add_subdirectory(volume) endif() diff --git a/src/audio/Kconfig b/src/audio/Kconfig index e3b5ec03ed7e..b05fd8a55df1 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -172,6 +172,7 @@ rsource "template/Kconfig" rsource "tensorflow/Kconfig" rsource "tone/Kconfig" rsource "up_down_mixer/Kconfig" +rsource "vad_gate/Kconfig" rsource "volume/Kconfig" # --- End Kconfig Sources (alphabetical order) --- diff --git a/src/audio/vad_gate/CMakeLists.txt b/src/audio/vad_gate/CMakeLists.txt new file mode 100644 index 000000000000..6c896ad86096 --- /dev/null +++ b/src/audio/vad_gate/CMakeLists.txt @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: BSD-3-Clause +add_local_sources(sof vad_gate.c) diff --git a/src/audio/vad_gate/Kconfig b/src/audio/vad_gate/Kconfig new file mode 100644 index 000000000000..dcc7bf58185e --- /dev/null +++ b/src/audio/vad_gate/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause + +config COMP_VAD_GATE + bool "VAD gate component" + depends on IPC_MAJOR_4 + help + Select to build the VAD (Voice Activity Detection) gate component. + The gate sits between the DMIC copier and the downstream Mixin in + a WOV capture pipeline. During silence it drains the DMIC input + and returns PPL_STATUS_PATH_STOP so KPB and WOV detectors idle. + When voice onset is detected audio flows through and the downstream + chain wakes and begins buffering. diff --git a/src/audio/vad_gate/vad_gate.c b/src/audio/vad_gate/vad_gate.c new file mode 100644 index 000000000000..41598d34327b --- /dev/null +++ b/src/audio/vad_gate/vad_gate.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2024 Intel Corporation. All rights reserved. +// +// VAD Gate — lightweight voice-activity detector placed between the DMIC +// copier and the downstream Mixin in a WOV pipeline. +// +// When the detected energy stays below the threshold for hangover_frames +// consecutive frames (silence), the gate drains its input but returns +// PPL_STATUS_PATH_STOP so the Mixin/KPB/WOV pipelines downstream do not +// run. When voice is detected (onset_frames consecutive frames above the +// threshold) the gate passes audio through and the downstream chain wakes. +// +// Energy estimator: first-order IIR on the peak |sample| amplitude per +// processing period; same pattern as detect_test.c's activation tracker. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if CONFIG_IPC_MAJOR_4 +#include +#endif +#include +#include +#include +#include + +LOG_MODULE_REGISTER(vad_gate, CONFIG_SOF_LOG_LEVEL); + +SOF_DEFINE_REG_UUID(vad_gate); +DECLARE_TR_CTX(vad_gate_tr, SOF_UUID(vad_gate_uuid), LOG_LEVEL_INFO); + +/* Private runtime data. */ +struct vad_gate_data { +#if CONFIG_IPC_MAJOR_4 + struct ipc4_base_module_cfg base_cfg; +#endif + struct ipc4_vad_gate_config config; + + /* IIR energy accumulator (same units as S32LE sample amplitude). */ + int32_t energy; + + /* Debounce counters. */ + uint16_t speech_cnt; + uint16_t silence_cnt; + + bool vad_active; +}; + +/* ------------------------------------------------------------------------- + * Energy estimation and VAD state machine + * ---------------------------------------------------------------------- */ + +/* Per-period energy update; assumes single-channel S32LE (mono DMIC capture). */ +static void vad_update_energy(struct comp_dev *dev, + struct audio_stream *s, uint32_t frames) +{ + struct vad_gate_data *cd = comp_get_drvdata(dev); + uint32_t i; + bool above; + + for (i = 0; i < frames; i++) { + 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; + } + + above = (cd->energy >= cd->config.threshold); + if (above) { + cd->silence_cnt = 0; + if (++cd->speech_cnt >= cd->config.onset_frames && !cd->vad_active) { + cd->vad_active = true; + comp_info(dev, "vad_gate: SPEECH onset"); + } + } else { + cd->speech_cnt = 0; + if (++cd->silence_cnt >= cd->config.hangover_frames && cd->vad_active) { + cd->vad_active = false; + comp_info(dev, "vad_gate: SILENCE hangover expired"); + } + } +} + +/* ------------------------------------------------------------------------- + * Component lifecycle + * ---------------------------------------------------------------------- */ + +static struct comp_dev *vad_gate_new(const struct comp_driver *drv, + const struct comp_ipc_config *config, + const void *spec) +{ + struct comp_dev *dev; + struct vad_gate_data *cd; + + comp_cl_info(&drv->tctx, "vad_gate_new"); + + dev = comp_alloc(drv, sizeof(*dev)); + if (!dev) + return NULL; + dev->ipc_config = *config; + + cd = rzalloc(SOF_MEM_FLAG_USER, sizeof(*cd)); + if (!cd) { + comp_free_device(dev); + return NULL; + } + +#if CONFIG_IPC_MAJOR_4 + const struct ipc4_base_module_cfg *base_cfg = spec; + memcpy_s(&cd->base_cfg, sizeof(cd->base_cfg), base_cfg, sizeof(*base_cfg)); +#endif + + cd->config.threshold = VAD_DEFAULT_THRESHOLD; + cd->config.onset_frames = VAD_DEFAULT_ONSET_FRAMES; + cd->config.hangover_frames = VAD_DEFAULT_HANGOVER; + cd->config.energy_shift = VAD_DEFAULT_ENERGY_SHIFT; + + comp_set_drvdata(dev, cd); + dev->direction = SOF_IPC_STREAM_CAPTURE; + dev->direction_set = true; + dev->state = COMP_STATE_READY; + + return dev; +} + +static void vad_gate_free(struct comp_dev *dev) +{ + comp_info(dev, "vad_gate_free"); + rfree(comp_get_drvdata(dev)); + comp_free_device(dev); +} + +static int vad_gate_prepare(struct comp_dev *dev) +{ + struct vad_gate_data *cd = comp_get_drvdata(dev); + + comp_info(dev, "vad_gate_prepare threshold=%d onset=%u hangover=%u", + cd->config.threshold, + cd->config.onset_frames, + cd->config.hangover_frames); + + cd->energy = 0; + cd->speech_cnt = 0; + cd->silence_cnt = 0; + cd->vad_active = false; + + return comp_set_state(dev, COMP_TRIGGER_PREPARE); +} + +static int vad_gate_reset(struct comp_dev *dev) +{ + struct vad_gate_data *cd = comp_get_drvdata(dev); + + comp_info(dev, "vad_gate_reset"); + + cd->energy = 0; + cd->speech_cnt = 0; + cd->silence_cnt = 0; + cd->vad_active = false; + + return comp_set_state(dev, COMP_TRIGGER_RESET); +} + +static int vad_gate_trigger(struct comp_dev *dev, int cmd) +{ + comp_info(dev, "vad_gate_trigger cmd %d", cmd); + return comp_set_state(dev, cmd); +} + +static int vad_gate_params(struct comp_dev *dev, + struct sof_ipc_stream_params *params) +{ +#if CONFIG_IPC_MAJOR_4 + struct vad_gate_data *cd = comp_get_drvdata(dev); + + memset(params, 0, sizeof(*params)); + params->channels = cd->base_cfg.audio_fmt.channels_count; + params->rate = cd->base_cfg.audio_fmt.sampling_frequency; + params->sample_container_bytes = cd->base_cfg.audio_fmt.depth / 8; + params->sample_valid_bytes = + cd->base_cfg.audio_fmt.valid_bit_depth / 8; + params->buffer_fmt = cd->base_cfg.audio_fmt.interleaving_style; + params->buffer.size = cd->base_cfg.ibs; +#endif + return comp_verify_params(dev, 0, params); +} + +/* ------------------------------------------------------------------------- + * IPC4 large-config — runtime tuning of threshold, onset, hangover. + * ---------------------------------------------------------------------- */ + +#if CONFIG_IPC_MAJOR_4 +static int vad_gate_set_large_config(struct comp_dev *dev, + uint32_t param_id, + bool first_block, + bool last_block, + uint32_t data_offset, + const char *data) +{ + struct vad_gate_data *cd = comp_get_drvdata(dev); + + if (param_id != IPC4_VAD_GATE_SET_CONFIG) + return -EINVAL; + + if (data_offset < sizeof(struct ipc4_vad_gate_config)) + return -EINVAL; + + const struct ipc4_vad_gate_config *cfg = + (const struct ipc4_vad_gate_config *)data; + + memcpy_s(&cd->config, sizeof(cd->config), cfg, sizeof(*cfg)); + + comp_info(dev, "vad_gate: config updated threshold=%d onset=%u hangover=%u shift=%u", + cd->config.threshold, + cd->config.onset_frames, + cd->config.hangover_frames, + cd->config.energy_shift); + + return 0; +} + +static int vad_gate_get_attribute(struct comp_dev *dev, + uint32_t type, void *value) +{ + struct vad_gate_data *cd = comp_get_drvdata(dev); + + if (type == COMP_ATTR_BASE_CONFIG) { + *(struct ipc4_base_module_cfg *)value = cd->base_cfg; + return 0; + } + return -EINVAL; +} +#endif /* CONFIG_IPC_MAJOR_4 */ + +/* ------------------------------------------------------------------------- + * copy() — main audio processing + * + * Always drains the source buffer to prevent DMIC DMA back-pressure. + * Only forwards data to the sink (and returns 0) when VAD is active. + * Returns PPL_STATUS_PATH_STOP during silence so downstream components idle. + * + * Assumes single-channel S32LE (mono DMIC capture at 16 kHz). + * ---------------------------------------------------------------------- */ +static int vad_gate_copy(struct comp_dev *dev) +{ + struct vad_gate_data *cd = comp_get_drvdata(dev); + struct comp_buffer *source, *sink; + uint32_t frame_bytes, frames, src_bytes; + + source = comp_dev_get_first_data_producer(dev); + 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; + if (!frames) + return PPL_STATUS_PATH_STOP; + src_bytes = frames * frame_bytes; + + buffer_stream_invalidate(source, src_bytes); + vad_update_energy(dev, &source->stream, frames); + + if (!cd->vad_active) { + /* Drain input to keep DMIC DMA running during silence. */ + comp_update_buffer_consume(source, src_bytes); + return PPL_STATUS_PATH_STOP; + } + + /* VAD active: limit frames to what sink can accept. */ + uint32_t sink_frame_bytes = audio_stream_frame_bytes(&sink->stream); + uint32_t sink_frames = audio_stream_get_free_bytes(&sink->stream) / sink_frame_bytes; + + if (sink_frames < frames) + frames = sink_frames; + if (!frames) + return 0; + + src_bytes = frames * frame_bytes; + uint32_t sink_bytes = frames * sink_frame_bytes; + + audio_stream_copy(&source->stream, 0, &sink->stream, 0, + frames * audio_stream_channels(&source->stream)); + buffer_stream_writeback(sink, sink_bytes); + comp_update_buffer_produce(sink, sink_bytes); + comp_update_buffer_consume(source, src_bytes); + + return 0; +} + +/* ------------------------------------------------------------------------- + * Component driver registration + * ---------------------------------------------------------------------- */ + +static const struct comp_driver vad_gate_drv = { + .type = SOF_COMP_NONE, + .uid = SOF_RT_UUID(vad_gate_uuid), + .tctx = &vad_gate_tr, + .ops = { + .create = vad_gate_new, + .free = vad_gate_free, + .params = vad_gate_params, + .trigger = vad_gate_trigger, + .copy = vad_gate_copy, + .prepare = vad_gate_prepare, + .reset = vad_gate_reset, +#if CONFIG_IPC_MAJOR_4 + .set_large_config = vad_gate_set_large_config, + .get_attribute = vad_gate_get_attribute, +#endif + }, +}; + +static SHARED_DATA struct comp_driver_info vad_gate_info = { + .drv = &vad_gate_drv, +}; + +UT_STATIC void sys_comp_vad_gate_init(void) +{ + comp_register(platform_shared_get(&vad_gate_info, + sizeof(vad_gate_info))); +} + +DECLARE_MODULE(sys_comp_vad_gate_init); +SOF_MODULE_INIT(vad_gate, sys_comp_vad_gate_init); diff --git a/src/include/sof/audio/vad_gate.h b/src/include/sof/audio/vad_gate.h new file mode 100644 index 000000000000..f64ec191e563 --- /dev/null +++ b/src/include/sof/audio/vad_gate.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2024 Intel Corporation. All rights reserved. + */ + +#ifndef __SOF_AUDIO_VAD_GATE_H__ +#define __SOF_AUDIO_VAD_GATE_H__ + +#include + +/* + * IPC4 SET_LARGE_CONFIG param ID to update the VAD gate tuning at runtime. + * Payload: struct ipc4_vad_gate_config. + */ +#define IPC4_VAD_GATE_SET_CONFIG 1 + +/* Default tuning — suitable for hand-clap / speech in a quiet lab. */ +#define VAD_DEFAULT_THRESHOLD (INT32_MAX / 500) /* ~-54 dBFS (int32 scale) */ +#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 */ + +/* Runtime config exchanged via SET_LARGE_CONFIG / GET_LARGE_CONFIG. */ +struct ipc4_vad_gate_config { + int32_t threshold; /* peak energy threshold in S32 amplitude units */ + uint16_t onset_frames; /* consecutive frames above threshold for SPEECH */ + uint16_t hangover_frames; /* consecutive frames below threshold for SILENCE */ + uint8_t energy_shift; /* IIR smoothing shift (alpha = 1 / 2^shift) */ + uint8_t _pad[3]; +} __attribute__((packed)); + +#ifdef UNIT_TEST +void sys_comp_vad_gate_init(void); +#endif + +#endif /* __SOF_AUDIO_VAD_GATE_H__ */ diff --git a/tools/topology/topology2/include/components/vad-gate.conf b/tools/topology/topology2/include/components/vad-gate.conf new file mode 100644 index 000000000000..3e2520816cf5 --- /dev/null +++ b/tools/topology/topology2/include/components/vad-gate.conf @@ -0,0 +1,58 @@ +# +# vad-gate widget +# +# Voice Activity Detection gate. Sits between the DMIC copier and the +# downstream Mixin in a WOV pipeline. During silence it drains the DMIC +# input but suppresses data flow to the downstream chain (via +# PPL_STATUS_PATH_STOP) so KPBs and WOV detectors idle. When voice is +# detected audio flows through and the downstream components wake. +# +# Usage: +# +# Object.Widget."vad-gate"."N" { +# uuid "8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f" +# num_input_audio_formats 1 +# num_output_audio_formats 1 +# } + +Class.Widget."vad-gate" { + + DefineAttribute."index" {} + DefineAttribute."instance" {} + + DefineAttribute."cpc" { + token_ref "comp.word" + } + + + + DefineAttribute."is_pages" { + token_ref "comp.word" + } + + attributes { + !constructor [ + "index" + "instance" + ] + + !mandatory [ + "no_pm" + "uuid" + "num_input_audio_formats" + "num_output_audio_formats" + ] + + !immutable [ + "type" + ] + + unique "instance" + } + + # 1 DMIC input, 1 Mixin output + type "effect" + no_pm "true" + num_input_pins 1 + num_output_pins 1 +} diff --git a/tools/topology/topology2/platform/intel/dmic-wov-multi.conf b/tools/topology/topology2/platform/intel/dmic-wov-multi.conf index e0e7c57887b3..b95031267f2d 100644 --- a/tools/topology/topology2/platform/intel/dmic-wov-multi.conf +++ b/tools/topology/topology2/platform/intel/dmic-wov-multi.conf @@ -14,7 +14,7 @@ # Core 0 : WOV Arbiter + Host copier # # Pipeline IDs used -# 100 DAI pipeline : DMIC DAI copier → Mixin +# 100 DAI pipeline : DMIC DAI copier → VAD Gate → Mixin # 101 KPB pipeline : Mixout_0 → KPB_0 → WOV_0 (Core 0) # 102 KPB pipeline : Mixout_1 → KPB_1 → WOV_1 (Core 0) # 103 KPB pipeline : Mixout_2 → KPB_2 → WOV_2 (Core 1) @@ -23,6 +23,7 @@ # Requires # CONFIG_COMP_KPB=y # CONFIG_COMP_WOV_ARBITER=y +# CONFIG_COMP_VAD_GATE=y # CONFIG_COMP_MIXIN_MIXOUT=y # CONFIG_AMS=y # CONFIG_COMP_KWD_DETECT (detect_test sample) @@ -34,6 +35,7 @@ + @@ -48,6 +50,9 @@ Define { # WOV arbiter UUID (registered in uuid-registry.txt as 'wov_arbiter') WOV_ARB_UUID "4a5b6c7d-8e9f-4a1b-2c3d-4e5f60718293" + # VAD gate UUID (registered in uuid-registry.txt as 'vad_gate') + VAD_GATE_UUID "8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f" + # DMIC DAI configuration — adjust for target hardware DMIC_DAI_INDEX 1 DMIC_PCM_ID 11 @@ -58,11 +63,13 @@ Define { # Detector cycle budget — clap test requires very little compute KWD_CPC 100000 WOV_ARB_CPC 20000 + VAD_GATE_CPC 5000 } # --------------------------------------------------------------------------- # Pipeline 100 — DAI (Core 0) -# DMIC DAI copier → Mixin (fan-out to 3 Mixouts in pipelines 101/102/103) +# DMIC DAI copier → VAD Gate → Mixin (fan-out to 3 Mixouts in 101/102/103) +# VAD Gate idles the downstream chain during silence. # --------------------------------------------------------------------------- Object.Pipeline.dai-kpb-be [ { @@ -106,6 +113,31 @@ Object.Pipeline.dai-kpb-be [ ] } + Object.Widget."vad-gate".1 { + uuid $VAD_GATE_UUID + cpc $VAD_GATE_CPC + + num_input_audio_formats 1 + num_output_audio_formats 1 + + Object.Base.input_audio_format [ + { + in_rate 16000 + in_channels 1 + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 16000 + out_channels 1 + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + Object.Widget.mixin.1 { type "mixer" mix_type "mix_in" @@ -486,9 +518,15 @@ Object.Pipeline.host-gateway-capture [ # --------------------------------------------------------------------------- Object.Base.route [ - # DAI copier → Mixin (pipeline 100 internal) + # DAI copier → VAD Gate (pipeline 100 internal) { source "dai-copier.DMIC.$DMIC_NAME.capture" + sink "vad-gate.100.1" + } + + # VAD Gate → Mixin (pipeline 100 internal) + { + source "vad-gate.100.1" sink "mixin.100.1" } diff --git a/uuid-registry.txt b/uuid-registry.txt index c87bf913e324..cf06a90eb89c 100644 --- a/uuid-registry.txt +++ b/uuid-registry.txt @@ -175,6 +175,7 @@ c51dc642-a2e1-48df-a490e2748cb6363e tflmcly 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 b77e677e-5ff4-4188-af14fba8bdbf8682 volume 8a171323-94a3-4e1d-afe9fe5dbaa4c393 volume4 1028070e-04e8-46ab-8d8110a0116ce738 wait From d87f00d2584f6e24e27528070cf8d6cce87cd62d Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Wed, 22 Jul 2026 15:41:33 +0100 Subject: [PATCH 3/3] audio: fix AMS callback signature in detect_test and wov_arbiter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ams_msg_callback_fn type is: void (*)(const struct ams_message_payload *const, void *) The on_wov_ctrl() and arb_on_detect() callbacks were written with the old four-argument form (msg_type_id, message, message_size, ctx) which no longer matches the typedef, causing build failures on mtl/tgl/lnl. Also fix the payload field access inside those callbacks: p->payload_size → p->message_length p->payload → p->message Co-Authored-By: Claude Sonnet 4.6 --- src/audio/wov_arbiter/wov_arbiter.c | 11 +++-------- src/samples/audio/detect_test.c | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/audio/wov_arbiter/wov_arbiter.c b/src/audio/wov_arbiter/wov_arbiter.c index c8307765b8be..bc67e8798949 100644 --- a/src/audio/wov_arbiter/wov_arbiter.c +++ b/src/audio/wov_arbiter/wov_arbiter.c @@ -92,21 +92,16 @@ static void arb_send_ctrl(const struct comp_dev *dev, uint8_t cmd, } /* AMS callback: a WOV detector has fired. */ -static void arb_on_detect(const uint32_t msg_type_id, - const void *message, - const uint32_t message_size, - void *ctx) +static void arb_on_detect(const struct ams_message_payload *const p, void *ctx) { struct comp_dev *dev = ctx; struct wov_arb_data *cd = comp_get_drvdata(dev); - const struct ams_message_payload *p = message; - if (message_size < sizeof(*p) || - p->payload_size < sizeof(struct wov_detect_payload)) + if (p->message_length < sizeof(struct wov_detect_payload)) return; const struct wov_detect_payload *det = - (const struct wov_detect_payload *)p->payload; + (const struct wov_detect_payload *)p->message; if (det->slot_id >= WOV_ARB_MAX_SLOTS) { comp_err(dev, "wov_arb: bad slot_id %u", det->slot_id); diff --git a/src/samples/audio/detect_test.c b/src/samples/audio/detect_test.c index f5bbb8fad9e0..8a777d4b3fc9 100644 --- a/src/samples/audio/detect_test.c +++ b/src/samples/audio/detect_test.c @@ -207,21 +207,16 @@ static void ams_notify_arb(const struct comp_dev *dev) } /* AMS callback: arbiter is broadcasting a PAUSE or RESUME command. */ -static void on_wov_ctrl(const uint32_t msg_type_id, - const void *message, - const uint32_t message_size, - void *ctx) +static void on_wov_ctrl(const struct ams_message_payload *const p, void *ctx) { struct comp_dev *dev = ctx; struct comp_data *cd = comp_get_drvdata(dev); - const struct ams_message_payload *p = message; - if (message_size < sizeof(*p) || - p->payload_size < sizeof(struct wov_ctrl_payload)) + if (p->message_length < sizeof(struct wov_ctrl_payload)) return; const struct wov_ctrl_payload *ctrl = - (const struct wov_ctrl_payload *)p->payload; + (const struct wov_ctrl_payload *)p->message; if (ctrl->cmd == WOV_CTRL_CMD_PAUSE) { if (cd->wov_slot_id != ctrl->active_slot) {