目 录CONTENT

文章目录

前端 WebRTC Stats 各浏览器数据及适配

邱少羽梦
2024-09-27 / 0 评论 / 0 点赞 / 18 阅读 / 30900 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-09-27,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

前端 WebRTC Stats 各浏览器数据及适配

1. WebRTC 及 Stats 简介

WebRTC(Web Real-Time Communication) 是一项让浏览器和移动设备通过简单的API进行实时通信的技术。它广泛应用于视频通话、语音聊天和数据传输等实时应用场景。为了确保实时通信的质量,开发者通常会使用 WebRTC 提供的统计数据(Stats)来监控、分析和优化连接的性能。

WebRTC Stats 是 WebRTC 提供的一组详细的指标数据,可以帮助我们评估实时通信的质量。这些统计信息包括音视频的码率、丢包率、延迟、抖动、帧率等,可以为我们提供全面的网络连接质量和媒体传输状况的数据支持。

浏览器通过不同的 API 暴露这些统计数据,因此不同浏览器的 WebRTC Stats 实现有一些差异,特别是不同版本的浏览器在统计字段上也会有所更新。因此,了解各浏览器的 Stats 格式并进行适配,是开发高质量 WebRTC 应用的重要步骤。

2. 各个浏览器的 Stats 原始数据

2.1 Chrome

Chrome 的 WebRTC 实现最为完整,也是许多 WebRTC 开发的主要调试工具。其 getStats() API 提供了丰富的统计数据。以下是 Chrome 浏览器的一些常见 Stats 数据字段:

  • inbound-rtp: 入站 RTP 流的统计数据,包括丢包率、延迟、抖动等。

  • outbound-rtp: 出站 RTP 流的统计数据,通常用于分析发送数据的质量。

  • candidate-pair: ICE 候选对信息,用于分析 WebRTC 连接的候选路径及其优劣。

  • remote-candidate/local-candidate: 远程/本地 ICE 候选者的信息。

新版 Chrome (v96+):

随着 Chrome 的版本更新,WebRTC Stats 数据的结构有所调整。新版 Chrome 中对部分字段进行了重命名,增加了更多的网络统计信息,尤其是网络状况分析(例如增加了丢包率预测功能)。主要变化包括:

  • 引入了 totalRoundTripTime 用来更加精准地衡量 RTCP 报文往返时间。

  • 新增了关于 SVC(Scalable Video Coding)相关的统计项。

  • 新版本 Stats 数据示例(注:数据来自浏览器版本 Mac 129.0.6668.71)
