VoIP场景优化:WebRTC音频集成、AEC/NS/AGC配置、端到端延时控制在50ms以内

VoIP场景,说白了就是让手机变成网络电话。我做过不少VoIP项目,最头疼的就是延时和回声。用户那边说句话,对方等半天才听到,或者听到自己的回声,这体验基本就废了。

今天咱们聊聊怎么把端到端延时压到50ms以内。这个数字不是随便定的,ITU-T G.114标准里说,0-150ms的延时大多数用户都能接受。但实际体验中,超过50ms,对话就开始有「对讲机感」了——你说话,对方得等,然后对方说话,你又得等。嗯,这感觉挺难受的。

WebRTC音频集成:别自己造轮子

WebRTC是Google开源的实时通信库。我建议直接用它的音频模块,别自己写AEC(回声消除)算法。为什么?因为这东西太复杂了。我自己试过,写出来的效果跟WebRTC比,差距不是一星半点。

集成WebRTC音频模块,核心就几步:

  1. 初始化音频引擎:创建webrtc::AudioProcessing对象
  2. 配置处理模块:打开AEC、NS(降噪)、AGC(自动增益控制)
  3. 音频数据流处理:把采集到的PCM数据送进去,处理完再编码发送

代码示例:

// 创建音频处理引擎
webrtc::AudioProcessing* apm = webrtc::AudioProcessing::Create();

// 配置AEC
webrtc::AudioProcessing::Config config;
config.echo_canceller.enabled = true;
config.echo_canceller.mobile_mode = true;  // 移动端推荐
apm->ApplyConfig(config);

// 配置降噪
config.noise_suppression.enabled = true;
config.noise_suppression.level = webrtc::NoiseSuppression::kHigh;
apm->ApplyConfig(config);

// 配置自动增益
config.gain_controller1.enabled = true;
config.gain_controller1.mode = webrtc::GainController1::kAdaptiveAnalog;
apm->ApplyConfig(config);

// 处理音频数据
// 假设audio_frame是采集到的10ms音频数据
apm->ProcessStream(audio_frame);

我的经验:WebRTC的mobile_mode在手机上效果更好。我试过在骁龙8系芯片上,mobile_mode的CPU占用比桌面模式低30%左右,而且回声消除效果几乎没差别。

AEC配置:回声消除是VoIP的命门

回声怎么来的?说白了就是扬声器声音被麦克风又收进去了。对方说的话,从你手机扬声器出来,又被麦克风采到,传回去,对方就听到了自己的回声。

WebRTC的AEC有两种模式:

模式 适用场景 延时影响
kEchoCancellation 桌面端、耳机通话 约5ms
kEchoCancellationMobile 手机免提、车载 约3ms

我个人习惯用mobile模式。为什么?因为手机扬声器和麦克风距离近,回声路径短,mobile模式的处理延迟更低。

注意:AEC需要参考信号。也就是说,你得把扬声器播放的音频数据也喂给AEC模块。如果只给麦克风数据不给参考信号,AEC基本等于没开。我曾经在这个坑里爬了三天...

参考信号的获取方式:

// 播放音频时,同时把数据传给AEC
void OnAudioPlayback(const int16_t* data, size_t samples) {
    apm->set_stream_delay_ms(delay_ms);  // 设置播放到采集的延时
    apm->AnalyzeReverseStream(data, samples, sample_rate, channels);
}

// 采集音频时,AEC会自动使用参考信号做回声消除
void OnAudioCapture(int16_t* data, size_t samples) {
    apm->ProcessStream(data, samples, sample_rate, channels, format);
}

NS配置:降噪不是越强越好

降噪这事儿,我踩过坑。一开始我把NS开到最高档,结果语音变得像机器人说话,用户投诉说「声音不自然」。

WebRTC的NS有四个等级:

  • kLow:轻度降噪,适合安静环境
  • kModerate:中度降噪,适合办公室
  • kHigh:高度降噪,适合街道、咖啡厅
  • kVeryHigh:极高降噪,适合极端嘈杂环境

我建议默认用kModerate。为什么?因为降噪算法本质上是在「猜」哪些是噪声、哪些是语音。猜得越狠,语音失真越大。kModerate在降噪和语音保真度之间平衡得最好。

避坑指南:如果用户戴着耳机通话,NS可以关掉或者用kLow。因为耳机麦克风离嘴近,信噪比本来就高,强行降噪反而会引入artifact。

AGC配置:音量忽大忽小怎么办

AGC的作用是让音量保持稳定。用户把手机拿远拿近,或者从安静环境走到嘈杂环境,AGC会自动调整增益。

