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 以内。这时候,任何多余的效果器都是累赘。

禁用效果器有两种方式:

  1. 代码层面禁用:不创建 AudioEffect 实例,或者创建后调用 setEnabled(false)。
  2. 策略层面禁用:在 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 框架的核心逻辑画了出来。你可以看到效果器在音频链路中的位置,以及低延时场景下的处理策略。

AudioEffect 框架与低延时策略 音频输入 AudioEffect 框架 AEC NS AGC 音频输出 低延时场景判断 延时 > 20ms? 是 否 禁用 AEC / NS 保留 AGC(可选) 保留全部效果器 正常处理链路 最终音频输出

从图中你可以看到,低延时场景的核心决策点就是「延时是否超过 20ms」。如果超过,我们就果断禁用 AEC 和 NS。AGC 可以保留,因为它延时小,而且能保证音量稳定。

最后说一句,效果器不是越多越好。在低延时场景下,少即是多。你想想看,用户是愿意听到一点点背景噪声,还是愿意忍受 50ms 的延迟?答案不言而喻。


公众号:蓝海资料掘金营,微信deep3321