WebRTC Internals工具在项目中的实践

FE @ 字节跳动

在上一篇浅聊WebRTC视频通话中,我们了解了WebRTC的基本原理以及如何实现一个p2p通话。但是在项目实际应用中,我们如何排查问题,如何对音视频通话质量做监控呢?不管接入哪家的WebRTC SDK都提供了一个漂亮的监控图表,这个又是如何实现呢?

其实底层RTCPeerConnection提供了getStats接口,这个接口返回的是RTCPeerConnection当前状态的一个快照,内容十分丰富,可以根据这些内容来得到或者计算出分辨率、码率、帧率、丢包率等等信息。Chrome浏览器也贴心地集成了Internals工具,不需要写代码也能得到上述的核心指标,甚至还有getUserMedia接口调用、RTCPeerConnection状态变化等信息。

Chrome的WebRTC Internals功能十分强大,这里抛砖引玉,简单介绍下基本功能以及如何排查问题。

1.整体介绍

打开Chrome浏览器,在地址栏输入chrome://webrtc-internals/即可打开调试工具,进入后可以看到类似的页面

image.png 如果项目中没有使用到WebRTC,可以使用这个Demo来本地模拟下音视频通话。

1.1 Create Dump 保存日志

将该页面的log保存到文件(该文件会包含以下所有章节的信息),遇到问题留档或者将由他人分析时该文件就派上了大用场。

1.2 读取状态日志

image.png
是WebRTC读取状态日志的2种标准:

  • Standardized:符合W3C的新标准,其代码调用是基于Promise
  • Legacy Non-Standard:废弃的google定义的旧标准,基代码调用是基于callback
// Standardized
pc.getStats().then(stats => console.log(stats))

// Legacy Non-Standard
pc.getStats(stats => console.log(stats))
复制代码

两种标准之间存在较大的差异,下面也是以基于Promise的新标准来讲解

1.3 GetUserMedia Request 日志记录

getUserMedia是浏览器获取摄像头和麦克风媒体流的接口,GetUserMedia Request的Tab中可以看到近期调用该API的记录,以及调用参数,有关getUserMedia的基本使用,可以看mediaDevices开启你本地视频之旅行

image.png

可以看到每条记录都会包括:

  • 调用的域名
  • 调用时间
  • 音频约束
  • 视频约束

2. RTCPeerConnection 监控信息

image.png

可以看到除了GetUserMedia Requests,还有其他的Tab,这些每一个Tab都对应了一个PeerConnection对象

打开后又可以分为4部分:

2.1 构造PeerConnection的参数

这一部分可以看到我们在构造PeerConnection的参数,这个参数是传入参数与默认参数的合并后的结果,PeerConnection的构造函数详情可以参考MDN

2.2 PeerConnection的操作与事件

这里按照时间顺序记录了对PeerConnection的一些操作和回调事件

2.2.1 操作

  • createOffer、createAnswer:生成offer和answer,点击展开后可以看到调用参数(参数同样是合并后的值)
  • setLocalDescription、setRemoteDescription: 设置的local sdp和remote sdp,展开后可以看到详情
  • addIceCandidate:将对端的candidate添加到PeerConnection中

2.2.2 回调事件

  • createOfferOnSuccess、createAnswerOnSuccess:由于createOffer和createAnswer是异步的,所以这里显示了调用成功之后的结果
  • setLocalDescriptionOnSuccess、setRemoteDescriptionOnSuccess:同样setLocalDescription和setRemoteDescription也是异步的,这里表明是正确调用(也会有调用Failed的情况)
  • signalingstatechange:信令状态的回调,信令状态是调用setLocalDescription、setRemoteDescription等API的结果,具体变化情况可以参考下图(信令状态同样重要,比如在'stable'的状态是不能直接设置remote sdp)

image.png

信令状态变化这里可能有点绕,但是如果像下图这样,设置时序或者任何SDP设置的问题可以很明显地看到

image.png

  • icecandidate:收集本地的candidate,收集动作一般是setLocalDescription后由WebRTC内部自动完成,然后由回调抛到JS层
  • iceconnectionstatechange:ICE的连接状态发生变化,具体可以参考MDN
  • connectionstatechange:PeerConnection的连接状态发生变化,具体可以参考MDN

2.3 流数据(数值格式 & 图表格式)

