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() 数据源 编码层指标 帧率 / 分辨率 / 编码器 传输层指标 码率 / 丢包 / RTT 接收层指标 抖动 / 延迟 / 丢帧 数据聚合与差分计算 码率差分 / 丢包率计算 / 滑动窗口平均 监控面板可视化 实时曲线 / 告警阈值 / 状态指示 反馈调整编码参数

你看,数据从 getStats() 出来,先分成编码、传输、接收三个维度,然后经过差分计算和聚合,最后展示到面板上。而且这里还有一个反馈回路——当监控面板发现丢包率过高时,可以主动调整编码器的目标码率,形成闭环。

注意:不要每帧都调用 getStats()。我见过有人用 requestAnimationFrame 循环调用,结果 CPU 占用直接飙到 30%。1 秒 1 次足够了,实时性完全够用。

避坑指南:我踩过的几个坑

  • bytesSent 不是码率:它只是累计字节数。你得自己算差分,再除以时间间隔,才能得到实时码率。我第一次用的时候直接拿 bytesSent 当码率用,结果画出来的曲线一路向上,完全不对。
  • 丢包率要平滑处理:单次丢包率波动很大。我习惯用滑动窗口取 5 秒的平均值,这样曲线更平滑,告警也更准确。
  • 注意 stats 的时效性:getStats 返回的是快照数据。如果你两次调用间隔太长,中间的数据就丢了。所以定时器一定要稳定,别用 setTimeout 递归,用 setInterval。
  • 不同浏览器 stats 字段名不一样:Chrome 用 framesPerSecond,Firefox 可能叫 framerateMean。写代码时要做兼容处理。

嗯,统计信息这块内容其实不难,但细节很多。你只要把 getStats 用熟,再搭一个简单的监控面板,屏幕共享的质量就能做到心中有数。下次用户投诉画面卡顿,你打开面板一看——丢包 8%,RTT 600ms,问题一目了然。

这就是数据的价值。