diff --git a/content/browser/media/capture/desktop_capture_device.cc b/content/browser/media/capture/desktop_capture_device.cc index 2ba2fd5eebde2..3bb6d5d176367 100644 --- a/content/browser/media/capture/desktop_capture_device.cc +++ b/content/browser/media/capture/desktop_capture_device.cc @@ -68,6 +68,10 @@ namespace { // UI responsive. const int kDefaultMaximumCpuConsumptionPercentage = 50; +// Constant which sets the cutoff frequency in an an exponential moving average +// (EMA) filter used to calculate the current frame rate (in frames per second). +constexpr float kAlpha = 0.1; + webrtc::DesktopRect ComputeLetterboxRect( const webrtc::DesktopSize& max_size, const webrtc::DesktopSize& source_size) { @@ -133,6 +137,17 @@ void LogDesktopCaptureFrameIsRefresh(DesktopMediaID::Type capturer_type, } } +void LogDesktopCaptureFrameRate(DesktopMediaID::Type capturer_type, + int frame_rate_fps) { + if (capturer_type == DesktopMediaID::TYPE_SCREEN) { + UMA_HISTOGRAM_COUNTS_100("WebRTC.DesktopCapture.FrameRate.Screen", + frame_rate_fps); + } else { + UMA_HISTOGRAM_COUNTS_100("WebRTC.DesktopCapture.FrameRate.Window", + frame_rate_fps); + } +} + } // namespace class DesktopCaptureDevice::Core : public webrtc::DesktopCapturer::Callback { @@ -205,6 +220,11 @@ class DesktopCaptureDevice::Core : public webrtc::DesktopCapturer::Callback { // Inverse of the requested frame rate. base::TimeDelta requested_frame_duration_; + // Contains the actual (measured) frame rate using an exponential moving + // average (EMA) filter. Uses a simple filter with 0.1 weight of the current + // sample. Unit is in frames per second (fps). + float frame_rate_; + // Records time of last call to CaptureFrame. base::TimeTicks capture_start_time_; @@ -260,6 +280,11 @@ class DesktopCaptureDevice::Core : public webrtc::DesktopCapturer::Callback { // The system time when we receive the first frame. base::TimeTicks first_ref_time_; + // The time when Core::CaptureFrame() is called. Used to derive the delta + // time since last call. The delta time then drives the frame-rate filter + // which results in an average capture frame rate in `frame_rate_`. + base::TimeTicks last_capture_time_; + // TODO(jiayl): Remove wake_lock_ when there is an API to keep the // screen from sleeping for the drive-by web. mojo::Remote<device::mojom::WakeLock> wake_lock_; @@ -300,6 +325,7 @@ void DesktopCaptureDevice::Core::AllocateAndStart( client_ = std::move(client); requested_frame_rate_ = params.requested_format.frame_rate; + frame_rate_ = requested_frame_rate_; requested_frame_duration_ = base::Microseconds(static_cast<int64_t>( static_cast<double>(base::Time::kMicrosecondsPerSecond) / requested_frame_rate_ + @@ -313,7 +339,10 @@ void DesktopCaptureDevice::Core::AllocateAndStart( constraints.fixed_aspect_ratio); VLOG(2) << __func__ << " (requested_frame_rate=" << requested_frame_rate_ << ", max_frame_size=" << constraints.max_frame_size.ToString() - << ")"; + << ", requested_frame_duration=" + << requested_frame_duration_.InMilliseconds() + << ", max_cpu_consumption_percentage=" + << max_cpu_consumption_percentage_ << ")"; DCHECK(!wake_lock_); RequestWakeLock(); @@ -577,6 +606,28 @@ void DesktopCaptureDevice::Core::CaptureFrame() { capture_start_time_ = NowTicks(); capture_in_progress_ = true; + if (last_capture_time_.is_null()) { + last_capture_time_ = capture_start_time_; + } else { + const base::TimeDelta delta_ms = capture_start_time_ - last_capture_time_; + // We use an exponential moving average (EMA) filter to calculate the + // current frame rate (in frames per second). The filter has the following + // difference (time-domain) equation: + // y[i]=α⋅x[i]+(1-α)⋅y[i−1] + // where + // y is the output, [i] denotes the sample number, x is the input, and α + // is a constant which sets the cutoff frequency (a value between 0 and + // 1 where 1 corresponds to "no filtering"). + // A value of α=0.1 results in a suitable amount of smoothing. + const float input_frame_rate_fps = (1000.0 / delta_ms.InMillisecondsF()); + frame_rate_ = kAlpha * input_frame_rate_fps + (1.0 - kAlpha) * frame_rate_; + last_capture_time_ = capture_start_time_; + VLOG(2) << " delta_ms=" << delta_ms.InMillisecondsF() + << ", frame_rate=" << frame_rate_ << " [fps]"; + const int frame_rate_fps = base::saturated_cast<int>(frame_rate_ + 0.5); + LogDesktopCaptureFrameRate(capturer_type_, frame_rate_fps); + } + desktop_capturer_->CaptureFrame(); } @@ -593,6 +644,7 @@ void DesktopCaptureDevice::Core::ScheduleNextCaptureFrame() { std::max((last_capture_duration * 100) / max_cpu_consumption_percentage_, requested_frame_duration_); + VLOG(2) << " capture_period=" << capture_period.InMilliseconds(); VLOG(2) << " timer(dT=" << (capture_period - last_capture_duration).InMilliseconds() << ")"; // Schedule a task for the next frame. diff --git a/tools/metrics/histograms/metadata/web_rtc/histograms.xml b/tools/metrics/histograms/metadata/web_rtc/histograms.xml index fee6d1fa76cd6..02f7b35391432 100644 --- a/tools/metrics/histograms/metadata/web_rtc/histograms.xml +++ b/tools/metrics/histograms/metadata/web_rtc/histograms.xml @@ -977,6 +977,22 @@ chromium-metrics-reviews@google.com. </token> </histogram> +<histogram name="WebRTC.DesktopCapture.FrameRate.{CapturerType}" units="fps" + expires_after="2024-04-25"> + <owner>henrika@chromium.org</owner> + <owner>webrtc-dev@chromium.org</owner> + <summary> + An integer value is recorded to this histogram for each captured video frame + where the desktop media source is given by {CapturerType}. The value is an + estimation of the current frame capture rate and the unit is in frames per + second. + </summary> + <token key="CapturerType"> + <variant name="Screen" summary="Screen capture type"/> + <variant name="Window" summary="Window capture type"/> + </token> +</histogram> + <histogram name="WebRTC.DesktopCapture.IsZeroHzActive.{CapturerType}" enum="BooleanActive" expires_after="2024-02-03"> <owner>henrika@chromium.org</owner>