blob: 0d0fac7291e8fddd41dc6b355a935f6496d164c1 [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
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "modules/video_coding/jitter_estimator.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000012
henrik.lundin@webrtc.org7d8c72e2011-12-21 15:24:01 +000013#include <assert.h>
niklase@google.com470e71d2011-07-07 08:21:25 +000014#include <math.h>
15#include <stdlib.h>
16#include <string.h>
Erik Språngb1e031a2018-11-01 11:20:49 +010017#include <algorithm>
philipel9d3ab612015-12-21 04:12:39 -080018#include <string>
19
Erik Språngb1e031a2018-11-01 11:20:49 +010020#include "absl/types/optional.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020021#include "modules/video_coding/internal_defines.h"
22#include "modules/video_coding/rtt_filter.h"
Erik Språngb1e031a2018-11-01 11:20:49 +010023#include "rtc_base/experiments/jitter_upper_bound_experiment.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020024#include "system_wrappers/include/clock.h"
25#include "system_wrappers/include/field_trial.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000026
27namespace webrtc {
Erik Språngb1e031a2018-11-01 11:20:49 +010028namespace {
29static constexpr uint32_t kStartupDelaySamples = 30;
30static constexpr int64_t kFsAccuStartupSamples = 5;
31static constexpr double kMaxFramerateEstimate = 200.0;
32static constexpr int64_t kNackCountTimeoutMs = 60000;
33static constexpr double kDefaultMaxTimestampDeviationInSigmas = 3.5;
34} // namespace
sprang@webrtc.org70e2d112014-09-24 14:06:56 +000035
36VCMJitterEstimator::VCMJitterEstimator(const Clock* clock,
37 int32_t vcmId,
38 int32_t receiverId)
stefan@webrtc.org34c5da62014-04-11 14:08:35 +000039 : _vcmId(vcmId),
40 _receiverId(receiverId),
41 _phi(0.97),
42 _psi(0.9999),
43 _alphaCountMax(400),
44 _thetaLow(0.000001),
45 _nackLimit(3),
46 _numStdDevDelayOutlier(15),
47 _numStdDevFrameSizeOutlier(3),
48 _noiseStdDevs(2.33), // ~Less than 1% chance
49 // (look up in normal distribution table)...
50 _noiseStdDevOffset(30.0), // ...of getting 30 ms freezes
sprang@webrtc.org70e2d112014-09-24 14:06:56 +000051 _rttFilter(),
52 fps_counter_(30), // TODO(sprang): Use an estimator with limit based on
53 // time, rather than number of samples.
Erik Språngb1e031a2018-11-01 11:20:49 +010054 time_deviation_upper_bound_(
55 JitterUpperBoundExperiment::GetUpperBoundSigmas().value_or(
56 kDefaultMaxTimestampDeviationInSigmas)),
sprang@webrtc.org70e2d112014-09-24 14:06:56 +000057 clock_(clock) {
58 Reset();
59}
60
philipel9d3ab612015-12-21 04:12:39 -080061VCMJitterEstimator::~VCMJitterEstimator() {}
niklase@google.com470e71d2011-07-07 08:21:25 +000062
philipel9d3ab612015-12-21 04:12:39 -080063VCMJitterEstimator& VCMJitterEstimator::operator=(
64 const VCMJitterEstimator& rhs) {
65 if (this != &rhs) {
66 memcpy(_thetaCov, rhs._thetaCov, sizeof(_thetaCov));
67 memcpy(_Qcov, rhs._Qcov, sizeof(_Qcov));
niklase@google.com470e71d2011-07-07 08:21:25 +000068
philipel9d3ab612015-12-21 04:12:39 -080069 _vcmId = rhs._vcmId;
70 _receiverId = rhs._receiverId;
71 _avgFrameSize = rhs._avgFrameSize;
72 _varFrameSize = rhs._varFrameSize;
73 _maxFrameSize = rhs._maxFrameSize;
74 _fsSum = rhs._fsSum;
75 _fsCount = rhs._fsCount;
76 _lastUpdateT = rhs._lastUpdateT;
77 _prevEstimate = rhs._prevEstimate;
78 _prevFrameSize = rhs._prevFrameSize;
79 _avgNoise = rhs._avgNoise;
80 _alphaCount = rhs._alphaCount;
81 _filterJitterEstimate = rhs._filterJitterEstimate;
82 _startupCount = rhs._startupCount;
83 _latestNackTimestamp = rhs._latestNackTimestamp;
84 _nackCount = rhs._nackCount;
85 _rttFilter = rhs._rttFilter;
Erik Språngb1e031a2018-11-01 11:20:49 +010086 clock_ = rhs.clock_;
philipel9d3ab612015-12-21 04:12:39 -080087 }
88 return *this;
niklase@google.com470e71d2011-07-07 08:21:25 +000089}
90
91// Resets the JitterEstimate
philipel9d3ab612015-12-21 04:12:39 -080092void VCMJitterEstimator::Reset() {
93 _theta[0] = 1 / (512e3 / 8);
94 _theta[1] = 0;
95 _varNoise = 4.0;
niklase@google.com470e71d2011-07-07 08:21:25 +000096
philipel9d3ab612015-12-21 04:12:39 -080097 _thetaCov[0][0] = 1e-4;
98 _thetaCov[1][1] = 1e2;
99 _thetaCov[0][1] = _thetaCov[1][0] = 0;
100 _Qcov[0][0] = 2.5e-10;
101 _Qcov[1][1] = 1e-10;
102 _Qcov[0][1] = _Qcov[1][0] = 0;
103 _avgFrameSize = 500;
104 _maxFrameSize = 500;
105 _varFrameSize = 100;
106 _lastUpdateT = -1;
107 _prevEstimate = -1.0;
108 _prevFrameSize = 0;
109 _avgNoise = 0.0;
110 _alphaCount = 1;
111 _filterJitterEstimate = 0.0;
112 _latestNackTimestamp = 0;
113 _nackCount = 0;
Gustavo Garciaf7a7c8a2018-01-08 15:23:38 +0100114 _latestNackTimestamp = 0;
philipel9d3ab612015-12-21 04:12:39 -0800115 _fsSum = 0;
116 _fsCount = 0;
117 _startupCount = 0;
118 _rttFilter.Reset();
119 fps_counter_.Reset();
niklase@google.com470e71d2011-07-07 08:21:25 +0000120}
121
philipel9d3ab612015-12-21 04:12:39 -0800122void VCMJitterEstimator::ResetNackCount() {
123 _nackCount = 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000124}
125
126// Updates the estimates with the new measurements
philipel9d3ab612015-12-21 04:12:39 -0800127void VCMJitterEstimator::UpdateEstimate(int64_t frameDelayMS,
128 uint32_t frameSizeBytes,
129 bool incompleteFrame /* = false */) {
130 if (frameSizeBytes == 0) {
131 return;
132 }
133 int deltaFS = frameSizeBytes - _prevFrameSize;
134 if (_fsCount < kFsAccuStartupSamples) {
135 _fsSum += frameSizeBytes;
136 _fsCount++;
137 } else if (_fsCount == kFsAccuStartupSamples) {
138 // Give the frame size filter
139 _avgFrameSize = static_cast<double>(_fsSum) / static_cast<double>(_fsCount);
140 _fsCount++;
141 }
142 if (!incompleteFrame || frameSizeBytes > _avgFrameSize) {
143 double avgFrameSize = _phi * _avgFrameSize + (1 - _phi) * frameSizeBytes;
144 if (frameSizeBytes < _avgFrameSize + 2 * sqrt(_varFrameSize)) {
145 // Only update the average frame size if this sample wasn't a
146 // key frame
147 _avgFrameSize = avgFrameSize;
niklase@google.com470e71d2011-07-07 08:21:25 +0000148 }
philipel9d3ab612015-12-21 04:12:39 -0800149 // Update the variance anyway since we want to capture cases where we only
150 // get
151 // key frames.
Yves Gerey665174f2018-06-19 15:03:05 +0200152 _varFrameSize = VCM_MAX(
153 _phi * _varFrameSize + (1 - _phi) * (frameSizeBytes - avgFrameSize) *
154 (frameSizeBytes - avgFrameSize),
155 1.0);
philipel9d3ab612015-12-21 04:12:39 -0800156 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000157
philipel9d3ab612015-12-21 04:12:39 -0800158 // Update max frameSize estimate
159 _maxFrameSize =
160 VCM_MAX(_psi * _maxFrameSize, static_cast<double>(frameSizeBytes));
niklase@google.com470e71d2011-07-07 08:21:25 +0000161
philipel9d3ab612015-12-21 04:12:39 -0800162 if (_prevFrameSize == 0) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000163 _prevFrameSize = frameSizeBytes;
philipel9d3ab612015-12-21 04:12:39 -0800164 return;
165 }
166 _prevFrameSize = frameSizeBytes;
niklase@google.com470e71d2011-07-07 08:21:25 +0000167
Erik Språngb1e031a2018-11-01 11:20:49 +0100168 // Cap frameDelayMS based on the current time deviation noise.
169 int64_t max_time_deviation_ms =
170 static_cast<int64_t>(time_deviation_upper_bound_ * sqrt(_varNoise) + 0.5);
171 frameDelayMS = std::max(std::min(frameDelayMS, max_time_deviation_ms),
172 -max_time_deviation_ms);
173
philipel9d3ab612015-12-21 04:12:39 -0800174 // Only update the Kalman filter if the sample is not considered
175 // an extreme outlier. Even if it is an extreme outlier from a
176 // delay point of view, if the frame size also is large the
177 // deviation is probably due to an incorrect line slope.
178 double deviation = DeviationFromExpectedDelay(frameDelayMS, deltaFS);
niklase@google.com470e71d2011-07-07 08:21:25 +0000179
philipel9d3ab612015-12-21 04:12:39 -0800180 if (fabs(deviation) < _numStdDevDelayOutlier * sqrt(_varNoise) ||
181 frameSizeBytes >
182 _avgFrameSize + _numStdDevFrameSizeOutlier * sqrt(_varFrameSize)) {
183 // Update the variance of the deviation from the
184 // line given by the Kalman filter
185 EstimateRandomJitter(deviation, incompleteFrame);
186 // Prevent updating with frames which have been congested by a large
187 // frame, and therefore arrives almost at the same time as that frame.
188 // This can occur when we receive a large frame (key frame) which
189 // has been delayed. The next frame is of normal size (delta frame),
190 // and thus deltaFS will be << 0. This removes all frame samples
191 // which arrives after a key frame.
192 if ((!incompleteFrame || deviation >= 0.0) &&
193 static_cast<double>(deltaFS) > -0.25 * _maxFrameSize) {
194 // Update the Kalman filter with the new data
195 KalmanEstimateChannel(frameDelayMS, deltaFS);
niklase@google.com470e71d2011-07-07 08:21:25 +0000196 }
philipel9d3ab612015-12-21 04:12:39 -0800197 } else {
198 int nStdDev =
199 (deviation >= 0) ? _numStdDevDelayOutlier : -_numStdDevDelayOutlier;
200 EstimateRandomJitter(nStdDev * sqrt(_varNoise), incompleteFrame);
201 }
202 // Post process the total estimated jitter
203 if (_startupCount >= kStartupDelaySamples) {
204 PostProcessEstimate();
205 } else {
206 _startupCount++;
207 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000208}
209
210// Updates the nack/packet ratio
philipel9d3ab612015-12-21 04:12:39 -0800211void VCMJitterEstimator::FrameNacked() {
philipel9d3ab612015-12-21 04:12:39 -0800212 if (_nackCount < _nackLimit) {
213 _nackCount++;
214 }
Gustavo Garciaf7a7c8a2018-01-08 15:23:38 +0100215 _latestNackTimestamp = clock_->TimeInMicroseconds();
niklase@google.com470e71d2011-07-07 08:21:25 +0000216}
217
218// Updates Kalman estimate of the channel
219// The caller is expected to sanity check the inputs.
philipel9d3ab612015-12-21 04:12:39 -0800220void VCMJitterEstimator::KalmanEstimateChannel(int64_t frameDelayMS,
221 int32_t deltaFSBytes) {
222 double Mh[2];
223 double hMh_sigma;
224 double kalmanGain[2];
225 double measureRes;
226 double t00, t01;
niklase@google.com470e71d2011-07-07 08:21:25 +0000227
philipel9d3ab612015-12-21 04:12:39 -0800228 // Kalman filtering
niklase@google.com470e71d2011-07-07 08:21:25 +0000229
philipel9d3ab612015-12-21 04:12:39 -0800230 // Prediction
231 // M = M + Q
232 _thetaCov[0][0] += _Qcov[0][0];
233 _thetaCov[0][1] += _Qcov[0][1];
234 _thetaCov[1][0] += _Qcov[1][0];
235 _thetaCov[1][1] += _Qcov[1][1];
niklase@google.com470e71d2011-07-07 08:21:25 +0000236
philipel9d3ab612015-12-21 04:12:39 -0800237 // Kalman gain
238 // K = M*h'/(sigma2n + h*M*h') = M*h'/(1 + h*M*h')
239 // h = [dFS 1]
240 // Mh = M*h'
241 // hMh_sigma = h*M*h' + R
242 Mh[0] = _thetaCov[0][0] * deltaFSBytes + _thetaCov[0][1];
243 Mh[1] = _thetaCov[1][0] * deltaFSBytes + _thetaCov[1][1];
244 // sigma weights measurements with a small deltaFS as noisy and
245 // measurements with large deltaFS as good
246 if (_maxFrameSize < 1.0) {
247 return;
248 }
249 double sigma = (300.0 * exp(-fabs(static_cast<double>(deltaFSBytes)) /
250 (1e0 * _maxFrameSize)) +
251 1) *
252 sqrt(_varNoise);
253 if (sigma < 1.0) {
254 sigma = 1.0;
255 }
256 hMh_sigma = deltaFSBytes * Mh[0] + Mh[1] + sigma;
257 if ((hMh_sigma < 1e-9 && hMh_sigma >= 0) ||
258 (hMh_sigma > -1e-9 && hMh_sigma <= 0)) {
259 assert(false);
260 return;
261 }
262 kalmanGain[0] = Mh[0] / hMh_sigma;
263 kalmanGain[1] = Mh[1] / hMh_sigma;
niklase@google.com470e71d2011-07-07 08:21:25 +0000264
philipel9d3ab612015-12-21 04:12:39 -0800265 // Correction
266 // theta = theta + K*(dT - h*theta)
267 measureRes = frameDelayMS - (deltaFSBytes * _theta[0] + _theta[1]);
268 _theta[0] += kalmanGain[0] * measureRes;
269 _theta[1] += kalmanGain[1] * measureRes;
niklase@google.com470e71d2011-07-07 08:21:25 +0000270
philipel9d3ab612015-12-21 04:12:39 -0800271 if (_theta[0] < _thetaLow) {
272 _theta[0] = _thetaLow;
273 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000274
philipel9d3ab612015-12-21 04:12:39 -0800275 // M = (I - K*h)*M
276 t00 = _thetaCov[0][0];
277 t01 = _thetaCov[0][1];
278 _thetaCov[0][0] = (1 - kalmanGain[0] * deltaFSBytes) * t00 -
279 kalmanGain[0] * _thetaCov[1][0];
280 _thetaCov[0][1] = (1 - kalmanGain[0] * deltaFSBytes) * t01 -
281 kalmanGain[0] * _thetaCov[1][1];
282 _thetaCov[1][0] = _thetaCov[1][0] * (1 - kalmanGain[1]) -
283 kalmanGain[1] * deltaFSBytes * t00;
284 _thetaCov[1][1] = _thetaCov[1][1] * (1 - kalmanGain[1]) -
285 kalmanGain[1] * deltaFSBytes * t01;
niklase@google.com470e71d2011-07-07 08:21:25 +0000286
philipel9d3ab612015-12-21 04:12:39 -0800287 // Covariance matrix, must be positive semi-definite
288 assert(_thetaCov[0][0] + _thetaCov[1][1] >= 0 &&
289 _thetaCov[0][0] * _thetaCov[1][1] -
290 _thetaCov[0][1] * _thetaCov[1][0] >=
291 0 &&
292 _thetaCov[0][0] >= 0);
niklase@google.com470e71d2011-07-07 08:21:25 +0000293}
294
295// Calculate difference in delay between a sample and the
296// expected delay estimated by the Kalman filter
philipel9d3ab612015-12-21 04:12:39 -0800297double VCMJitterEstimator::DeviationFromExpectedDelay(
298 int64_t frameDelayMS,
299 int32_t deltaFSBytes) const {
300 return frameDelayMS - (_theta[0] * deltaFSBytes + _theta[1]);
niklase@google.com470e71d2011-07-07 08:21:25 +0000301}
302
303// Estimates the random jitter by calculating the variance of the
304// sample distance from the line given by theta.
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000305void VCMJitterEstimator::EstimateRandomJitter(double d_dT,
306 bool incompleteFrame) {
307 uint64_t now = clock_->TimeInMicroseconds();
308 if (_lastUpdateT != -1) {
309 fps_counter_.AddSample(now - _lastUpdateT);
310 }
311 _lastUpdateT = now;
312
313 if (_alphaCount == 0) {
314 assert(false);
315 return;
316 }
317 double alpha =
318 static_cast<double>(_alphaCount - 1) / static_cast<double>(_alphaCount);
319 _alphaCount++;
320 if (_alphaCount > _alphaCountMax)
321 _alphaCount = _alphaCountMax;
322
Erik Språngb1e031a2018-11-01 11:20:49 +0100323 // In order to avoid a low frame rate stream to react slower to changes,
324 // scale the alpha weight relative a 30 fps stream.
325 double fps = GetFrameRate();
326 if (fps > 0.0) {
327 double rate_scale = 30.0 / fps;
328 // At startup, there can be a lot of noise in the fps estimate.
329 // Interpolate rate_scale linearly, from 1.0 at sample #1, to 30.0 / fps
330 // at sample #kStartupDelaySamples.
331 if (_alphaCount < kStartupDelaySamples) {
332 rate_scale =
333 (_alphaCount * rate_scale + (kStartupDelaySamples - _alphaCount)) /
334 kStartupDelaySamples;
niklase@google.com470e71d2011-07-07 08:21:25 +0000335 }
Erik Språngb1e031a2018-11-01 11:20:49 +0100336 alpha = pow(alpha, rate_scale);
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000337 }
338
339 double avgNoise = alpha * _avgNoise + (1 - alpha) * d_dT;
340 double varNoise =
341 alpha * _varNoise + (1 - alpha) * (d_dT - _avgNoise) * (d_dT - _avgNoise);
342 if (!incompleteFrame || varNoise > _varNoise) {
343 _avgNoise = avgNoise;
344 _varNoise = varNoise;
345 }
346 if (_varNoise < 1.0) {
347 // The variance should never be zero, since we might get
348 // stuck and consider all samples as outliers.
349 _varNoise = 1.0;
350 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000351}
352
philipel9d3ab612015-12-21 04:12:39 -0800353double VCMJitterEstimator::NoiseThreshold() const {
354 double noiseThreshold = _noiseStdDevs * sqrt(_varNoise) - _noiseStdDevOffset;
355 if (noiseThreshold < 1.0) {
356 noiseThreshold = 1.0;
357 }
358 return noiseThreshold;
niklase@google.com470e71d2011-07-07 08:21:25 +0000359}
360
361// Calculates the current jitter estimate from the filtered estimates
philipel9d3ab612015-12-21 04:12:39 -0800362double VCMJitterEstimator::CalculateEstimate() {
363 double ret = _theta[0] * (_maxFrameSize - _avgFrameSize) + NoiseThreshold();
niklase@google.com470e71d2011-07-07 08:21:25 +0000364
philipel9d3ab612015-12-21 04:12:39 -0800365 // A very low estimate (or negative) is neglected
366 if (ret < 1.0) {
367 if (_prevEstimate <= 0.01) {
368 ret = 1.0;
369 } else {
370 ret = _prevEstimate;
niklase@google.com470e71d2011-07-07 08:21:25 +0000371 }
philipel9d3ab612015-12-21 04:12:39 -0800372 }
373 if (ret > 10000.0) { // Sanity
374 ret = 10000.0;
375 }
376 _prevEstimate = ret;
377 return ret;
niklase@google.com470e71d2011-07-07 08:21:25 +0000378}
379
philipel9d3ab612015-12-21 04:12:39 -0800380void VCMJitterEstimator::PostProcessEstimate() {
381 _filterJitterEstimate = CalculateEstimate();
niklase@google.com470e71d2011-07-07 08:21:25 +0000382}
383
philipel9d3ab612015-12-21 04:12:39 -0800384void VCMJitterEstimator::UpdateRtt(int64_t rttMs) {
385 _rttFilter.Update(rttMs);
niklase@google.com470e71d2011-07-07 08:21:25 +0000386}
387
philipel9d3ab612015-12-21 04:12:39 -0800388void VCMJitterEstimator::UpdateMaxFrameSize(uint32_t frameSizeBytes) {
389 if (_maxFrameSize < frameSizeBytes) {
390 _maxFrameSize = frameSizeBytes;
391 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000392}
393
394// Returns the current filtered estimate if available,
395// otherwise tries to calculate an estimate.
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000396int VCMJitterEstimator::GetJitterEstimate(double rttMultiplier) {
397 double jitterMS = CalculateEstimate() + OPERATING_SYSTEM_JITTER;
Gustavo Garciaf7a7c8a2018-01-08 15:23:38 +0100398 uint64_t now = clock_->TimeInMicroseconds();
399
400 if (now - _latestNackTimestamp > kNackCountTimeoutMs * 1000)
401 _nackCount = 0;
402
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000403 if (_filterJitterEstimate > jitterMS)
404 jitterMS = _filterJitterEstimate;
405 if (_nackCount >= _nackLimit)
406 jitterMS += _rttFilter.RttMs() * rttMultiplier;
407
Erik Språngb1e031a2018-11-01 11:20:49 +0100408 static const double kJitterScaleLowThreshold = 5.0;
409 static const double kJitterScaleHighThreshold = 10.0;
410 double fps = GetFrameRate();
411 // Ignore jitter for very low fps streams.
412 if (fps < kJitterScaleLowThreshold) {
413 if (fps == 0.0) {
414 return rtc::checked_cast<int>(std::max(0.0, jitterMS) + 0.5);
niklase@google.com470e71d2011-07-07 08:21:25 +0000415 }
Erik Språngb1e031a2018-11-01 11:20:49 +0100416 return 0;
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000417 }
418
Erik Språngb1e031a2018-11-01 11:20:49 +0100419 // Semi-low frame rate; scale by factor linearly interpolated from 0.0 at
420 // kJitterScaleLowThreshold to 1.0 at kJitterScaleHighThreshold.
421 if (fps < kJitterScaleHighThreshold) {
422 jitterMS = (1.0 / (kJitterScaleHighThreshold - kJitterScaleLowThreshold)) *
423 (fps - kJitterScaleLowThreshold) * jitterMS;
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000424 }
Erik Språngb1e031a2018-11-01 11:20:49 +0100425
426 return rtc::checked_cast<int>(std::max(0.0, jitterMS) + 0.5);
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000427}
428
429double VCMJitterEstimator::GetFrameRate() const {
Peter Boström74451a52016-02-01 16:31:10 +0100430 if (fps_counter_.ComputeMean() == 0.0)
sprang@webrtc.org70e2d112014-09-24 14:06:56 +0000431 return 0;
432
433 double fps = 1000000.0 / fps_counter_.ComputeMean();
434 // Sanity check.
435 assert(fps >= 0.0);
436 if (fps > kMaxFramerateEstimate) {
437 fps = kMaxFramerateEstimate;
438 }
439 return fps;
niklase@google.com470e71d2011-07-07 08:21:25 +0000440}
philipel9d3ab612015-12-21 04:12:39 -0800441} // namespace webrtc