12、音频处理:音频编解码器、回声消除、降噪、自动增益控制
各位同学,欢迎来到第十二章。前面我们搞定了视频的采集和传输,但说实话,一个视频会议如果只有画面没有声音,或者声音稀烂,那体验基本就废了。我自己就经历过一次线上会议,画面清晰得能数睫毛,但对方说话像在水缸里一样,还带着刺耳的回声——那感觉,真不如打电话。
音频处理,说白了就是让声音“听得清、不刺耳、不卡顿”。今天咱们就聊聊音频编解码器、回声消除、降噪和自动增益控制这四个核心模块。嗯,每个都是坑,但踩过去就是经验。
12.1 音频编解码器:选对格式,事半功倍
音频编解码器,就是把声音压缩再解压的算法。WebRTC 里最常用的是 Opus,这玩意儿几乎是万能的。我记得第一次用 Opus 时,发现它既能处理语音,又能处理音乐,比特率从 6 kbps 到 510 kbps 都能自适应——说白了,网络差时它自动降码率,网络好时给你高音质。
除了 Opus,还有 G.711 和 G.722。G.711 是古董级,64 kbps 固定码率,但兼容性极好。G.722 是宽带音频,16 kHz 采样率,比 G.711 清晰不少。不过我个人习惯,只要条件允许,一律用 Opus。为什么?因为它支持 48 kHz 采样率,而且丢包补偿做得特别好。
核心要点:WebRTC 默认优先使用 Opus,备选 G.711。你可以在 SDP 协商中强制指定编解码器,但别轻易禁用 Opus——除非你遇到兼容性问题。
来看一段 SDP 中音频编解码器的配置片段:
// SDP 中的音频媒体行示例
m=audio 49170 RTP/AVP 111 0 8
a=rtpmap:111 opus/48000/2
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=fmtp:111 minptime=10; useinbandfec=1
注意最后一行 useinbandfec=1,这是 Opus 的内带前向纠错。我在项目中遇到过网络抖动严重的情况,打开这个参数后,丢包率从 15% 降到 3% 以下,效果立竿见影。
12.2 回声消除:别让用户听到自己的回音
回声消除(Acoustic Echo Cancellation,AEC)是视频会议里最头疼的问题之一。你想想看,对方说话的声音从你的扬声器出来,又被你的麦克风收进去,再传回给对方——对方就听到了自己的回音,那体验简直崩溃。
WebRTC 内置了 AEC 模块,原理是自适应滤波器。说白了,就是系统记住你播放了什么声音,然后从麦克风采集的信号里减去这部分。但实际效果取决于很多因素:扬声器音量、麦克风灵敏度、房间混响等等。
避坑指南:我曾经在项目中遇到 AEC 完全失效的情况,排查了半天发现是音频驱动把采样率设成了 8 kHz,而 AEC 模块期望 16 kHz。采样率不匹配,回声消除直接罢工。所以,务必确保采集和播放的采样率一致。
在 WebRTC 中,AEC 默认是开启的。你可以通过 googEchoCancellation 约束来控制:
const constraints = {
audio: {
echoCancellation: true, // 开启回声消除
noiseSuppression: true, // 降噪
autoGainControl: true // 自动增益
}
};
但要注意,浏览器端的 AEC 实现参差不齐。Chrome 的 AEC 做得不错,但某些移动端浏览器可能效果很差。我的建议是:如果条件允许,在服务端做二次 AEC 处理,或者使用专业的音频处理库。
12.3 降噪:把背景噪音干掉
降噪(Noise Suppression,NS)的目标很明确:去掉风扇声、键盘声、马路声,只保留人声。WebRTC 的降噪模块基于频谱减法,简单说就是分析音频频谱,把非人声频段衰减掉。
但降噪不是万能的。降噪太强,人声会失真,听起来像机器人。降噪太弱,背景噪音又压不下去。我个人的经验是:对于普通办公环境,WebRTC 默认的降噪强度就够了。但如果你在咖啡厅或工厂里开会,建议配合深度学习降噪模型。
小技巧:WebRTC 的降噪有四个等级:0(关闭)、1(轻度)、2(中度)、3(重度)。你可以通过 googNoiseSuppression 的 level 参数调整。我一般用 2 级,平衡效果和音质。
来看一个调整降噪等级的示例:
// 通过 Chrome 的音频处理扩展调整降噪等级
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
noiseSuppression: true,
// 注意:level 参数不是标准 API,需要浏览器支持
googNoiseSuppression: { level: 2 }
}
});
嗯,这里要提醒一下:googNoiseSuppression 是 Chrome 的私有扩展,其他浏览器可能不支持。跨平台时,最好用标准 API 加降噪后处理。
12.4 自动增益控制:音量别忽大忽小
自动增益控制(Automatic Gain Control,AGC)解决的是音量不一致的问题。有人说话声音小,有人声音大,AGC 会自动把音量归一化到合适的范围。
WebRTC 的 AGC 模块会分析输入音频的峰值和均方根值,然后动态调整增益。但这里有个坑:如果增益调得太大,底噪也会被放大。我曾经遇到一个用户,在安静房间里说话,AGC 把增益拉到最大,结果他稍微动一下椅子,那个摩擦声被放大了好几倍,反而更吵了。
我的建议:AGC 的目标音量设置在 -3 dB 到 -6 dB 之间比较合适。太接近 0 dB 容易削波失真,太低又听不清。另外,AGC 的响应速度要适中——太快会导致音量频繁波动,太慢又跟不上说话节奏。
在 WebRTC 中,AGC 也是默认开启的。你可以通过 autoGainControl 控制:
const constraints = {
audio: {
autoGainControl: true,
// 有些浏览器支持 targetLevel 参数
googAutoGainControl: { targetLevel: 0.5 } // 0~1 之间
}
};
但说实话,浏览器端的 AGC 控制能力有限。如果你需要精细控制,建议在服务端或使用 Web Audio API 做后处理。
12.5 实战:音频处理管线配置
最后,咱们把四个模块串起来,看看一个完整的音频处理管线怎么配置。以下是一个基于 WebRTC 的音频处理示例:
async function setupAudio() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
// 采样率建议 48000,与 Opus 匹配
sampleRate: 48000,
// 声道数:单声道就够了,立体声浪费带宽
channelCount: 1
}
});
// 创建 RTCPeerConnection
const pc = new RTCPeerConnection();
stream.getAudioTracks().forEach(track => {
pc.addTrack(track, stream);
});
// 创建 Offer 时指定 Opus 编解码器
const offer = await pc.createOffer();
// 可以在这里修改 SDP,强制使用 Opus
console.log('音频处理管线已启动');
return stream;
} catch (err) {
console.error('音频初始化失败:', err);
}
}
这段代码里,我开启了回声消除、降噪和自动增益控制,采样率设为 48 kHz,声道数设为单声道。为什么用单声道?因为视频会议里,立体声不仅浪费带宽,还会增加回声消除的复杂度。除非你的场景需要区分左右声道,否则单声道就够了。
最后一个小提醒:音频处理不是万能的。硬件问题(比如麦克风太差、扬声器破音)靠软件是救不回来的。我建议你在项目初期就测试不同设备的表现,别等到上线才发现问题。
好了,音频处理这块就聊到这儿。记住四个关键词:编解码器选 Opus,回声消除要匹配采样率,降噪别过度,增益控制要平滑。下一章咱们聊聊更刺激的话题——网络传输优化。嗯,到时候见。