11、音频调试:使用 getStats() 分析音频编码类型、采样率、丢包补偿效果
音频调试,说实话,比视频调试更容易被忽视。
画面卡了,大家一眼就能看出来。但音频出问题,往往是「听不清」、「断断续续」、「有杂音」——这些描述很模糊,很难定位。我早期做WebRTC项目时,就吃过这个亏。用户投诉通话质量差,我盯着视频参数调了半天,结果问题出在音频编码上。
今天我们就来聊聊,怎么用 getStats() 这把利器,把音频问题揪出来。
11.1 音频相关的统计指标概览
先看一张图,理清音频调试的核心脉络。
音频调试,说白了就是盯着这五个维度:编码类型对不对、采样率够不够、丢包补偿有没有生效、延迟抖动能接受吗、音量是否正常。我们一个一个来拆。
11.2 获取音频统计:inbound-rtp 与 outbound-rtp
音频的统计信息,主要藏在两个地方:
- inbound-rtp:接收端统计。看对方发过来的音频质量。
- outbound-rtp:发送端统计。看自己发出去的音频质量。
我习惯先看 inbound,因为用户抱怨的「听不清」,绝大多数是接收端的问题。
获取代码很简单:
const stats = await peerConnection.getStats();
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
console.log('音频接收统计:', report);
}
if (report.type === 'outbound-rtp' && report.kind === 'audio') {
console.log('音频发送统计:', report);
}
});
11.3 音频编码类型:codec 字段
编码类型决定了音质和带宽的平衡。WebRTC 最常用的音频编码是 Opus,它支持可变比特率,从 6 kbps 到 510 kbps 都能跑。
在 stats 中,编码信息长这样:
{
"type": "inbound-rtp",
"kind": "audio",
"codecId": "RTCCodec_audio_1"
}
// 再查 codec 对象
{
"type": "codec",
"payloadType": 111,
"mimeType": "audio/opus",
"clockRate": 48000,
"channels": 2
}
这里要注意几点:
- mimeType 如果是
audio/opus,说明用了 Opus。如果是audio/PCMU或audio/PCMA,那是 G.711,音质差一些,但兼容性好。 - clockRate 就是采样率。Opus 通常是 48000 Hz,G.711 是 8000 Hz。
- channels 表示声道数。2 是立体声,1 是单声道。
11.4 采样率:clockRate 与实际效果
采样率越高,声音越清晰。但也不是越高越好——高采样率意味着更多带宽。
Opus 支持 8 kHz、12 kHz、16 kHz、24 kHz、48 kHz。WebRTC 默认用 48 kHz,但网络差的时候可能会降级。
怎么看当前实际采样率?
stats.forEach(report => {
if (report.type === 'codec' && report.mimeType === 'audio/opus') {
console.log(`当前采样率: ${report.clockRate} Hz`);
}
});
如果发现采样率掉到了 16000 或 8000,说明网络状况不太好,系统自动降级了。
11.5 丢包补偿效果:packetsLost 与 fractionLost
音频丢包比视频丢包更致命。视频丢几帧,人眼可能看不出来。音频丢一个包,耳朵立马能感觉到「卡顿」或「滋滋声」。
WebRTC 的音频丢包补偿(PLC,Packet Loss Concealment)机制,就是尽量掩盖丢包的影响。Opus 内置了 PLC,效果还不错。
在 stats 中,丢包相关的字段有:
| 字段 | 说明 | 正常范围 |
|---|---|---|
| packetsLost | 累计丢包数 | 持续增长说明有问题 |
| fractionLost | 丢包率(小数,0~1) | < 0.05 良好,< 0.1 可接受 |
| jitterBufferDelay | 抖动缓冲总延迟(秒) | 越小越好 |
| jitterBufferEmittedCount | 从抖动缓冲输出的音频包数 | 用于计算平均延迟 |
怎么判断 PLC 有没有生效?
嗯,这里有个小技巧。如果 packetsLost 在增加,但用户没有明显感觉到卡顿,说明 PLC 起作用了。反之,如果丢包率只有 2%,用户却抱怨断断续续,那可能是 PLC 没生效,或者抖动缓冲设置有问题。
// 计算平均抖动缓冲延迟
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
const avgDelay = report.jitterBufferDelay / report.jitterBufferEmittedCount;
console.log(`平均抖动缓冲延迟: ${avgDelay.toFixed(3)} 秒`);
const lossRate = report.fractionLost;
console.log(`丢包率: ${(lossRate * 100).toFixed(2)}%`);
if (lossRate > 0.1) {
console.warn('丢包率过高,建议检查网络或启用 FEC');
}
}
});
jitterBufferDelay 高达 500ms。原来是抖动缓冲设得太大,虽然不丢包,但延迟导致声音变形。后来我把抖动缓冲上限调小了,问题解决。
11.6 音量与能量:audioLevel 与 totalAudioEnergy
音量问题也是音频调试的常客。用户说「听不见」,可能是对方没说话,也可能是音量太小。
WebRTC 提供了两个字段:
- audioLevel:当前音量级别,0~1 之间。0 是静音,1 是最大。
- totalAudioEnergy:累计音频能量,用于计算平均音量。
怎么用?
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
if (report.audioLevel !== undefined) {
console.log(`当前音量: ${(report.audioLevel * 100).toFixed(1)}%`);
}
}
});
如果 audioLevel 一直为 0,说明对方可能静音了,或者麦克风没工作。如果 audioLevel 很低(比如 0.01),说明麦克风增益不够。
audioLevel 的实时值,每 100ms 取一个点,连成线。这样用户说「声音忽大忽小」时,一眼就能看出规律。
11.7 实战:一个完整的音频调试流程
说了这么多,我们来走一遍完整的调试流程。假设用户反馈「通话声音不好」,我会这么做:
- 拉 stats:获取 inbound-rtp 和 outbound-rtp 的音频统计。
- 看编码:确认是 Opus 还是其他。如果是 G.711,先查 SDP 协商。
- 看采样率:如果低于 48000,检查网络或中间设备。
- 看丢包:计算丢包率。如果 > 5%,建议开启 FEC(前向纠错)。
- 看抖动缓冲:平均延迟是否过大?过大则调整抖动缓冲参数。
- 看音量:audioLevel 是否正常?过低则检查麦克风。
这个流程,我用了好几年,基本能覆盖 90% 的音频问题。
好了,音频调试就聊到这里。记住,getStats() 是你的眼睛,但怎么解读数据,靠的是经验。多动手,多踩坑,慢慢就熟了。
公众号:蓝海资料掘金营,微信deep3321