先说一个我自己调试时经常遇到的场景:在杰理平台做蓝牙音箱、车载免提这类项目时,音频通路为了接外置D类功放或者独立Codec,会习惯性地把音源输出配置成I2S输出。音乐和提示音一切正常,但只要一进入通话,对方马上反馈听到自己的回音,偶尔本地也能听到一声声断断续续的重复。最头疼的是这种问题不是稳定复现,音量调大一点就明显,调小一点好像又能忍,加上I2S链路本身牵涉到主控、外部Codec、功放、麦克风多条通路,排查起来非常容易绕远路。这篇笔记,就是把我在这类问题上踩过的坑、验证过的步骤、以及最终沉淀下来的排查思路整理出来,希望能帮到正卡在同样问题上的朋友。
1. 先分清“回音”到底是哪一种
1.1 远端回音、本地回音和啸叫是三回事
通话回音问题里,很多人一上来就笼统说“有回音”,但实际听感差异很大,处理方向也完全不同。
第一类是对端听到自己声音的回传,也就是远端回音。核心链路是:本机扬声器把远端说话人的声音放出来,被本机麦克风再次拾取,再通过上行语音送回远端,远端就听到了自己刚说过的话。这个路径不复杂,但涉及扬声器、麦克风、功放、DSP处理好几级。
第二类是本地听到自己的声音,也叫侧音或本地回音。通常是本机麦克风信号被意外路由回本机扬声器,形成类似对讲机“监听”的效果,一般由上下行通路配置错误引起。
第三类是尖锐的“滋滋”声或持续高频啸叫,那已经不是回音,而是声反馈自激,属于系统增益过高导致的稳定性问题。
在I2S输出场景里,最典型的是第一类。原因也直白:声音从I2S出去后往往经过独立功放,响度比内部DAC直接驱动耳机或小喇叭大得多,麦克风能感知到的回声路径增益会更高。再加上外置功放的底噪、电源波动都会混进麦克风信号,整个回音的听感就更明显。
1.2 用两个动作快速判断回音来源
先做一个对比实验:保持通话状态,把I2S输出临时切回主控内置DAC,不用外置功放。如果回音立刻消失或明显减弱,说明问题与I2S这条通路强相关,接下来就往I2S配置、外部功放、Codec参数方向查。反之,如果内置DAC下回音一样严重,那大概率不是I2S引起的,先去看麦克风增益、结构泄漏这些基础项。
第二个动作是让对端播放固定语音但本机静音,也就是关掉本机MIC采集,观察对端是否还能听到回音。如果本机麦克风已经被关掉,对端仍然听到自己的声音,说明问题不在本机声学拾音,而是下行语音处理链路里出现了数据环回或混返。这两个动作做完,基本能把问题范围切成“声学回音”还是“链路回音”,后续排查目标会清晰很多。
2. 为什么I2S输出后回音会特别明显
2.1 通话语音链路里I2S的位置很关键
要先理解通话时蓝牙芯片内部音频信号怎么走。远端语音从蓝牙控制器接收后,进入音频DSP做解码、噪声抑制、回声消除等处理,然后送到DAC或I2S输出。本机麦克风信号进入ADC,经过上行链路编码后发回对端。回声消除模块AEC要做的事情,是从麦克风信号里去掉“扬声器正在播放的那部分远端语音”。它必须同时拿到两路信号:麦克风输入,以及扬声器的参考信号。参考信号一般就近取自DAC输入端或I2S发送端,越接近实际发声点越好。
I2S输出场景的问题就出在这里。AEC的参考信号不一定跟着I2S走。很多平台的默认配置,参考点是从内部DAC路径取的。如果通话声音走了I2S到外部Codec或外部功放,而AEC参考还停留在内部DAC路径,AEC拿到的参考语音和实际播放的语音根本不是同一路。这时候AEC非但不能压制回音,反而可能把正常的近端语音当成回声消掉,表现就是回音依旧明显、声音发飘发空。
2.2 AEC参考取错路径是最常见的坑
在杰理平台上,AEC参考源一般能在音频模块或通话模块配置里选择,常见选项包括内部DAC通路、I2S输出通路、外部Codec回环通路。我见过一种出错形态:外部Codec的ADC输入也通过I2S把语音回传给主控,通话的MIC信号走I2S输入,而AEC参考配置却选了I2S输出。这看起来“已经和I2S相关了”,实际问题是:I2S是一条双向数字总线,同一时刻既承载下行播放数据,也承载上行采集数据。参考点选的是下行方向还是上行方向,必须明确分开。选错了之后,AEC的自适应滤波器会把一个无关信号从上行里去掉,真正的回音一点没少。
还有一些项目用的是外部模拟功放,麦克风直接接主控ADC引脚,AEC参考却默认从内部DAC取。通话时如果播放通路切到了I2S,内部DAC根本没有信号,AEC等于在没有任何参考的情况下空转。这种状态在日志里很难看出来,因为AEC模块本身是使能的,看起来一切正常,实际上参考信号为空或恒为零,回音自然压不住。
2.3 参考信号饱和与动态范围失配
通话场景里还有一个容易被忽略的点:I2S输出沿用音乐播放时的数字增益。音乐播放追求响度,数字增益往往调得比较高;到了通话场景,同样一路I2S输出,参考信号如果已经接近满幅甚至削波,AEC的表现会急剧恶化。
原因是AEC的自适应滤波基于线性时不变系统假设,参考信号一旦削波,会产生大量非线性谐波,滤波器永远收敛不到稳定状态。削波越严重,残留在上行里的回音越明显,而且听感是那种“脏脏的、破破的”回声,不是干净反射声。
麦克风这一侧的AGC也不能太激进。AGC会自动调整增益,把弱信号放大,回音和近端语音会一起被放大。AEC收敛速度往往跟不上AGC的变化,结果就是回音一阵一阵地冒出来。
2.4 硬件声学路径变化拉长了回声时延
外接功放和扬声器布局变化会改变回声路径。回音路径延迟由几个部分组成:芯片内部DSP处理延迟、I2S帧传输缓冲延迟、声音从扬声器到麦克风的空气传播延迟、外部Codec的AD/DA转换延迟。
空气传播延迟其实很小,按声音速度约340米/秒计算,扬声器到麦克风距离10厘米,传播延迟只有约0.3毫秒。真正占大头的是DSP帧处理和缓冲,一般10到30毫秒很常见。如果AEC尾长只配了5到10毫秒,根本覆盖不到真实回声路径。外接功放如果还带内置DSP,比如部分数字功放有内部低通滤波和延迟,端到端延迟会更高。这时必须把AEC尾长加长,或者从软件上压缩中间缓冲。
2.5 通话时AEC本身没开到位
有些项目为了节省DSP资源,通话时只开了噪声抑制NR,没有真正打开AEC,或者只开了适合窄带语音的AEC。I2S输出如果是16kHz宽带数据,参考信号和麦克风信号采样率不匹配,AEC处理效果也会大打折扣。通话语音默认就是窄带8kHz或宽带16kHz,I2S链路里如果存在44.1/48kHz重采样,中间插入的SRC会进一步增大延迟并且引入量化噪声。所以通话场景的I2S配置,不应该直接沿用音乐播放的参数,这一点后面实操部分还会展开。
3. 排查与修复实操:从现象到配置
3.1 先用固定语音验证通路
调回音问题,我建议不要一开始就看代码或者改参数,先做一次通路的“体检”。
第一步,进入主控的测试模式,让I2S持续播放一段固定测试音,比如1kHz正弦波或一段已知语音样本。第二步,在麦克风端接入录音设备,或者用示波器观察MIC信号幅度。第三步,对比播放信号峰峰值和MIC端回波峰峰值,估算回声路径的损耗。
这个过程能回答几个问题:I2S输出到底有没有声音到扬声器?麦克风到底能不能听到扬声器的声音?回声路径的衰减量大概是多少?例如,播放信号数字满幅是1V峰峰值,MIC端测到回波只有100mV峰峰值,那声学路径大约衰减20dB。这个数字后期用来判断AEC需要压掉多少回声。如果MIC端测到的回波电平和播放信号几乎一样大,甚至更大,那说明麦克风布局或增益一定有问题,光靠AEC很难解决。
3.2 通话配置检查清单
通路验证做完,就该把软件的配置检查一遍。我通常按以下顺序过:
- AEC使能状态是否为真;
- AEC参考源是否指向I2S输出,而不是内部DAC;
- AEC处理和通话链路的采样率是否一致;
- AEC尾长是否覆盖端到端延迟;
- 是否打开了非线性处理,比如中心削波NLP;
- 上下行增益,特别是I2S输出数字增益是否过高。
每一条对应的问题都前面提过。AEC没使能,回音完全压不住;参考源不对,压不住的同时还可能导致近端语音被误删;采样率不匹配,AEC处理结果会出现频率失真;尾长不够,延迟大的回声路径覆盖不到;NLP没有打开,即使AEC收敛了,残余回声依然能被对端感知;数字增益过高,参考信号削波,AEC永远收敛不好。
把这些项逐条过完,大多数软件层面的回音问题已经能解决掉一半。
3.3 I2S本身的通话参数要单独配
I2S配置在通话场景下不应该沿用音乐模式的参数。通话一般是8kHz或16kHz采样率,I2S如果仍然配置成44.1kHz或48kHz,主控内部必然做SRC重采样。重采样一方面增加延迟,一方面可能引入量化噪声,让AEC的参考信号和MIC信号之间出现细微的相位不匹配。
主从模式也要确认。最常用的接法是主控做主机,输出MCLK、BCLK、LRCK,外部Codec做从机接收。如果外部Codec要求自己做主时钟,主控要切到从机模式,这时候MCLK频率必须严格对上,否则BCLK和LRCK之间会产生漂移。位宽一般配16bit就够通话使用,配24bit不会带来明显音质提升,反而占I2S带宽。
还要检查BCLK和LRCK的极性。外部Codec的时序要求可能与主控默认极性相反,这种“能出声但时序不完全对”的情况会导致数据错位,表现出来就是声音发糊、有延迟,间接让回音问题变得复杂。
3.4 调整增益的顺序和原则
调增益有一个原则:先固定模拟音量,把数字增益调到0dB附近,然后再从扬声器响度和麦克风灵敏度两个方向分别微调。不要一上来就动AEC的内部参数,更不要在数字增益削波的状态下去调AEC。
AEC参数里,最先调整的是尾长,其次是收敛速度,最后才是非线性处理强度。尾长不够,回音压不干净;收敛速度太快,语音会伴随明显的“颤音”或“水声”;太慢,远端说话之后会有一段回音残留。一般做法是先把尾长调到覆盖系统延迟的1.5倍以上,再逐步增加收敛速度直到回音残留低于本底噪声。最后微调NLP的强度,目标是听感自然,不出现明显的语音截断。
3.5 示意配置与参考值
不同版本SDK里字段名会不一样,但配置逻辑是通用的。下面给一个示意结构,关键是参考源和采样率的匹配:
/* 示意:通话音频路径的AEC与I2S参考配置 */ audio_aec_config.aec_enable = 1; audio_aec_config.ref_source = AUDIO_REF_I2S_OUT; // 参考源指向I2S发送 audio_aec_config.sample_rate = 16000; // 通话16k audio_aec_config.frame_ms = 10; // 每帧10ms audio_aec_config.tail_ms = 30; // 尾长覆盖系统延迟 audio_aec_config.nlp_enable = 1; // 非线性抑制 audio_aec_config.erle_enable = 0;这里特别说明一下:AUDIO_REF_I2S_OUT只是示意写法,实际SDK里可能是宏定义、枚举或者直接填一个通道索引。各位拿到代码后,搜索关键字AEC、REF、I2S、DAC就能找到对应位置。
如果参考源选错了,有个很典型的验证现象:开启AEC后,回音不但没变弱,反而可能在某几段语音里变得更奇怪。这是因为自适应滤波器把上行路径里某个与参考信号相关的成分误当成回声来抵销,导致残余信号出现“啵啵”的抽动感。遇到这种听感,基本可以确定参考路径取错了。
3.6 用录音判断AEC是否真的在工作
判断AEC有没有真正干活,我有一个简单但稳定的方法:准备一台录音设备,或者直接用主控内部回读功能,录制上行MIC信号。
第一步,播放远端语音,让扬声器持续发声,但不让近端说话。此时AEC应该把MIC端的回波压到很低。如果录音结果显示回波几乎没有变化,说明AEC没有正常运行,或者参考源没取到。第二步,关掉播放,单独让近端说话。此时AEC不应该消除近端语音。如果近端语音也被消掉一大截,说明参考信号串到了上行路径,或者AEC收敛过度。
这两步录音对比做完,AEC是否在工作、参考是否取对,基本一目了然。比单纯看代码配置靠谱得多。
4. 软件与硬件配合的避坑经验
4.1 麦克风和扬声器布局不能只靠算法兜底
再强的AEC也怕物理结构的“硬回音”。扬声器正对着麦克风开孔,或者喇叭后腔直接泄漏到MIC前腔,都会让回声路径变得不稳定。D类功放的电感振动、喇叭音圈振动还会传导到麦克风膜片,形成机械振动回声。这种振动信号不是声学路径的简单线性滤波,AEC很难处理。
经验上,MIC开孔不要正对喇叭,尽量与喇叭发声方向呈90度以上的角度,结构上要做隔断和密封。喇叭后腔如果有泄漏,低频会直接从壳体缝隙传到MIC,造成那种“闷闷的”回音。调杰理平台这类项目时,我遇到过算法参数怎么改都压不住回音的情况,后来发现是喇叭后腔漏气到MIC前腔,结构打样改了一轮才解决。
4.2 外接功放的电源地处理直接决定回音底噪
I2S外接功放后,功放电源往往是独立供电,或者从同一颗电池取大电流。大电流瞬态会在PCB地线上产生电位差,这个电位差如果耦合到MIC放大器的输入参考地,就形成“电回音”。表现是通话时对端听到的不是清晰的自己声音,而是跟着音量变化的嗡嗡声、电流声。
走线时MIC地和功放地要设计成单点连接,模拟MIC走线远离功放输出和电感走线。MIC供电要加RC或LC去耦,功放的电源输入也要就近放稳压电容。这些内容在原理图阶段就要检查,等板子出来后靠软件几乎救不回来。
4.3 外部Codec的ADC回传也要对齐
如果麦克风信号是通过外部Codec的ADC经I2S回传主控的,去AEC的MIC数据经过了外部Codec的AD转换和I2S传输,与I2S输出的参考信号在时间上会有微小相位偏移。这个偏移只要不超过AEC尾长就能接受,但如果外部Codec的ADC和DAC使用不同的主时钟源,采样率会存在微小偏差。这个偏差会导致AEC的参考信号和MIC信号之间发生持续漂移,回音会周期性冒出来。
这种周期性问题非常难查,因为大部分时间AEC工作正常,偶尔某个时间段回音突然变明显,过一会又恢复。遇到这种案例,优先检查外部Codec的MCLK是不是统一供给,I2S主从模式是否一致。
4.4 通话模式下CPU资源分配要优先保证AEC
杰理这类蓝牙SoC在通话时DSP负载本来就高,如果同时开着EQ、混响、环绕音效这类效果器,AEC处理的时间片可能被挤占。表现出来就是回音处理时好时坏,时延抖动明显。通话模式下的音频处理链路应该以AEC和NR为最高优先级,平时音乐播放用的各种音效尽量关掉。这个优化看起来不起眼,实际项目里往往就是它决定了通话回音能不能稳定压住。
5. 常见问题速查表
把我在调试中遇到的典型问题整理成一张速查表,方便大家对照排查:
| 现象 | 可能原因 | 检查点 | 解决方向 |
|---|---|---|---|
| 对端听到清晰的自己声音 | AEC未使能或参考源未指向I2S输出 | 查AEC使能状态、参考源选择 | 打开AEC,参考源切到I2S输出 |
| 回音随本机音量变大而严重 | 参考信号削波或线性回声路径失配 | 看I2S输出数字增益、功放增益 | 降低数字增益保留峰值余量 |
| 只有外接功放时回音明显 | 外置功放引入电路耦合和声学路径变化 | 对比内置DAC实验 | 调整功放电源地、布局,降低耦合 |
| 本地也听到自己的声音 | 上行MIC数据意外路由到本地I2S播放 | 查通话上下行路由 | 检查通话通路开关,断开本地监听 |
| 声音发闷、吃字 | AEC收敛过快或尾长不足 | 调整尾长和收敛速度 | 先加尾长,再调收敛速度 |
| 回音周期性冒出来 | 外部Codec时钟不同源导致采样率漂移 | 检查MCLK、BCLK配置 | 统一时钟源,归一主从模式 |
| 开启AEC后近端语音也被消掉 | 参考信号串到上行或AEC收敛过深 | 查参考源与输入路径 | 修正参考路径,降低NLP强度 |
| 对端听到电流声、哼声 | 功放电源地耦合到MIC | 查电源布局和走线 | MIC地单点连接,加去耦 |
| I2S从机模式下回音时延时延抖动 | 主从模式不匹配,采样率漂移 | 查I2S配置与外部Codec | 统一主从模式、采样率一致 |
| 特定采样率下才出现回音 | SRC重采样引入延迟和失真 | 查通话I2S采样率 | 通话固定16k带宽,避免重采样 |
表格用的时候有一个顺序:先看第1、3、5行,确认软件通路正常;再看第7、9行,确认参考路径和时钟没问题;最后才查第8行以及硬件布局。一上来就动硬件,往往会白费功夫。
我自己调这类回音问题时,最深的体会是不要一上来就埋进AEC参数里翻,先做一次“内置DAC与I2S输出切换”实验,10分钟就能把问题范围切成两半。还有一个很实用的小技巧:先在I2S输出和MIC输入之间建立一条固定校准通路,播放同一段参考语音,让AEC的参考信号和实际发声路径先对齐,再开始调增益和尾长。这样一步步走下来,绝大多数由I2S输出引发的通话回音问题都能在软件层解决干净,剩下的再回到结构和电源上去抠。希望这篇笔记能让你少走几步弯路。