WebRTC进阶八 / 如何进行数据统计并绘制图表

972 阅读4分钟

前言

在使用webRTC过程中,总会有想了解当前状态的时候,这个时候就需要对当前的WebRTC连接进行监控和统计,比如当前接收的视频帧率,分辨率,丢包率等等,有了这些信息,就可能评估当前用户的质量是否达到标准,而且还可以通过这些来调整你的策略,或者告知用户

浏览器的WebRTC监控查看

你可以通过浏览器自带的监控查看,你只需要在 Chrome 浏览器下输入“chrome://webrtc-internals”这个 URL并访问就可以看到所有的统计信息了。不过你看统计数据有一个前提,就是当前浏览器必须创建了RTCPeerConnection对象,才可以查看对应的内容。我在localhost建立了一下下行的连接,下面的图片是一个关于浏览器控制台的一些例子,具体名称的意义后面会介绍。

image.png

image.png

如何自行采集

上面已经介绍了如何通过浏览器的控制台查看对用的统计信息,但是在实际场景中,不可能让用户开启控制台来查看,那么就需要统计并上报,这个时候就要用到一个API是getStats,这个API是在对应的RTCPeerConnection上,需要先获取对应连接的PeerConnection,比如房间里有A、B、C用户,想要统计A用户对B用户的下行,就要获取对应A对B的Peer,不然统计的数据是错误的。

具体调用格式如下

RTCPeerConnection.getStats(selector?: MediaStreamTrack | null)

上面的selector是什么意思,简单来讲,你要获取对应轨道的信息统计,你就传入对应的视频轨道或音频轨道。如果不传,则获取所有的统计信息。同时这个函数是一个Promise函数,需要.then来接收返回值,或者使用await。返回值为Promise<RTCStatsReport>。 下面是一个代码示例

RTCPeerConnection.getStats().then(stats => {
    stats.forEach(report => {
    //会有不同的report type返回
       console.log('-> report', report);
    })
  })

image.png 如果你亲自测试了,你将会发现非常多的记录,里面有几个比较关键的东西,

  • id:唯一标识符
  • timestamp:时间戳,用来标记是什么时候生成的这条日志
  • type:类型,是 RTCStatsType 类型,用来区分这条记录是什么数据,类型是RTCStatsType
type RTCStatsType = 
// 候选者对
"candidate-pair" |
// 证书相关的统计信息
"certificate" | 
// 当前音视频编解码器的统计信息
"codec" | 
// CSRC相关的统计信息
"csrc" | 
// 数据通道的相关统计信息
"data-channel" | 
// 传入数据流的相关统计信息
"inbound-rtp" | 
// 本地候选连接的相关统计信息
"local-candidate" | 
// 媒体源的相关统计信息
"media-source" |
// 传出数据流的相关统计信息
"outbound-rtp" | 
// 对等连接的相关统计信息
"peer-connection" | 
// 对等连接的相关统计信息
"remote-candidate" | 
// 远程传入数据流的相关统计信息
"remote-inbound-rtp" | 
// 远程传出数据流的相关统计信息
"remote-outbound-rtp" | 
// 媒体轨道的相关统计信息
"track" | 
// 传输协议的相关统计信息
"transport"
;

这些类型都有可以在第一张图的右边看到,比如我们在控制台查看type为codec的数据和webrtc控制台的进行对比,数据其实是一致的。

image.png

image.png

其实在RTCRtpSender或RTCRtpReceiver中也是可以获取数据统计相关的信息的,只不过只能获取对应的发送或接受相关的信息了。RTCPeerConnection则能获取所有的。

video的inbound-rtp 数据整理

如果想要绘制图标,那么就要对数据进行处理,这里以视频的inbound-rtp进行解析,音频和outbound的是类似的,大家可以举一反三。

假设我们要统计接受的视频的分辨率,帧率,延时,丢包率 ,带宽。 首先我们要获取对应下行的RTCPeerConnection