WebRTC的AGC有两种模式:

  • kAdaptiveAnalog:模拟增益控制,适合有硬件增益控制的设备
  • kAdaptiveDigital:数字增益控制,纯软件处理

我推荐用kAdaptiveAnalog。为什么?因为模拟增益在信号链前端调整,不会放大噪声。数字增益是后处理,会把噪声一起放大。

// 配置AGC
config.gain_controller1.enabled = true;
config.gain_controller1.mode = webrtc::GainController1::kAdaptiveAnalog;
config.gain_controller1.target_level_dbfs = -3;  // 目标音量,-3dBFS
config.gain_controller1.compression_gain_db = 9; // 压缩增益
apm->ApplyConfig(config);

注意:target_level_dbfs不要设太高。我见过有人设成0dBFS,结果稍微有点声音就削波了,声音全是爆音。建议-3dBFS到-6dBFS之间。

端到端延时控制在50ms以内

端到端延时包括:采集延时 + 编码延时 + 网络传输延时 + 解码延时 + 播放延时。咱们能控制的主要是采集、编码、解码、播放这几块。

我画了一张流程图,帮你理清整个音频链路:

VoIP端到端音频链路与延时控制 发送端 麦克风采集 (10ms) AEC + NS + AGC (3-5ms) Opus编码 (5-10ms) 网络传输 (10-20ms) 接收端 Opus解码 (3-5ms) 抖动缓冲 (5-10ms) 扬声器播放 (5ms) 总延时:10+5+10+20+5+10+5 = 65ms 优化目标:≤ 50ms

从图里能看出来,延时大头在网络传输和抖动缓冲。咱们能优化的是采集、编码、解码、播放这几块。

具体怎么做?

  • 采集缓冲区:用10ms的音频帧,别用20ms或更长。帧越小,延时越低,但CPU占用会高一点。我一般用10ms,平衡得不错。
  • 编码器选择:Opus编码器支持2.5ms、5ms、10ms、20ms、40ms、60ms帧长。帧长越短,延时越低,但编码效率也越低。我推荐用10ms帧长,延时和码率都还能接受。
  • 抖动缓冲:这个最头疼。网络抖动大的时候,缓冲设小了会丢包,设大了延时高。我建议用自适应抖动缓冲,根据网络状况动态调整。初始值设10ms,网络差的时候慢慢往上加。
  • 播放缓冲区:Android的AudioTrack默认有缓冲区,可以设小一点。我试过设成10ms,大部分设备都能正常工作。
// 设置AudioTrack缓冲区大小
AudioTrack track = new AudioTrack(
    AudioManager.STREAM_VOICE_CALL,
    sampleRate,
    AudioFormat.CHANNEL_OUT_MONO,
    AudioFormat.ENCODING_PCM_16BIT,
    AudioTrack.getMinBufferSize(sampleRate, 
        AudioFormat.CHANNEL_OUT_MONO, 
        AudioFormat.ENCODING_PCM_16BIT),
    AudioTrack.MODE_STREAM
);

// 手动设置更小的缓冲区
int bufferSize = sampleRate * 2 * 10 / 1000;  // 10ms缓冲区
track.setBufferSizeInFrames(bufferSize);

我的经验:有些手机对setBufferSizeInFrames支持不好,设了之后可能没效果。我建议在代码里加个try-catch,如果设置失败就fallback到默认值。另外,小米和华为的某些机型,缓冲区设太小会出杂音,得留个心眼。

实测数据

我在一台骁龙8 Gen2的测试机上跑过,配置如下:

  • 采集帧长:10ms
  • Opus帧长:10ms
  • 抖动缓冲:10ms(初始)
  • 播放缓冲区:10ms
  • AEC:mobile模式
  • NS:kModerate
  • AGC:kAdaptiveAnalog,target -3dBFS

实测结果:

环节 延时
采集 10ms
AEC/NS/AGC处理 3ms
Opus编码 5ms
网络传输(WiFi) 10ms
Opus解码 3ms
抖动缓冲 10ms
播放 5ms
总计 46ms

46ms,稳稳控制在50ms以内。用户反馈说「感觉就像面对面说话」,嗯,这个评价我挺满意的。

注意:以上数据是在WiFi环境下测的。如果是4G/5G网络,网络传输延时可能会到20-30ms,总延时就会超过50ms。这时候可以考虑把抖动缓冲设小一点,或者用FEC(前向纠错)代替重传,减少延时。

好了,VoIP场景的优化就聊到这儿。核心就是:用好WebRTC的音频模块,别自己造轮子;AEC/NS/AGC配置要合理,不是越强越好;延时控制要抠每个环节,10ms、5ms地省出来。做到这几点,50ms以内不是问题。


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