[
    {
        "id": "AP",
        "timestamp": 1727401120702.777,
        "type": "media-playout",
        "kind": "audio",
        "synthesizedSamplesDuration": 0,
        "synthesizedSamplesEvents": 0,
        "totalPlayoutDelay": 8073.20178,
        "totalSamplesCount": 367353,
        "totalSamplesDuration": 8.33
    },
    {
        "id": "CF43:96:FE:BD:BF:A4:08:A3:BA:0A:B0:BB:DC:9A:EF:B1:43:F3:4E:40",
        "timestamp": 1727401120702.777,
        "type": "certificate",
        "base64Certificate": "MIIBnzCCAQigAwIBAgIEH20M8zANBgkqhkiG9w0BAQUFADAUMRIwEAYDVQQDDAl3c3J0Yy5uZXQwHhcNMjQwOTI2MjAyNTQ2WhcNMjUwOTI2MjAyNTQ2WjAUMRIwEAYDVQQDDAl3c3J0Yy5uZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMpipgS8lMcklbmrt3vhhnX+j1P76exTnjWukH/tXNeHKZ20+8C2DjIzRkrWTulvBGi987vt9Q1UyFeUAs9pG5T7WN1UrElBZw9L5NecsbLTG5igxu3ynPE5MKSOOXSB4t3yo3EXws+pDz67Isq6vVweB+ICSB1WVT/YxpkHUqDdAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAYczbDpZXjvlr5B+90KYRuHnrdofFXSQHxMQ3uy14AvYNBRIW3bFf3Y1GtTITaqnlGaYRm16PlltySKE4gQxdP0ahqixYjCxVrSTQ2QwYXfcGCuOblGEUXq2U+jMzo7pt/WkeWRnbzknMEuiDZ+PGY8+mD4XNsF+eP9D6phbxIa0=",
        "fingerprint": "43:96:FE:BD:BF:A4:08:A3:BA:0A:B0:BB:DC:9A:EF:B1:43:F3:4E:40",
        "fingerprintAlgorithm": "sha-1"
    },
    {
        "id": "CFCE:02:0C:4F:12:A3:53:2B:6A:B6:F4:B8:78:C7:DC:D7:9C:B3:A7:C1:3E:C2:2B:48:BD:94:2B:9C:DA:52:BC:35",
        "timestamp": 1727401120702.777,
        "type": "certificate",
        "base64Certificate": "MIIBFjCBvKADAgECAghk09klkHIPojAKBggqhkjOPQQDAjARMQ8wDQYDVQQDDAZXZWJSVEMwHhcNMjQwOTI2MDEzODM0WhcNMjQxMDI3MDEzODM0WjARMQ8wDQYDVQQDDAZXZWJSVEMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARDXeUiumrGnDDCh7Fo3mUcnTLig/acbA6Obn1ATt55Ipb3AcgCK63mu14VeVq/4TntjKNN8dMrSkagcMJZGV4/MAoGCCqGSM49BAMCA0kAMEYCIQDyMMuS+Agp6WWuwEFy+9z7hSqbcvuwBgXbJZCC4jagJAIhAOybx9a5zzHZxGJDsZSd4+ttDZbddjwAm164x9Ink1Jb",
        "fingerprint": "CE:02:0C:4F:12:A3:53:2B:6A:B6:F4:B8:78:C7:DC:D7:9C:B3:A7:C1:3E:C2:2B:48:BD:94:2B:9C:DA:52:BC:35",
        "fingerprintAlgorithm": "sha-256"
    },
    {
        "id": "CIT01_108_level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f",
        "timestamp": 1727401120702.777,
        "type": "codec",
        "clockRate": 90000,
        "mimeType": "video/H264",
        "payloadType": 108,
        "sdpFmtpLine": "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f",
        "transportId": "T01"
    },
    {
        "id": "CIT01_111_minptime=10;stereo=1;useinbandfec=1",
        "timestamp": 1727401120702.777,
        "type": "codec",
        "channels": 2,
        "clockRate": 48000,
        "mimeType": "audio/opus",
        "payloadType": 111,
        "sdpFmtpLine": "minptime=10;stereo=1;useinbandfec=1",
        "transportId": "T01"
    },
    {
        "id": "CPVgNtU6tj_nrA8FDQ7",
        "timestamp": 1727401120702.777,
        "type": "candidate-pair",
        "availableIncomingBitrate": 883696,
        "availableOutgoingBitrate": 300000,
        "bytesDiscardedOnSend": 0,
        "bytesReceived": 1080356,
        "bytesSent": 1572,
        "consentRequestsSent": 5,
        "currentRoundTripTime": 0.017,
        "lastPacketReceivedTimestamp": 1727401120692,
        "lastPacketSentTimestamp": 1727401120701,
        "localCandidateId": "IVgNtU6tj",
        "nominated": true,
        "packetsDiscardedOnSend": 0,
        "packetsReceived": 1347,
        "packetsSent": 16,
        "priority": 7926369428764100000,
        "remoteCandidateId": "InrA8FDQ7",
        "requestsReceived": 0,
        "requestsSent": 6,
        "responsesReceived": 6,
        "responsesSent": 0,
        "state": "succeeded",
        "totalRoundTripTime": 0.101,
        "transportId": "T01",
        "writable": true
    },
    {
        "id": "I6lTzyVHX",
        "timestamp": 1727401120702.777,
        "type": "local-candidate",
        "address": "",
        "candidateType": "host",
        "foundation": "2032006369",
        "ip": "",
        "isRemote": false,
        "networkType": "unknown",
        "port": 61297,
        "priority": 2113937151,
        "protocol": "udp",
        "transportId": "T01",
        "usernameFragment": "ZIbK"
    },
    {
        "id": "IDhnF7mGP",
        "timestamp": 1727401120702.777,
        "type": "local-candidate",
        "address": "",
        "candidateType": "host",
        "foundation": "871967914",
        "ip": "",
        "isRemote": false,
        "networkType": "unknown",
        "port": 56782,
        "priority": 2113929727,
        "protocol": "udp",
        "transportId": "T01",
        "usernameFragment": "ZIbK"
    },
    {
        "id": "IT01A371045484",
        "timestamp": 1727401120702.777,
        "type": "inbound-rtp",
        "codecId": "CIT01_111_minptime=10;stereo=1;useinbandfec=1",
        "kind": "audio",
        "mediaType": "audio",
        "ssrc": 371045484,
        "transportId": "T01",
        "jitter": 0.02,
        "packetsLost": 17,
        "packetsReceived": 424,
        "audioLevel": 0.20187994018372143,
        "bytesReceived": 110568,
        "concealedSamples": 13941,
        "concealmentEvents": 3,
        "fecPacketsDiscarded": 0,
        "fecPacketsReceived": 0,
        "headerBytesReceived": 5088,
        "insertedSamplesForDeceleration": 0,
        "jitterBufferDelay": 588652.8,
        "jitterBufferEmittedCount": 327360,
        "jitterBufferMinimumDelay": 36806.4,
        "jitterBufferTargetDelay": 36883.2,
        "lastPacketReceivedTimestamp": 1727401120692.054,
        "mid": "0",
        "packetsDiscarded": 0,
        "playoutId": "AP",
        "removedSamplesForAcceleration": 37876,
        "silentConcealedSamples": 11880,
        "totalAudioEnergy": 0.13451713600243226,
        "totalProcessingDelay": 587198.44416,
        "totalSamplesDuration": 6.2799999999999105,
        "totalSamplesReceived": 301440,
        "trackIdentifier": "5ce99502-0cf8-450a-8b31-4ac8379cb394"
    },
    {
        "id": "IT01V730841480",
        "timestamp": 1727401120702.777,
        "type": "inbound-rtp",
        "codecId": "CIT01_108_level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f",
        "kind": "video",
        "mediaType": "video",
        "ssrc": 730841480,
        "transportId": "T01",
        "jitter": 0.021,
        "packetsLost": 0,
        "packetsReceived": 921,
        "bytesReceived": 938669,
        "firCount": 0,
        "frameHeight": 720,
        "frameWidth": 1280,
        "framesAssembledFromMultiplePackets": 173,
        "framesDecoded": 188,
        "framesDropped": 0,
        "framesPerSecond": 21,
        "framesReceived": 188,
        "freezeCount": 0,
        "headerBytesReceived": 11052,
        "jitterBufferDelay": 1.5556219999999998,
        "jitterBufferEmittedCount": 188,
        "jitterBufferMinimumDelay": 8.519053999999999,
        "jitterBufferTargetDelay": 8.519053999999999,
        "keyFramesDecoded": 5,
        "lastPacketReceivedTimestamp": 1727401120690.82,
        "mid": "1",
        "nackCount": 3,
        "pauseCount": 0,
        "pliCount": 0,
        "totalAssemblyTime": 0.175033,
        "totalDecodeTime": 0.5236,
        "totalFreezesDuration": 0,
        "totalInterFrameDelay": 5.996,
        "totalPausesDuration": 0,
        "totalProcessingDelay": 2.093067,
        "totalSquaredInterFrameDelay": 0.41580599999999973,
        "trackIdentifier": "14a84e86-40c4-4db7-80c0-9d979574de61"
    },
    {
        "id": "IVgNtU6tj",
        "timestamp": 1727401120702.777,
        "type": "local-candidate",
        "address": "",
        "candidateType": "prflx",
        "foundation": "1536824329",
        "ip": "",
        "isRemote": false,
        "networkType": "unknown",
        "port": 61297,
        "priority": 1845501695,
        "protocol": "udp",
        "transportId": "T01",
        "usernameFragment": "ZIbK"
    },
    {
        "id": "InrA8FDQ7",
        "timestamp": 1727401120702.777,
        "type": "remote-candidate",
        "address": "61.182.142.106",
        "candidateType": "host",
        "foundation": "1",
        "ip": "61.182.142.106",
        "isRemote": true,
        "port": 16000,
        "priority": 2013266431,
        "protocol": "udp",
        "transportId": "T01",
        "usernameFragment": "3D0D_80521_66F60C9A_712_175"
    },
    {
        "id": "P",
        "timestamp": 1727401120702.777,
        "type": "peer-connection",
        "dataChannelsClosed": 0,
        "dataChannelsOpened": 0
    },
    {
        "id": "T01",
        "timestamp": 1727401120702.777,
        "type": "transport",
        "bytesReceived": 1080356,
        "bytesSent": 1572,
        "dtlsCipher": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
        "dtlsRole": "client",
        "dtlsState": "connected",
        "iceLocalUsernameFragment": "ZIbK",
        "iceRole": "controlling",
        "iceState": "connected",
        "localCertificateId": "CFCE:02:0C:4F:12:A3:53:2B:6A:B6:F4:B8:78:C7:DC:D7:9C:B3:A7:C1:3E:C2:2B:48:BD:94:2B:9C:DA:52:BC:35",
        "packetsReceived": 1347,
        "packetsSent": 16,
        "remoteCertificateId": "CF43:96:FE:BD:BF:A4:08:A3:BA:0A:B0:BB:DC:9A:EF:B1:43:F3:4E:40",
        "selectedCandidatePairChanges": 1,
        "selectedCandidatePairId": "CPVgNtU6tj_nrA8FDQ7",
        "srtpCipher": "AES_CM_128_HMAC_SHA1_80",
        "tlsVersion": "FEFD"
    }
]
  • 旧版本 Stats 数据示例(注:数据来自浏览器版本 Mac 66.0.3359.181)
