webrtc 本身对时间性能参数有比较完备的统计,分析 webrtc 延时性能,直接分析其中关于性能统计的代码,理解其中各个参数的含义基本能够找到自己需要的参数,本文主要关注视频延时,会忽略音频延时。
备注:本文的 webrtc 代码来自 chromium(64)开源代码。
发送端涉及到的文件包括:
src\third_party\webrtc\video\send_statistics_proxy.cc
src\third_party\webrtc\video\send_statistics_proxy.h
统计的是平均性能,只有在在结束桌面共享之后,才可以查看统计结果:
SendStatisticsProxy::~SendStatisticsProxy() {
rtc::CritScope lock(&crit_);
RTC_LOG(LS_INFO) << "QingTest ";
uma_container_->UpdateHistograms(rtp_config_, stats_);
int64_t elapsed_sec = (clock_->TimeInMilliseconds() - start_ms_) / 1000;
RTC_HISTOGRAM_COUNTS_100000("WebRTC.Video.SendStreamLifetimeInSeconds",
elapsed_sec);
if (elapsed_sec >= metrics::kMinRunTimeInSeconds)
UpdateCodecTypeHistogram(payload_name_);
}
在一次屏幕共享之后,查看统计结果如下:
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(254)] WebRTC.Video.Screenshare.InputWidthInPixels 1280
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(255)] WebRTC.Video.Screenshare.InputHeightInPixels 720
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(261)] WebRTC.Video.Screenshare.InputFramesPerSecond, periodic_samples:17, {min:22, avg:28, max:30}
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(272)] WebRTC.Video.Screenshare.SentWidthInPixels 1280
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(273)] WebRTC.Video.Screenshare.SentHeightInPixels 720
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(279)] WebRTC.Video.Screenshare.SentFramesPerSecond, periodic_samples:17, {min:15, avg:27, max:30}
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(298)] WebRTC.Video.Screenshare.SentToInputFpsRatioPercent 96
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(307)] WebRTC.Video.Screenshare.EncodeTimeInMs 5
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(314)] WebRTC.Video.Screenshare.KeyFramesSentInPermille 1
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(442)] WebRTC.Video.Screenshare.SentPacketsLostInPercent 0
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(490)] WebRTC.Video.Screenshare.NumberOfPauseEvents 0
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(498)] WebRTC.Video.Screenshare.PausedTimeInPercent 0
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(523)] WebRTC.Video.Screenshare.BitrateSentInBps, periodic_samples:17, {min:66152, avg:156096, max:346976}
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(530)] WebRTC.Video.Screenshare.MediaBitrateSentInBps, periodic_samples:17, {min:62968, avg:139328, max:336448}
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(538)] WebRTC.Video.Screenshare.PaddingBitrateSentInBps, periodic_samples:17, {min:0, avg:1528, max:16128}
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(547)] WebRTC.Video.Screenshare.RetransmittedBitrateSentInBps, periodic_samples:17, {min:0, avg:0, max:0}
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(555)] WebRTC.Video.Screenshare.RtxBitrateSentInBps, periodic_samples:8, {min:0, avg:23672, max:100592}
[6596:3116:0815/131345.285:INFO:send_statistics_proxy.cc(571)] WebRTC.Video.Screenshare.FecBitrateSentInBps, periodic_samples:17, {min:0, avg:0, max:0}
[6596:3116:0815/131345.286:INFO:send_statistics_proxy.cc(575)] Frames encoded 931
[6596:3116:0815/131345.286:INFO:send_statistics_proxy.cc(576)] WebRTC.Video.Screenshare.DroppedFrames.Capturer 0
[6596:3116:0815/131345.286:INFO:send_statistics_proxy.cc(580)] WebRTC.Video.Screenshare.DroppedFrames.EncoderQueue 11
[6596:3116:0815/131345.286:INFO:send_statistics_proxy.cc(584)] WebRTC.Video.Screenshare.DroppedFrames.Encoder 0
[6596:3116:0815/131345.286:INFO:send_statistics_proxy.cc(588)] WebRTC.Video.Screenshare.DroppedFrames.Ratelimiter 15
从其中能够看到的有用数据为视频编码延时为 5ms,这基本是一个可以忽略的延时。
接收端统计性能统计分析的代码主要位于文件:
src\third_party\webrtc\video\receive_statistics_proxy.cc
src\third_party\webrtc\video\receive_statistics_proxy.h
在 receive_statistics_proxy.h 文件中,定义了类 ReceiveStatisticsProxy,这个类中包括了视频处理相关的一些参数,部分与时间性能相关的参数如下。
SampleCounter sync_offset_counter_;
SampleCounter decode_time_counter_;
SampleCounter jitter_buffer_delay_counter_;
SampleCounter target_delay_counter_;
SampleCounter current_delay_counter_;
SampleCounter delay_counter_;
其中,SampleCounter 结构体定义如下:
struct SampleCounter {
SampleCounter() : sum(0), num_samples(0) {}
void Add(int sample);
int Avg(int64_t min_required_samples) const;
int Max() const;
void Reset();
void Add(const SampleCounter& other);
private:
int64_t sum;
int64_t num_samples;
rtc::Optional<int> max;
};
其中, Avg() 函数有最小样本数量限制,如果样本数量小于 kMinRequiredSamples = 200, 将返回 -1,这种情况下就不会有 Avg() 相关参数打印出来。
int ReceiveStatisticsProxy::SampleCounter::Avg(
int64_t min_required_samples) const {
if (num_samples < min_required_samples || num_samples == 0)
return -1;
return static_cast<int>(sum / num_samples);
}
根据 ReceiveStatisticsProxy 工作的方式,只有在 类析构的时候才会输出统计参数。之前也疑惑为什么在通过 webrtc 共享桌面的时候没有统计参数输出。
ReceiveStatisticsProxy::~ReceiveStatisticsProxy() {
UpdateHistograms();
}
void ReceiveStatisticsProxy::UpdateHistograms() {
int stream_duration_sec = (clock_->TimeInMilliseconds() - start_ms_) / 1000;
if (stats_.frame_counts.key_frames > 0 ||
stats_.frame_counts.delta_frames > 0) {
RTC_HISTOGRAM_COUNTS_100000("WebRTC.Video.ReceiveStreamLifetimeInSeconds",
stream_duration_sec);
RTC_LOG(LS_INFO) << "WebRTC.Video.ReceiveStreamLifetimeInSeconds "
<< stream_duration_sec;
}
...
}
在结束桌面共享之后,查看日志文件,可以看到如下结果:
[9688:12688:0818/100031.401:INFO:video_receive_stream.cc(149)] ~VideoReceiveStream: {decoders: [{decoder: (VideoDecoder), payload_type: 96, payload_name: VP8, codec_params: {}}, {decoder: (VideoDecoder), payload_type: 98, payload_name: VP9, codec_params: {}}, {decoder: (VideoDecoder), payload_type: 100, payload_name: H264, codec_params: {level-asymmetry-allowed: 1packetization-mode: 1profile-level-id: 42001f}}, {decoder: (VideoDecoder), payload_type: 102, payload_name: H264, codec_params: {level-asymmetry-allowed: 1packetization-mode: 1profile-level-id: 42e01f}}], rtp: {remote_ssrc: 1426571530, local_ssrc: 1, rtcp_mode: RtcpMode::kReducedSize, rtcp_xr: {receiver_reference_time_report: off}, remb: on, transport_cc: on, nack: {rtp_history_ms: 1000}, ulpfec_payload_type: 125, red_type: 127, rtx_ssrc: 518982694, rtx_payload_types: {97 (pt) -> 96 (apt), 99 (pt) -> 98 (apt), 101 (pt) -> 100 (apt), 123 (pt) -> 127 (apt), 124 (pt) -> 102 (apt), }, extensions: [{uri: http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01, id: 5}, {uri: http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time, id: 3}, {uri: http://www.webrtc.org/experiments/rtp-hdrext/playout-delay, id: 6}, {uri: http://www.webrtc.org/experiments/rtp-hdrext/video-content-type, id: 7}, {uri: http://www.webrtc.org/experiments/rtp-hdrext/video-timing, id: 8}, {uri: urn:3gpp:video-orientation, id: 4}, {uri: urn:ietf:params:rtp-hdrext:toffset, id: 2}]}, renderer: (renderer), render_delay_ms: 10, sync_group: 2znW7AKmGSPRduSc5KrkYVc16yDUzQKHPmKH, pre_decode_callback: nullptr, target_delay_ms: 0}
[9688:12688:0818/100031.404:INFO:receive_statistics_proxy.cc(134)] WebRTC.Video.ReceiveStreamLifetimeInSeconds 927
[9688:12688:0818/100031.404:INFO:receive_statistics_proxy.cc(145)] WebRTC.Video.ReceivedPacketsLostInPercent 0
[9688:12688:0818/100031.404:INFO:receive_statistics_proxy.cc(163)] WebRTC.Video.AVSyncOffsetInMs 65
[9688:12688:0818/100031.404:INFO:receive_statistics_proxy.cc(169)] WebRTC.Video.RtpToNtpFreqOffsetInKhz, periodic_samples:23, {min:0, avg:0, max:0}
[9688:12688:0818/100031.404:INFO:receive_statistics_proxy.cc(181)] WebRTC.Video.KeyFramesReceivedInPermille 0
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(188)] WebRTC.Video.Decoded.Vp8.Qp 2
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(193)] WebRTC.Video.DecodeTimeInMs 1
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(199)] WebRTC.Video.JitterBufferDelayInMs 221
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(205)] WebRTC.Video.TargetDelayInMs 246
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(211)] WebRTC.Video.CurrentDelayInMs 243
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(279)] WebRTC.Video.Screenshare.InterframeDelayInMs 34
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(287)] WebRTC.Video.Screenshare.InterframeDelayMaxInMs 613
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(297)] WebRTC.Video.Screenshare.InterframeDelay95PercentileInMs 37
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(305)] WebRTC.Video.Screenshare.ReceivedWidthInPixels 1280
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(313)] WebRTC.Video.Screenshare.ReceivedHeightInPixels 720
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(327)] WebRTC.Video.Screenshare.MediaBitrateReceivedInKbps 2
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(348)] WebRTC.Video.Screenshare.Decoded.Vp8.Qp 2
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(279)] WebRTC.Video.Screenshare.InterframeDelayInMs.S0 34
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(287)] WebRTC.Video.Screenshare.InterframeDelayMaxInMs.S0 613
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(297)] WebRTC.Video.Screenshare.InterframeDelay95PercentileInMs.S0 37
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(305)] WebRTC.Video.Screenshare.ReceivedWidthInPixels.S0 1280
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(313)] WebRTC.Video.Screenshare.ReceivedHeightInPixels.S0 720
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(327)] WebRTC.Video.Screenshare.MediaBitrateReceivedInKbps.S0 2
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(348)] WebRTC.Video.Screenshare.Decoded.Vp8.Qp.S0 2
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(279)] WebRTC.Video.Screenshare.InterframeDelayInMs.ExperimentGroup3 34
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(287)] WebRTC.Video.Screenshare.InterframeDelayMaxInMs.ExperimentGroup3 613
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(297)] WebRTC.Video.Screenshare.InterframeDelay95PercentileInMs.ExperimentGroup3 37
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(305)] WebRTC.Video.Screenshare.ReceivedWidthInPixels.ExperimentGroup3 1280
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(313)] WebRTC.Video.Screenshare.ReceivedHeightInPixels.ExperimentGroup3 720
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(327)] WebRTC.Video.Screenshare.MediaBitrateReceivedInKbps.ExperimentGroup3 2
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(348)] WebRTC.Video.Screenshare.Decoded.Vp8.Qp.ExperimentGroup3 2
[9688:12688:0818/100031.405:INFO:receive_statistics_proxy.cc(371)] WebRTC.Video.MediaBitrateReceivedInKbps 294
其中,QP 这个参数重点介绍一下,因为之前并没有接触过这个参数,对应到上面的统计结果为 WebRTC.Video.Screenshare.Decoded.Vp8.Qp.S0。
在不抓屏的情况下,例如采集摄像头,webrtc 会开启 QualityScaler 。在编码的时候,计算每幅图像的量化参数(QP,Quantization Parameter),相当于图像的复杂度。当一系列图像的平均 QP 超过阈值时会调整分辨率(H264 的合法范围是 24~37),超过 37 要降低分辨率,低于 24 要提高分辨率。
但对于抓屏而言这种策略并不合理,因为屏幕图像有很多细节,文字也会提升 QP 值。如果在抓屏时开启分辨率自动调整会造成模糊和不可读文字,因为抓屏和摄像头采集不一样,图像细节更加丰富,并且基本不会切换到细节少的状态。
分析参数JitterBufferDelayInMs,这只是整个屏幕共享过程的平均缓冲延时,从统计数据看出缓冲区延时为 250 ms。要看实时缓冲延时需要查看:
src\third_party\webrtc\modules\video_coding\timing.h
VCMTiming 类的 jitter_delay_ms_ 成员,通过在日志文件中打印整个变量分析,这个值在屏幕共享过程中是动态变化的:
47ms –> 379ms –> 258ms
开始屏幕共享的时候抖动缓冲延时是 47ms,在屏幕共享过程中逐渐增大一直到最大 379ms,之后又慢慢回落到 258ms。
从统计数据可以看到音视频同步(WebRTC.Video.AVSyncOffsetInMs)占用了 65ms ,解码占用了 1ms,抖动缓冲延时(WebRTC.Video.JitterBufferDelayInMs)占据了 221ms。
根据统计结果,可以知道 webrtc 屏幕共享延时主要在接收端,并且主要是缓冲这一块占据了绝对大头。
参考:
WebRTC的QP、分辨率自动调整