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 的参考缓冲区没有对齐。解决办法是使用 AudioRecordAudioTrackgetTimestamp 方法做同步。

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>

另外,在代码中也可以通过 AudioManagersetParameters 方法动态控制:

// 禁用所有效果器
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