blob: a6cb4efe522f8a10216c20ef80c6901bce3896e2 [file] [log] [blame]
niklase@google.com470e71d2011-07-07 08:21:25 +00001/*
2 * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
Rasmus Brandt10944e62022-05-25 10:12:42 +020011#include "modules/video_coding/timing/jitter_estimator.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000012
niklase@google.com470e71d2011-07-07 08:21:25 +000013#include <math.h>
niklase@google.com470e71d2011-07-07 08:21:25 +000014#include <string.h>
Jonas Olssona4d87372019-07-05 19:08:33 +020015
Erik Språngb1e031a2018-11-01 11:20:49 +010016#include <algorithm>
Yves Gerey3e707812018-11-28 16:47:49 +010017#include <cstdint>
philipel9d3ab612015-12-21 04:12:39 -080018
Erik Språngb1e031a2018-11-01 11:20:49 +010019#include "absl/types/optional.h"
Jonas Orelande62c2f22022-03-29 11:04:48 +020020#include "api/field_trials_view.h"
Evan Shrubsole13e42a82022-03-07 13:21:51 +010021#include "api/units/data_size.h"
22#include "api/units/frequency.h"
23#include "api/units/time_delta.h"
24#include "api/units/timestamp.h"
Rasmus Brandt23772262022-05-23 09:53:15 +020025#include "modules/video_coding/timing/rtt_filter.h"
Yves Gerey3e707812018-11-28 16:47:49 +010026#include "rtc_base/numerics/safe_conversions.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020027#include "system_wrappers/include/clock.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000028
29namespace webrtc {
Erik Språngb1e031a2018-11-01 11:20:49 +010030namespace {
31static constexpr uint32_t kStartupDelaySamples = 30;
32static constexpr int64_t kFsAccuStartupSamples = 5;
Evan Shrubsole13e42a82022-03-07 13:21:51 +010033static constexpr Frequency kMaxFramerateEstimate = Frequency::Hertz(200);
34static constexpr TimeDelta kNackCountTimeout = TimeDelta::Seconds(60);
Erik Språngb1e031a2018-11-01 11:20:49 +010035static constexpr double kDefaultMaxTimestampDeviationInSigmas = 3.5;
philipel55a9a3d2022-08-19 13:23:26 +020036static constexpr double kDefaultAvgAndMaxFrameSize = 500;
Evan Shrubsole13e42a82022-03-07 13:21:51 +010037
38constexpr double kPhi = 0.97;
39constexpr double kPsi = 0.9999;
40constexpr uint32_t kAlphaCountMax = 400;
Evan Shrubsole13e42a82022-03-07 13:21:51 +010041constexpr uint32_t kNackLimit = 3;
42constexpr int32_t kNumStdDevDelayOutlier = 15;
43constexpr int32_t kNumStdDevFrameSizeOutlier = 3;
44// ~Less than 1% chance (look up in normal distribution table)...
45constexpr double kNoiseStdDevs = 2.33;
46// ...of getting 30 ms freezes
47constexpr double kNoiseStdDevOffset = 30.0;
48
Erik Språngb1e031a2018-11-01 11:20:49 +010049} // namespace
sprang@webrtc.org70e2d112014-09-24 14:06:56 +000050
Rasmus Brandt10944e62022-05-25 10:12:42 +020051JitterEstimator::JitterEstimator(Clock* clock,
52 const FieldTrialsView& field_trials)
Evan Shrubsole13e42a82022-03-07 13:21:51 +010053 : fps_counter_(30), // TODO(sprang): Use an estimator with limit based on
sprang@webrtc.org70e2d112014-09-24 14:06:56 +000054 // time, rather than number of samples.
sprang@webrtc.org70e2d112014-09-24 14:06:56 +000055 clock_(clock) {
56 Reset();
57}
58
Rasmus Brandt10944e62022-05-25 10:12:42 +020059JitterEstimator::~JitterEstimator() = default;
niklase@google.com470e71d2011-07-07 08:21:25 +000060
Åsa Persson3fcc5be2019-04-04 09:40:27 +020061// Resets the JitterEstimate.
Rasmus Brandt10944e62022-05-25 10:12:42 +020062void JitterEstimator::Reset() {
Evan Shrubsole13e42a82022-03-07 13:21:51 +010063 var_noise_ = 4.0;
niklase@google.com470e71d2011-07-07 08:21:25 +000064
philipel55a9a3d2022-08-19 13:23:26 +020065 avg_frame_size_bytes_ = kDefaultAvgAndMaxFrameSize;
66 max_frame_size_bytes_ = kDefaultAvgAndMaxFrameSize;
Evan Shrubsole13e42a82022-03-07 13:21:51 +010067 var_frame_size_ = 100;
68 last_update_time_ = absl::nullopt;
69 prev_estimate_ = absl::nullopt;
70 prev_frame_size_ = absl::nullopt;
71 avg_noise_ = 0.0;
72 alpha_count_ = 1;
73 filter_jitter_estimate_ = TimeDelta::Zero();
74 latest_nack_ = Timestamp::Zero();
75 nack_count_ = 0;
philipel55a9a3d2022-08-19 13:23:26 +020076 frame_size_sum_bytes_ = 0;
Evan Shrubsole13e42a82022-03-07 13:21:51 +010077 frame_size_count_ = 0;
78 startup_count_ = 0;
79 rtt_filter_.Reset();
philipel9d3ab612015-12-21 04:12:39 -080080 fps_counter_.Reset();
Rasmus Brandt39ae6962022-08-09 14:40:05 +020081
Rasmus Brandtae4a8322022-08-12 13:45:34 +020082 kalman_filter_ = FrameDelayDeltaKalmanFilter();
niklase@google.com470e71d2011-07-07 08:21:25 +000083}
84
Åsa Persson3fcc5be2019-04-04 09:40:27 +020085// Updates the estimates with the new measurements.
Rasmus Brandt10944e62022-05-25 10:12:42 +020086void JitterEstimator::UpdateEstimate(TimeDelta frame_delay,
philipele1c707c2022-07-05 14:03:25 +020087 DataSize frame_size) {
Evan Shrubsole13e42a82022-03-07 13:21:51 +010088 if (frame_size.IsZero()) {
philipel9d3ab612015-12-21 04:12:39 -080089 return;
90 }
Evan Shrubsole13e42a82022-03-07 13:21:51 +010091 // Can't use DataSize since this can be negative.
92 double delta_frame_bytes =
93 frame_size.bytes() - prev_frame_size_.value_or(DataSize::Zero()).bytes();
94 if (frame_size_count_ < kFsAccuStartupSamples) {
philipel55a9a3d2022-08-19 13:23:26 +020095 frame_size_sum_bytes_ += frame_size.bytes();
Evan Shrubsole13e42a82022-03-07 13:21:51 +010096 frame_size_count_++;
97 } else if (frame_size_count_ == kFsAccuStartupSamples) {
Åsa Persson3fcc5be2019-04-04 09:40:27 +020098 // Give the frame size filter.
philipel55a9a3d2022-08-19 13:23:26 +020099 avg_frame_size_bytes_ =
100 frame_size_sum_bytes_ / static_cast<double>(frame_size_count_);
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100101 frame_size_count_++;
philipel9d3ab612015-12-21 04:12:39 -0800102 }
philipele1c707c2022-07-05 14:03:25 +0200103
philipel55a9a3d2022-08-19 13:23:26 +0200104 double avg_frame_size_bytes =
105 kPhi * avg_frame_size_bytes_ + (1 - kPhi) * frame_size.bytes();
106 double deviation_size_bytes = 2 * sqrt(var_frame_size_);
107 if (frame_size.bytes() < avg_frame_size_bytes_ + deviation_size_bytes) {
philipele1c707c2022-07-05 14:03:25 +0200108 // Only update the average frame size if this sample wasn't a key frame.
philipel55a9a3d2022-08-19 13:23:26 +0200109 avg_frame_size_bytes_ = avg_frame_size_bytes;
philipel9d3ab612015-12-21 04:12:39 -0800110 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000111
philipel55a9a3d2022-08-19 13:23:26 +0200112 double delta_bytes = frame_size.bytes() - avg_frame_size_bytes;
philipele1c707c2022-07-05 14:03:25 +0200113 var_frame_size_ = std::max(
114 kPhi * var_frame_size_ + (1 - kPhi) * (delta_bytes * delta_bytes), 1.0);
115
philipel55a9a3d2022-08-19 13:23:26 +0200116 // Update max_frame_size_bytes_ estimate.
117 max_frame_size_bytes_ =
118 std::max<double>(kPsi * max_frame_size_bytes_, frame_size.bytes());
niklase@google.com470e71d2011-07-07 08:21:25 +0000119
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100120 if (!prev_frame_size_) {
121 prev_frame_size_ = frame_size;
philipel9d3ab612015-12-21 04:12:39 -0800122 return;
123 }
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100124 prev_frame_size_ = frame_size;
niklase@google.com470e71d2011-07-07 08:21:25 +0000125
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100126 // Cap frame_delay based on the current time deviation noise.
Erik Språng93b107d2022-07-26 10:42:08 +0200127 TimeDelta max_time_deviation = TimeDelta::Millis(
128 kDefaultMaxTimestampDeviationInSigmas * sqrt(var_noise_) + 0.5);
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100129 frame_delay.Clamp(-max_time_deviation, max_time_deviation);
Erik Språngb1e031a2018-11-01 11:20:49 +0100130
Åsa Persson3fcc5be2019-04-04 09:40:27 +0200131 // Only update the Kalman filter if the sample is not considered an extreme
132 // outlier. Even if it is an extreme outlier from a delay point of view, if
133 // the frame size also is large the deviation is probably due to an incorrect
134 // line slope.
Rasmus Brandt39ae6962022-08-09 14:40:05 +0200135 double deviation =
Rasmus Brandtae4a8322022-08-12 13:45:34 +0200136 frame_delay.ms() -
137 kalman_filter_.GetFrameDelayVariationEstimateTotal(delta_frame_bytes);
niklase@google.com470e71d2011-07-07 08:21:25 +0000138
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100139 if (fabs(deviation) < kNumStdDevDelayOutlier * sqrt(var_noise_) ||
philipel55a9a3d2022-08-19 13:23:26 +0200140 frame_size.bytes() > avg_frame_size_bytes_ + kNumStdDevFrameSizeOutlier *
141 sqrt(var_frame_size_)) {
Åsa Persson3fcc5be2019-04-04 09:40:27 +0200142 // Update the variance of the deviation from the line given by the Kalman
143 // filter.
philipele1c707c2022-07-05 14:03:25 +0200144 EstimateRandomJitter(deviation);
Åsa Persson3fcc5be2019-04-04 09:40:27 +0200145 // Prevent updating with frames which have been congested by a large frame,
146 // and therefore arrives almost at the same time as that frame.
147 // This can occur when we receive a large frame (key frame) which has been
148 // delayed. The next frame is of normal size (delta frame), and thus deltaFS
149 // will be << 0. This removes all frame samples which arrives after a key
150 // frame.
philipel55a9a3d2022-08-19 13:23:26 +0200151 if (delta_frame_bytes > -0.25 * max_frame_size_bytes_) {
philipel9d3ab612015-12-21 04:12:39 -0800152 // Update the Kalman filter with the new data
philipel55a9a3d2022-08-19 13:23:26 +0200153 kalman_filter_.PredictAndUpdate(frame_delay.ms(), delta_frame_bytes,
154 max_frame_size_bytes_, var_noise_);
niklase@google.com470e71d2011-07-07 08:21:25 +0000155 }
philipel9d3ab612015-12-21 04:12:39 -0800156 } else {
157 int nStdDev =
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100158 (deviation >= 0) ? kNumStdDevDelayOutlier : -kNumStdDevDelayOutlier;
philipele1c707c2022-07-05 14:03:25 +0200159 EstimateRandomJitter(nStdDev * sqrt(var_noise_));
philipel9d3ab612015-12-21 04:12:39 -0800160 }
161 // Post process the total estimated jitter
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100162 if (startup_count_ >= kStartupDelaySamples) {
philipel9d3ab612015-12-21 04:12:39 -0800163 PostProcessEstimate();
164 } else {
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100165 startup_count_++;
philipel9d3ab612015-12-21 04:12:39 -0800166 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000167}
168
Åsa Persson3fcc5be2019-04-04 09:40:27 +0200169// Updates the nack/packet ratio.
Rasmus Brandt10944e62022-05-25 10:12:42 +0200170void JitterEstimator::FrameNacked() {
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100171 if (nack_count_ < kNackLimit) {
172 nack_count_++;
philipel9d3ab612015-12-21 04:12:39 -0800173 }
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100174 latest_nack_ = clock_->CurrentTime();
niklase@google.com470e71d2011-07-07 08:21:25 +0000175}
176
Åsa Persson3fcc5be2019-04-04 09:40:27 +0200177// Estimates the random jitter by calculating the variance of the sample
178// distance from the line given by theta.
philipele1c707c2022-07-05 14:03:25 +0200179void JitterEstimator::EstimateRandomJitter(double d_dT) {
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100180 Timestamp now = clock_->CurrentTime();
181 if (last_update_time_.has_value()) {
182 fps_counter_.AddSample((now - *last_update_time_).us());
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000183 }
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100184 last_update_time_ = now;
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000185
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100186 if (alpha_count_ == 0) {
Artem Titovd3251962021-11-15 16:57:07 +0100187 RTC_DCHECK_NOTREACHED();
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000188 return;
189 }
190 double alpha =
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100191 static_cast<double>(alpha_count_ - 1) / static_cast<double>(alpha_count_);
192 alpha_count_++;
193 if (alpha_count_ > kAlphaCountMax)
194 alpha_count_ = kAlphaCountMax;
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000195
Erik Språngb1e031a2018-11-01 11:20:49 +0100196 // In order to avoid a low frame rate stream to react slower to changes,
197 // scale the alpha weight relative a 30 fps stream.
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100198 Frequency fps = GetFrameRate();
199 if (fps > Frequency::Zero()) {
200 constexpr Frequency k30Fps = Frequency::Hertz(30);
201 double rate_scale = k30Fps / fps;
Erik Språngb1e031a2018-11-01 11:20:49 +0100202 // At startup, there can be a lot of noise in the fps estimate.
203 // Interpolate rate_scale linearly, from 1.0 at sample #1, to 30.0 / fps
204 // at sample #kStartupDelaySamples.
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100205 if (alpha_count_ < kStartupDelaySamples) {
Erik Språngb1e031a2018-11-01 11:20:49 +0100206 rate_scale =
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100207 (alpha_count_ * rate_scale + (kStartupDelaySamples - alpha_count_)) /
Erik Språngb1e031a2018-11-01 11:20:49 +0100208 kStartupDelaySamples;
niklase@google.com470e71d2011-07-07 08:21:25 +0000209 }
Erik Språngb1e031a2018-11-01 11:20:49 +0100210 alpha = pow(alpha, rate_scale);
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000211 }
212
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100213 double avgNoise = alpha * avg_noise_ + (1 - alpha) * d_dT;
214 double varNoise = alpha * var_noise_ +
215 (1 - alpha) * (d_dT - avg_noise_) * (d_dT - avg_noise_);
philipele1c707c2022-07-05 14:03:25 +0200216 avg_noise_ = avgNoise;
217 var_noise_ = varNoise;
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100218 if (var_noise_ < 1.0) {
Åsa Persson3fcc5be2019-04-04 09:40:27 +0200219 // The variance should never be zero, since we might get stuck and consider
220 // all samples as outliers.
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100221 var_noise_ = 1.0;
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000222 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000223}
224
Rasmus Brandt10944e62022-05-25 10:12:42 +0200225double JitterEstimator::NoiseThreshold() const {
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100226 double noiseThreshold = kNoiseStdDevs * sqrt(var_noise_) - kNoiseStdDevOffset;
philipel9d3ab612015-12-21 04:12:39 -0800227 if (noiseThreshold < 1.0) {
228 noiseThreshold = 1.0;
229 }
230 return noiseThreshold;
niklase@google.com470e71d2011-07-07 08:21:25 +0000231}
232
Åsa Persson3fcc5be2019-04-04 09:40:27 +0200233// Calculates the current jitter estimate from the filtered estimates.
Rasmus Brandt10944e62022-05-25 10:12:42 +0200234TimeDelta JitterEstimator::CalculateEstimate() {
Rasmus Brandtae4a8322022-08-12 13:45:34 +0200235 double retMs = kalman_filter_.GetFrameDelayVariationEstimateSizeBased(
philipel55a9a3d2022-08-19 13:23:26 +0200236 max_frame_size_bytes_ - avg_frame_size_bytes_) +
Rasmus Brandt39ae6962022-08-09 14:40:05 +0200237 NoiseThreshold();
niklase@google.com470e71d2011-07-07 08:21:25 +0000238
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100239 TimeDelta ret = TimeDelta::Millis(retMs);
240
Johannes Kronef4a2cf2022-08-10 22:39:51 +0000241 constexpr TimeDelta kMinEstimate = TimeDelta::Millis(1);
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100242 constexpr TimeDelta kMaxEstimate = TimeDelta::Seconds(10);
Åsa Persson3fcc5be2019-04-04 09:40:27 +0200243 // A very low estimate (or negative) is neglected.
Johannes Kronef4a2cf2022-08-10 22:39:51 +0000244 if (ret < kMinEstimate) {
245 ret = prev_estimate_.value_or(kMinEstimate);
246 // Sanity check to make sure that no other method has set `prev_estimate_`
247 // to a value lower than `kMinEstimate`.
248 RTC_DCHECK_GE(ret, kMinEstimate);
249 } else if (ret > kMaxEstimate) { // Sanity
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100250 ret = kMaxEstimate;
philipel9d3ab612015-12-21 04:12:39 -0800251 }
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100252 prev_estimate_ = ret;
philipel9d3ab612015-12-21 04:12:39 -0800253 return ret;
niklase@google.com470e71d2011-07-07 08:21:25 +0000254}
255
Rasmus Brandt10944e62022-05-25 10:12:42 +0200256void JitterEstimator::PostProcessEstimate() {
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100257 filter_jitter_estimate_ = CalculateEstimate();
niklase@google.com470e71d2011-07-07 08:21:25 +0000258}
259
Rasmus Brandt10944e62022-05-25 10:12:42 +0200260void JitterEstimator::UpdateRtt(TimeDelta rtt) {
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100261 rtt_filter_.Update(rtt);
niklase@google.com470e71d2011-07-07 08:21:25 +0000262}
263
niklase@google.com470e71d2011-07-07 08:21:25 +0000264// Returns the current filtered estimate if available,
265// otherwise tries to calculate an estimate.
Rasmus Brandt10944e62022-05-25 10:12:42 +0200266TimeDelta JitterEstimator::GetJitterEstimate(
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100267 double rtt_multiplier,
268 absl::optional<TimeDelta> rtt_mult_add_cap) {
269 TimeDelta jitter = CalculateEstimate() + OPERATING_SYSTEM_JITTER;
270 Timestamp now = clock_->CurrentTime();
Gustavo Garciaf7a7c8a2018-01-08 15:23:38 +0100271
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100272 if (now - latest_nack_ > kNackCountTimeout)
273 nack_count_ = 0;
Gustavo Garciaf7a7c8a2018-01-08 15:23:38 +0100274
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100275 if (filter_jitter_estimate_ > jitter)
276 jitter = filter_jitter_estimate_;
277 if (nack_count_ >= kNackLimit) {
278 if (rtt_mult_add_cap.has_value()) {
279 jitter += std::min(rtt_filter_.Rtt() * rtt_multiplier,
280 rtt_mult_add_cap.value());
“Michaele0f37042019-06-04 10:04:12 -0500281 } else {
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100282 jitter += rtt_filter_.Rtt() * rtt_multiplier;
“Michaele0f37042019-06-04 10:04:12 -0500283 }
284 }
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000285
Erik Språng23370f22022-07-27 14:09:02 +0200286 static const Frequency kJitterScaleLowThreshold = Frequency::Hertz(5);
287 static const Frequency kJitterScaleHighThreshold = Frequency::Hertz(10);
288 Frequency fps = GetFrameRate();
289 // Ignore jitter for very low fps streams.
290 if (fps < kJitterScaleLowThreshold) {
291 if (fps.IsZero()) {
292 return std::max(TimeDelta::Zero(), jitter);
niklase@google.com470e71d2011-07-07 08:21:25 +0000293 }
Erik Språng23370f22022-07-27 14:09:02 +0200294 return TimeDelta::Zero();
295 }
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000296
Erik Språng23370f22022-07-27 14:09:02 +0200297 // Semi-low frame rate; scale by factor linearly interpolated from 0.0 at
298 // kJitterScaleLowThreshold to 1.0 at kJitterScaleHighThreshold.
299 if (fps < kJitterScaleHighThreshold) {
300 jitter = (1.0 / (kJitterScaleHighThreshold - kJitterScaleLowThreshold)) *
301 (fps - kJitterScaleLowThreshold) * jitter;
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000302 }
Erik Språngb1e031a2018-11-01 11:20:49 +0100303
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100304 return std::max(TimeDelta::Zero(), jitter);
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000305}
306
Rasmus Brandt10944e62022-05-25 10:12:42 +0200307Frequency JitterEstimator::GetFrameRate() const {
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100308 TimeDelta mean_frame_period = TimeDelta::Micros(fps_counter_.ComputeMean());
309 if (mean_frame_period <= TimeDelta::Zero())
310 return Frequency::Zero();
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000311
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100312 Frequency fps = 1 / mean_frame_period;
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000313 // Sanity check.
Evan Shrubsole13e42a82022-03-07 13:21:51 +0100314 RTC_DCHECK_GE(fps, Frequency::Zero());
315 return std::min(fps, kMaxFramerateEstimate);
niklase@google.com470e71d2011-07-07 08:21:25 +0000316}
philipel9d3ab612015-12-21 04:12:39 -0800317} // namespace webrtc