这里可以真正的可以看到上行、下行的流数据,其中可以重点观注以下4行(上下连接部分分别的数值和图表格式,其含义和数据源是一致的):

  • inbound-rtp:下行数据,可以分为音频和视频
  • outbound-rtp:上行数据,可以分为音频和视频

image.png

2.3.1 audio outbound-rtp/remote-inbound-rtp

image.png

image.png

根据上图对比再次验证这是同一种数据源的不同展示形式,从数值版本可以看到

  • Kind: audio
  • Ssrc: 2840249774 和sdp的mline相对应
  • [codec]: opus (111, minptime=10;useinbandfec=1) 音频编码

从图表中可以关注

  • bytesSent_in_bits/s:上行码率,音频一般在30k左右(静音后会减半),该值并不直接体现在getStats的返回值中,而是需要两次stats计算得出:
码率 = (delta bytesSent) / (delta timestamp)
复制代码
  • packetsSent/s: 每秒发送的数据包 同理是由计算得出
  • retransmittedPacketsSent: 重传包

TIPS: 在最新版本的Chrome中已经没有bytesSent_in_bits/s了,这块需要自己计算了

如果看到上行的丢包呢,可以看和outbound-rtp配套的remote-inbound-rtp数据,代表接收端的一些数据,同样有2种展示形式,这里只看数值版本:

  • packetsLost:丢包,除以发送的总包数可以得到丢包率
const deltaLost = currentLost - prevLost;

const deltaSent = (currentPacketsSent + currentLost) - (prevPacketsSent + prevLost);

const lostRate = deltaLost / deltaSent;
复制代码
  • roundTripTime: 单位是秒

2.3.2 video outbound-rtp/remote-inbound-rtp

视频上行和音频类似,可以重点关注以下几个字段:

image.png ssrc、codec、packetsSent、bytesSent_in_bit/s这些和音频统计一致

  • frameWidth: 上行视频宽度
  • frameHeight: 上行视频高度

同样可以对应的remote-inbound-rtp里看到远端接收的情况来计算丢包率和roundTripTime

image.png

2.3.3 audio inbound-rtp、video inbound-rtp

这里代表下行数据,大体和outbound-rtp类似,只是这里会包含丢包、audioLevel等数据

3. 具体问题分析

在实际应用中,双端通话一般并非直连,而是需要经过一道服务器转发,有些问题是需要服务端配合排查

3.1 收到画面模糊

一般发生成通话刚建立时,下图是一个码率比较大的视频上行数据,可以看到刚开始有个明显爬坡的过程,这是因为WebRTC刚建立连接时并不知道网络状况如何,会一点点放开上行的码率,以确保视频的流畅,所以对端刚开始接收的是比较模糊的画面,这个属于正常现象。

image.png

如果在通话中画面模糊一直未恢复,可能是发送端网络比较差,或者采集码率极大,但是限制了RTC的上行码率,限制码率可以查看sdp中是否有b=AS:行,或者如果是通过sender来限制可以通过transceiverModified事件来确定是否有maxBitrate的限制

3.2 拉流画面黑屏

需要查看video的inbound-rtp的下行码率是否有值,如果有值可以排查渲染的问题,如果无值的话,可以查看下发流方的码率,排除发流方发送异常。如果也正常的话需要服务器排查问题。

3.3 拉流无声

需要查看audio的inbound-rtp接收的audioLevel和totalAudioEnergy,audioLevel由totalAudioEnergy计算得出,值在[0, 1]之间,正常说话应该在0.1以上

image.png 在实际应用中可能会遇到各种各样的问题,比如打开通话Demo页面开始通话,查看audioLevel是正常有值的,但是如果在开始通话前在控制台输入:

Array(50).fill(0).forEach(() => new AudioContext());
复制代码

然后再开始通话,这时会有什么异常,造成异常的根据原因是什么,欢迎大家在评论区讨论。

4. 在Firefox中如何调试

git clone https://github.com/fippo/webrtc-externals
cd webrtc-externals
sudo npm i web-ext -g
web-ext run
复制代码

运行后会自动打开Firefox,然后在右侧可以看到有个RTC的插件,正常通过就可以使用Internals工具了

image.png
上面介绍的只是WebRTC Internals调试工具的冰山一角,如何在项目中高效使用,是需要通过不断地实践。

文章分类
前端
文章标签