Bootstrap

第三十一章 视频内容检测和分析手段

当我们完成整体的流转后,需要检测其中的每帧数据时,需要用到一些工具检测和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

},

;