21、音频处理:回声消除AEC、噪声抑制NS、自动增益控制AGC

音频处理这块,说白了就是让通话双方能听清、听舒服。我做了这么多年WebRTC,最头疼的其实不是视频,反而是音频。你想想看,视频卡一卡还能忍,音频要是炸了、回音绕梁、声音忽大忽小,用户直接挂电话。

今天咱们就聊聊WebRTC音频引擎里的三驾马车:AEC(回声消除)NS(噪声抑制)AGC(自动增益控制)。这三个模块,我习惯叫它们“音频三件套”。

核心观点:音频处理不是独立作战,AEC、NS、AGC必须协同工作。顺序错了,效果直接打对折。

音频三件套协同工作流 麦克风输入 AEC 回声消除 NS 噪声抑制 AGC 自动增益 编码发送 远端参考信号 各模块核心职责 ● AEC:消除扬声器播放又被麦克风采集的回声 ● NS:滤除环境噪声(风扇、空调、马路声) ● AGC:自动调整音量,保证声音平稳清晰 顺序不可颠倒:先消回声 → 再降噪 → 最后调增益

一、回声消除(AEC)—— 最让人头疼的模块

回声消除,我愿称之为音频处理里最难啃的骨头。为什么?因为回声是动态的、非线性的,而且跟设备、环境强相关。

简单解释一下原理:你在麦克风里听到自己的声音,是因为扬声器放出来的声音又被麦克风收了回去。AEC要做的,就是把这个“回环”给切断。

我的经验:WebRTC的AEC模块默认是开启的,但很多开发者不知道它需要远端参考信号。如果没有提供参考信号,AEC基本等于没开。我曾经排查过一个回声问题,查了两天,最后发现是参考信号没传对。

WebRTC里AEC的核心算法是自适应滤波器。它通过NLMS(归一化最小均方)算法,不断调整滤波器系数,去逼近真实的回声路径。

// WebRTC AEC 核心配置示例
webrtc::AudioProcessing* apm = webrtc::AudioProcessingBuilder().Create();

// 配置AEC
apm->echo_canceller()->Enable(true);
apm->echo_canceller()->set_delay_agnostic_enabled(true);
apm->echo_canceller()->set_extended_filter_enabled(true);

// 设置模拟模式(移动端常用)
apm->echo_canceller()->set_suppression_level(
    webrtc::EchoCanceller::SuppressionLevel::kHighSuppression);

这里有个关键点:延迟估计。回声路径的延迟如果算不准,滤波器就白搭。WebRTC内部有一个延迟估计器,但我在项目中遇到过,某些Android设备延迟抖动特别大,估计器跟不上。这时候我建议手动设置一个固定延迟范围。

警告:不要同时开启系统级的AEC和WebRTC的AEC!两个回声消除器打架,效果反而更差。我见过不少开发者踩这个坑。

二、噪声抑制(NS)—— 把环境声“掐掉”

噪声抑制,说白了就是把背景噪音干掉。但这里有个度:降噪太狠,语音会失真;降噪太轻,噪音还在

WebRTC的NS模块用的是谱减法维纳滤波的结合。它先估计噪声的频谱,然后从原始信号中减去噪声成分。

NS等级 适用场景 副作用
kLow 安静办公室 几乎无副作用
kModerate 普通家庭环境 轻微语音失真
kHigh 嘈杂街道、咖啡厅 语音可能变“闷”
kVeryHigh 极端噪音环境 语音明显失真

我个人习惯,在大多数场景下用kModerate。kHigh虽然降噪猛,但语音听起来不自然。你想想看,用户打电话听到对方声音像在水里说话,体验能好吗?

// NS 配置示例
apm->noise_suppression()->Enable(true);
apm->noise_suppression()->set_level(
    webrtc::NoiseSuppression::Level::kModerate);

避坑指南:我曾经在车载场景下测试,发现NS把转向灯的“滴答”声也当噪音滤掉了。后来我加了瞬态检测,把这类短时脉冲信号保护起来。如果你做的是IoT设备,建议也考虑这个。

三、自动增益控制(AGC)—— 让声音“稳”住

AGC的作用,就是让说话声音大的人不爆音,声音小的人不蚊子叫。它本质上是一个动态范围压缩器

WebRTC的AGC有两种模式:

  • 自适应数字模式:根据语音信号实时调整增益,适合大多数场景
  • 固定增益模式:设置一个固定增益值,适合已知环境

我建议优先用自适应模式。但要注意,AGC不能跟AEC抢资源。如果AGC增益拉得太高,会把回声也放大,AEC就压不住了。

// AGC 配置示例
apm->gain_control()->Enable(true);
apm->gain_control()->set_mode(
    webrtc::GainControl::Mode::kAdaptiveDigital);
apm->gain_control()->set_target_level_dbfs(3);  // 目标电平 -3dBFS
apm->gain_control()->set_compression_gain_db(9); // 最大增益 9dB
apm->gain_control()->enable_limiter(true);       // 开启限幅器

关键参数:target_level_dbfs 我一般设 -3dBFS。设得太高(比如0dBFS)容易爆音,设得太低(比如-10dBFS)声音偏小。这个值是我在多个项目中试出来的“甜点”。

四、三者的协同与调试顺序

嗯,这里要重点说一下。AEC、NS、AGC不是各自为战,它们有严格的处理顺序

  1. 先AEC:消除回声,因为回声是最大的干扰源
  2. 再NS:在回声消除的基础上,进一步滤除环境噪声
  3. 最后AGC:对干净的语音信号做增益调整

为什么不能反过来?你想想看,如果先做AGC,把信号整体放大,回声和噪音也一起放大了。这时候AEC和NS的压力就大了,效果肯定打折扣。

注意:WebRTC的AudioProcessing模块内部已经帮你排好了顺序,你不需要手动调整。但如果你自己写音频处理链,一定要按这个顺序来。

调试的时候,我习惯先关掉NS和AGC,只开AEC。确认回声消除没问题了,再逐步打开NS和AGC。这样定位问题快很多。

五、实战中的常见问题

  • 回声残留:检查参考信号是否对齐,延迟估计是否准确
  • 语音断续:可能是NS等级太高,把语音也当噪音滤了
  • 音量忽大忽小:AGC的attack/release时间常数没调好
  • 双讲时回声突然出现:AEC的双讲检测(double-talk detection)没处理好

我记得有一次,一个客户反馈说通话时偶尔有“金属声”。排查了半天,发现是AGC的限幅器开得太猛,产生了削波失真。把enable_limiter关掉,换成软限幅,问题就解决了。

音频处理就是这样,每个参数都有它的“脾气”。你得多试、多听,才能找到最适合你场景的那组配置。


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