[
  {
    "id": "RTCCertificate_26:A7:E3:EE:D1:6B:FD:DD:D2:AE:E9:42:7B:99:A6:12:59:73:FF:33",
    "timestamp": 1727403044443.757,
    "type": "certificate",
    "fingerprint": "26:A7:E3:EE:D1:6B:FD:DD:D2:AE:E9:42:7B:99:A6:12:59:73:FF:33",
    "fingerprintAlgorithm": "sha-1",
    "base64Certificate": "MIIBnzCCAQigAwIBAgIEDpwPGzANBgkqhkiG9w0BAQUFADAUMRIwEAYDVQQDDAl3c3J0Yy5uZXQwHhcNMjQwOTI2MjAyNTQ1WhcNMjUwOTI2MjAyNTQ1WjAUMRIwEAYDVQQDDAl3c3J0Yy5uZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMNMdScj78fxKYTb4IL6nsHlhjd6nTInBmNpPDi+Y6YMQ5Ak37VF+uffM2GC4w8B+66ZFLC5ghik+FuMJo1OOWtiYw4qosaIeX4MlAuZblRw0Rbl6YT4XC1RMvnsKytMGfHAH/o32nDle5xyyDhUwH7Z0dH7HMEVKtDjyqa7Me0NAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEADcjvHm/82INUWMSwaF9gGRyjKimiTC092TEldd6+t2/VRtJyDJS/6ufs93MnYupg4lgWmG8+8abISaScA3N1c3dKsH20easx/StjO5lof2a0eaR5u7x7ebyh+koa7smVjlHRSmdT4/XKYEiJgabcEal9oxqrZnrkovW14o2sqdA="
  },
  {
    "id": "RTCCertificate_54:0D:09:82:96:09:A2:16:88:CD:1C:04:3A:60:28:24:F0:57:BF:90:17:47:DA:54:A3:5E:E1:16:45:14:49:71",
    "timestamp": 1727403044443.757,
    "type": "certificate",
    "fingerprint": "54:0D:09:82:96:09:A2:16:88:CD:1C:04:3A:60:28:24:F0:57:BF:90:17:47:DA:54:A3:5E:E1:16:45:14:49:71",
    "fingerprintAlgorithm": "sha-256",
    "base64Certificate": "MIIBFTCBvaADAgECAgkA6k2QoqVLAGkwCgYIKoZIzj0EAwIwETEPMA0GA1UEAwwGV2ViUlRDMB4XDTI0MDkyNjAyMTAyNVoXDTI0MTAyNzAyMTAyNVowETEPMA0GA1UEAwwGV2ViUlRDMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+tI+LMWxz9j57gu/55vNAuZWA5vXZJBykGu1Hxe+ZFUrcjPEDawhaO1NUrSKSdq7rShzbPqRyFMryXipGEBlRDAKBggqhkjOPQQDAgNHADBEAiB483H4mumr7QBSnU6HUdMZVTziN5tQ5pYvB3jJ57Oz4AIgIeqOy2UZ3tOzPEIrlDfVQtgzytKQfW+5AjWxoCtNWdE="
  },
  {
    "id": "RTCCodec_audio_Inbound_0",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 0,
    "mimeType": "audio/PCMU",
    "clockRate": 8000
  },
  {
    "id": "RTCCodec_audio_Inbound_103",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 103,
    "mimeType": "audio/ISAC",
    "clockRate": 16000
  },
  {
    "id": "RTCCodec_audio_Inbound_104",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 104,
    "mimeType": "audio/ISAC",
    "clockRate": 32000
  },
  {
    "id": "RTCCodec_audio_Inbound_110",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 110,
    "mimeType": "audio/telephone-event",
    "clockRate": 48000
  },
  {
    "id": "RTCCodec_audio_Inbound_111",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 111,
    "mimeType": "audio/opus",
    "clockRate": 48000
  },
  {
    "id": "RTCCodec_audio_Inbound_112",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 112,
    "mimeType": "audio/telephone-event",
    "clockRate": 32000
  },
  {
    "id": "RTCCodec_audio_Inbound_113",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 113,
    "mimeType": "audio/telephone-event",
    "clockRate": 16000
  },
  {
    "id": "RTCCodec_audio_Inbound_126",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 126,
    "mimeType": "audio/telephone-event",
    "clockRate": 8000
  },
  {
    "id": "RTCCodec_audio_Inbound_8",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 8,
    "mimeType": "audio/PCMA",
    "clockRate": 8000
  },
  {
    "id": "RTCCodec_audio_Inbound_9",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 9,
    "mimeType": "audio/G722",
    "clockRate": 8000
  },
  {
    "id": "RTCCodec_audio_Outbound_111",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 111,
    "mimeType": "audio/opus",
    "clockRate": 48000
  },
  {
    "id": "RTCCodec_video_Inbound_100",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 100,
    "mimeType": "video/H264",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Inbound_101",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 101,
    "mimeType": "video/rtx",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Inbound_102",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 102,
    "mimeType": "video/H264",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Inbound_123",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 123,
    "mimeType": "video/rtx",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Inbound_124",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 124,
    "mimeType": "video/rtx",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Inbound_125",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 125,
    "mimeType": "video/ulpfec",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Inbound_127",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 127,
    "mimeType": "video/red",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Inbound_96",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 96,
    "mimeType": "video/VP8",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Inbound_97",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 97,
    "mimeType": "video/rtx",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Inbound_98",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 98,
    "mimeType": "video/VP9",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Inbound_99",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 99,
    "mimeType": "video/rtx",
    "clockRate": 90000
  },
  {
    "id": "RTCCodec_video_Outbound_102",
    "timestamp": 1727403044443.757,
    "type": "codec",
    "payloadType": 102,
    "mimeType": "video/H264",
    "clockRate": 90000
  },
  {
    "id": "RTCIceCandidatePair_AjuJCZ7+_IW154alq",
    "timestamp": 1727403044443.757,
    "type": "candidate-pair",
    "transportId": "RTCTransport_audio_1",
    "localCandidateId": "RTCIceCandidate_AjuJCZ7+",
    "remoteCandidateId": "RTCIceCandidate_IW154alq",
    "state": "succeeded",
    "priority": 7926369428764100000,
    "nominated": true,
    "writable": true,
    "bytesSent": 7282,
    "bytesReceived": 2215418,
    "totalRoundTripTime": 0.161,
    "currentRoundTripTime": 0.013,
    "availableOutgoingBitrate": 300000,
    "availableIncomingBitrate": 1432511,
    "requestsReceived": 0,
    "requestsSent": 1,
    "responsesReceived": 11,
    "responsesSent": 0,
    "consentRequestsSent": 10
  },
  {
    "id": "RTCIceCandidate_AjuJCZ7+",
    "timestamp": 1727403044443.757,
    "type": "local-candidate",
    "transportId": "RTCTransport_audio_1",
    "isRemote": false,
    "networkType": "unknown",
    "ip": "220.249.52.114",
    "port": 56338,
    "protocol": "udp",
    "candidateType": "prflx",
    "priority": 1845501695,
    "deleted": false
  },
  {
    "id": "RTCIceCandidate_IW154alq",
    "timestamp": 1727403044443.757,
    "type": "remote-candidate",
    "transportId": "RTCTransport_audio_1",
    "isRemote": true,
    "ip": "61.182.142.106",
    "port": 16000,
    "protocol": "udp",
    "candidateType": "host",
    "priority": 2013266431,
    "deleted": false
  },
  {
    "id": "RTCInboundRTPAudioStream_447985672",
    "timestamp": 1727403044443.757,
    "type": "inbound-rtp",
    "ssrc": 447985672,
    "isRemote": false,
    "mediaType": "audio",
    "trackId": "RTCMediaStreamTrack_receiver_11",
    "transportId": "RTCTransport_audio_1",
    "codecId": "RTCCodec_audio_Inbound_111",
    "packetsReceived": 925,
    "bytesReceived": 250173,
    "packetsLost": 0,
    "jitter": 0.023,
    "fractionLost": 0
  },
  {
    "id": "RTCInboundRTPVideoStream_751354887",
    "timestamp": 1727403044443.757,
    "type": "inbound-rtp",
    "ssrc": 751354887,
    "isRemote": false,
    "mediaType": "video",
    "trackId": "RTCMediaStreamTrack_receiver_12",
    "transportId": "RTCTransport_audio_1",
    "codecId": "RTCCodec_video_Inbound_102",
    "firCount": 0,
    "pliCount": 26,
    "nackCount": 3,
    "packetsReceived": 1891,
    "bytesReceived": 1934115,
    "packetsLost": 0,
    "fractionLost": 0,
    "framesDecoded": 39
  },
  {
    "id": "RTCMediaStreamTrack_receiver_11",
    "timestamp": 1727403044443.757,
    "type": "track",
    "trackIdentifier": "wsrtca0",
    "remoteSource": true,
    "ended": false,
    "detached": false,
    "kind": "audio",
    "jitterBufferDelay": 50755.2,
    "audioLevel": 0.16251106295968504,
    "totalAudioEnergy": 0.43269636448123144,
    "totalSamplesReceived": 888960,
    "totalSamplesDuration": 18.580000000000105,
    "concealedSamples": 5862,
    "concealmentEvents": 7
  },
  {
    "id": "RTCMediaStreamTrack_receiver_12",
    "timestamp": 1727403044443.757,
    "type": "track",
    "trackIdentifier": "wsrtcv0",
    "remoteSource": true,
    "ended": false,
    "detached": false,
    "kind": "video",
    "frameWidth": 1280,
    "frameHeight": 720,
    "framesReceived": 346,
    "framesDecoded": 39,
    "framesDropped": 85
  },
  {
    "id": "RTCMediaStream_wsrtc",
    "timestamp": 1727403044443.757,
    "type": "stream",
    "streamIdentifier": "wsrtc",
    "trackIds": [
      "RTCMediaStreamTrack_receiver_11",
      "RTCMediaStreamTrack_receiver_12"
    ]
  },
  {
    "id": "RTCPeerConnection",
    "timestamp": 1727403044443.757,
    "type": "peer-connection",
    "dataChannelsOpened": 0,
    "dataChannelsClosed": 0
  },
  {
    "id": "RTCTransport_audio_1",
    "timestamp": 1727403044443.757,
    "type": "transport",
    "bytesSent": 7282,
    "bytesReceived": 2215418,
    "dtlsState": "connected",
    "selectedCandidatePairId": "RTCIceCandidatePair_AjuJCZ7+_IW154alq",
    "localCertificateId": "RTCCertificate_54:0D:09:82:96:09:A2:16:88:CD:1C:04:3A:60:28:24:F0:57:BF:90:17:47:DA:54:A3:5E:E1:16:45:14:49:71",
    "remoteCertificateId": "RTCCertificate_26:A7:E3:EE:D1:6B:FD:DD:D2:AE:E9:42:7B:99:A6:12:59:73:FF:33"
  }
]

