/* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "webrtc/video/receive_statistics_proxy.h" #include #include "webrtc/base/checks.h" #include "webrtc/modules/video_coding/include/video_codec_interface.h" #include "webrtc/system_wrappers/include/clock.h" #include "webrtc/system_wrappers/include/metrics.h" namespace webrtc { ReceiveStatisticsProxy::ReceiveStatisticsProxy( const VideoReceiveStream::Config* config, Clock* clock) : clock_(clock), config_(*config), // 1000ms window, scale 1000 for ms to s. decode_fps_estimator_(1000, 1000), renders_fps_estimator_(1000, 1000), render_fps_tracker_(100, 10u), render_pixel_tracker_(100, 10u) { stats_.ssrc = config_.rtp.remote_ssrc; for (auto it : config_.rtp.rtx) rtx_stats_[it.second.ssrc] = StreamDataCounters(); } ReceiveStatisticsProxy::~ReceiveStatisticsProxy() { UpdateHistograms(); } void ReceiveStatisticsProxy::UpdateHistograms() { int fraction_lost = report_block_stats_.FractionLostInPercent(); if (fraction_lost != -1) { RTC_LOGGED_HISTOGRAM_PERCENTAGE("WebRTC.Video.ReceivedPacketsLostInPercent", fraction_lost); } const int kMinRequiredSamples = 200; int samples = static_cast(render_fps_tracker_.TotalSampleCount()); if (samples > kMinRequiredSamples) { RTC_LOGGED_HISTOGRAM_COUNTS_100( "WebRTC.Video.RenderFramesPerSecond", round(render_fps_tracker_.ComputeTotalRate())); RTC_LOGGED_HISTOGRAM_COUNTS_100000( "WebRTC.Video.RenderSqrtPixelsPerSecond", round(render_pixel_tracker_.ComputeTotalRate())); } int width = render_width_counter_.Avg(kMinRequiredSamples); int height = render_height_counter_.Avg(kMinRequiredSamples); if (width != -1) { RTC_LOGGED_HISTOGRAM_COUNTS_10000("WebRTC.Video.ReceivedWidthInPixels", width); RTC_LOGGED_HISTOGRAM_COUNTS_10000("WebRTC.Video.ReceivedHeightInPixels", height); } int sync_offset_ms = sync_offset_counter_.Avg(kMinRequiredSamples); if (sync_offset_ms != -1) { RTC_LOGGED_HISTOGRAM_COUNTS_10000("WebRTC.Video.AVSyncOffsetInMs", sync_offset_ms); } int qp = qp_counters_.vp8.Avg(kMinRequiredSamples); if (qp != -1) RTC_LOGGED_HISTOGRAM_COUNTS_200("WebRTC.Video.Decoded.Vp8.Qp", qp); // TODO(asapersson): DecoderTiming() is call periodically (each 1000ms) and // not per frame. Change decode time to include every frame. const int kMinRequiredDecodeSamples = 5; int decode_ms = decode_time_counter_.Avg(kMinRequiredDecodeSamples); if (decode_ms != -1) RTC_LOGGED_HISTOGRAM_COUNTS_1000("WebRTC.Video.DecodeTimeInMs", decode_ms); int jb_delay_ms = jitter_buffer_delay_counter_.Avg(kMinRequiredDecodeSamples); if (jb_delay_ms != -1) { RTC_LOGGED_HISTOGRAM_COUNTS_10000("WebRTC.Video.JitterBufferDelayInMs", jb_delay_ms); } int target_delay_ms = target_delay_counter_.Avg(kMinRequiredDecodeSamples); if (target_delay_ms != -1) { RTC_LOGGED_HISTOGRAM_COUNTS_10000("WebRTC.Video.TargetDelayInMs", target_delay_ms); } int current_delay_ms = current_delay_counter_.Avg(kMinRequiredDecodeSamples); if (current_delay_ms != -1) { RTC_LOGGED_HISTOGRAM_COUNTS_10000("WebRTC.Video.CurrentDelayInMs", current_delay_ms); } int delay_ms = delay_counter_.Avg(kMinRequiredDecodeSamples); if (delay_ms != -1) RTC_LOGGED_HISTOGRAM_COUNTS_10000("WebRTC.Video.OnewayDelayInMs", delay_ms); StreamDataCounters rtp = stats_.rtp_stats; StreamDataCounters rtx; for (auto it : rtx_stats_) rtx.Add(it.second); StreamDataCounters rtp_rtx = rtp; rtp_rtx.Add(rtx); int64_t elapsed_sec = rtp_rtx.TimeSinceFirstPacketInMs(clock_->TimeInMilliseconds()) / 1000; if (elapsed_sec > metrics::kMinRunTimeInSeconds) { RTC_LOGGED_HISTOGRAM_COUNTS_10000( "WebRTC.Video.BitrateReceivedInKbps", static_cast(rtp_rtx.transmitted.TotalBytes() * 8 / elapsed_sec / 1000)); RTC_LOGGED_HISTOGRAM_COUNTS_10000( "WebRTC.Video.MediaBitrateReceivedInKbps", static_cast(rtp.MediaPayloadBytes() * 8 / elapsed_sec / 1000)); RTC_LOGGED_HISTOGRAM_COUNTS_10000( "WebRTC.Video.PaddingBitrateReceivedInKbps", static_cast(rtp_rtx.transmitted.padding_bytes * 8 / elapsed_sec / 1000)); RTC_LOGGED_HISTOGRAM_COUNTS_10000( "WebRTC.Video.RetransmittedBitrateReceivedInKbps", static_cast(rtp_rtx.retransmitted.TotalBytes() * 8 / elapsed_sec / 1000)); if (!rtx_stats_.empty()) { RTC_LOGGED_HISTOGRAM_COUNTS_10000( "WebRTC.Video.RtxBitrateReceivedInKbps", static_cast(rtx.transmitted.TotalBytes() * 8 / elapsed_sec / 1000)); } if (config_.rtp.fec.ulpfec_payload_type != -1) { RTC_LOGGED_HISTOGRAM_COUNTS_10000( "WebRTC.Video.FecBitrateReceivedInKbps", static_cast(rtp_rtx.fec.TotalBytes() * 8 / elapsed_sec / 1000)); } const RtcpPacketTypeCounter& counters = stats_.rtcp_packet_type_counts; RTC_LOGGED_HISTOGRAM_COUNTS_10000("WebRTC.Video.NackPacketsSentPerMinute", counters.nack_packets * 60 / elapsed_sec); RTC_LOGGED_HISTOGRAM_COUNTS_10000("WebRTC.Video.FirPacketsSentPerMinute", counters.fir_packets * 60 / elapsed_sec); RTC_LOGGED_HISTOGRAM_COUNTS_10000("WebRTC.Video.PliPacketsSentPerMinute", counters.pli_packets * 60 / elapsed_sec); if (counters.nack_requests > 0) { RTC_LOGGED_HISTOGRAM_PERCENTAGE( "WebRTC.Video.UniqueNackRequestsSentInPercent", counters.UniqueNackRequestsInPercent()); } } } VideoReceiveStream::Stats ReceiveStatisticsProxy::GetStats() const { rtc::CritScope lock(&crit_); return stats_; } void ReceiveStatisticsProxy::OnIncomingPayloadType(int payload_type) { rtc::CritScope lock(&crit_); stats_.current_payload_type = payload_type; } void ReceiveStatisticsProxy::OnDecoderImplementationName( const char* implementation_name) { rtc::CritScope lock(&crit_); stats_.decoder_implementation_name = implementation_name; } void ReceiveStatisticsProxy::OnIncomingRate(unsigned int framerate, unsigned int bitrate_bps) { rtc::CritScope lock(&crit_); stats_.network_frame_rate = framerate; stats_.total_bitrate_bps = bitrate_bps; } void ReceiveStatisticsProxy::OnDecoderTiming(int decode_ms, int max_decode_ms, int current_delay_ms, int target_delay_ms, int jitter_buffer_ms, int min_playout_delay_ms, int render_delay_ms, int64_t rtt_ms) { rtc::CritScope lock(&crit_); stats_.decode_ms = decode_ms; stats_.max_decode_ms = max_decode_ms; stats_.current_delay_ms = current_delay_ms; stats_.target_delay_ms = target_delay_ms; stats_.jitter_buffer_ms = jitter_buffer_ms; stats_.min_playout_delay_ms = min_playout_delay_ms; stats_.render_delay_ms = render_delay_ms; decode_time_counter_.Add(decode_ms); jitter_buffer_delay_counter_.Add(jitter_buffer_ms); target_delay_counter_.Add(target_delay_ms); current_delay_counter_.Add(current_delay_ms); // Network delay (rtt/2) + target_delay_ms (jitter delay + decode time + // render delay). delay_counter_.Add(target_delay_ms + rtt_ms / 2); } void ReceiveStatisticsProxy::RtcpPacketTypesCounterUpdated( uint32_t ssrc, const RtcpPacketTypeCounter& packet_counter) { rtc::CritScope lock(&crit_); if (stats_.ssrc != ssrc) return; stats_.rtcp_packet_type_counts = packet_counter; } void ReceiveStatisticsProxy::StatisticsUpdated( const webrtc::RtcpStatistics& statistics, uint32_t ssrc) { rtc::CritScope lock(&crit_); // TODO(pbos): Handle both local and remote ssrcs here and RTC_DCHECK that we // receive stats from one of them. if (stats_.ssrc != ssrc) return; stats_.rtcp_stats = statistics; report_block_stats_.Store(statistics, ssrc, 0); } void ReceiveStatisticsProxy::CNameChanged(const char* cname, uint32_t ssrc) { rtc::CritScope lock(&crit_); // TODO(pbos): Handle both local and remote ssrcs here and RTC_DCHECK that we // receive stats from one of them. if (stats_.ssrc != ssrc) return; stats_.c_name = cname; } void ReceiveStatisticsProxy::DataCountersUpdated( const webrtc::StreamDataCounters& counters, uint32_t ssrc) { rtc::CritScope lock(&crit_); if (ssrc == stats_.ssrc) { stats_.rtp_stats = counters; } else { auto it = rtx_stats_.find(ssrc); if (it != rtx_stats_.end()) { it->second = counters; } else { RTC_NOTREACHED() << "Unexpected stream ssrc: " << ssrc; } } } void ReceiveStatisticsProxy::OnDecodedFrame() { uint64_t now = clock_->TimeInMilliseconds(); rtc::CritScope lock(&crit_); decode_fps_estimator_.Update(1, now); stats_.decode_frame_rate = decode_fps_estimator_.Rate(now).value_or(0); } void ReceiveStatisticsProxy::OnRenderedFrame(int width, int height) { RTC_DCHECK_GT(width, 0); RTC_DCHECK_GT(height, 0); uint64_t now = clock_->TimeInMilliseconds(); rtc::CritScope lock(&crit_); renders_fps_estimator_.Update(1, now); stats_.render_frame_rate = renders_fps_estimator_.Rate(now).value_or(0); render_width_counter_.Add(width); render_height_counter_.Add(height); render_fps_tracker_.AddSamples(1); render_pixel_tracker_.AddSamples(sqrt(width * height)); } void ReceiveStatisticsProxy::OnSyncOffsetUpdated(int64_t sync_offset_ms) { rtc::CritScope lock(&crit_); sync_offset_counter_.Add(std::abs(sync_offset_ms)); stats_.sync_offset_ms = sync_offset_ms; } void ReceiveStatisticsProxy::OnReceiveRatesUpdated(uint32_t bitRate, uint32_t frameRate) { } void ReceiveStatisticsProxy::OnFrameCountsUpdated( const FrameCounts& frame_counts) { rtc::CritScope lock(&crit_); stats_.frame_counts = frame_counts; } void ReceiveStatisticsProxy::OnDiscardedPacketsUpdated(int discarded_packets) { rtc::CritScope lock(&crit_); stats_.discarded_packets = discarded_packets; } void ReceiveStatisticsProxy::OnPreDecode( const EncodedImage& encoded_image, const CodecSpecificInfo* codec_specific_info) { if (!codec_specific_info || encoded_image.qp_ == -1) { return; } if (codec_specific_info->codecType == kVideoCodecVP8) { qp_counters_.vp8.Add(encoded_image.qp_); } } void ReceiveStatisticsProxy::SampleCounter::Add(int sample) { sum += sample; ++num_samples; } int ReceiveStatisticsProxy::SampleCounter::Avg(int min_required_samples) const { if (num_samples < min_required_samples || num_samples == 0) return -1; return sum / num_samples; } } // namespace webrtc