let lastData: Record<string, any>;
RTCPeerConnection.getStats().then(stats => {
    stats.forEach(report => {
        if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
          // 总带宽
          const videoBitrate = Math.round(report.bytesReceived * 8 / (report.timestamp - lastData?.timestamp));
          // 视频丢包率
          const videoPacketLoss = report.packetsLost / report.packetsReceived * 100;
          // 视频帧速率
          const videoFrameRate = report.framesPerSecond;
          // 视频延时
          const videoDelay = report.totalProcessingDelay - lastData?.totalProcessingDelay;
          // 视频宽度
          const videoWidth = report.frameWidth;
          // 视频高度
          const videoHeight = report.frameHeight;

          console.log('Video resolution: ' + videoWidth + 'x' + videoHeight);
          console.log('Video frame rate: ' + videoFrameRate + ' fps');
          console.log('Video delay: ' + videoDelay + ' ms');
          console.log('Video packet loss: ' + videoPacketLoss + '%');
          console.log('Video bitrate: ' + videoBitrate + ' kbps');
          lastData = report;
        }
    })
  })

上述的代码就是处理并获取了对应的数据信息。如果想要一直获取,只需要添加一个定时器即可。如果你想要绘制成表格,你可以使用echart进行绘制。

使用echart绘制

这里以视频的宽高和帧率来举例 首先需要安装echart,

npm i echarts

接下来你要引入echart

import * as echarts from 'echarts';

接下来创建三个容器用来放三个图表

<div style="display: flex;">
  <div id="width" style="width: 600px;height: 300px;"></div>
  <div id="height" style="width: 600px;height: 300px;"></div>
  <div id="frame" style="width: 600px;height: 300px;"></div>
</div>
let myChartWidth: any;
let myChartHeight: any;
let myChartFrame: any;
const data = {
  time: [] as Array<number>,
  delay: [] as Array<number>,
  width: [] as Array<number>,
  height: [] as Array<number>,
  frame: [] as Array<number>,
}

const draw = () => {
  myChartWidth = echarts.init(document.querySelector('#width')!);
  myChartWidth.setOption({
    title: {
      text: '下行宽度'
    },
    series: [
      {
        name: '宽度',
        type: 'line',
        data: []
      }
    ]
  });
  myChartHeight = echarts.init(document.querySelector('#height')!);
  myChartHeight.setOption({
    title: {
      text: '下行高度'
    },
    series: [
      {
        name: '高度',
        type: 'line',
        data: []
      }
    ]
  });
  myChartFrame = echarts.init(document.querySelector('#frame')!);
  myChartFrame.setOption({
    title: {
      text: '下行帧率'
    },
    series: [
      {
        name: '帧率',
        type: 'line',
        data: []
      }
    ]
  });
}

const getStatus = () => {
  // 绘制echart
  draw();
  // 定时更新
  setInterval(() => {
    RTCPeerConnection.getStats().then(stats => {
        stats.forEach(report => {
            if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
              const videoFrameRate = report.framesPerSecond;
              const videoWidth = report.frameWidth;
              const videoHeight = report.frameHeight;
              data.time.push(report.timestamp);
              data.delay.push(videoDelay);
              data.width.push(videoWidth);
              data.height.push(videoHeight);
              data.frame.push(videoFrameRate);
              myChartWidth.setOption({
                xAxis: {
                  data: data.time
                },
                series: [
                  {
                    name: '宽度',
                    data: data.width
                  }
                ]
              });
              myChartHeight.setOption({
                xAxis: {
                  data: data.time
                },
                series: [
                  {
                    name: '高度',
                    data: data.height
                  }
                ]
              });
              myChartFrame.setOption({
                xAxis: {
                  data: data.time
                },
                series: [
                  {
                    name: '帧率',
                    data: data.frame
                  }
                ]
              });
            }
        })
      })
  }, 1000)
}

以下是一个截图示例。

获取视频.gif

参考 w3c.github.io/webrtc-stat…