尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

车载回声消除在NXP平台上的原理与工程实现

车载回声消除在NXP平台上的原理与工程实现 汽车回声消除Automotive Echo Cancellation这几年在车载音频圈里越来越被重视尤其是最近不少AEC算法方案开始直接放话支持NXP处理器平台。上周跟一个做车载语音方案的团队聊他们正把回声消除算法往NXP的i.MX RT和S32K系列上迁移。这个动作放在前几年不算常见——AEC算法大多跑在手机DSP、蓝牙芯片或者高算力座舱SoC上而NXP在车载处理器里的覆盖面很广从跨界MCU到车规MCU都有大量装机。汽车回声消除能在NXP处理器上原生跑起来意味着车载免提通话、座舱语音助手可以在更贴近硬件、成本更可控的方案上直接落地。这篇文章就围绕这个主题讲清楚车载AEC的原理、工程实现、NXP平台上的算力评估以及你把它调通时会遇到的典型坑适合做车载音频、嵌入式声学处理和座舱语音方案的工程师参考。1. 为什么车载环境绕不开回声消除1.1 车载声学环境的特殊性车厢是一个典型的封闭混响空间玻璃、塑料饰板、皮革座椅对声音的反射和吸收差异很大而且空间尺寸远小于客厅或办公室中低频混响严重直达声和反射声混在一起。麦克风通常安装在顶棚、后视镜附近或者方向盘柱上扬声器分布在车门、仪表台、中置位置两者之间没有物理隔离远端讲话人的声音从扬声器放出来会直接被麦克风拾取再传回远端对方听到自己的回声。这个场景比手机通话的远场条件要苛刻得多。手机通话时麦克风贴近嘴巴扬声器音量也不会开很大回声路径相对简单车载免提是真正意义上的远场语音交互扬声器到麦克风距离可能超过一米而且音量经常开到一半以上。更麻烦的是高速行驶时风噪、胎噪、空调声都在变化回声路径还在缓变前排乘客晃动、车窗开关都会改变声学反射条件。你如果没有一个能实时跟踪回声路径、快速收敛的AEC模块远端体验会非常糟糕。车载语音助手兴起之后这个问题变得更加突出。语音交互需要麦克风一直处于激活状态系统播放导航提示、音乐或者虚拟助手回复时扬声器的声音会进入语音识别前端导致识别误触发或者唤醒率下降。AEC解决的并不只是通话回声它也是整个车载语音前端处理链路的基石。没有AEC后面的波束成形、噪声抑制、唤醒词检测都像是在沙地上盖楼。1.2 AEC在车载场景中解决什么问题AEC的核心任务是估计扬声器到麦克风之间的声学回声路径然后从麦克风信号里减去估计出的回声成分让远端只能听到本地说话人的声音听不到自己刚才说的话。但在车载场景里光有基础的AEC还不够。免提通话时经常出现双讲double-talk情况也就是远端和本地同时说话这时候AEC要既能抑制回声又不能让本地语音被削弱。车机在放音乐、导航、提示音时AEC要区分这些是本地播放的内容还是远端来的语音保证本地提示音不会又被送去远端。这些场景比办公室电话会议复杂得多因为声源、音量和内容都是动态变化的。业界对车载免提回声性能也有明确要求比如ITU-T G.167建议里规定了回波返回损耗的指标实际车规项目中通常还要参考汽车厂商自己的音频释放标准。做车载音频的朋友应该清楚TCLwTerminal Coupling Loss weighted加权终端耦合损耗是经常挂在嘴边的指标它衡量回声衰减量车规项目一般要求这个值在一定频段内达到多少dB以上。要满足这类指标AEC算法本身要足够稳滤波器的收敛速度要快NLP非线性处理还要把残余回声压到听不见的程度。所以AEC在车载场景里的定位不是某一个孤立的降噪工具而是整个语音链路的第一环。后面接的降噪、AGC自动增益控制、EQ甚至编解码器都是在AEC把回声清干净之后才有意义。2. AEC核心原理与车载落地的关键算法模块2.1 自适应滤波从LMS到NLMS再到频域分块AEC最基础的模型是线性回声估计。扬声器输出的参考信号经过功放、扬声器、车内声学空间、麦克风这条路径形成一个回声信号。如果不考虑非线性失真这条路径可以用一个FIR滤波器来近似。自适应滤波器的任务就是不断调整这组滤波器系数让滤波器的输出尽量接近真实的回声信号然后用麦克风信号减去它。最经典的自适应算法是LMSLeast Mean Square最小均方算法它的更新公式很简单权向量沿着误差信号的负梯度方向调整。但LMS对输入信号的功率变化很敏感扬声器播放音量忽大忽小的时候收敛速度不稳定。所以实际工程里基本都用NLMS归一化LMS在更新的时候用输入信号的功率对步长做归一化稳定性好很多。滤波器长度怎么定直接关系算力和回声消除效果。车内混响时间通常在300毫秒到500毫秒如果采样率是16kHz一个300ms的混响尾部就需要16k乘0.3大约4800个滤波器抽头。也就是说NLMS每处理一个采样点就要做4800次乘加运算一秒钟16k个采样点那就是大约76M次乘加。这个算力对单片机来说不算小但更麻烦的是NLMS的收敛速度跟滤波器长度强相关抽头太多收敛反而跟不上回声路径变化。所以商用AEC方案很少用纯时域NLMS更普遍的做法是分块频域自适应滤波PBFDAF。把信号分成块用FFT转到频域做滤波和权值更新利用重叠保留法或重叠相加法复杂度从O(N)降到O(logN量级)。操作上就是几个基2 FFT的事用NXP处理器的DSP指令或者CMSIS-DSP库可以跑得很轻松。频域分块处理会引入一定的算法延迟通常一个块大小对应几毫秒对语音交互来说完全可接受。还有一点很容易被忽略自适应滤波器需要一个参考信号。这个参考信号不能直接拿应用层的音频数据最好是从扬声器功放输出之前回采的PCM信号。如果用蓝牙音源还要考虑蓝牙协议栈内部的时延这个时延不固定会在AEC里表现为回声路径变长需要额外的时延对齐模块去估测。2.2 双讲检测与非线性处理如果只有线性自适应滤波AEC在双讲场景下肯定会翻车。远端说话、本地也在说话时麦克风信号里包含远端回声加本地语音自适应滤波器会把本地语音当成“期望误差”的一部分去更新系数结果就是滤波器系数被带偏出现发散之后回声会一股脑漏出来。这个问题严重的时候一次双讲就能让整个通话质量崩掉。所以双讲检测器Double-Talk Detector, DTD是AEC里绕不开的模块。它的任务是在检测到双讲时冻结或减慢自适应滤波器的系数更新。常见做法是基于参考信号与麦克风信号的互相关、相干性或者能量比做判决。双讲时的本地语音跟参考信号相关性很低回声则跟参考信号强相关利用这个区别可以做判决。DTD只是保护了滤波器不发散真正要把残余回声压到可听阈值以下还需要非线性处理NLP和残余回声抑制RES。NLP的经典手段是中心削波也就是把低能量的信号直接压掉但这个操作做不好会把轻声细语切掉。主流的商用方案改用频谱域抑制在频域上对残余回声频点做增益衰减。因为车载扬声器在低频可能产生明显失真线性滤波器估计不出来所以NLP还要针对低频段做额外的过减。车载环境里混响和扬声器失真程度比消费电子更复杂。我见过不少方案只在实验室环境下调NLP上车之后底噪变大NLP为了压回声把噪声也一起处理了导致远端听到的背景声忽大忽小。这个问题的根源是NLP的噪声地板处理没有跟底噪估计联动工程上需要在NLP后面保留一个最小电平输出。2.3 车载场景下的特殊处理策略车载AEC跟耳机AEC最大的区别在于车载的扬声器位置、麦克风位置相对固定声学回波路径在系统装车之后就基本确定只是会随人和物体移动而小范围变化。这其实是好消息意味着滤波器有充分的时间收敛到一个稳定解前提是你的参考信号链路没有掉链子。多麦克风阵列在车上也很常见通常是双麦或四麦阵列。这时候处理顺序有两种选择一种是在波束成形之前逐通道做AEC另一种是先做波束成形再做AEC。我的经验是先在每个麦克风通道上做AEC再做波束成形因为波束成形会把回声和近端语音混在一起之后再想直接滤除回声就难了逐通道AEC可以保证各路信号进波束成形器之前是干净的最终的波束输出也更干净。车速变化带来的背景噪声变化也需要处理。高速行驶时风噪和胎噪会让AEC的收敛过程变得比较抖因为NLP的增益会被噪声推高回声残余也可能被噪声掩蔽。部分方案会引入车速信号或者基于麦克风信号的噪声估计做自适应参数映射这不是AEC算法本身的规定动作但上车之后很实用。车载还有一个跟回声相关但常被忽略的场景就是导航和音乐混播。车机在播音乐时插播导航语音助手还要同时唤醒这时候AEC的参考信号里既有音乐又有导航人声DTD的动作会非常频繁。设计参考信号混音策略的时候最好让AEC拿到的参考信号跟实际从扬声器出去的声音严格一致否则某些混音路径里的信号回不来AEC就无从消起。3. NXP处理器平台上做AEC算力评估与方案选型3.1 i.MX RT系列与S32K系列的定位差异NXP车载处理器产品线很宽但做汽车音频最常碰到的两条线是i.MX RT跨界MCU和S32K车规MCU。i.MX RT1176是这一代跨界MCU里的代表Cortex-M7主频最高能到1GHz还带一个Cortex-M4协处理核片内RAM有2MB。它虽然叫MCU算力已经摸到入门级应用处理器的门槛很多车机的仪表、HUD、车载音频网关会用它。跑一个中等规模的AEC哪怕实时性要求很高M7核上的余量也相当大。S32K3系列比如S32K344是真正的车规MCU内核是Cortex-M33带DSP指令和浮点单元通过ASIL-B/D功能安全认证更适合车身域、域控制器这类需要功能安全的场景。如果要在S32K344上做AEC算力就没那么宽裕了需要对AEC算法做裁剪比如把采样率锁在16kHz、滤波器长度缩短、采用定点实现。它更适合做单通道或双通道的免提通话而不是同时处理多音区多麦克风。还有一条线是i.MX 8系列应用处理器跑Linux或Android算力更高可以承载更复杂的AEC方案和神经网络后处理。但这类平台的AEC实现通常不是本文重点因为量产车上这类座舱域控制器用的是高通、瑞萨或者NXP i.MX 8软件栈更接近应用处理器生态。标题里说的“Available for NXP Processors”更多是在强调AEC算法可以下沉到MCU级别去跑不再局限于高端SoC。3.2 资源预算内存、MIPS与实时性在NXP上评估AEC方案能不能跑核心看三个维度采样率、滤波器长度、并发通道数。以16kHz采样率、滤波器长度4096点为例如果用分块频域自适应滤波块大小取128点或256点FFT规模在512点或1024点级别CMSIS-DSP库在Cortex-M7上跑一个1024点FFT只需要几十微秒一帧20ms内算法总耗时可以控制在几毫秒以内。M7核跑到600MHz以上时这个负载非常轻松甚至可以同时跑波束成形和降噪。但内存占用需要认真算。一个1024点浮点FFT的频域缓冲单通道大约需要4KB多一点如果滤波器复数权值保存两份一份频域系数、一份时域缓冲再算上双讲检测、NLP、残余回声抑制的中间buffer单通道16kHz AEC用掉30到60KB RAM都很正常。RT1176有2MB RAM两路麦克风加立体声参考完全不在话下S32K344片内RAM一般是512KB做两通道也够但就要注意别把音视频缓冲也堆太大。对于S32K3这种不带Cache却带ETM调试接口的MCU使用DSP指令和浮点单元时要注意编译器选项。默认情况下GCC可能不开FPU优化要在编译选项里显式加-mfpufpv5-d16 -mfloat-abihard否则浮点运算会被编译器转成软浮点库调用性能可能掉5到10倍。这一点我在S32K344上实测过同样的代码不加FPU选项和加了FPU选项AEC单帧处理时间差距非常明显。实时性方面音频处理链路通常要求端到端延迟小于50毫秒AEC模块自身引入的算法延迟要控制在10到20毫秒内。分块频域处理只要块大小合理不会有问题。真正的风险在于操作系统调度和DMA中断。如果你在RTOS里跑音频任务优先级处理不好出现帧丢失AEC的参考信号和麦克风信号就错位了回声瞬间爆发。NXP的SDK默认把DMA中断优先级设置得比较低做音频时要手动调高。3.3 给算法厂商与方案集成商的建议AEC算法要在NXP处理器上大规模落地算法厂商应该考虑提供多档配置。窄带8kHz采样和宽带16kHz采样各一个配置单通道和双通道各一个版本这样下游集成商根据具体MCU型号选型而不是一套算法硬塞进去再压算力。授权模式也影响落地效率。很多车载Tier 1希望拿到源代码或者能够改参数的库因为上车之后需要针对具体车型调麦克风阵列、扬声器布局、风噪状态纯二进制库很难做深度适配。NXP本地的MCUXpresso和S32 Design Studio生态都支持静态库和源码工程混编算法厂商可以按这两类交付物分别打包。对做方案集成的朋友我建议在项目初期就明确算法运行的核。比如RT1176可以用M7跑AECM4跑显示器刷新或者诊断任务S32K344就一个核同时跑AEC、CAN通信、电源管理任务划分需要提前规划。别最后所有任务都塞在一个核上中断一多音频帧就被打穿这是我在实际项目里经常看到的失败模式。4. 从移植到调通NXP平台上的集成步骤与工程实践4.1 初始化音频链路SAI/麦克风阵列与回采参考信号在NXP平台上集成AEC第一步不是调算法而是先把音频链路打通。以RT1176加一颗外部Codec比如SGTL5000或WM8960为例音频数据走SAI外设SAI的TX和RX分别接Codec的DAC和ADC。AEC需要的参考信号在硬件上可以单独拉一路Codec输出到ADC的回采通道也可以直接在数字域把送给DAC的PCM数据复制一份作为参考。第二种方案更常用省一路ADC但要注意拿参考信号的位置必须和真正送到扬声器的信号是同一份数据如果中间有DSP音效或者EQ处理参考信号要取在音效处理之后。SDK初始化的时候这几件事的顺序特别重要先配置系统时钟确保SAI输出位时钟和主时钟频率正确再初始化Codec的I2C控制通道配置采样率、数据格式、输出音量之后配置SAI和DMA用双缓冲或环形缓冲区接收和发送PCM数据。顺序反了经常出现SAI时钟输出错误或者I2C通信失败听上去是硬件问题实际是初始化时序没走对。AEC处理的音频数据流通常是48kHz的但很多AEC算法内部工作在16kHz。这就需要在算法前面加一个采样率转换。我建议用SDK里现成的重采样库或者手写一个多相滤波器别用最简单的线性插值否则高频段的相位失真会影响AEC收敛效果。重采样之后参考信号和麦克风信号要做时延对齐。蓝牙音频的时延可能到几十毫秒AEC滤波器系数估计的时候会把这段时延也估计进去只要时延稳定就没问题但蓝牙时延一旦抖动滤波器就得不断重新收敛。4.2 在S32DS/MCUXpresso中搭建工程与调试注意点NXP的IDE目前主要两个MCUXpresso针对i.MX RT和大部分Kinetis系列S32 Design StudioS32DS针对S32K系列。S32DS的工程结构跟MCUXpresso差异不小S32K3用S32 Configuration Tools生成处理器配置代码包括时钟、引脚、外设。我在第一次用S32DS调S32K344的时候被调试器启动设置坑过一次调试配置里默认的启动脚本没有正确初始化外部FLASH控制器导致程序load进去之后全速跑直接HardFault。后来在Debug Configuration的Startup选项里把FLASH初始化脚本勾选上再设置Reset and Halt配合调试器协议才跑通。这个问题在NXP社区里被问过很多次热词里也一直有“nxp s32ds的debuger的startup设置”说明很多人都遇到过。工程里跑AEC算法堆栈和堆的设置不能沿用默认值。音频算法在处理一帧数据的时候会分配不少临时buffer如果RTOS任务栈给得不够栈溢出会以非常隐蔽的方式出现跑几分钟才崩一次或者只是偶发回声残留。建议先把任务栈设大一点比如8KB稳定之后再往下减。链接脚本里也要把音频数据和系数buffer放在RAM区别放DMA无法访问的Flash区域否则每次访问都有等待周期对实时性不利。调试AEC算法时我强烈建议把算法处理前后的PCM数据通过串口或者SD卡导出来用电脑端工具分析光靠IDE里看变量波形不够直观。S32DS的表达式窗口看音频数组很痛苦数据太大了。更合理的做法是在算法模块里留一个调试钩子把某个时间窗口的音频原始数据push到环形缓冲然后通过后台任务导出。这一步能省大量排查时间。4.3 调参顺序与实车实测方法AEC算法参数很多滤波器长度、自适应步长、DTD阈值、NLP抑制深度、残留回声抑制强度哪个先调哪个后调是有讲究的。我的习惯是先保证参考信号正确、时延对齐这是地基。用扫频信号从扬声器播放观察麦克风信号和参考信号的互相关峰值确定时延偏移量在代码里补偿。时延对齐如果差了几毫秒后面的参数再怎么调回声残余都会明显偏大而且表现为远端听到“空洞”的声音。第二步调线性滤波器。把NLP和RES暂时关到最弱只观察线性回声抵消的效果。这个阶段可以用播放一段语音来测双讲先不测就看纯回声能不能被消掉大部分。如果纯回声都消不掉多半是参考信号不是真正的扬声器信号或者滤波器长度不够。在车内环境滤波器长度需要覆盖混响尾部我会在参数配置里留出试验位从2048到8192逐档测看哪一档之后回声不再明显变好就说明长度够了。第三步再调DTD和NLP。双讲场景下本地播放一段语音同时对着麦克风说话听远端录音里本地语音是否完整、回声是否压住。这里有个容易被忽视的细节NLP的噪声地板要跟底噪估计同步否则车里安静的时候NLP压得太死背景噪声有气无力车速上来之后噪声变大NLP又不工作了回声也跟着漏出来。最好是叠加几种车速状态的噪声样本测一遍。实车实测的时候建议把参考信号、AEC输出信号分轨录音。实际经验是单听AEC输出觉得还可以但跟远端接收端一比就会发现回声还是多。原因是中高频段的残余回声在人耳主观打分里加权很重而NLP默认对高频处理得偏轻。这时候需要针对2kHz以上频段提高抑制增益但幅度不能太大否则本地语音的齿音会发闷。这个度要在试听环节跟音效工程师一起敲定。5. 常见问题与排查技巧实录5.1 回声残留严重最典型的现象是远端打电话进来听到自己刚才说的话像在空旷的房间里喊话一样。排查的时候先不要怀疑算法先确认参考通道是不是真的有信号。我遇到过一次很隐蔽的问题硬件上参考信号从DAC输出的必经路径上加了EQ系统里有两个音频线程一个线程把EQ后的数据送给了Codec播放另一个线程把EQ前的数据复制了一份给AEC当参考。结果AEC消掉的不是扬声器真正放出来的那个信号回声当然消不干净。正确做法是参考信号必须取在真正进入DMA发送缓冲区的那个位置如果中间有音效、音量、混音模块全部都要绕过去。时延对齐也是高频问题。蓝牙音频源进入AEC链路时时延可能是几十毫秒如果参考信号没有做相应的延迟匹配自适应滤波器会尝试用很长的滤波器覆盖这段延迟导致收敛变慢。我在i.MX RT1176上调蓝牙音源的时候通过回采信号和参考信号做互相关算出固定时延然后先把参考信号在buff里延迟对齐滤波器长度就可以只用覆盖声学混响部分效果立竿见影。5.2 双讲语音被吞、声音断断续续双讲时本地语音消失或者变结巴是AEC调参里最容易让人头疼的问题。本质上是DTD把本地语音错判成了回声或者NLP把本地语音一起压制了。先看DTD的判决阈值。双讲检测判据通常基于参考信号和麦克风信号的能量比、互相关函数阈值设太严算法会频繁进入“双讲冻结”状态本地语音被当成回声冻住阈值设太松滤波器在双讲时仍然更新容易发散。我一般把阈值设置在召回率和误警率的平衡点用一段实际双讲录音循环测观察滤波器系数的能量是否出现漂移。NLP导致语音被吞的情况更微妙。在频域做残余回声抑制时输入语音的基频和谐波如果跟残余回声频点重叠抑制增益会把这些语言成分一并压掉。解决思路是加一个语音存在性(SPP)概率估计控制抑制增益的上下限别让增益低到把语音连根拔掉。还有一个土办法在NLP里设置一个最大抑制深度上限比如-25dB避免过度压制实测下来对双讲语音的完整度改善明显。5.3 调试启动与SDK底层配置的坑NXP平台上下载和调试AEC工程时最常见的坑集中在启动配置和SDK底层。很多朋友在S32DS里打开一个S32K3的demo工程编译没问题一用调试器load就卡住。多数情况是Debug Configuration里Startup选项没设置好没有正确选择Flash算法或者没勾选Reset and Halt。这个排查步骤很花时间但经验就是先把调试器换到低速率模式确认连接没问题之后再去调Startup选项。另一个坑是S32K3系列的SDK里默认的时钟配置可能没有使能某些外设的总线时钟。比如SAI外设对应的总线时钟没开代码里设置SAI寄存器看似成功实际上硬件没反应。用S32 Configuration Tools查看时钟树配置确保SAI、DMA、中断控制器相关的时钟源和分频系数都是活跃状态。热词里提到的“s32k118芯片配置底层 nxp sdk”在S32K3上也适用底层配置一步错了上层AEC算法跑得再好都没有输出。我在调S32K344的时候还遇到过DMA中断进不来的问题。现象是AEC任务只在初始化时跑了一次然后就再也没进过回调。原因是SDK默认把DMA中断优先级设得很低而AEC任务里又用了一个while循环等待信号量结果信号量一直等不到任务调度就僵住了。解决方法是把DMA中断优先级提到比音频任务优先级高同时在SDK配置工具里确认NVIC对应的IRQ已经使能。这个排查经验可以整理成一个速查表备查。问题常见现象排查思路回声残留严重远端听到自己的声音检查参考信号取点位置用互相关确认时延对齐调大滤波器长度或步长双讲语音被吞本地说话被消掉、声音断断续续放宽DTD判决阈值检查NLP是否过度抑制本地语音调整最大抑制深度滤波器发散回声突然变大、有哨声或爆音检查DTD是否生效确认没有参考信号中断降低NLMS步长音频任务不运行AEC无输出、或启动后卡死检查DMA中断优先级和NVIC使能确认RTOS任务栈和信号量分配程序加载失败调试器连不上或跑飞检查S32DS的Startup选项切换到低速调试速率确认Flash算法配置最后再说一个我做车载AEC项目时非常重要的体会AEC这个问题真正难的不是算法本身而是很多看起来跟算法无关的东西。参考信号取错了位置、时延没有对齐、DMA优先级不对、调试器启动设置没弄好都会让一套原理上正确的算法在车上表现得很糟糕。在NXP平台上做AEC先把音频链路和工程环境的基础打牢再让自适应滤波器去收敛你会觉得事情顺很多。我个人的经验是别一上来就调NLP深度和抑制增益那些是锦上添花的环节先把回声路径估计准、时延对齐AEC就已经解决了80%的问题。
返回列表