2.2 Firefox

Firefox 也提供了丰富的 WebRTC Stats,但其数据格式和 Chrome 略有不同。

  • inbound-rtp/outbound-rtp: 类似 Chrome 的字段,用于衡量媒体流的入站和出站质量。

  • iceCandidatePair: 提供连接的 ICE 候选对数据,帮助分析连接状况。

  • track: Firefox 特有的字段,用于获取媒体轨道的质量数据,如编码率和丢包率。

新版 Firefox (v91+):

Firefox 在新版本中开始支持更多的网络层面的统计数据,比如增加了关于 TURN 和 STUN 服务器性能的统计。另外,Firefox 的 Stats 在编码/解码方面也有更加详细的数据,如丢帧和延迟统计。

2.3 Safari

Safari 对 WebRTC 的支持较 Chrome 和 Firefox 来说起步较晚,但也已经提供了基本的 WebRTC Stats 数据。

  • inbound-rtp/outbound-rtp: 与其他浏览器类似,包含入站和出站流的基本数据。

  • candidate-pair: 提供候选对数据,帮助分析连接路径。

Safari 在 WebRTC Stats 上的数据相对简化,没有提供非常详细的统计数据,不过新版 Safari 正在逐步增加更多细节,尤其是在 iOS 平台的 Safari 上,对于 WebRTC 的支持还在逐渐改进。

