From cd92b76e07c24751bd05cae8ccc1c1805eac0d47 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 10 Nov 2026 15:18:29 +0200 Subject: [PATCH 1/6] topology2: cavs-sdw: add mono WebRTC AEC on SoundWire DMIC capture Add an SDW_SPK_AEC_CAPTURE_PCM option that routes the speaker echo reference to a webrtc-aec module instead of the echo reference host PCM. The SoundWire DMIC capture from module-copier.41.0 and the speaker echo reference are both downmixed to mono with micsel and resampled from 48 kHz to 16 kHz with SRC before the AEC, so the AECm backend cancels the echo of both speaker channels. The echo cancelled signal is captured with a new mono 16 kHz "Microphone AEC Capture" PCM 14. The AEC pins use 10 ms buffers to match the AEC processing block size. Add the development target sof-ptl-rt713-l3-rt1320-l12-aec for testing. The topologies without SDW_SPK_AEC_CAPTURE_PCM are binary identical. Signed-off-by: Seppo Ingalsuo --- tools/topology/topology2/cavs-sdw.conf | 7 + .../topology2/development/tplg-targets.cmake | 7 + .../topology2/platform/intel/sdw-amp-aec.conf | 337 ++++++++++++++++++ .../platform/intel/sdw-amp-echo-ref.conf | 5 + 4 files changed, 356 insertions(+) create mode 100644 tools/topology/topology2/platform/intel/sdw-amp-aec.conf diff --git a/tools/topology/topology2/cavs-sdw.conf b/tools/topology/topology2/cavs-sdw.conf index 7edd0975d4f6..f2b852668d0d 100644 --- a/tools/topology/topology2/cavs-sdw.conf +++ b/tools/topology/topology2/cavs-sdw.conf @@ -64,6 +64,8 @@ + + Define { PLATFORM "none" @@ -133,6 +135,7 @@ Define { SDW_AMP_PIPELINE_SRC "generic" SDW_JACK_PIPELINE_SRC "generic" SDW_ECHO_REF_DAI "false" + SDW_SPK_AEC_CAPTURE_PCM "false" } # override defaults with platform-specific config @@ -273,6 +276,10 @@ IncludeByKey.SDW_SPK_ECHO_REF { "true" "platform/intel/sdw-amp-echo-ref.conf" } +IncludeByKey.SDW_SPK_AEC_CAPTURE_PCM { + "true" "platform/intel/sdw-amp-aec.conf" +} + IncludeByKey.SDW_JACK_AUDIO_FEATURE_CAPTURE { "true" "platform/intel/sdw-jack-audio-feature.conf" } diff --git a/tools/topology/topology2/development/tplg-targets.cmake b/tools/topology/topology2/development/tplg-targets.cmake index 9713c7088182..554dcf92f861 100644 --- a/tools/topology/topology2/development/tplg-targets.cmake +++ b/tools/topology/topology2/development/tplg-targets.cmake @@ -479,6 +479,13 @@ PREPROCESS_PLUGINS=nhlt,NHLT_BIN=nhlt-sof-ptl-rt721-4ch-echoref.bin,DMIC0_ENHANC EFX_DMIC0_TDFB_PARAMS=line4_pass,EFX_DMIC0_DRC_PARAMS=dmic_default,\ DEEPBUFFER_FW_DMA_MS=10,DEEP_BUF_SPK=true,SDW_JACK_ECHO_REF=true,SDW_SPK_ECHO_REF=true,SDW_ECHO_REF_DAI=true" +# RT713 headset on link 3 and RT1320 amplifiers on links 1 and 2 with mono +# 16 kHz WebRTC AEC of the SoundWire DMIC against the speaker echo reference +"cavs-sdw\;sof-ptl-rt713-l3-rt1320-l12-aec\;PLATFORM=ptl,SDW_DMIC=1,NUM_SDW_AMP_LINKS=2,\ +SDW_AMP_FEEDBACK=false,SDW_SPK_STREAM=Playback-SmartAmp,SDW_DMIC_STREAM=Capture-SmartMic,\ +SDW_JACK_OUT_STREAM=Playback-SimpleJack,SDW_JACK_IN_STREAM=Capture-SimpleJack,\ +SDW_SPK_ECHO_REF=true,SDW_ECHO_REF_DAI=true,SDW_SPK_AEC_CAPTURE_PCM=true" + # RT721 eval board with PCH-DMIC, sof_sdw_quirk_table with SOC_SDW_PCH_DMIC # Enable echo reference capture from jack and speaker output # Enable SSP2 BT diff --git a/tools/topology/topology2/platform/intel/sdw-amp-aec.conf b/tools/topology/topology2/platform/intel/sdw-amp-aec.conf new file mode 100644 index 000000000000..ec11b6cfc16b --- /dev/null +++ b/tools/topology/topology2/platform/intel/sdw-amp-aec.conf @@ -0,0 +1,337 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Mono 16 kHz WebRTC AEC for SoundWire DMIC with speaker echo reference. +# +# module-copier.41.0 (SDW DMIC, 48 kHz stereo) -> micsel.141.1 (L+R)/2 -> +# src.141.1 (48 -> 16 kHz) -> webrtc-aec.140.1 pin 0 +# speaker echo reference siggen.120.1 (48 kHz stereo) -> micsel.120.1 (L+R)/2 -> +# src.120.1 (48 -> 16 kHz) -> webrtc-aec.140.1 pin 1 +# webrtc-aec.140.1 -> host-copier PCM "Microphone AEC Capture" (16 kHz mono) +# +# Included from sdw-amp-echo-ref.conf when SDW_SPK_AEC_CAPTURE_PCM is true. The echo +# reference DAI (SDW_ECHO_REF_DAI) must be enabled. + +Class.Pipeline."sdw-aec-capture" { + + + attributes { + !constructor [ + "index" + ] + unique "instance" + } + + Object.Widget.pipeline."1" { + priority 0 + lp_mode 0 + } + + period 1000 + direction "capture" + time_domain "timer" + dynamic_pipeline 1 +} + +Define { + SDW_AEC_PCM_ID 14 + SDW_AEC_PCM_NAME "Microphone AEC Capture" + SDW_AEC_PIPELINE_ID 140 + SDW_AEC_MIC_PIPELINE_ID 141 +} + +# Echo reference: siggen -> micsel stereo to mono -> SRC 48 kHz to 16 kHz +Object.Pipeline."sdw-aec-capture" [ + { + index $SDW_SPK_ECHO_REF_PIPELINE_ID + + Object.Widget.siggen.1 { + num_input_pins 1 + num_output_pins 1 + num_input_audio_formats 1 + num_output_audio_formats 1 + Object.Base.input_audio_format [ + { + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_bit_depth 32 + out_valid_bit_depth 32 + } + ] + } + + Object.Widget.micsel.1 { + num_input_audio_formats 1 + num_output_audio_formats 1 + Object.Control.bytes."1" { + name "AEC reference micsel bytes" + + } + Object.Base.input_audio_format [ + { + in_rate 48000 + in_channels 2 + in_ch_cfg $CHANNEL_CONFIG_STEREO + in_ch_map $CHANNEL_MAP_STEREO + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 48000 + 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.src.1 { + num_input_audio_formats 1 + num_output_audio_formats 1 + Object.Base.input_audio_format [ + { + in_rate 48000 + 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 + } + ] + } + } +] + +# Microphone: micsel stereo to mono -> SRC 48 kHz to 16 kHz +Object.Pipeline."sdw-aec-capture" [ + { + index $SDW_AEC_MIC_PIPELINE_ID + + Object.Widget.micsel.1 { + num_input_audio_formats 1 + num_output_audio_formats 1 + Object.Control.bytes."1" { + name "AEC microphone micsel bytes" + + } + Object.Base.input_audio_format [ + { + in_rate 48000 + in_channels 2 + in_ch_cfg $CHANNEL_CONFIG_STEREO + in_ch_map $CHANNEL_MAP_STEREO + in_bit_depth 32 + in_valid_bit_depth 32 + } + ] + Object.Base.output_audio_format [ + { + out_rate 48000 + 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.src.1 { + num_input_audio_formats 1 + num_output_audio_formats 1 + Object.Base.input_audio_format [ + { + in_rate 48000 + 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 + } + ] + } + } +] + +# AEC and host capture +Object.Pipeline.host-gateway-capture [ + { + index $SDW_AEC_PIPELINE_ID + + Object.Widget.host-copier.1 { + stream_name $SDW_AEC_PCM_NAME + pcm_id $SDW_AEC_PCM_ID + num_input_audio_formats 1 + num_output_audio_formats 2 + 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 16 + out_valid_bit_depth 16 + } + { + 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.webrtc-aec.1 { + index $SDW_AEC_PIPELINE_ID + num_input_pins 2 + + Object.Control { + mixer."1" { + name "AEC switch" + } + mixer."2" { + name "AEC high suppression switch" + } + } + + Object.Base.input_pin_binding.1 { + input_pin_binding_name "src.$SDW_AEC_MIC_PIPELINE_ID.1" + } + Object.Base.input_pin_binding.2 { + input_pin_binding_name "src.$SDW_SPK_ECHO_REF_PIPELINE_ID.1" + } + + Object.Base.input_audio_format [ + { + input_pin_index 0 + 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 + ibs 640 + } + { + input_pin_index 1 + 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 + ibs 640 + } + ] + 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 + obs 640 + } + ] +} + +Object.PCM.pcm [ + { + name $SDW_AEC_PCM_NAME + id $SDW_AEC_PCM_ID + direction "capture" + Object.Base.fe_dai.1 { + name $SDW_AEC_PCM_NAME + } + Object.PCM.pcm_caps.1 { + name $SDW_AEC_PCM_NAME + formats 'S16_LE,S32_LE' + rates "16000" + rate_min 16000 + rate_max 16000 + channels_min 1 + channels_max 1 + } + } +] + +Object.Base.route [ + { + source "alh-copier.Loopback_Virtual.25" + sink "siggen.$SDW_SPK_ECHO_REF_PIPELINE_ID.1" + } + { + source "module-copier.21.22" + sink "siggen.$SDW_SPK_ECHO_REF_PIPELINE_ID.1" + } + { + source "siggen.$SDW_SPK_ECHO_REF_PIPELINE_ID.1" + sink "micsel.$SDW_SPK_ECHO_REF_PIPELINE_ID.1" + } + { + source "micsel.$SDW_SPK_ECHO_REF_PIPELINE_ID.1" + sink "src.$SDW_SPK_ECHO_REF_PIPELINE_ID.1" + } + { + source "src.$SDW_SPK_ECHO_REF_PIPELINE_ID.1" + sink "webrtc-aec.$SDW_AEC_PIPELINE_ID.1" + } + { + source "module-copier.41.0" + sink "micsel.$SDW_AEC_MIC_PIPELINE_ID.1" + } + { + source "micsel.$SDW_AEC_MIC_PIPELINE_ID.1" + sink "src.$SDW_AEC_MIC_PIPELINE_ID.1" + } + { + source "src.$SDW_AEC_MIC_PIPELINE_ID.1" + sink "webrtc-aec.$SDW_AEC_PIPELINE_ID.1" + } + { + source "webrtc-aec.$SDW_AEC_PIPELINE_ID.1" + sink "host-copier.$SDW_AEC_PCM_ID.capture" + } +] diff --git a/tools/topology/topology2/platform/intel/sdw-amp-echo-ref.conf b/tools/topology/topology2/platform/intel/sdw-amp-echo-ref.conf index abde20519f2e..1f43e63353aa 100644 --- a/tools/topology/topology2/platform/intel/sdw-amp-echo-ref.conf +++ b/tools/topology/topology2/platform/intel/sdw-amp-echo-ref.conf @@ -5,6 +5,9 @@ Define { IncludeByKey.SDW_SPK_ECHO_REF { "true" { + # With SDW_SPK_AEC_CAPTURE_PCM the reference feeds webrtc-aec instead of a host PCM + IncludeByKey.SDW_SPK_AEC_CAPTURE_PCM { + "false" { Object.Pipeline { siggen-host-copier-capture [ { @@ -48,5 +51,7 @@ IncludeByKey.SDW_SPK_ECHO_REF { sink "host-copier.$SDW_SPK_ECHO_REF_PCM_ID.capture" } ] + } # SDW_SPK_AEC_CAPTURE_PCM false + } } # SDW_SPK_ECHO_REF true } From 001869bffc6a0824b9cf4edbbdcfe8dc447a0657 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 10 Nov 2026 20:01:35 +0200 Subject: [PATCH 2/6] audio: module_adapter: allocate pin formats from module allocation context The IPC4 base config extension pin formats were allocated from the heap of the module allocation context. For a userspace DP module that heap is the shared system user heap, which is not mapped into the DP thread memory domain, so a module reading its input pin formats in prepare faulted with a load/store privilege exception. Allocate and free the pin formats with sof_ctx_alloc() and sof_ctx_free() so they are placed in the module vregion when one exists, the same as the module and device objects. Modules without a vregion keep using the heap as before. Signed-off-by: Seppo Ingalsuo --- src/audio/module_adapter/module_adapter.c | 2 +- src/audio/module_adapter/module_adapter_ipc4.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/module_adapter/module_adapter.c b/src/audio/module_adapter/module_adapter.c index ac7e05521e33..b0075453e434 100644 --- a/src/audio/module_adapter/module_adapter.c +++ b/src/audio/module_adapter/module_adapter.c @@ -205,7 +205,7 @@ __cold static void module_adapter_mem_free(struct processing_module *mod) * on the module heap since we're freeing the heap itself too */ #if CONFIG_IPC_MAJOR_4 - sof_heap_free(alloc->heap, mod->priv.cfg.input_pins); + sof_ctx_free(alloc, mod->priv.cfg.input_pins); #endif sof_ctx_free(alloc, mod->dev); sof_ctx_free(alloc, mod); diff --git a/src/audio/module_adapter/module_adapter_ipc4.c b/src/audio/module_adapter/module_adapter_ipc4.c index 6b66c89a8257..7c61d20c1fed 100644 --- a/src/audio/module_adapter/module_adapter_ipc4.c +++ b/src/audio/module_adapter/module_adapter_ipc4.c @@ -201,7 +201,7 @@ __cold int module_adapter_init_data(struct comp_dev *dev, dst->nb_input_pins = n_in; dst->nb_output_pins = n_out; - dst->input_pins = sof_heap_alloc(dev->mod->priv.resources.alloc->heap, + dst->input_pins = sof_ctx_alloc(dev->mod->priv.resources.alloc, SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, pinsz, 0); if (!dst->input_pins) From dbd424c7b8dd1c3152f069b95e6710fe8bb8b849 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 10 Nov 2026 20:01:35 +0200 Subject: [PATCH 3/6] audio: webrtc_aec: select inputs by pin and request pin formats The microphone and echo reference were identified by comparing the source pipeline IDs with the sink pipeline. When neither source is in the output pipeline, e.g. both arrive via SRC pipelines, the code fell back to the bind order and could pass the reference through as the microphone. With IPC4 use the destination queue ID of each source instead, pin 0 is the microphone and pin 1 the reference. IPC3 has no queue IDs in the buffer ID, so it keeps the pipeline based selection. Set init_config in the manifest so the kernel sends the per-pin formats that prepare uses for the reference, and fail prepare cleanly if they are missing. Signed-off-by: Seppo Ingalsuo --- src/audio/webrtc_aec/webrtc_aec.c | 28 +++++++++++++++++----------- src/audio/webrtc_aec/webrtc_aec.toml | 1 + 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/audio/webrtc_aec/webrtc_aec.c b/src/audio/webrtc_aec/webrtc_aec.c index 4c3f8f027a27..7b27f44f1746 100644 --- a/src/audio/webrtc_aec/webrtc_aec.c +++ b/src/audio/webrtc_aec/webrtc_aec.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -229,17 +230,17 @@ __cold static int webrtc_aec_prepare(struct processing_module *mod, if (num_of_sources == 1) { cd->mic_src = 0; cd->ref_src = -1; - } else if (source_get_pipeline_id(sources[0]) == sink_get_pipeline_id(sinks[0]) && - source_get_pipeline_id(sources[1]) != sink_get_pipeline_id(sinks[0])) { - cd->mic_src = 0; - cd->ref_src = 1; - } else if (source_get_pipeline_id(sources[1]) == sink_get_pipeline_id(sinks[0]) && - source_get_pipeline_id(sources[0]) != sink_get_pipeline_id(sinks[0])) { - cd->mic_src = 1; - cd->ref_src = 0; } else { - cd->mic_src = 0; - cd->ref_src = 1; +#ifdef CONFIG_IPC_MAJOR_4 + /* Bind order is arbitrary, input pin 0 is mic and pin 1 is reference */ + cd->mic_src = IPC4_SINK_QUEUE_ID(source_get_id(sources[0])) == 0 ? 0 : 1; +#else + /* The mic is the source in the same pipeline as the output */ + cd->mic_src = source_get_pipeline_id(sources[1]) == sink_get_pipeline_id(sinks[0]) && + source_get_pipeline_id(sources[0]) != sink_get_pipeline_id(sinks[0]) ? + 1 : 0; +#endif + cd->ref_src = 1 - cd->mic_src; } mic_fmt = source_get_frm_fmt(sources[cd->mic_src]); @@ -313,9 +314,14 @@ __cold static int webrtc_aec_prepare(struct processing_module *mod, #ifdef CONFIG_IPC_MAJOR_4 /* Apply reference format override from topology pin descriptor. */ - if (cd->ref_src >= 0) + if (cd->ref_src >= 0) { + if (!mod->priv.cfg.input_pins || mod->priv.cfg.nb_input_pins < 2) { + comp_err(dev, "webrtc_aec: no reference pin format"); + return -EINVAL; + } ipc4_update_source_format(sources[cd->ref_src], &mod->priv.cfg.input_pins[1].audio_fmt); + } #endif if (cd->backend->configure) { diff --git a/src/audio/webrtc_aec/webrtc_aec.toml b/src/audio/webrtc_aec/webrtc_aec.toml index a6f10aa3cb4c..7a72ac57c5f1 100644 --- a/src/audio/webrtc_aec/webrtc_aec.toml +++ b/src/audio/webrtc_aec/webrtc_aec.toml @@ -12,6 +12,7 @@ domain_types = "1" load_type = LOAD_TYPE module_type = "9" + init_config = "1" auto_start = "0" sched_caps = [1, 0x00008000] REM # pin = [dir, type, sample rate, size, container, channel-cfg] From c0d95df3d3532df9d87c5e1a3fb5f962897b4f88 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 10 Nov 2026 20:01:35 +0200 Subject: [PATCH 4/6] audio: webrtc_aec: fix LLEXT build and allow float backend as module The AECm LLEXT module did not load since the cross-built library referenced __assert_func and the realloc shim called k_panic(), neither of which is exported to modules. Build the library with NDEBUG and drop the panic from the unused realloc shim. The library build script is now regenerated only on content change and is a dependency of the library, so flag changes trigger a rebuild. The float AEC backend was limited to built-in builds. Let the same script cross-build either the float AEC or the AECm sources and link the matching glue code into the module. The float AEC needs sqrtf(), which is provided in the module math helpers for LLEXT builds. Signed-off-by: Seppo Ingalsuo --- src/audio/webrtc_aec/Kconfig | 1 - src/audio/webrtc_aec/llext/CMakeLists.txt | 8 ++++- src/audio/webrtc_aec/webrtc_aec-math.c | 19 ++++++++++++ src/audio/webrtc_aec/webrtc_aec-shims.c | 3 +- src/audio/webrtc_aec/webrtc_aec.cmake | 36 +++++++++++++++++------ 5 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/audio/webrtc_aec/Kconfig b/src/audio/webrtc_aec/Kconfig index 9d8a78b2b2ae..a91df12ee506 100644 --- a/src/audio/webrtc_aec/Kconfig +++ b/src/audio/webrtc_aec/Kconfig @@ -32,7 +32,6 @@ choice WEBRTC_AEC_BACKEND config WEBRTC_AEC_BACKEND_FLOAT bool "Classic Float AEC (Multidelay Block Frequency Domain with hardware FPU)" - depends on COMP_WEBRTC_AEC = y help Single-precision floating-point AEC using partitioned-block frequency domain adaptive filtering (MDF) accelerated by the diff --git a/src/audio/webrtc_aec/llext/CMakeLists.txt b/src/audio/webrtc_aec/llext/CMakeLists.txt index 175ee9420caf..68632b2498f3 100644 --- a/src/audio/webrtc_aec/llext/CMakeLists.txt +++ b/src/audio/webrtc_aec/llext/CMakeLists.txt @@ -10,9 +10,15 @@ if(CONFIG_COMP_WEBRTC_AEC_STUB) else() include(${CMAKE_CURRENT_LIST_DIR}/../webrtc_aec.cmake) + if(CONFIG_WEBRTC_AEC_BACKEND_FLOAT) + set(_aec_backend_glue ../webrtc_aec-aec.c ../webrtc_aec-math.c) + else() + set(_aec_backend_glue ../webrtc_aec-aecm.c) + endif() + sof_llext_build("webrtc_aec" SOURCES ../webrtc_aec.c - ../webrtc_aec-aecm.c + ${_aec_backend_glue} ../webrtc_aec-shims.c INCLUDES "${WEBRTC_AECM_INSTALL_DIR}/include" "${SOF_WEBRTC_APM_SRC_DIR}" diff --git a/src/audio/webrtc_aec/webrtc_aec-math.c b/src/audio/webrtc_aec/webrtc_aec-math.c index e55ca2b2e056..0d7e61f8d1ae 100644 --- a/src/audio/webrtc_aec/webrtc_aec-math.c +++ b/src/audio/webrtc_aec/webrtc_aec-math.c @@ -61,6 +61,25 @@ float exp2f(float x) return p * aec_ldexp2(n); } +#ifdef LL_EXTENSION_BUILD +/* --- sqrtf: not exported by the base firmware, bit estimate + 3 Newton steps --- */ +float sqrtf(float x) +{ + union { float f; uint32_t i; } u = { .f = x }; + float y; + + if (x <= 0.0f) + return 0.0f; + + u.i = (u.i >> 1) + 0x1fc00000; + y = u.f; + y = 0.5f * (y + x / y); + y = 0.5f * (y + x / y); + y = 0.5f * (y + x / y); + return y; +} +#endif + /* --- powf: x^y = 2^(y * log2(x)) --- */ float powf(float x, float y) diff --git a/src/audio/webrtc_aec/webrtc_aec-shims.c b/src/audio/webrtc_aec/webrtc_aec-shims.c index 868ecfb9e9fd..a838db26f593 100644 --- a/src/audio/webrtc_aec/webrtc_aec-shims.c +++ b/src/audio/webrtc_aec/webrtc_aec-shims.c @@ -73,7 +73,6 @@ void *webrtc_realloc(void *ptr, size_t size) return NULL; } - /* Not called by AEC3 */ - k_panic(); + /* Not called by AECm; k_panic() is not exported to LLEXT modules */ return NULL; } diff --git a/src/audio/webrtc_aec/webrtc_aec.cmake b/src/audio/webrtc_aec/webrtc_aec.cmake index 094f55e4bb0b..13e56d89af22 100644 --- a/src/audio/webrtc_aec/webrtc_aec.cmake +++ b/src/audio/webrtc_aec/webrtc_aec.cmake @@ -33,22 +33,35 @@ set(WEBRTC_AECM_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/webrtc-aecm-install" set(_aecm_src "${SOF_WEBRTC_APM_SRC_DIR}/webrtc") -file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/webrtc-aecm-build.sh" +if(CONFIG_WEBRTC_AEC_BACKEND_FLOAT) + set(_aec_backend_srcs " + $SRC/modules/audio_processing/aec/echo_cancellation.c + $SRC/modules/audio_processing/aec/aec_core.c + $SRC/modules/audio_processing/aec/aec_rdft.c + $SRC/modules/audio_processing/aec/aec_resampler.c") + set(_aec_backend_hdr "$SRC/modules/audio_processing/aec/include/echo_cancellation.h") +else() + set(_aec_backend_srcs " + $SRC/modules/audio_processing/aecm/aecm_core.c + $SRC/modules/audio_processing/aecm/aecm_core_c.c + $SRC/modules/audio_processing/aecm/echo_control_mobile.c") + set(_aec_backend_hdr "$SRC/modules/audio_processing/aecm/include/echo_control_mobile.h") +endif() + +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/webrtc-aecm-build.sh.tmp" "#!/bin/sh set -e SRC=${_aecm_src} INST=${WEBRTC_AECM_INSTALL_DIR} OBJ=${CMAKE_CURRENT_BINARY_DIR}/webrtc-aecm-obj +rm -rf \"$OBJ\" \"$INST/lib/libwebrtc_aecm.a\" mkdir -p \"$OBJ\" \"$INST/lib\" \"$INST/include\" \"$INST/include/webrtc\" -CFLAGS=\"-O2 -fPIC -I${SOF_WEBRTC_APM_SRC_DIR} -I$SRC -I$SRC/common_audio/signal_processing/include -DWEBRTC_ARCH_32_BITS= -DWEBRTC_ARCH_LITTLE_ENDIAN= -Dmalloc=webrtc_malloc -Dcalloc=webrtc_calloc -Drealloc=webrtc_realloc -Dfree=webrtc_free\" +CFLAGS=\"-O2 -fPIC -DNDEBUG -I${SOF_WEBRTC_APM_SRC_DIR} -I$SRC -I$SRC/common_audio/signal_processing/include -DWEBRTC_ARCH_32_BITS= -DWEBRTC_ARCH_LITTLE_ENDIAN= -Dmalloc=webrtc_malloc -Dcalloc=webrtc_calloc -Drealloc=webrtc_realloc -Dfree=webrtc_free\" -# AECm core -SRCS=\" - $SRC/modules/audio_processing/aecm/aecm_core.c - $SRC/modules/audio_processing/aecm/aecm_core_c.c - $SRC/modules/audio_processing/aecm/echo_control_mobile.c +# AEC backend core +SRCS=\"${_aec_backend_srcs} $SRC/common_audio/ring_buffer.c $SRC/modules/audio_processing/utility/delay_estimator.c $SRC/modules/audio_processing/utility/delay_estimator_wrapper.c @@ -78,14 +91,19 @@ for f in \$SRCS; do done ${_aecm_ar} rcs \"$INST/lib/libwebrtc_aecm.a\" \"$OBJ\"/*.o -cp $SRC/modules/audio_processing/aecm/include/echo_control_mobile.h \"$INST/include/\" +cp ${_aec_backend_hdr} \"$INST/include/\" cp $SRC/typedefs.h \"$INST/include/webrtc/\" 2>/dev/null || true ") +# Only touch the script when its content changes so that the library is +# rebuilt on flag changes but not on every reconfigure. +configure_file("${CMAKE_CURRENT_BINARY_DIR}/webrtc-aecm-build.sh.tmp" + "${CMAKE_CURRENT_BINARY_DIR}/webrtc-aecm-build.sh" COPYONLY) + add_custom_command( OUTPUT "${WEBRTC_AECM_INSTALL_DIR}/lib/libwebrtc_aecm.a" - "${WEBRTC_AECM_INSTALL_DIR}/include/echo_control_mobile.h" COMMAND sh "${CMAKE_CURRENT_BINARY_DIR}/webrtc-aecm-build.sh" + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/webrtc-aecm-build.sh" VERBATIM) add_custom_target(webrtc_aecm_ext From 72cf4d009153728dd8c25e99c369b03d814a9e10 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Tue, 10 Nov 2026 20:01:35 +0200 Subject: [PATCH 5/6] topology2: webrtc-aec: set DP heap size requirement The default 24 KiB DP module heap is not enough for the AEC module private data and the WebRTC echo canceller state. Request 256 KiB, which covers the float AEC backend with margin. Signed-off-by: Seppo Ingalsuo --- tools/topology/topology2/include/components/webrtc-aec.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/topology/topology2/include/components/webrtc-aec.conf b/tools/topology/topology2/include/components/webrtc-aec.conf index f90cd3d8f108..99c628a96e4f 100644 --- a/tools/topology/topology2/include/components/webrtc-aec.conf +++ b/tools/topology/topology2/include/components/webrtc-aec.conf @@ -142,6 +142,7 @@ Class.Widget."webrtc-aec" { cpc 2000 scheduler_domain "DP" stack_bytes_requirement 16384 + heap_bytes_requirement 262144 num_input_pins 2 num_output_pins 1 } From 6404b7c4fb67d76adce1dea2213ea033153814ba Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Wed, 11 Nov 2026 14:26:12 +0200 Subject: [PATCH 6/6] app: add WebRTC AEC only Kconfig overlay Add an overlay that enables only the WebRTC AEC module as LLEXT with the fixed-point AECm backend. It is lighter than the full WebRTC overlay and is the configuration tested with the SoundWire microphone AEC capture topology on PTL. Signed-off-by: Seppo Ingalsuo --- app/webrtc_aec_overlay.conf | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 app/webrtc_aec_overlay.conf diff --git a/app/webrtc_aec_overlay.conf b/app/webrtc_aec_overlay.conf new file mode 100644 index 000000000000..65d5ebd96d7a --- /dev/null +++ b/app/webrtc_aec_overlay.conf @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# WebRTC AEC only Kconfig overlay (LLEXT, AECm fixed-point backend) + +CONFIG_COMP_WEBRTC_AEC=m +CONFIG_WEBRTC_AEC_BACKEND_AECM=y +CONFIG_WEBRTC_AEC_SUPPRESSION_LEVEL=1 +CONFIG_WEBRTC_AEC_SAMPLE_RATE_HZ=16000