14、音频效果器:AudioEffect框架、延时类效果器(AEC/NS/AGC)、低延时场景下禁用效果器
说到音频效果器,很多人第一反应是「调音台」或者「修音软件」。但在 Android 系统里,AudioEffect 是一个完整的框架,它管理着从硬件 DSP 到软件算法的各种音频处理单元。
我最早接触 AudioEffect 是在做 VoLTE 通话优化的时候。那时候发现,明明算法都开了,但回声就是消不干净。后来一查,原来是 AudioEffect 的链路配置出了问题。嗯,这节课我们就来把这个框架彻底讲透。
AudioEffect 框架长什么样?
AudioEffect 是 Android 提供的一套标准接口。它允许上层应用或系统服务,向音频链路中插入各种效果器。说白了,就是一条音频流水线上,你可以随时加一个「处理节点」。
这些节点包括但不限于:
- 均衡器(Equalizer):调整频段增益
- 混响(Reverb):模拟空间感
- 降噪(Noise Suppression, NS):去除背景噪声
- 回声消除(Acoustic Echo Cancellation, AEC):消除扬声器反馈
- 自动增益控制(Automatic Gain Control, AGC):稳定音量
每个效果器都对应一个 AudioEffect 子类。系统通过 AudioEffect.Descriptor 来查询和创建它们。
核心概念:AudioEffect 的底层通过 AudioFlinger 与 HAL 层交互。效果器可以运行在 AP(应用处理器)侧,也可以卸载到 DSP(数字信号处理器)上。低延时场景下,我们通常希望效果器跑在 DSP 上,但前提是 DSP 支持。
延时类效果器:AEC / NS / AGC
这三兄弟是通话场景的标配。我习惯叫它们「延时三件套」。为什么强调延时?因为它们都需要处理「历史数据」才能工作。
AEC(回声消除)
AEC 的原理是:把扬声器播放的参考信号,与麦克风采集的信号做对比,然后从麦克风信号中减去回声部分。这个过程需要缓存一段时间的参考信号,所以必然引入延时。
我在项目中遇到过一个问题:AEC 的参考信号延迟和实际播放延迟不匹配,导致回声消除效果极差。后来发现是 AudioTrack 的写入缓冲区和 AEC 的参考缓冲区没有对齐。解决办法是使用 AudioRecord 和 AudioTrack 的 getTimestamp 方法做同步。
NS(降噪)
NS 相对简单一些。它通过分析噪声的频谱特征,把非人声的部分衰减掉。但注意,NS 也会引入延时,因为需要做 FFT(快速傅里叶变换)和 IFFT(逆快速傅里叶变换)。
你想想看,如果 NS 的帧长是 10ms,那至少就要等 10ms 的数据才能输出第一帧结果。这就是延时来源。
AGC(自动增益控制)
AGC 的作用是让音量保持稳定。它根据输入信号的峰值或 RMS 值,动态调整增益。AGC 的延时通常较小,但如果启用了「平滑过渡」功能,也会引入几毫秒的延迟。
| 效果器 | 典型延时 | 主要延时来源 |
|---|---|---|
| AEC | 10~30ms | 参考信号缓存、自适应滤波器 |
| NS | 5~20ms | FFT 帧处理、频谱分析 |
| AGC | 1~5ms | 平滑窗口、增益计算 |
避坑指南:我曾经在调试一个实时 K 歌应用时,发现 AEC 和 NS 同时开启后,总延时超过了 50ms。用户明显感觉到「声音跟不上嘴」。后来我强制在 K 歌模式下禁用了 AEC 和 NS,只保留 AGC,延时降到了 5ms 以内。用户体验反而更好了。
低延时场景下如何禁用效果器?
低延时场景,比如游戏语音、实时 K 歌、乐器演奏 App,对延时的要求通常在 20ms 以内。这时候,任何多余的效果器都是累赘。
禁用效果器有两种方式:
- 代码层面禁用:不创建 AudioEffect 实例,或者创建后调用
setEnabled(false)。 - 策略层面禁用:在 AudioPolicy 或 AudioFlinger 中,根据使用场景(Usage)自动关闭某些效果器。
我个人更推荐第二种方式。因为代码层面容易遗漏,而策略层面是全局生效的。
举个例子,在 audio_policy_configuration.xml 中,你可以为不同的产品模块配置不同的效果器链:
<!-- 低延时场景:只保留 AGC,禁用 AEC 和 NS -->
<module name="primary">
<attachedDevices>
<item>Speaker</item>
<item>Built-In Mic</item>
</attachedDevices>
<effects>
<effect name="AGC">
<parameter name="targetLevel" value="-3dB"/>
</effect>
<!-- AEC 和 NS 不在这里配置,即默认禁用 -->
</effects>
</module>
另外,在代码中也可以通过 AudioManager 的 setParameters 方法动态控制:
// 禁用所有效果器
audioManager.setParameters("audio_effect_enabled=false");
// 只禁用 AEC
audioManager.setParameters("aec_enabled=false");
注意:动态禁用效果器时,要确保音频链路已经停止。否则可能会出现「咔哒」声或爆音。我建议在音频会话(AudioSession)创建之前就配置好效果器策略。
知识体系总览
下面这张图,我把 AudioEffect 框架的核心逻辑画了出来。你可以看到效果器在音频链路中的位置,以及低延时场景下的处理策略。
从图中你可以看到,低延时场景的核心决策点就是「延时是否超过 20ms」。如果超过,我们就果断禁用 AEC 和 NS。AGC 可以保留,因为它延时小,而且能保证音量稳定。
最后说一句,效果器不是越多越好。在低延时场景下,少即是多。你想想看,用户是愿意听到一点点背景噪声,还是愿意忍受 50ms 的延迟?答案不言而喻。
公众号:蓝海资料掘金营,微信deep3321