新版 Safari (v15+):

新版 Safari 开始支持更多的连接状态数据以及媒体轨道的细节数据,如音视频轨道的帧率、码率等。这使得 Safari 在监控 WebRTC 连接质量上变得更加可靠。

2.4 Microsoft Edge

Microsoft Edge 使用 Chromium 内核,因此其 WebRTC Stats 与 Chrome 几乎完全一致。Edge 会继承 Chrome 的绝大多数统计字段,主要区别在于 Edge 的部分实现可能会有一些定制化的扩展字段。

3. 统一的适配

为了让 WebRTC 应用在所有浏览器中都能稳定运行,开发者需要对不同浏览器的 Stats 数据进行适配。主要的适配工作包括:

3.1 标准化 Stats 获取

通过 RTCPeerConnection.getStats() 方法,可以获取到不同浏览器的 WebRTC Stats 数据。为了确保跨浏览器的兼容性,我们可以通过一个统一的封装函数来适配各浏览器不同的字段格式。例如,可以创建一个通用的适配层,将所有浏览器的 Stats 数据标准化为统一的格式。


function getStandardizedStats(peerConnection) {

  return new Promise((resolve, reject) => {

    peerConnection.getStats(null).then(stats => {

      let standardizedStats = {};

      stats.forEach(report => {

        // 针对不同类型的报告进行处理

        if (report.type === 'inbound-rtp') {

          standardizedStats.inbound = {

            packetsLost: report.packetsLost,

            jitter: report.jitter,

            roundTripTime: report.roundTripTime || report.totalRoundTripTime,

            bytesReceived: report.bytesReceived,

          };

        } else if (report.type === 'outbound-rtp') {

          standardizedStats.outbound = {

            bytesSent: report.bytesSent,

            packetsSent: report.packetsSent,

          };

        }

      });

      resolve(standardizedStats);

    }).catch(reject);

  });

}

3.2 处理不同浏览器字段差异

为了应对不同浏览器在 WebRTC Stats 字段上的差异,可以创建一份映射表,将不同浏览器的字段映射为统一的字段。例如,Chrome 中的 totalRoundTripTime 在 Firefox 中可能叫做 roundTripTime,需要在适配层做相应的转换。

3.3 统一格式输出

无论是 Chrome、Firefox 还是 Safari,经过适配层处理后,所有浏览器的 WebRTC Stats 都可以输出成统一的格式。开发者只需基于这个标准化格式进行性能分析和问题排查,从而避免在不同浏览器中编写不同的处理逻辑。

4. 总结

WebRTC Stats 是监控实时通信质量的重要工具。各个浏览器在实现上存在一定差异,尤其是在不同版本的浏览器中,WebRTC Stats 的字段和功能有所不同。通过统一的适配层,开发者可以简化跨浏览器的兼容性处理,确保 WebRTC 应用在所有主流浏览器中都能顺利运行。

借助 WebRTC Stats,开发者可以实时监控视频、音频流的质量,优化用户体验,并快速定位网络连接或媒体传输中的问题。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
  3. QQ打赏

    qrcode qq

评论区