• WebRTC 视频流接收统计报告


    WebRTC 视频流接收统计报告

    在每次视频推流或拉流结束后,WebRTC都会输出本次视频推拉流的统计报告。其中包含了关于评价本次推拉流质量相关的若干参数。本文的主要目的是介绍视频拉流相关的统计指标含义。

    关于推流相关的统计指标,另外的文章进行单独描述。

    本文源码基于 WebRTC M94 编写。后续 WebRTC 版本可能有所变化(如 receive_statistics_proxy 已被移除,改为 receive_statistics_proxy2),细节可能不同,但基本原理应该适用。

    相关源码

    • video\receive_statistics_proxy.cc

    • call\video_receive_stream.h

    统计指标

    ReceiveStreamLifetimeInSeconds

    含义如字面意思。表示从视频拉流对象创建,到结束的总时长,单位是秒。

    计算的起始时间是 ReceiveStatisticsProxy类构造的时候,在 ReceiveStatisticsProxy::UpdateHistograms()中,当前时间减去起始时间,得到耗时。

    VideoReceiveStream的构造函数,同时构造了 ReceiveStatisticsProxy

    VideoReceiveStream::Stop()的时候,会调用 ReceiveStatisticsProxy::UpdateHistograms()完成各种度量指标计算。

    Frames decoded

    顾名思义,表示解码总帧数。对应 VideoReceiveStream::Stats的成员变量 frames_decoded。 视频帧解码通知时序:

    @startuml
    Actor Decoder
    Decoder -> VCMDecodedFrameCallback : Decoded
    VCMDecodedFrameCallback -> VideoStreamDecoder : FrameToRender
    VideoStreamDecoder -> ReceiveStatisticsProxy : OnDecodedFrame
    @enduml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Frames decoded sequence

    解码器解码一帧后,会通知到 ReceiveStatisticsProxy::OnDecodedFrame()中对解码帧数增加计数。

    DroppedFrames.Receiver

    VideoReceiveStream::Stop()执行的时候,会调用 RtpVideoStreamReceiver::GetUniqueFramesSeen()获取一个视频帧计数器 frame_counter_,它的值是在 RtpVideoStreamReceiver::OnReceivedPayloadData()中根据收包时间戳来增加,具体见源码 video\rtp_video_stream_receiver.cc

    frame_counter_.Add(packet->timestamp);
    
    • 1

    这个视频帧计数器可以理解为从收到的视频帧总数,然后用这个总数减去解码帧数Frames decoded,就得到了丢掉的总帧数:

    int num_dropped_frames = *num_unique_frames_ - stats_.frames_decoded;
    
    • 1

    ReceivedPacketsLostInPercent

    顾名思义,丢包率(百分比)。当 ReceiveStreamLifetimeInSeconds大于10秒时才可能输出这个数值。这个数值实际的计算位置来自 StreamStatisticianImpl::GetFractionLostInPercent()。丢包率的分子和分母不在本文介绍范围内,感兴趣的读者自行阅读源码。

    DecodedFramesPerSecond

    平均解码帧率。以当前时间减去解码第一帧开始时间得到的差值做为分母,解码总帧数做为分子,计算得出的整型数值。

    RenderFramesPerSecond

    平均渲染帧率。数值来自于 ReceiveStatisticsProxy的成员变量 render_fps_tracker_,变量类型是rtc::RateTracker。关于rtc::RateTracker的实现原理,可以参考这篇文章

    ReceiveStatisticsProxy::OnRenderedFrame中会调用 AddSamples增加一个采样计数。最后调用 RateTracker::ComputeTotalRate返回值并对其四舍五入获得渲染帧率。例如渲染帧数总共是 545,总时长是 60000 毫秒,得到的帧率就是 545 x 1000 ÷ 60000 = 9.08 ≈ 9 帧/秒。

    代码中对采样计数有最少200个的约束,即少于200帧是不会输出渲染帧率的。

    渲染帧通知时序大概如下:

    @startuml
    Actor Decoder
    Decoder -> VideoStreamDecoder : FrameToRender
    VideoStreamDecoder -> IncomingVideoStream : OnFrame
    note right
    如果 VideoReceiveStream::Config 的 
    enable_prerenderer_smoothing
    是 false,则会直接送给 VideoReceiveStream
    end note
    IncomingVideoStream -> VideoReceiveStream : OnFrame
    note right
    onFrame 是实现 
    rtc::VideoSinkInterface 的虚函数
    end note
    VideoReceiveStream -> ReceiveStatisticsProxy : OnRenderedFrame
    @enduml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    RenderFramesPerSecond

    KeyFramesReceivedInPermille

    permille是“千分率”的意思,因此,这个数值是表示接收关键帧的千分率。计算公式在代码中如下:

    (关键帧总数 x 1000 + 总帧数 / 2) / 总帧数    (1)
    
    • 1

    其中总帧数是关键帧和非关键帧的总和。注意计算的结果会被强转成整型。

    其实我看到这个公式是有点不太理解的。如果只是计算千分率,为何不直接就使用

    (关键帧总数 ÷ 总帧数) x 1000    (2)
    
    • 1

    就行了?如果按照上面的公式(1)计算,意味着计算出来的结果始终是会比公式(2)要大一些的(约等于0.5)。不知道作者是出于什么考虑。

    DecodeTimeInMs

    平均解码耗时。数值来自于 ReceiveStatisticsProxy的成员变量 decode_time_counter_,变量类型是rtc::SampleCounter(源码:rtc_base\numerics\sample_counter.cc)。rtc::SampleCounter的源码可以阅读一下,比较简单,ReceiveStatisticsProxy有很多变量类型都是它。

    ReceiveStatisticsProxy::OnDecodedFrame被调用的时候,会将当前帧的解码耗时decode_time_ms追加到decode_time_counter_中记录下来,并增加一个采样计数。最终计算的平均解码耗时,就是调用rtc::SampleCounterAvg方法计算得到的,其原理比较简单:全部帧的解码耗时总时长÷采样计数。与 RenderFramesPerSecond一样,对采样计数目前有最小200的要求,即小于200个采样计数不输出此项数据。

    JitterBufferDelayInMs, TargetDelayInMs, CurrentDelayInMs

    这三个数值具有强相关性,因此放到一起描述。它们分别对应 ReceiveStatisticsProxy的成员变量 jitter_buffer_delay_counter_target_delay_counter_current_delay_counter_,变量类型是rtc::SampleCounter

    ReceiveStatisticsProxy::OnFrameBufferTimingsUpdated被调用的时候,会传入jitter_buffer_mstarget_delay_ms以及current_delay_ms,累加到对应的采样计数器变量中。最终用于计算平均值:分子是每次传入的数值总和,分母是次数

    ReceiveStatisticsProxy::OnFrameBufferTimingsUpdatedFrameBuffer调用,源码:modules\video_coding\frame_buffer2.cc

    @startuml
    Actor jitter_buffer
    jitter_buffer -> FrameBuffer : IntersetFrame/NextFrame
    FrameBuffer -> FrameBuffer : StartWaitForNextFrameOnQueue
    FrameBuffer -> FrameBuffer : GetNextFrame
    FrameBuffer -> FrameBuffer : UpdateJitterDelay
    FrameBuffer -> ReceiveStatisticsProxy : OnFrameBufferTimingsUpdated
    @enduml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    JitterBufferDelay

    FrameBuffer 和 JitterBuffer 的实现原理不在本文介绍范围之内,感兴趣的读者自行研读源码。

    EndToEndDelayInMs

    端到端平均延时。对应 VideoReceiveStream::Stats的成员变量 e2e_delay_counter。 它的类型是rtc::SampleCounter。在ReceiveStatisticsProxy::OnRenderedFrame中会计算单帧的延时,累加到e2e_delay_counter。单次的延时数值是当前 ntp_time(webrtc::Clock::CurrentNtpInMilliseconds())减去webrtc::VideoFramentp_time_ms_得到的。

    EndToEndDelayMaxInMs

    端到端最大延时。它是 VideoReceiveStream::Stats的成员变量 e2e_delay_counter记录的所有单帧延时的最大值。

    InterframeDelayInMs

    解码帧间隔平均值。对应 VideoReceiveStream::Stats的成员变量 interframe_delay_counter。 它的类型是rtc::SampleCounter。在ReceiveStatisticsProxy::OnDecodedFrame中会计算单帧的解码时间间隔(即ReceiveStatisticsProxy::OnDecodedFrame被调用的两次间隔),累加到interframe_delay_counter

    InterframeDelayMaxInMs

    最大解码帧间隔。它是 VideoReceiveStream::Stats的成员变量 interframe_delay_counter记录的所有单帧解码帧间隔数值的最大者。

    InterframeDelay95PercentileInMs

    这个数值代表:**超过95%帧间隔数值里的最小值。**或者理解为 95%的帧间隔都小于多少。对应 VideoReceiveStream::Stats的成员变量 interframe_delay_percentiles。它的类型是rtc::HistogramPercentileCounter。这个数值乍一看不太容易理解,这里简单介绍一下。

    要理解这个数值,需要先理解rtc::HistogramPercentileCounter。它与 rtc::SampleCounter有类似之处,但本质上还是有很多不同。它存在几个关键的成员变量:

    std::vector histogram_low_;
    std::map histogram_high_;
    const uint32_t long_tail_boundary_;
    size_t total_elements_;
    size_t total_elements_low_;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    long_tail_boundary_作为分界点,小于long_tail_boundary_的值,记录到histogram_low_,否则记录到histogram_high_。目前就InterframeDelay95PercentileInMs来说,这个分界点是500kMaxCommonInterframeDelayMs)。

    histogram_low_的每个元素索引序号代表具体的一个帧间隔数值,值表示此帧间隔的次数。分界点是500的话,histogram_low_所代表的帧间隔数值范围就是0~499。

    histogram_high_histogram_low_的作用类似,但它的类型是 std::map,key表示帧间隔数值,value表示次数。

    InterframeDelay95PercentileInMs调用的是rtc::HistogramPercentileCounterGetPercentile()方法,传入参数0.95f。它的目的是跳过全部计数 total_elements_的 95% 减 1 以后,寻找剩余部分里最小的帧间隔数值。注意这里的 95% 是帧间隔的发生总次数,不是帧间隔数值。

    下面举个例子:

    测试帧间隔数值:{ 10,  20,  10,  30,  10,  50,  30,  70,  225, 10, 110, 120, 530, 145, 15, 560, 127, 138, 15, 200 };
    全部添加到 rtc::HistogramPercentileCounter 后的排列:
    ---------- histogram_low_:
     [10] = 4
     [15] = 2
     [20] = 1
     [30] = 2
     [50] = 1
     [70] = 1
     [110] = 1
     [120] = 1
     [127] = 1
     [138] = 1
     [145] = 1
     [200] = 1
     [225] = 1
    ---------- long_tail_boundary_:500
    ---------- histogram_high_:
     <530, 1>
     <560, 1>
     
     按照上面测试序列,(20 x 0.95) - 1 = 18,跳过18个以后,首个帧间隔是 530,所以 InterframeDelay95PercentileInMs 的结果就是 530。表示 95% 的帧间隔都小于 530。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    ReceivedWidthInPixels

    拉取的视频流分辨率的平均宽度。由于远程视频流推流分辨率可能产生变化,这里输出的是平均值,而非其中的某一次。它的类型是rtc::SampleCounter

    ReceivedHeightInPixels

    拉取的视频流分辨率的平均高度。其他与ReceivedWidthInPixels相同,不再赘述。

    MediaBitrateReceivedInKbps

    平均接收码率。接收的字节数来自 VideoReceiveStream::Stats的成员变量 total_media_bytes。在ReceiveStatisticsProxy::OnCompleteFrame会对接收字节数进行累加,最后作为计算平均接收码率的分子。

  • 相关阅读:
    网站安全防护
    前端的数据标记协议
    【项目】基于负载均衡的在线OJ项目
    Vue-3.0路由
    【Python脚本进阶】2.2、构建一个SSH僵尸网络(上):SSH交互脚本
    Axure教程——分级下拉选择器
    使用RobustPCA 进行时间序列的异常检测
    数据可视化
    Python跳动的爱心(双爱心版)
    [附源码]计算机毕业设计springboot基于SpringBoot的演唱会购票系统论文2022
  • 原文地址:https://blog.csdn.net/epubcn/article/details/126907603