21、屏幕共享统计信息:getStats API、关键指标解读、性能监控面板
屏幕共享跑起来了,画面也看到了,但你真的放心吗?
我见过太多这样的场景:演示会上,共享画面突然卡住,主讲人还在滔滔不绝,观众看到的却是一张静态图。或者更糟——画面模糊得像马赛克,帧率掉到个位数。
嗯,这就是为什么我们需要统计信息。说白了,没有数据,你就是在盲飞。
getStats API:你的数据探针
WebRTC 内置了一套强大的统计接口——RTCPeerConnection.getStats()。我个人习惯在屏幕共享开始后,每隔 1-2 秒调用一次,把数据喂给监控面板。
基本用法很简单:
const pc = new RTCPeerConnection(config);
// 启动屏幕共享后
setInterval(async () => {
const stats = await pc.getStats();
stats.forEach(report => {
if (report.type === 'outbound-rtp' && report.mediaType === 'video') {
console.log('编码帧率:', report.framesPerSecond);
console.log('发送码率:', report.bytesSent);
console.log('丢包率:', report.packetsLost / report.packetsSent);
}
});
}, 1000);
这里有个坑——getStats() 返回的是 Map,不是数组。你得用 forEach 或者 for...of 来遍历。我曾经在项目里直接用 map(),结果半天没跑通,后来才发现是类型搞错了。
关键指标解读:哪些数据真正重要?
getStats 返回的数据很多,但真正对屏幕共享有用的,其实就几个核心指标。我整理了一张表,方便你对照排查:
| 指标名称 | 获取方式 | 正常范围 | 异常信号 |
|---|---|---|---|
| 帧率 (FPS) | framesPerSecond |
15-30 | < 10 说明编码或网络瓶颈 |
| 发送码率 | bytesSent 差分计算 |
500kbps - 4Mbps | 持续下降可能网络拥塞 |
| 丢包率 | packetsLost / packetsSent |
< 2% | > 5% 画面会出现明显卡顿 |
| 往返时延 (RTT) | currentRoundTripTime |
< 200ms | > 500ms 交互感极差 |
| 编码分辨率 | frameWidth / frameHeight |
1280x720 或更高 | 分辨率骤降说明编码器降级 |
| 抖动 (Jitter) | jitter |
< 30ms | > 50ms 音频会断断续续 |
核心原则:不要只看单个指标。帧率低 + 丢包率高 + 码率下降,三者同时出现,基本可以断定是网络问题。如果只有帧率低但码率正常,那多半是编码器或 CPU 瓶颈。
性能监控面板:把数据变成可读的界面
光有数据还不够,你得把它展示出来。我做过一个轻量级的监控面板,用 Canvas 绘制实时曲线图。核心思路是这样的:
class StatsPanel {
constructor(container) {
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
container.appendChild(this.canvas);
this.dataPoints = {
fps: [],
bitrate: [],
packetLoss: [],
rtt: []
};
this.maxPoints = 60; // 保留最近60个数据点
}
update(stats) {
// 从 stats 中提取关键指标
const fps = stats.framesPerSecond || 0;
const bitrate = this.calcBitrate(stats);
const packetLoss = stats.packetsLost / stats.packetsSent || 0;
const rtt = stats.currentRoundTripTime * 1000 || 0;
// 追加到数据队列
this.dataPoints.fps.push(fps);
this.dataPoints.bitrate.push(bitrate);
this.dataPoints.packetLoss.push(packetLoss);
this.dataPoints.rtt.push(rtt);
// 限制队列长度
if (this.dataPoints.fps.length > this.maxPoints) {
this.dataPoints.fps.shift();
this.dataPoints.bitrate.shift();
this.dataPoints.packetLoss.shift();
this.dataPoints.rtt.shift();
}
this.draw();
}
draw() {
// 清空画布
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制四条曲线(略)
// 每条曲线用不同颜色:帧率-绿色,码率-蓝色,丢包-红色,RTT-橙色
}
calcBitrate(stats) {
// 通过 bytesSent 差分计算实时码率
if (!this.lastBytes) {
this.lastBytes = stats.bytesSent;
this.lastTime = Date.now();
return 0;
}
const now = Date.now();
const deltaBytes = stats.bytesSent - this.lastBytes;
const deltaTime = (now - this.lastTime) / 1000;
this.lastBytes = stats.bytesSent;
this.lastTime = now;
return deltaTime > 0 ? (deltaBytes * 8) / deltaTime / 1000 : 0; // kbps
}
}
我的经验:监控面板不要做得太花哨。红黄绿三色就够了——绿色表示正常,黄色表示警告,红色表示需要干预。我曾经加了一堆渐变和阴影,结果性能开销反而影响了共享本身,得不偿失。
知识体系:统计信息的核心逻辑
下面这张图,是我对屏幕共享统计信息整个知识体系的梳理。你看一遍就能明白数据是怎么流动的:
你看,数据从 getStats() 出来,先分成编码、传输、接收三个维度,然后经过差分计算和聚合,最后展示到面板上。而且这里还有一个反馈回路——当监控面板发现丢包率过高时,可以主动调整编码器的目标码率,形成闭环。
注意:不要每帧都调用 getStats()。我见过有人用 requestAnimationFrame 循环调用,结果 CPU 占用直接飙到 30%。1 秒 1 次足够了,实时性完全够用。
避坑指南:我踩过的几个坑
- bytesSent 不是码率:它只是累计字节数。你得自己算差分,再除以时间间隔,才能得到实时码率。我第一次用的时候直接拿 bytesSent 当码率用,结果画出来的曲线一路向上,完全不对。
- 丢包率要平滑处理:单次丢包率波动很大。我习惯用滑动窗口取 5 秒的平均值,这样曲线更平滑,告警也更准确。
- 注意 stats 的时效性:getStats 返回的是快照数据。如果你两次调用间隔太长,中间的数据就丢了。所以定时器一定要稳定,别用 setTimeout 递归,用 setInterval。
- 不同浏览器 stats 字段名不一样:Chrome 用
framesPerSecond,Firefox 可能叫framerateMean。写代码时要做兼容处理。
嗯,统计信息这块内容其实不难,但细节很多。你只要把 getStats 用熟,再搭一个简单的监控面板,屏幕共享的质量就能做到心中有数。下次用户投诉画面卡顿,你打开面板一看——丢包 8%,RTT 600ms,问题一目了然。
这就是数据的价值。