当我们完成整体的流转后,需要检测其中的每帧数据时,需要用到一些工具检测和API获取。以下我们来介绍2个方法。
内置性能测试工具webrtc-internals
针对云渲染产品进行性能测试,除过一些专业测试工具,企业自研测试工具外,我们还可以借助基于Google浏览器的调试工具Webrtc。
Google浏览器输入chrome://webrtc-internals/,
就会展示出有webrtc相关功能被启用的网页以及通信过程中产生的统计数据:
音频统计数据:
audioInputLevel
发送端采集的音频能量大小,数值越大,说明音频保真度越高。
bitsSentPerSecond
每秒发送出去的比特数,数值越大越好,发送字节的吞吐量。
packetsSentPerSecond
每秒发送出去的音频包数,数值越大越好。
googResidualEchoLikelihood
Chrome 56中新增的,主要用来标识是否存在回声,范围为0 (没有回声)- 1(有回声),当值大于0.5时表明存在回声。
视频统计数据:
bitsSentPerSecond
每秒发送出去的比特数,根据当前网络情况会进行动态调整,数值越大性能表现越好。
framesEncoded
累计编码出来的视频帧数,没有异常情况的话会一直增长。
packetsLost
发送端从接收端发送过来的RTCP Receiver Report中得到的累积丢包数量,可以和googNacksReceived数据进行对照。该数值越小越好,数值偏大说明网络存在瓶颈。
googRtt
Rtt全称为Round-trip time,是发送端从接受端发送过来的RTCPReceiver Report中得到的时间戳通过计算得到的往返时延。
packetsSentPerSecond
Chrome 56中新增的,每秒发送出去的视频包数量,数值越大性能越好。
qpSum
发送端编码出的带有QP值的帧的数量,QP全称为QuantizationParameter。帧值达到60以上一般都是良好的。
googAdaptationChanges
发送端因为CPU的负载变化导致的分辨变高或者变低的次数,需要设置。
googAvgEncodeMs
发送端平均编码时间,越小越好。
googEncodeUsagePercent
发送端(平均每帧编码时间)/(平均每帧采集时间),反应编码效率。
googFirsReceived
发送端收到的关键帧请求数量,FIR全称为Full Intra Request,一般来说在video conference模式下,有新的参与者进来会发出。
googPlisReceived
发送端收到的关键帧请求数量,PLI全称为Picture Loss Indication,一般来说在解码失败时会发出。
googNacksReceived
发送端收到的重传包请求数量,Nack全称为Negative ACKnowledgement可以和packetsLost数据进行对照。
googFrameHeightSent
发送端发送的分辨率高度,根据当前网络会进行动态调整。
googFrameWidthSent
发送端发送的分辨率宽度,根据当前网络会进行动态调整。
googFrameRateInput
发送端设置的初始帧率。
googFrameRateSent
发送端实际发送的帧率,根据当前网络会进行动态调整
通过API方式来检测
用PeerConnection->GetStats获取WebRTC状态信息,实现webrtc::RTCStatsCollectorCallback接口。然后调用PeerConnection→GetStats()函数即可。
可以设置轮询,也可以借用Pong消息,加入到MicroProfiler信息中
以下是具体的接口实现
#pragma once #include <api/stats/rtc_stats_collector_callback.h> #include <api/peer_connection_interface.h> #include "api/stats/rtc_stats_report.h" #include "QLSingleton.h" #include "QLLogSystem.h" #include "QLCommonDefine.h" #include "QLJsonHelper.h" namespace QL { class QLStatsCollection :virtual public webrtc::RTCStatsCollectorCallback, public QLSingleton<QLStatsCollection> { public: friend class QLSingleton<QLStatsCollection>; virtual void AddRef() const override { mRefCount.IncRef(); } virtual rtc::RefCountReleaseStatus Release() const override { rtc::RefCountReleaseStatus status = mRefCount.DecRef(); return status; } /// <summary> /// /// </summary> /// <param name="report"></param> virtual void OnStatsDelivered(const rtc::scoped_refptr<const webrtc::RTCStatsReport>& report) override { if (!QLGlobalConfig::Get().IsEnablePongProfiler()) { return; } for (const auto& stats : *report) { std::string elementName = stats.id(); std::string allElementData = stats.ToJson(); Json::Value myJsonValue = TO_JSON(allElementData); std::string myType = JSON_STR(myJsonValue, "type"); std::string myID = JSON_STR(myJsonValue, "id"); std::string myKind = JSON_STR(myJsonValue, "kind"); if ((myType == "outbound-rtp") && (myKind == "video")) { std::string trackId = JSON_STR(myJsonValue, "trackId"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " trackId: " + trackId)); std::string codecId = JSON_STR(myJsonValue, "codecId"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " codecId: " + codecId)); int targetBitrate = JSON_INT(myJsonValue, "targetBitrate"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG+ " targetBitrate: " + std::to_string(targetBitrate))); int framesEncoded = JSON_INT(myJsonValue, "framesEncoded"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " framesEncoded: " + std::to_string(framesEncoded))); int framesSent = JSON_INT(myJsonValue, "framesSent"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " framesSent: " + std::to_string(framesSent))); int keyFramesEncoded = JSON_INT(myJsonValue, "keyFramesEncoded"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " keyFramesEncoded: " + std::to_string(keyFramesEncoded))); int totalEncodeTime = JSON_INT(myJsonValue, "totalEncodeTime"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " totalEncodeTime: " + std::to_string(totalEncodeTime) + "(ms)")); float totalPacketSendDelay = JSON_FLOAT(myJsonValue, "totalPacketSendDelay"); //ms QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " totalPacketSendDelay: " + std::to_string(totalPacketSendDelay)+"(ms)")); int framesPerSecond = JSON_INT(myJsonValue, "framesPerSecond"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " framesPerSecond: " + std::to_string(framesPerSecond))); } if ((myType == "remote-inbound-rtp") && (myKind == "video")) { float jitter = JSON_FLOAT(myJsonValue, "jitter"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " jitter: " + std::to_string(jitter))); int packetLost = JSON_INT(myJsonValue, "packetLost"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " packetLost: " + std::to_string(packetLost))); int fractionLost = JSON_INT(myJsonValue, "fractionLost"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " fractionLost: " + std::to_string(fractionLost))); float roundTripTime = JSON_FLOAT(myJsonValue, "roundTripTime"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " roundTripTime: " + std::to_string(roundTripTime) + "(ms)")); float totalRoundTripTime = JSON_FLOAT(myJsonValue, "totalRoundTripTime"); QL_LOG(std::string(OBSERVER_MARK_EACH_PONG + " totalRoundTripTime: " + std::to_string(totalRoundTripTime) + "(ms)")); } if (myType == "local-candidate") { //暂时不输出这些ICE信息 } //不限定trackIdentifier,因为我们的类型很多啊。匹配麻烦 if ((myType == "track") && (myKind == "video")) { //前端接受到帧,所以只能前端计算丢帧率 } } } QLStatsCollection() :mRefCount(0) { } private: mutable webrtc::webrtc_impl::RefCounter mRefCount; }; }
最终Console中打印的信息
主要重要的数据字段:
“type”: “codec”, 可以确定当前编码 “mimeType”: “video/H264”,
outbound-rtp中重要的数据
【以下的Json数据我们只罗列出来部分】
获取到的Json可视化后的数据
[
{
“type”: “certificate”,
“id”: “CF93:8A:53:EE:1F:44:3A:B6:A7:C8:74:6F:CB:9E:23:15:38:A0:9F:11:33:12:B8:11:3D:78:07:F0:90:CF:5F:0F”,
“timestamp”: 1694055419635000,
“fingerprint”: “93:8A:53:EE:1F:44:3A:B6:A7:C8:74:6F:CB:9E:23:15:38:A0:9F:11:33:12:B8:11:3D:78:07:F0:90:CF:5F:0F”,
“fingerprintAlgorithm”: “sha-256”,
“base64Certificate”: “MIIBFTCBvKADAgECAggWxMkFdNwv7TAKBggqhkjOPQQDAjARMQ8wDQYDVQQDDAZXZWJSVEMwHhcNMjMwOTA2MDI1NjQ2WhcNMjMxMDA3MDI1NjQ2WjARMQ8wDQYDVQQDDAZXZWJSVEMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASM16HqFnNsOJSNXP3QC7PUkStRtgEDqfhZDvBT/UMCTOZp2xFG2ZHH2MhTPkjC6kXPCyVejDpFVcZSNpnhAyEgMAoGCCqGSM49BAMCA0gAMEUCIQCuGjErcgGwukrWZ8E5HzGouvWYwQGsTzx2Oj+zkjEKDgIgSZxxF9E+6G2Ge6UjmZfHTtrnP/wopvZkBXNJqobCtgI=”
},
{
“type”: “certificate”,
“id”: “CFDD:03:FA:EA:CB:E2:F8:0E:25:FA:D9:01:4C:B4:74:F1:02:01:EA:7A:8F:78:4E:6F:57:2E:43:D6:5D:85:23:BE”,
“timestamp”: 1694055419635000,
“fingerprint”: “DD:03:FA:EA:CB:E2:F8:0E:25:FA:D9:01:4C:B4:74:F1:02:01:EA:7A:8F:78:4E:6F:57:2E:43:D6:5D:85:23:BE”,
“fingerprintAlgorithm”: “sha-256”,
“base64Certificate”: “MIIBFjCBvKADAgECAghN0S0ay/cXIDAKBggqhkjOPQQDAjARMQ8wDQYDVQQDDAZXZWJSVEMwHhcNMjMwOTA2MDI1NjQ3WhcNMjMxMDA3MDI1NjQ3WjARMQ8wDQYDVQQDDAZXZWJSVEMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARaCm3N+d0WXuSnqlNagGO4LMP1VR2pVyv+wDxw4lZB6cIR2Z1hAqdDLSrPafd0NsvMmVrLkxt85zRtOjkJyOt+MAoGCCqGSM49BAMCA0kAMEYCIQDjA7ogoIslpIrWXoJoqyJ17a2pD5/JeBRj3h7CbMuH+gIhANi0r8i4DenVioKdI7BmPwJ5O22tsuimZjGHVNAfekzf”
},
{
“type”: “codec”,
“id”: “COT01_102_level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f”,
“timestamp”: 1694055419635000,
“transportId”: “T01”,
“payloadType”: 102,
“mimeType”: “video/H264”,
“clockRate”: 90000,
“sdpFmtpLine”: “level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f”
},
{
“type”: “codec”,
“id”: “COT01_111_minptime=10;sprop-maxcapturerate=48000;stereo=1;useinbandfec=1”,
“timestamp”: 1694055419635000,
“transportId”: “T01”,
“payloadType”: 111,
“mimeType”: “audio/opus”,
“clockRate”: 48000,
“channels”: 2,
“sdpFmtpLine”: “minptime=10;sprop-maxcapturerate=48000;stereo=1;useinbandfec=1”
},
{
“type”: “candidate-pair”,
“id”: “CP18ZFk+UQ_5F/MfElY”,
“timestamp”: 1694055419635000,
“transportId”: “T01”,
“localCandidateId”: “I18ZFk+UQ”,
“remoteCandidateId”: “I5F/MfElY”,
“state”: “succeeded”,
“priority”: 7926380424097976000,
“nominated”: false,
“writable”: true,
“packetsSent”: 0,
“packetsReceived”: 0,
“bytesSent”: 0,
“bytesReceived”: 0,
“totalRoundTripTime”: 0.003,
“currentRoundTripTime”: 0.001,
“requestsReceived”: 5,
“requestsSent”: 4,
“responsesReceived”: 4,
“responsesSent”: 5,
“consentRequestsSent”: 3,
“packetsDiscardedOnSend”: 0,
“bytesDiscardedOnSend”: 0
},
{
“type”: “candidate-pair”,
“id”: “CP18ZFk+UQ_l7760kOD”,
“timestamp”: 1694055419635000,
“transportId”: “T01”,
“localCandidateId”: “I18ZFk+UQ”,
“remoteCandidateId”: “Il7760kOD”,
“state”: “in-progress”,
“priority”: 9079301928704823000,
“nominated”: false,
“writable”: false,
“packetsSent”: 0,
“packetsReceived”: 0,
“bytesSent”: 0,
“bytesReceived”: 0,
“totalRoundTripTime”: 0,
“requestsReceived”: 0,
“requestsSent”: 7,
“responsesReceived”: 0,
“responsesSent”: 0,
“consentRequestsSent”: 0,
“packetsDiscardedOnSend”: 0,
“bytesDiscardedOnSend”: 0
},