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會對接收字節數進行累加,最後作為計算平均接收碼率的分子。
如果你對音視頻開發感興趣,覺得文章對您有幫助,別忘了點讚、收藏哦!或者對本文的一些闡述有自己的看法,有任何問題,歡迎在下方評論區討論!