視頻推流或拉流 WebRTC視頻流接收統計報告

音視頻開發老舅 發佈 2022-10-01T09:17:57.734470+00:00

WebRTC 視頻流接收統計報告在每次視頻推流或拉流結束後,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

解碼器解碼一幀後,會通知到 ReceiveStatisticsProxy::OnDecodedFrame() 中對解碼幀數增加計數。

【更多音視頻學習資料,點擊下方連結免費領取,先碼住不迷路~】

音視頻開發(資料文檔+視頻教程+面試題)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

DroppedFrames.Receiver

當 VideoReceiveStream::Stop() 執行的時候,會調用 RtpVideoStreamReceiver::GetUniqueFramesSeen() 獲取一個視頻幀計數器 frame_counter_,它的值是在 RtpVideoStreamReceiver::OnReceivedPayloadData() 中根據收包時間戳來增加,具體見源碼 video\rtp_video_stream_receiver.cc。

frame_counter_.Add(packet->timestamp);

這個視頻幀計數器可以理解為從收到的視頻幀總數,然後用這個總數減去解碼幀數Frames decoded,就得到了丟掉的總幀數:

int num_dropped_frames = *num_unique_frames_ - stats_.frames_decoded;

ReceivedPacketsLostInPercent

顧名思義,丟包率(百分比)。當 ReceiveStreamLifetimeInSeconds大於10秒時才可能輸出這個數值。這個數值實際的計算位置來自 StreamStatisticianImpl::GetFractionLostInPercent()。丟包率的分子和分母不在本文介紹範圍內,感興趣的讀者自行閱讀源碼。

DecodedFramesPerSecond

平均解碼幀率。以當前時間減去解碼第一幀開始時間得到的差值做為分母,解碼總幀數做為分子,計算得出的整型數值。

RenderFramesPerSecond

平均渲染幀率。數值來自於 ReceiveStatisticsProxy的成員變量 render_fps_tracker_,變量類型是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

KeyFramesReceivedInPermille

permille是「千分率」的意思,因此,這個數值是表示接收關鍵幀的千分率。計算公式在代碼中如下:

(關鍵幀總數 x 1000 + 總幀數 / 2) / 總幀數    (1)

其中總幀數是關鍵幀和非關鍵幀的總和。注意計算的結果會被強轉成整型。

其實我看到這個公式是有點不太理解的。如果只是計算千分率,為何不直接就使用

(關鍵幀總數 ÷ 總幀數) x 1000    (2)

就行了?如果按照上面的公式(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::SampleCounter的Avg方法計算得到的,其原理比較簡單:全部幀的解碼耗時總時長÷採樣計數。與 RenderFramesPerSecond一樣,對採樣計數目前有最小200的要求,即小於200個採樣計數不輸出此項數據。

JitterBufferDelayInMs, TargetDelayInMs, CurrentDelayInMs

這三個數值具有強相關性,因此放到一起描述。它們分別對應 ReceiveStatisticsProxy的成員變量 jitter_buffer_delay_counter_,target_delay_counter_和current_delay_counter_,變量類型是rtc::SampleCounter。

在ReceiveStatisticsProxy::OnFrameBufferTimingsUpdated被調用的時候,會傳入jitter_buffer_ms,target_delay_ms以及current_delay_ms,累加到對應的採樣計數器變量中。最終用於計算平均值:分子是每次傳入的數值總和,分母是次數

【更多音視頻學習資料,點擊下方連結免費領取,先碼住不迷路~】

音視頻開發(資料文檔+視頻教程+面試題)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

ReceiveStatisticsProxy::OnFrameBufferTimingsUpdated由 FrameBuffer調用,源碼: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

FrameBuffer 和 JitterBuffer 的實現原理不在本文介紹範圍之內,感興趣的讀者自行研讀源碼。

EndToEndDelayInMs

端到端平均延時。對應 VideoReceiveStream::Stats 的成員變量 e2e_delay_counter。 它的類型是rtc::SampleCounter。在ReceiveStatisticsProxy::OnRenderedFrame中會計算單幀的延時,累加到e2e_delay_counter。單次的延時數值是當前 ntp_time(webrtc::Clock::CurrentNtpInMilliseconds())減去webrtc::VideoFrame的ntp_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<size_t> histogram_low_;
std::map<uint32_t, size_t> histogram_high_;
const uint32_t long_tail_boundary_;
size_t total_elements_;
size_t total_elements_low_;

以long_tail_boundary_作為分界點,小於long_tail_boundary_的值,記錄到histogram_low_,否則記錄到histogram_high_。目前就InterframeDelay95PercentileInMs來說,這個分界點是500(kMaxCommonInterframeDelayMs)。

histogram_low_的每個元素索引序號代表具體的一個幀間隔數值,值表示此幀間隔的次數。分界點是500的話,histogram_low_所代表的幀間隔數值範圍就是0~499。

histogram_high_與histogram_low_的作用類似,但它的類型是 std::map,key表示幀間隔數值,value表示次數。

InterframeDelay95PercentileInMs調用的是rtc::HistogramPercentileCounter的GetPercentile()方法,傳入參數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。

ReceivedWidthInPixels

拉取的視頻流解析度的平均寬度。由於遠程視頻流推流解析度可能產生變化,這裡輸出的是平均值,而非其中的某一次。它的類型是rtc::SampleCounter。

【更多音視頻學習資料,點擊下方連結免費領取,先碼住不迷路~】

音視頻開發(資料文檔+視頻教程+面試題)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

ReceivedHeightInPixels

拉取的視頻流解析度的平均高度。其他與ReceivedWidthInPixels相同,不再贅述。

MediaBitrateReceivedInKbps

平均接收碼率。接收的字節數來自 VideoReceiveStream::Stats 的成員變量 total_media_bytes。在ReceiveStatisticsProxy::OnCompleteFrame會對接收字節數進行累加,最後作為計算平均接收碼率的分子。

如果你對音視頻開發感興趣,覺得文章對您有幫助,別忘了點讚、收藏哦!或者對本文的一些闡述有自己的看法,有任何問題,歡迎在下方評論區討論!

關鍵字: