
很多找我聊语音方案的朋友都问过同一个问题代码里明明把AEC打开了扬声器一放音乐或者一播报对端还是能听到自己的说话声被回放出来严重一点的甚至能听到整个房间的混响。这个问题在ESP32这类方案上特别常见因为ESP32的音频处理链路相对灵活可配置点多但这也意味着任何一个环节没配对AEC就等于白开。先说结论AEC不是你在配置里打开一个开关就完事它是一个完整的实时回声路径估计算法需要依赖正确的参考信号、合格的硬件回采链路、匹配的延时范围以及合理的双讲检测策略。任何一个条件不满足AEC的表现就会退化到“好像开了又好像没开”的状态。这篇文章我从硬件回采、软件链路、排查流程三个角度把“为什么开了AEC还是听到自己”这件事彻底讲透后面附一份可以直接抄的排查清单。1. 先搞清楚“开了AEC”为什么不算开了1.1 AEC不是开关是一套实时自适应估算系统AECAcoustic Echo Cancellation的原理说起来很直接算法提前拿到一段“扬声器即将播放的数字信号”也就是参考信号再根据麦克风采集到的信号估计出扬声器声音经过空间传播、壳体反射、麦克风拾音之后形成的回声路径然后用这个估计值去抵消麦克风信号里的回声成分。用生活里的话解释你坐在包房里唱歌伴奏从音箱放出来你听到伴奏完全正常。AEC干的事情就是提前知道伴奏的“乐谱”然后在你耳朵接收到声音时把乐谱对应的那部分能量减掉。可现在你听到的不只是伴奏还听到了自己唱歌的回声那就说明你用来做减法的那份“乐谱”和音箱里真实播放的“伴奏”对不上或者减法的时机、幅度不对。AEC的核心是一个自适应滤波器它的目标就是逼近真实的回声路径。这个路径不是一条固定的线它包含了功放、扬声器频响、空气传播衰减、墙壁反射、麦克风灵敏度、ADC/DAC路径等一大堆因素而且会随着温度、湿度、扬声器音圈发热、物体移动而缓慢变化。所以算法必须持续运行、持续更新才对得上当前的真实状态。既然AEC是实时跟踪系统那它工作得好的前提就很明确了参考信号必须真实反映扬声器在播什么、回采链路必须可靠、回声路径的延时必须落在算法覆盖的范围内、双讲时滤波器不能被近端语音带偏。这几个条件缺一个AEC就形同虚设。1.2 三种“假开启”和它们暴露的问题我调试过不少设备发现“看似开了AEC”通常有两种情况一种是芯片或算法库的AEC模块确实被调用了但喂进去的参考信号是错的另一种是压根没跑起来只是配置项里填了enable代码路径上根本没执行。先说代码路径问题。很多方案里AEC只是语音处理链的一个节点它需要放在唤醒引擎、VAD、降噪等模块之前。如果节点顺序反了或者某些厂商SDK里AEC节点被宏开关剪掉了那实际效果就是“配置了但没启用”。判断方法很简单把扬声器声音开到中等音量对着设备说话抓取进入算法前的MIC音频和算法输出后的音频对比回声残留。如果两者几乎没有区别那基本可以确定AEC没有在链路里生效。再说参考信号错误。这是最隐蔽的一类问题。有些板子支持从多个位置拿参考信号比如I2S TX总线、DAC输出引脚、功放输出端。如果参考信号是DAC后的模拟回采经过了一堆电阻分压、运放跟随、电容耦合波形已经失真了算法拿它当“乐谱”那减掉的自然就不是真实回声。这种情况下AEC也不是完全无效但只能抵消掉一部分线性成分残余回声照样能被人耳捕捉到。第三种情况是参数不匹配。AEC滤波器的抽头数、双讲检测阈值、收敛步长这些参数跟使用的采样率、扬声器到麦克风的距离、房间大小直接相关。默认参数往往不是为你的设备调的。你要是直接用默认配置跑很可能出现“开着AEC但尾音不够长或者双讲检测过于敏感导致滤波器冻结太久”这种隐性问题。这三种情况基本覆盖了“为什么开了AEC还是会听到自己”的绝大多数根因。接下来我把两个最关键的坑单独展开讲因为它们最常被人忽略排查成本也最高。2. 参考信号回采最容易被忽略的硬件陷阱2.1 回采点选错滤波器等于在看错剧本AEC算法需要参考信号这是它的“剧本”。剧本必须跟扬声器实际播放的内容一致AEC才知道要从麦克风信号里减掉什么。问题出在很多开发板的参考信号并不是从数字域直接取的而是经过了层层模拟电路之后才回到ADC这时候剧本已经“串味”了。典型的结构是这样主控把PCM数据通过I2S发给CodecCodec内部做DAC转换成模拟信号送到功放功放推动扬声器发声。回声则通过空气传播到麦克风经过ADC采样回到主控。AEC要是想抵消这条路径上的回声它最好的“剧本”就是I2S总线上那份还没被DAC转换的数字PCM数据或者从Codec内部直接引出的数字回采点。但有些板子为了省成本、少走线直接用功放输出端的信号分压一下再引回给ADC当参考。这样做不是不能用问题在于功放输出信号里包含了功放的非线性、电源纹波、功率级开关噪声甚至扬声器反电动势的干扰。这些成分本不属于回声路径的一部分算法却被要求用这个“加了料”的参考信号去估计回声结果就是滤波器无论如何收敛残差里始终有去不掉的分量。更麻烦的是如果你从扬声器输出端通过电阻分压后接到ADC那你还需要同时考虑ADC输入阻抗、采样保持电路对信号的影响。稍微有点不匹配高频段的相位和幅度就变了。AEC对参考和回声之间的线性关系很敏感高频段相位一偏差自适应滤波器就会花很长时间去调整系数甚至反复震荡。实际听感就是“回声去得慢”“忽大忽小”而不是彻底干净。所以我的建议很明确首选在I2S TX总线做数字抽头或者在Codec允许时使用其内部回采通道不要让参考信号经过额外的模拟分压和第二个ADC。如果硬件已经固定、只能从模拟回采那就必须检查回采链路引入的额外延时和失真先把这部分补偿掉再谈AEC效果。2.2 回采信号的质量问题与排查方法回采链路的数据质量问题听起来像是硬件工程师该操心的但实际上它直接决定AEC效果嵌入式软件工程师也必须会查。常见问题包括参考信号电平太低、参考信号带有明显底噪、回采ADC采样时钟与DAC时钟不同源。第一电平太低。AEC内部一般会把参考信号和麦克风信号按一定比例归一化后处理。如果参考电平比麦克风里的回声电平低太多算法会把噪声当成回声的一部分去估计收敛速度变慢最终残差也降不下去。更底层的逻辑是自适应滤波器的更新步长跟参考信号的能量有关能量太小步长就大滤波器容易发飘能量太大步长太小收敛又太慢。理想的参考信号应该在整个频段上保持合理的动态范围波形平直、无削波、无明显串扰。第二底噪高。回采链路如果从功放输出端取信号底噪往往跟功放供电纹波强相关特征明显表现为某个特定频点的哼声或者宽频噪声抬升。这种噪声进入参考信号后AEC会尝试把噪声也当作“回声”去抵消但真正的麦克风端未必有同等噪声于是残差里会出现“反向噪声”比原来的回声还要难听。第三时钟不同源。如果回采ADC的时钟和DAC的时钟不完全同步参考信号和麦克风信号之间就会出现周期性的时间漂移。AEC滤波器好不容易收敛到一个状态时钟漂移一来相位关系变了滤波器又得重新追。反映到听感上就是缺陷回声会“冒泡”一样周期性出现间隔几十毫秒到几百毫秒不等。排查方法其实不难我给你一个可落地的流程播放一段平坦的扫频信号或白噪声音量开到正常使用值。分别录制参考信号和麦克风信号导出为WAV文件。在PC上用Audacity或Python脚本对齐两者观察频谱和波形差异。重点看参考信号里是不是有麦克风信号里不存在的底噪、谐波、电源哼声。再用示波器量一下回采ADC的采样时钟和DAC的MCLK是否同源。实测中我遇到过参考信号底噪比麦克风信号底噪还高出10dB以上的板子那种情况下任凭你怎么调AEC参数效果都有限。后来把回采点改到DAC输入侧的数字抽头问题直接消失。2.3 参考增益与电平匹配还有一个细节容易被忽略参考信号的电平和麦克风信号里实际回声成分的电平比例影响AEC的启动和收敛。很多算法库里有参考通道的自动增益控制但这个控制只对数字增益有效如果硬件上参考信号已经严重衰减或者过载软件再怎么拉也补不回来。以ESP32常见方案为例如果使用代码内部回采参考信号通常和播放数据同源幅度一致这是最理想的状态。如果是外挂Codec的方案Codec内部往往有独立的ADC通道用于回采这时要注意它的模拟增益、输入阻抗匹配和满量程范围。你希望看到的是参考信号在正常音量下峰值幅度保持在满量程的-6dB到-12dB之间不上不潜、不削波、不淹没在底噪里。在调试阶段我会用一个固定频率的正弦波做测试比如1kHz播放的时候对比参考信号和麦克风信号的幅度。正常情况下参考信号应该远大于回声信号至少大6dB以上。如果参考信号比回声还小那说明回采链路衰减太厉害或者扬声器到麦克风的声学耦合过于强烈这时候应该先优化声学结构而不是指望AEC硬扛。有一点要特别提醒麦克风信号里的“自己声音”不一定都是声学回声还有可能是机壳振动沿结构传导到麦克风的那部分机械串扰。这部分信号也是通过参考信号可以预测的前提是参考信号足够干净、链路足够线性。如果结构振动很严重AEC能压掉一部分但往往会伴随明显的非线性成分残差里听到的“嗡嗡声”就是它。遇到这种问题单纯调AEC没有出路需要做减振处理。3. 软件链路上的隐蔽坑处理顺序、双讲检测与滤波长度3.1 参考信号串了音效/动态处理AEC必然失配很多做过语音方案的人都有过这种经历硬件回采也没问题AEC也开了但还是听到自己。这时候十有八九是软件链路上参考信号的“纯洁度”出了问题。正常播放语音时主控产生的PCM数据会送到播放通路。如果在AEC取参考点之前播放通路里串入了EQ、动态范围压缩、限幅器、音量渐变、低音增强这些音效模块那么AEC拿到的参考信号跟扬声器真正播出来的声音就不一致了。特别常见的是开发者为了让扬声器人声更清晰加了一级动态压缩结果AEC拿的是压缩前的信号做参考但扬声器实际播出来的是压缩后的声音。这两者之间的差异虽然不是天壤之别但足够让AEC的线性抵消失效残余回声在语音段落尤其明显因为动态压缩对语音包络的影响最显著。更隐蔽的情况是播放音量突然调整。很多系统的音量控制是在DSP链路的后端、AEC参考点之后才生效的。也就是说AEC拿到的参考信号是满音量幅度的数字信号但扬声器实际播放的声音被数字音量调低了不少。参考幅度的不匹配会让自适应滤波器不断调整增益系数整个过程像一个人在不停地拧音量旋钮AEC自然无法稳定。正确的做法是AEC的参考信号必须取自播放链路上距离扬声器最近的那个数字节点也就是经过所有音效处理之后、送入DAC之前的PCM数据流。你可以在播放通路的末端做一次抽头把这个抽头数据送给AEC做参考。如果算法库支持多参考通道尽量把原始播放数据和经过音效处理的最终数据都送进去让库自己选择或加权这样能在一定程度上减少失配。判断参考信号“干不干净”有一个笨办法让设备播放一段固定音频同时抓取参考信号和模拟回采到的扬声器信号在PC上做频域对比。如果参考信号在高频段明显比真实扬声器信号多出一截或者包络形状不一致那就说明链路中还有没被发现的处理模块。顺着播放通路逐级排查把每个可能改动音频数据的模块都禁用后再试往往能找到元凶。3.2 双讲检测阈值与收敛控制的取舍双讲double-talk指的是近端说话和远端说话同时出现的情况。这时候麦克风信号里既有远端扬声器播出的声音回声又有近端用户自己的说话声。AEC如果还继续更新滤波器很容易把近端语音当成回声的一部分去“抵消”导致近端语音被吃掉或者产生鬼音。所以成熟的AEC实现里都有一个双讲检测器检测到双讲场景时冻结滤波器更新让近端语音通过。问题就出在双讲检测器的阈值和判断逻辑上。阈值设得太敏感稍微有点近端语音就立刻冻结滤波器冻结时间一长扬声器持续播放、回声路径又发生变化解冻后滤波器需要重新收敛期间回声就会漏出来。阈值设得太迟钝滤波器在双讲期间持续被错误更新系数发散之后即使回到纯回声场景也收敛不回来这时候听到的不是简单回声而是一种类似金属声的失真回声。我在实际项目中调整双讲检测的习惯是这样的先把双讲检测关掉或者调到最迟钝播放单端语音让AEC纯收敛看看回声抑制量能达到多少。如果纯回声场景下ERLE能达到15dB以上说明声学条件和参考链路基本合格然后再逐步调高双讲检测灵敏度以近端语音不被打折扣、远端播放时残余回声可接受为最终标准。这里有一个值得重视的点双讲检测的动态性。很多设备在不同的音量档位下回声能量差异很大。如果双讲检测只看能量绝对值那么音量一高就容易误判为双讲。合理的做法是同时看相对能量比和信号相关性回声信号一般与参考信号高度相关近端语音与参考信号相关性则低得多。基于相关性的判断比基于能量的判断稳健得多如果你的算法库支持选择双讲检测策略优先选相关性方案。3.3 滤波长度也就是“尾巴”到底够不够长AEC的自适应滤波器需要覆盖整个回声路径的时延范围。从主控输出PCM到真正“听到”回声中间包括DAC缓冲、功放响应、空气传播、壳体和墙面反射、MIC拾音等环节。滤波器的长度换算成时间如果太短超过尾巴范围的回声就漏掉了。很多算法库里滤波长度是用taps抽头数来表达的。一个常见误解是以为taps数量越大越好但实际要考虑采样率和算力预算。比如同样16k抽头在16kHz采样率下对应1秒的回声路径覆盖在48kHz采样率下只对应约333毫秒。如果设备放在普通房间里扬声器到墙再到麦克风的反射路径轻松超过300毫秒那你就会发现AEC能消掉直达声但混响声还是能听到。听感上就是“说话有回声但声音发闷发散”。这个问题的排查方法比较直接在设备端播放一个短暂的脉冲声用麦克风录制响应看回声能量从发出到衰减到不可闻到底持续了多长时间。这个时间就是你需要给AEC配置的最短尾巴长度。注意要留出余量一般建议按实测最大回声时间的1.5到2倍配置滤波长度给滤波器留出收敛空间。如果算法库的滤波长度已经开到最大还是不够那就得回到物理结构了减少扬声器到麦克风的直达耦合、增加麦克风与扬声器的距离、用防震泡棉隔离结构传导。不要指望AEC能处理超出它能力范围的混响算法再强也有物理天花板。4. 现场实操四步定位你的AEC为什么没效果4.1 第一步做闭环录音先把回声路径录下来排查AEC问题第一件事永远是抓数据而不是改参数。先让设备处于正常工作状态播放一段测试音频同时把麦克风信号录下来。你需要的文件有三个原始播放文件、AEC参考信号文件、麦克风采集文件。如果你的算法库没有导出参考信号的能力那就直接在播放链路抽头处抓取PCM数据存成文件。拿到文件后先做主观听感确认麦克风文件里能不能清楚听到扬声器在播什么如果能说明声学耦合足够强正好适合测试AEC。然后再开AEC播放同样的音频再抓一次麦克风信号。对比两次录音就能知道AEC到底压掉了多少能量。这一步虽然简单但它能帮你判断问题到底出在哪个环节。如果两次录音几乎没有差别那就要怀疑AEC链路有没有真正执行。如果差异明显但残余还是很大那就继续看参考信号的相关性和延时。4.2 第二步用互相关估算延时确认算法覆盖范围回声相对于参考信号的延时是AEC是否有效的核心。这一步可以在PC上离线完成。我常用Python写一个简单的互相关脚本把参考信号和麦克风信号做对齐找出相关性最高的偏移点这个偏移就是当前设备在本次测试中的整体回声时延。import numpy as np from scipy.signal import correlate ref np.loadtxt(ref_pcm.csv, delimiter,) mic np.loadtxt(mic_pcm.csv, delimiter,) # 取同一段数据避免开头静音影响 n 32000 r ref[:n] - np.mean(ref[:n]) m mic[:n] - np.mean(mic[:n]) corr correlate(m, r, modefull) lag -(len(r) - 1) np.argmax(corr) sample_rate 16000 print(festimated delay: {lag} samples, {lag / sample_rate * 1000:.1f} ms)这个脚本只是一个示意实际使用时要注意如果参考信号和麦克风信号存在频响差异互相关峰值可能被低频成分支配。建议先把信号做高通滤波比如滤掉200Hz以下成分再做互相关。也可以把信号切成多段分别估算延时看延时是否稳定在一个固定值附近。延时抖动超过几毫秒就要怀疑时钟同步问题。算出延时后对照算法库的滤波长度配置。如果延时已经接近或超过尾巴长度那无论怎么调参都救不回来。如果延时远小于尾巴长度那就继续下一步。4.3 第三步测ERLE量化AEC真实效果ERLEEcho Return Loss Enhancement是衡量AEC效果最直接的指标它表示开AEC前后回声能量的下降程度单位是dB。计算公式是ERLE 10 * log10(开AEC前的残留回声能量 / 开AEC后的残留回声能量)。实际操作里我一般不看瞬时值而是看一段纯回声区域的统计值。比如让设备播放语音、音乐各10秒取麦克风中只有回声、没有近端语音的片段分别计算开AEC前后的能量比。理想情况下单讲收敛后ERLE应该在15到25dB之间表现好的设备能做到30dB以上。如果ERLE连10dB都不到AEC基本等于没工作。这里特别提醒ERLE低不一定是AEC的锅。如果你在测试AEC的同时开了噪声抑制或者自动增益控制这些模块会影响残差能量ERLE数值会变得不准确。测ERLE时最好把其他语音处理模块全部旁路只保留AEC这样才能真实评估AEC本身的能力。4.4 第四步确认AEC前后的处理链位置最后一步是确认AEC在整个音频处理链里的位置。一个完整的语音链路通常是这样麦克风ADC采样 - AEC - 降噪 - 自动增益 - 唤醒引擎 - 云端或本地识别。AEC必须在任何非线性处理之前。如果麦克风信号先经过降噪或自动增益控制再做AEC那参考信号与麦克风信号之间就插入了动态变化的分量AEC的自适应滤波器会追得异常辛苦效果也必然打折扣。还有一个容易被忽略的场景唤醒引擎和AEC共用一个DSP核但唤醒引擎为了降低功耗可能在系统休眠期间只运行一个精简的AEC实例。如果你在低功耗状态下测试AEC发现效果和正常状态不一致就要检查是不是切换到了不同的算法参数或不同的处理路径。这种“休眠态做了简化处理”的问题在量产设备上很难靠主观听感发现得靠日志里打印的算法实例标识来确认。5. 一份能直接抄的排查清单5.1 常见AEC失效现象速查表我把日常调试中遇到过的典型现象、原因和排查顺序整理成一张表遇到问题时可以直接对照着查。现象最可能的原因排查顺序开AEC前后无差别AEC链路未真正执行节点顺序错误或宏剪裁抓进入AEC前后的数据确认代码路径查函数调用日志高频残余明显、低频尚可参考信号链路高频失真、扬声器非线性对比参考信号与模拟回采信号频谱检查功放和回采链路回声周期性“冒泡”回采ADC与DAC时钟不同源、有定时抖检查MCLK、采样率配置测量时钟抖动延时估算分段看稳定性讲话时回声大不说话时还好双讲检测阈值过降、近端语音误触发调低双讲检测灵敏度或改用相关性判断策略房间混响收不干净滤波长度不足覆盖不到长延时的反射路径实测回声衰减时长按1.5~2倍配置尾巴长度音量一高回声就大参考信号幅度与播放音量不联动确认参考抽头在音量控制之后检查数字增益链路播放音乐时有金属感残响参考信号串了音效处理AEC失配打开播放链路全图逐模块旁路测试5.2 几条经验值最后说几条我在实际项目里验证过的经验未必适合所有场景但作为起点很靠谱。参考信号最好从播放链路的最终数字节点抽取也就是送入DAC之前的那一级。任何后级处理都应该在抽头之前完成否则AEC效果会大打折扣。纯回声收敛后的ERLE低于10dB先不要盲目调双讲检测和步长回过头查参考信号和回采链路大概率问题出在硬件侧。近端说话声被削、发闷往往是双讲检测把近端语音当成回声抑制掉了。把双讲检测策略改成相关性判断或者把双讲检测的冻结时间缩短一些通常能改善。尾音长度按采样率换算成秒再评估不要只看抽头数。问清楚算法库的默认配置在你的采样率下到底能覆盖多少毫秒。在设备量产前一定要做不同音量、不同距离、不同环境下的AEC回归测试。很多问题在开发桌上测不出来但到了真实房间就暴露了因为回声路径的复杂度完全不同。我在实际调试中最大的体会是AEC出了问题八成的精力应该花在确认参考信号和声学路径上剩下的两成才是调参数。每次我忍不住想去改双讲阈值或者滤波步长之前都会先强迫自己录一段音、看一次频谱。数据说话比经验说话可靠得多。如果你现在也正被这个“开了AEC还是听到自己”的问题折磨不妨从回采点和参考信号下手先把这两块查干净大概率能少走很多弯路。