blob: 6962224d6175068cbb03ef6173fb3d86f943c518 [file] [log] [blame]
/*
* Copyright (c) 2011 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 "modules/video_coding/timing/rtt_filter.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include "absl/algorithm/container.h"
#include "absl/container/inlined_vector.h"
#include "api/units/time_delta.h"
namespace webrtc {
namespace {
constexpr TimeDelta kMaxRtt = TimeDelta::Seconds(3);
constexpr uint32_t kFilterFactorMax = 35;
constexpr double kJumpStddev = 2.5;
constexpr double kDriftStdDev = 3.5;
} // namespace
RttFilter::RttFilter()
: avg_rtt_(TimeDelta::Zero()),
var_rtt_(0),
max_rtt_(TimeDelta::Zero()),
jump_buf_(kMaxDriftJumpCount, TimeDelta::Zero()),
drift_buf_(kMaxDriftJumpCount, TimeDelta::Zero()) {
Reset();
}
void RttFilter::Reset() {
got_non_zero_update_ = false;
avg_rtt_ = TimeDelta::Zero();
var_rtt_ = 0;
max_rtt_ = TimeDelta::Zero();
filt_fact_count_ = 1;
absl::c_fill(jump_buf_, TimeDelta::Zero());
absl::c_fill(drift_buf_, TimeDelta::Zero());
}
void RttFilter::Update(TimeDelta rtt) {
if (!got_non_zero_update_) {
if (rtt.IsZero()) {
return;
}
got_non_zero_update_ = true;
}
// Sanity check
if (rtt > kMaxRtt) {
rtt = kMaxRtt;
}
double filt_factor = 0;
if (filt_fact_count_ > 1) {
filt_factor = static_cast<double>(filt_fact_count_ - 1) / filt_fact_count_;
}
filt_fact_count_++;
if (filt_fact_count_ > kFilterFactorMax) {
// This prevents filt_factor from going above
// (_filt_fact_max - 1) / filt_fact_max_,
// e.g., filt_fact_max_ = 50 => filt_factor = 49/50 = 0.98
filt_fact_count_ = kFilterFactorMax;
}
TimeDelta old_avg = avg_rtt_;
int64_t old_var = var_rtt_;
avg_rtt_ = filt_factor * avg_rtt_ + (1 - filt_factor) * rtt;
int64_t delta_ms = (rtt - avg_rtt_).ms();
var_rtt_ = filt_factor * var_rtt_ + (1 - filt_factor) * (delta_ms * delta_ms);
max_rtt_ = std::max(rtt, max_rtt_);
if (!JumpDetection(rtt) || !DriftDetection(rtt)) {
// In some cases we don't want to update the statistics
avg_rtt_ = old_avg;
var_rtt_ = old_var;
}
}
bool RttFilter::JumpDetection(TimeDelta rtt) {
TimeDelta diff_from_avg = avg_rtt_ - rtt;
// Unit of var_rtt_ is ms^2.
TimeDelta jump_threshold = TimeDelta::Millis(kJumpStddev * sqrt(var_rtt_));
if (diff_from_avg.Abs() > jump_threshold) {
bool positive_diff = diff_from_avg >= TimeDelta::Zero();
if (!jump_buf_.empty() && positive_diff != last_jump_positive_) {
// Since the signs differ the samples currently
// in the buffer is useless as they represent a
// jump in a different direction.
jump_buf_.clear();
}
if (jump_buf_.size() < kMaxDriftJumpCount) {
// Update the buffer used for the short time statistics.
// The sign of the diff is used for updating the counter since
// we want to use the same buffer for keeping track of when
// the RTT jumps down and up.
jump_buf_.push_back(rtt);
last_jump_positive_ = positive_diff;
}
if (jump_buf_.size() >= kMaxDriftJumpCount) {
// Detected an RTT jump
ShortRttFilter(jump_buf_);
filt_fact_count_ = kMaxDriftJumpCount + 1;
jump_buf_.clear();
} else {
return false;
}
} else {
jump_buf_.clear();
}
return true;
}
bool RttFilter::DriftDetection(TimeDelta rtt) {
// Unit of sqrt of var_rtt_ is ms.
TimeDelta drift_threshold = TimeDelta::Millis(kDriftStdDev * sqrt(var_rtt_));
if (max_rtt_ - avg_rtt_ > drift_threshold) {
if (drift_buf_.size() < kMaxDriftJumpCount) {
// Update the buffer used for the short time statistics.
drift_buf_.push_back(rtt);
}
if (drift_buf_.size() >= kMaxDriftJumpCount) {
// Detected an RTT drift
ShortRttFilter(drift_buf_);
filt_fact_count_ = kMaxDriftJumpCount + 1;
drift_buf_.clear();
}
} else {
drift_buf_.clear();
}
return true;
}
void RttFilter::ShortRttFilter(const BufferList& buf) {
RTC_DCHECK_EQ(buf.size(), kMaxDriftJumpCount);
max_rtt_ = TimeDelta::Zero();
avg_rtt_ = TimeDelta::Zero();
for (const TimeDelta& rtt : buf) {
if (rtt > max_rtt_) {
max_rtt_ = rtt;
}
avg_rtt_ += rtt;
}
avg_rtt_ = avg_rtt_ / static_cast<double>(buf.size());
}
TimeDelta RttFilter::Rtt() const {
return max_rtt_;
}
} // namespace webrtc