aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/remote_bitrate_estimator
diff options
context:
space:
mode:
authorChih-hung Hsieh <chh@google.com>2015-12-01 17:07:48 +0000
committerandroid-build-merger <android-build-merger@google.com>2015-12-01 17:07:48 +0000
commita4acd9d6bc9b3b033d7d274316e75ee067df8d20 (patch)
tree672a185b294789cf991f385c3e395dd63bea9063 /webrtc/modules/remote_bitrate_estimator
parent3681b90ba4fe7a27232dd3e27897d5d7ed9d651c (diff)
parentfe8b4a657979b49e1701bd92f6d5814a99e0b2be (diff)
downloadwebrtc-a4acd9d6bc9b3b033d7d274316e75ee067df8d20.tar.gz
Merge changes I7bbf776e,I1b827825
am: fe8b4a6579 * commit 'fe8b4a657979b49e1701bd92f6d5814a99e0b2be': (7237 commits) WIP: Changes after merge commit 'cb3f9bd' Make the nonlinear beamformer steerable Utilize bitrate above codec max to protect video. Enable VP9 internal resize by default. Filter overlapping RTP header extensions. Make VCMEncodedFrameCallback const. MediaCodecVideoEncoder: Add number of quality resolution downscales to Encoded callback. Remove redudant encoder rate calls. Create isolate files for nonparallel tests. Register header extensions in RtpRtcpObserver to avoid log spam. Make an enum class out of NetEqDecoder, and hide the neteq_decoders_ table ACM: Move NACK functionality inside NetEq Fix chromium-style warnings in webrtc/sound/. Create a 'webrtc_nonparallel_tests' target. Update scalability structure data according to updates in the RTP payload profile. audio_coding: rename interface -> include Rewrote perform_action_on_all_files to be parallell. Update reference indices according to updates in the RTP payload profile. Disable P2PTransport...TestFailoverControlledSide on Memcheck pass clangcl compile options to ignore warnings in gflags.cc ...
Diffstat (limited to 'webrtc/modules/remote_bitrate_estimator')
-rw-r--r--webrtc/modules/remote_bitrate_estimator/BUILD.gn57
-rw-r--r--webrtc/modules/remote_bitrate_estimator/OWNERS11
-rw-r--r--webrtc/modules/remote_bitrate_estimator/aimd_rate_control.cc314
-rw-r--r--webrtc/modules/remote_bitrate_estimator/aimd_rate_control.h87
-rw-r--r--webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc452
-rw-r--r--webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h60
-rw-r--r--webrtc/modules/remote_bitrate_estimator/include/mock/mock_remote_bitrate_estimator.h42
-rw-r--r--webrtc/modules/remote_bitrate_estimator/include/mock/mock_remote_bitrate_observer.h29
-rw-r--r--webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h103
-rw-r--r--webrtc/modules/remote_bitrate_estimator/include/send_time_history.h48
-rw-r--r--webrtc/modules/remote_bitrate_estimator/inter_arrival.cc130
-rw-r--r--webrtc/modules/remote_bitrate_estimator/inter_arrival.h85
-rw-r--r--webrtc/modules/remote_bitrate_estimator/inter_arrival_unittest.cc422
-rw-r--r--webrtc/modules/remote_bitrate_estimator/overuse_detector.cc155
-rw-r--r--webrtc/modules/remote_bitrate_estimator/overuse_detector.h66
-rw-r--r--webrtc/modules/remote_bitrate_estimator/overuse_detector_unittest.cc740
-rw-r--r--webrtc/modules/remote_bitrate_estimator/overuse_estimator.cc153
-rw-r--r--webrtc/modules/remote_bitrate_estimator/overuse_estimator.h70
-rw-r--r--webrtc/modules/remote_bitrate_estimator/rate_statistics.cc85
-rw-r--r--webrtc/modules/remote_bitrate_estimator/rate_statistics.h53
-rw-r--r--webrtc/modules/remote_bitrate_estimator/rate_statistics_unittest.cc97
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi168
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.cc448
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h154
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time_unittest.cc292
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.cc237
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.h69
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream_unittest.cc73
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.cc667
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h218
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimators_test.cc434
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc164
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.h77
-rw-r--r--webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy_unittest.cc272
-rw-r--r--webrtc/modules/remote_bitrate_estimator/send_time_history.cc101
-rw-r--r--webrtc/modules/remote_bitrate_estimator/send_time_history_unittest.cc234
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe.cc280
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe.h194
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc983
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test.h195
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc168
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.h45
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.cc97
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h59
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc817
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h468
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc1051
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.cc250
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h323
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/bwe_unittest.cc393
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc287
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h109
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc495
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/remb.cc164
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h83
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc151
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h69
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc54
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h38
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc445
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h189
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/metric_recorder_unittest.cc108
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/packet.h205
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/packet_receiver.cc147
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h71
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc494
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/packet_sender.h194
-rwxr-xr-xwebrtc/modules/remote_bitrate_estimator/test/plot_bars.sh286
-rw-r--r--webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py166
-rwxr-xr-xwebrtc/modules/remote_bitrate_estimator/test/plot_dynamics.sh87
-rw-r--r--webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.cc133
-rw-r--r--webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.h36
-rw-r--r--webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp_play.cc113
-rw-r--r--webrtc/modules/remote_bitrate_estimator/tools/rtp_to_text.cc69
-rw-r--r--webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter.cc135
-rw-r--r--webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter.h67
-rw-r--r--webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter_unittest.cc325
77 files changed, 16910 insertions, 0 deletions
diff --git a/webrtc/modules/remote_bitrate_estimator/BUILD.gn b/webrtc/modules/remote_bitrate_estimator/BUILD.gn
new file mode 100644
index 0000000000..99c297dda6
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/BUILD.gn
@@ -0,0 +1,57 @@
+# Copyright (c) 2014 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.
+
+source_set("remote_bitrate_estimator") {
+ sources = [
+ "include/bwe_defines.h",
+ "include/remote_bitrate_estimator.h",
+ "rate_statistics.cc",
+ "rate_statistics.h",
+ ]
+
+ configs += [ "../../:common_inherited_config" ]
+
+ deps = [
+ ":rbe_components",
+ "../..:webrtc_common",
+ "../../system_wrappers",
+ ]
+}
+
+source_set("rbe_components") {
+ sources = [
+ "aimd_rate_control.cc",
+ "aimd_rate_control.h",
+ "include/send_time_history.h",
+ "inter_arrival.cc",
+ "inter_arrival.h",
+ "overuse_detector.cc",
+ "overuse_detector.h",
+ "overuse_estimator.cc",
+ "overuse_estimator.h",
+ "remote_bitrate_estimator_abs_send_time.cc",
+ "remote_bitrate_estimator_single_stream.cc",
+ "remote_estimator_proxy.cc",
+ "remote_estimator_proxy.h",
+ "send_time_history.cc",
+ "transport_feedback_adapter.cc",
+ "transport_feedback_adapter.h",
+ ]
+
+ configs += [ "../..:common_config" ]
+ public_configs = [ "../..:common_inherited_config" ]
+ deps = [
+ "../..:webrtc_common",
+ ]
+
+ if (is_clang) {
+ # Suppress warnings from Chrome's Clang plugins.
+ # See http://code.google.com/p/webrtc/issues/detail?id=163 for details.
+ configs -= [ "//build/config/clang:find_bad_constructs" ]
+ }
+}
diff --git a/webrtc/modules/remote_bitrate_estimator/OWNERS b/webrtc/modules/remote_bitrate_estimator/OWNERS
new file mode 100644
index 0000000000..fd12dcea0c
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/OWNERS
@@ -0,0 +1,11 @@
+stefan@webrtc.org
+henrik.lundin@webrtc.org
+mflodman@webrtc.org
+asapersson@webrtc.org
+
+# These are for the common case of adding or renaming files. If you're doing
+# structural changes, please get a review from a reviewer in this file.
+per-file *.gyp=*
+per-file *.gypi=*
+
+per-file BUILD.gn=kjellander@webrtc.org
diff --git a/webrtc/modules/remote_bitrate_estimator/aimd_rate_control.cc b/webrtc/modules/remote_bitrate_estimator/aimd_rate_control.cc
new file mode 100644
index 0000000000..2d5573228d
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/aimd_rate_control.cc
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2014 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/modules/remote_bitrate_estimator/aimd_rate_control.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+
+#include "webrtc/base/checks.h"
+
+#include "webrtc/modules/remote_bitrate_estimator/overuse_detector.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+
+namespace webrtc {
+
+static const int64_t kDefaultRttMs = 200;
+static const int64_t kLogIntervalMs = 1000;
+static const double kWithinIncomingBitrateHysteresis = 1.05;
+static const int64_t kMaxFeedbackIntervalMs = 1000;
+
+AimdRateControl::AimdRateControl()
+ : min_configured_bitrate_bps_(
+ RemoteBitrateEstimator::kDefaultMinBitrateBps),
+ max_configured_bitrate_bps_(30000000),
+ current_bitrate_bps_(max_configured_bitrate_bps_),
+ avg_max_bitrate_kbps_(-1.0f),
+ var_max_bitrate_kbps_(0.4f),
+ rate_control_state_(kRcHold),
+ rate_control_region_(kRcMaxUnknown),
+ time_last_bitrate_change_(-1),
+ current_input_(kBwNormal, 0, 1.0),
+ updated_(false),
+ time_first_incoming_estimate_(-1),
+ bitrate_is_initialized_(false),
+ beta_(0.85f),
+ rtt_(kDefaultRttMs),
+ time_of_last_log_(-1),
+ in_experiment_(AdaptiveThresholdExperimentIsEnabled()) {}
+
+void AimdRateControl::SetMinBitrate(int min_bitrate_bps) {
+ min_configured_bitrate_bps_ = min_bitrate_bps;
+ current_bitrate_bps_ = std::max<int>(min_bitrate_bps, current_bitrate_bps_);
+}
+
+bool AimdRateControl::ValidEstimate() const {
+ return bitrate_is_initialized_;
+}
+
+int64_t AimdRateControl::GetFeedbackInterval() const {
+ // Estimate how often we can send RTCP if we allocate up to 5% of bandwidth
+ // to feedback.
+ static const int kRtcpSize = 80;
+ int64_t interval = static_cast<int64_t>(
+ kRtcpSize * 8.0 * 1000.0 / (0.05 * current_bitrate_bps_) + 0.5);
+ const int64_t kMinFeedbackIntervalMs = 200;
+ return std::min(std::max(interval, kMinFeedbackIntervalMs),
+ kMaxFeedbackIntervalMs);
+}
+
+bool AimdRateControl::TimeToReduceFurther(int64_t time_now,
+ uint32_t incoming_bitrate_bps) const {
+ const int64_t bitrate_reduction_interval =
+ std::max<int64_t>(std::min<int64_t>(rtt_, 200), 10);
+ if (time_now - time_last_bitrate_change_ >= bitrate_reduction_interval) {
+ return true;
+ }
+ if (ValidEstimate()) {
+ const int threshold = static_cast<int>(kWithinIncomingBitrateHysteresis *
+ incoming_bitrate_bps);
+ const int bitrate_difference = LatestEstimate() - incoming_bitrate_bps;
+ return bitrate_difference > threshold;
+ }
+ return false;
+}
+
+uint32_t AimdRateControl::LatestEstimate() const {
+ return current_bitrate_bps_;
+}
+
+uint32_t AimdRateControl::UpdateBandwidthEstimate(int64_t now_ms) {
+ current_bitrate_bps_ = ChangeBitrate(current_bitrate_bps_,
+ current_input_._incomingBitRate,
+ now_ms);
+ if (now_ms - time_of_last_log_ > kLogIntervalMs) {
+ time_of_last_log_ = now_ms;
+ }
+ return current_bitrate_bps_;
+}
+
+void AimdRateControl::SetRtt(int64_t rtt) {
+ rtt_ = rtt;
+}
+
+void AimdRateControl::Update(const RateControlInput* input, int64_t now_ms) {
+ assert(input);
+
+ // Set the initial bit rate value to what we're receiving the first half
+ // second.
+ if (!bitrate_is_initialized_) {
+ const int64_t kInitializationTimeMs = 5000;
+ RTC_DCHECK_LE(kBitrateWindowMs, kInitializationTimeMs);
+ if (time_first_incoming_estimate_ < 0) {
+ if (input->_incomingBitRate > 0) {
+ time_first_incoming_estimate_ = now_ms;
+ }
+ } else if (now_ms - time_first_incoming_estimate_ > kInitializationTimeMs &&
+ input->_incomingBitRate > 0) {
+ current_bitrate_bps_ = input->_incomingBitRate;
+ bitrate_is_initialized_ = true;
+ }
+ }
+
+ if (updated_ && current_input_._bwState == kBwOverusing) {
+ // Only update delay factor and incoming bit rate. We always want to react
+ // on an over-use.
+ current_input_._noiseVar = input->_noiseVar;
+ current_input_._incomingBitRate = input->_incomingBitRate;
+ } else {
+ updated_ = true;
+ current_input_ = *input;
+ }
+}
+
+void AimdRateControl::SetEstimate(int bitrate_bps, int64_t now_ms) {
+ updated_ = true;
+ bitrate_is_initialized_ = true;
+ current_bitrate_bps_ = ChangeBitrate(bitrate_bps, bitrate_bps, now_ms);
+}
+
+uint32_t AimdRateControl::ChangeBitrate(uint32_t current_bitrate_bps,
+ uint32_t incoming_bitrate_bps,
+ int64_t now_ms) {
+ if (!updated_) {
+ return current_bitrate_bps_;
+ }
+ // An over-use should always trigger us to reduce the bitrate, even though
+ // we have not yet established our first estimate. By acting on the over-use,
+ // we will end up with a valid estimate.
+ if (!bitrate_is_initialized_ && current_input_._bwState != kBwOverusing)
+ return current_bitrate_bps_;
+ updated_ = false;
+ ChangeState(current_input_, now_ms);
+ // Calculated here because it's used in multiple places.
+ const float incoming_bitrate_kbps = incoming_bitrate_bps / 1000.0f;
+ // Calculate the max bit rate std dev given the normalized
+ // variance and the current incoming bit rate.
+ const float std_max_bit_rate = sqrt(var_max_bitrate_kbps_ *
+ avg_max_bitrate_kbps_);
+ switch (rate_control_state_) {
+ case kRcHold:
+ break;
+
+ case kRcIncrease:
+ if (avg_max_bitrate_kbps_ >= 0 &&
+ incoming_bitrate_kbps >
+ avg_max_bitrate_kbps_ + 3 * std_max_bit_rate) {
+ ChangeRegion(kRcMaxUnknown);
+ avg_max_bitrate_kbps_ = -1.0;
+ }
+ if (rate_control_region_ == kRcNearMax) {
+ // Approximate the over-use estimator delay to 100 ms.
+ const int64_t response_time = rtt_ + 100;
+ uint32_t additive_increase_bps = AdditiveRateIncrease(
+ now_ms, time_last_bitrate_change_, response_time);
+ current_bitrate_bps += additive_increase_bps;
+
+ } else {
+ uint32_t multiplicative_increase_bps = MultiplicativeRateIncrease(
+ now_ms, time_last_bitrate_change_, current_bitrate_bps);
+ current_bitrate_bps += multiplicative_increase_bps;
+ }
+
+ time_last_bitrate_change_ = now_ms;
+ break;
+
+ case kRcDecrease:
+ bitrate_is_initialized_ = true;
+ if (incoming_bitrate_bps < min_configured_bitrate_bps_) {
+ current_bitrate_bps = min_configured_bitrate_bps_;
+ } else {
+ // Set bit rate to something slightly lower than max
+ // to get rid of any self-induced delay.
+ current_bitrate_bps = static_cast<uint32_t>(beta_ *
+ incoming_bitrate_bps + 0.5);
+ if (current_bitrate_bps > current_bitrate_bps_) {
+ // Avoid increasing the rate when over-using.
+ if (rate_control_region_ != kRcMaxUnknown) {
+ current_bitrate_bps = static_cast<uint32_t>(
+ beta_ * avg_max_bitrate_kbps_ * 1000 + 0.5f);
+ }
+ current_bitrate_bps = std::min(current_bitrate_bps,
+ current_bitrate_bps_);
+ }
+ ChangeRegion(kRcNearMax);
+
+ if (incoming_bitrate_kbps < avg_max_bitrate_kbps_ -
+ 3 * std_max_bit_rate) {
+ avg_max_bitrate_kbps_ = -1.0f;
+ }
+
+ UpdateMaxBitRateEstimate(incoming_bitrate_kbps);
+ }
+ // Stay on hold until the pipes are cleared.
+ ChangeState(kRcHold);
+ time_last_bitrate_change_ = now_ms;
+ break;
+
+ default:
+ assert(false);
+ }
+ if ((incoming_bitrate_bps > 100000 || current_bitrate_bps > 150000) &&
+ current_bitrate_bps > 1.5 * incoming_bitrate_bps) {
+ // Allow changing the bit rate if we are operating at very low rates
+ // Don't change the bit rate if the send side is too far off
+ current_bitrate_bps = current_bitrate_bps_;
+ time_last_bitrate_change_ = now_ms;
+ }
+ return current_bitrate_bps;
+}
+
+uint32_t AimdRateControl::MultiplicativeRateIncrease(
+ int64_t now_ms, int64_t last_ms, uint32_t current_bitrate_bps) const {
+ double alpha = 1.08;
+ if (last_ms > -1) {
+ int time_since_last_update_ms = std::min(static_cast<int>(now_ms - last_ms),
+ 1000);
+ alpha = pow(alpha, time_since_last_update_ms / 1000.0);
+ }
+ uint32_t multiplicative_increase_bps = std::max(
+ current_bitrate_bps * (alpha - 1.0), 1000.0);
+ return multiplicative_increase_bps;
+}
+
+uint32_t AimdRateControl::AdditiveRateIncrease(
+ int64_t now_ms, int64_t last_ms, int64_t response_time_ms) const {
+ assert(response_time_ms > 0);
+ double beta = 0.0;
+ if (last_ms > 0) {
+ beta = std::min((now_ms - last_ms) / static_cast<double>(response_time_ms),
+ 1.0);
+ if (in_experiment_)
+ beta /= 2.0;
+ }
+ double bits_per_frame = static_cast<double>(current_bitrate_bps_) / 30.0;
+ double packets_per_frame = std::ceil(bits_per_frame / (8.0 * 1200.0));
+ double avg_packet_size_bits = bits_per_frame / packets_per_frame;
+ uint32_t additive_increase_bps = std::max(
+ 1000.0, beta * avg_packet_size_bits);
+ return additive_increase_bps;
+}
+
+void AimdRateControl::UpdateMaxBitRateEstimate(float incoming_bitrate_kbps) {
+ const float alpha = 0.05f;
+ if (avg_max_bitrate_kbps_ == -1.0f) {
+ avg_max_bitrate_kbps_ = incoming_bitrate_kbps;
+ } else {
+ avg_max_bitrate_kbps_ = (1 - alpha) * avg_max_bitrate_kbps_ +
+ alpha * incoming_bitrate_kbps;
+ }
+ // Estimate the max bit rate variance and normalize the variance
+ // with the average max bit rate.
+ const float norm = std::max(avg_max_bitrate_kbps_, 1.0f);
+ var_max_bitrate_kbps_ = (1 - alpha) * var_max_bitrate_kbps_ +
+ alpha * (avg_max_bitrate_kbps_ - incoming_bitrate_kbps) *
+ (avg_max_bitrate_kbps_ - incoming_bitrate_kbps) / norm;
+ // 0.4 ~= 14 kbit/s at 500 kbit/s
+ if (var_max_bitrate_kbps_ < 0.4f) {
+ var_max_bitrate_kbps_ = 0.4f;
+ }
+ // 2.5f ~= 35 kbit/s at 500 kbit/s
+ if (var_max_bitrate_kbps_ > 2.5f) {
+ var_max_bitrate_kbps_ = 2.5f;
+ }
+}
+
+void AimdRateControl::ChangeState(const RateControlInput& input,
+ int64_t now_ms) {
+ switch (current_input_._bwState) {
+ case kBwNormal:
+ if (rate_control_state_ == kRcHold) {
+ time_last_bitrate_change_ = now_ms;
+ ChangeState(kRcIncrease);
+ }
+ break;
+ case kBwOverusing:
+ if (rate_control_state_ != kRcDecrease) {
+ ChangeState(kRcDecrease);
+ }
+ break;
+ case kBwUnderusing:
+ ChangeState(kRcHold);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+void AimdRateControl::ChangeRegion(RateControlRegion region) {
+ rate_control_region_ = region;
+}
+
+void AimdRateControl::ChangeState(RateControlState new_state) {
+ rate_control_state_ = new_state;
+}
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/aimd_rate_control.h b/webrtc/modules/remote_bitrate_estimator/aimd_rate_control.h
new file mode 100644
index 0000000000..bc5ca41dff
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/aimd_rate_control.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2014 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h"
+
+namespace webrtc {
+
+// A rate control implementation based on additive increases of
+// bitrate when no over-use is detected and multiplicative decreases when
+// over-uses are detected. When we think the available bandwidth has changes or
+// is unknown, we will switch to a "slow-start mode" where we increase
+// multiplicatively.
+class AimdRateControl {
+ public:
+ AimdRateControl();
+ virtual ~AimdRateControl() {}
+
+ // Returns true if there is a valid estimate of the incoming bitrate, false
+ // otherwise.
+ bool ValidEstimate() const;
+ void SetMinBitrate(int min_bitrate_bps);
+ int64_t GetFeedbackInterval() const;
+ // Returns true if the bitrate estimate hasn't been changed for more than
+ // an RTT, or if the incoming_bitrate is more than 5% above the current
+ // estimate. Should be used to decide if we should reduce the rate further
+ // when over-using.
+ bool TimeToReduceFurther(int64_t time_now,
+ uint32_t incoming_bitrate_bps) const;
+ uint32_t LatestEstimate() const;
+ uint32_t UpdateBandwidthEstimate(int64_t now_ms);
+ void SetRtt(int64_t rtt);
+ void Update(const RateControlInput* input, int64_t now_ms);
+ void SetEstimate(int bitrate_bps, int64_t now_ms);
+
+ private:
+ // Update the target bitrate according based on, among other things,
+ // the current rate control state, the current target bitrate and the incoming
+ // bitrate. When in the "increase" state the bitrate will be increased either
+ // additively or multiplicatively depending on the rate control region. When
+ // in the "decrease" state the bitrate will be decreased to slightly below the
+ // incoming bitrate. When in the "hold" state the bitrate will be kept
+ // constant to allow built up queues to drain.
+ uint32_t ChangeBitrate(uint32_t current_bit_rate,
+ uint32_t incoming_bit_rate,
+ int64_t now_ms);
+ uint32_t MultiplicativeRateIncrease(int64_t now_ms, int64_t last_ms,
+ uint32_t current_bitrate_bps) const;
+ uint32_t AdditiveRateIncrease(int64_t now_ms, int64_t last_ms,
+ int64_t response_time_ms) const;
+ void UpdateChangePeriod(int64_t now_ms);
+ void UpdateMaxBitRateEstimate(float incoming_bit_rate_kbps);
+ void ChangeState(const RateControlInput& input, int64_t now_ms);
+ void ChangeState(RateControlState new_state);
+ void ChangeRegion(RateControlRegion region);
+
+ uint32_t min_configured_bitrate_bps_;
+ uint32_t max_configured_bitrate_bps_;
+ uint32_t current_bitrate_bps_;
+ uint32_t max_hold_rate_bps_;
+ float avg_max_bitrate_kbps_;
+ float var_max_bitrate_kbps_;
+ RateControlState rate_control_state_;
+ RateControlRegion rate_control_region_;
+ int64_t time_last_bitrate_change_;
+ RateControlInput current_input_;
+ bool updated_;
+ int64_t time_first_incoming_estimate_;
+ bool bitrate_is_initialized_;
+ float beta_;
+ int64_t rtt_;
+ int64_t time_of_last_log_;
+ bool in_experiment_;
+};
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc b/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc
new file mode 100644
index 0000000000..cb8d0db5c7
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc
@@ -0,0 +1,452 @@
+/*
+ * Copyright (c) 2014 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 "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
+#include "webrtc/test/testsupport/fileutils.h"
+
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+// This test fixture is used to instantiate tests running with adaptive video
+// senders.
+class BweSimulation : public BweTest,
+ public ::testing::TestWithParam<BandwidthEstimatorType> {
+ public:
+ BweSimulation()
+ : BweTest(), random_(Clock::GetRealTimeClock()->TimeInMicroseconds()) {}
+ virtual ~BweSimulation() {}
+
+ protected:
+ void SetUp() override {
+ BweTest::SetUp();
+ VerboseLogging(true);
+ }
+
+ test::Random random_;
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweSimulation);
+};
+
+INSTANTIATE_TEST_CASE_P(VideoSendersTest,
+ BweSimulation,
+ ::testing::Values(kRembEstimator,
+ kFullSendSideEstimator,
+ kNadaEstimator));
+
+TEST_P(BweSimulation, SprintUplinkTest) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ RateCounterFilter counter1(&uplink_, 0, "sender_output",
+ bwe_names[GetParam()]);
+ TraceBasedDeliveryFilter filter(&uplink_, 0, "link_capacity");
+ RateCounterFilter counter2(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), true, true);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("sprint-uplink", "rx")));
+ RunFor(60 * 1000);
+}
+
+TEST_P(BweSimulation, Verizon4gDownlinkTest) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&downlink_, &source, GetParam());
+ RateCounterFilter counter1(&downlink_, 0, "sender_output",
+ bwe_names[GetParam()] + "_up");
+ TraceBasedDeliveryFilter filter(&downlink_, 0, "link_capacity");
+ RateCounterFilter counter2(&downlink_, 0, "Receiver",
+ bwe_names[GetParam()] + "_down");
+ PacketReceiver receiver(&downlink_, 0, GetParam(), true, true);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("verizon4g-downlink", "rx")));
+ RunFor(22 * 60 * 1000);
+}
+
+TEST_P(BweSimulation, Choke1000kbps500kbps1000kbpsBiDirectional) {
+
+ const int kFlowIds[] = {0, 1};
+ const size_t kNumFlows = sizeof(kFlowIds) / sizeof(kFlowIds[0]);
+
+ AdaptiveVideoSource source(kFlowIds[0], 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter choke(&uplink_, kFlowIds[0]);
+ RateCounterFilter counter(&uplink_, kFlowIds[0], "Receiver_0",
+ bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, kFlowIds[0], GetParam(), true, false);
+
+ AdaptiveVideoSource source2(kFlowIds[1], 30, 300, 0, 0);
+ VideoSender sender2(&downlink_, &source2, GetParam());
+ ChokeFilter choke2(&downlink_, kFlowIds[1]);
+ DelayFilter delay(&downlink_, CreateFlowIds(kFlowIds, kNumFlows));
+ RateCounterFilter counter2(&downlink_, kFlowIds[1], "Receiver_1",
+ bwe_names[GetParam()]);
+ PacketReceiver receiver2(&downlink_, kFlowIds[1], GetParam(), true, false);
+
+ choke2.set_capacity_kbps(500);
+ delay.SetOneWayDelayMs(0);
+
+ choke.set_capacity_kbps(1000);
+ choke.set_max_delay_ms(500);
+ RunFor(60 * 1000);
+ choke.set_capacity_kbps(500);
+ RunFor(60 * 1000);
+ choke.set_capacity_kbps(1000);
+ RunFor(60 * 1000);
+}
+
+TEST_P(BweSimulation, Choke1000kbps500kbps1000kbps) {
+
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter choke(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), true, false);
+
+ choke.set_capacity_kbps(1000);
+ choke.set_max_delay_ms(500);
+ RunFor(60 * 1000);
+ choke.set_capacity_kbps(500);
+ RunFor(60 * 1000);
+ choke.set_capacity_kbps(1000);
+ RunFor(60 * 1000);
+}
+
+TEST_P(BweSimulation, PacerChoke1000kbps500kbps1000kbps) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ PacedVideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter filter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), true, true);
+ filter.set_capacity_kbps(1000);
+ filter.set_max_delay_ms(500);
+ RunFor(60 * 1000);
+ filter.set_capacity_kbps(500);
+ RunFor(60 * 1000);
+ filter.set_capacity_kbps(1000);
+ RunFor(60 * 1000);
+}
+
+TEST_P(BweSimulation, PacerChoke10000kbps) {
+ PeriodicKeyFrameSource source(0, 30, 300, 0, 0, 1000);
+ PacedVideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter filter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), true, true);
+ filter.set_capacity_kbps(10000);
+ filter.set_max_delay_ms(500);
+ RunFor(60 * 1000);
+}
+
+TEST_P(BweSimulation, PacerChoke200kbps30kbps200kbps) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ PacedVideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter filter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), true, true);
+ filter.set_capacity_kbps(200);
+ filter.set_max_delay_ms(500);
+ RunFor(60 * 1000);
+ filter.set_capacity_kbps(30);
+ RunFor(60 * 1000);
+ filter.set_capacity_kbps(200);
+ RunFor(60 * 1000);
+}
+
+TEST_P(BweSimulation, Choke200kbps30kbps200kbps) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter filter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), true, true);
+ filter.set_capacity_kbps(200);
+ filter.set_max_delay_ms(500);
+ RunFor(60 * 1000);
+ filter.set_capacity_kbps(30);
+ RunFor(60 * 1000);
+ filter.set_capacity_kbps(200);
+ RunFor(60 * 1000);
+}
+
+TEST_P(BweSimulation, GoogleWifiTrace3Mbps) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ RateCounterFilter counter1(&uplink_, 0, "sender_output",
+ bwe_names[GetParam()]);
+ TraceBasedDeliveryFilter filter(&uplink_, 0, "link_capacity");
+ filter.set_max_delay_ms(500);
+ RateCounterFilter counter2(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), true, true);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("google-wifi-3mbps", "rx")));
+ RunFor(300 * 1000);
+}
+
+TEST_P(BweSimulation, LinearIncreasingCapacity) {
+ PeriodicKeyFrameSource source(0, 30, 300, 0, 0, 1000000);
+ PacedVideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter filter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), true, true);
+ filter.set_max_delay_ms(500);
+ const int kStartingCapacityKbps = 150;
+ const int kEndingCapacityKbps = 1500;
+ const int kStepKbps = 5;
+ const int kStepTimeMs = 1000;
+
+ for (int i = kStartingCapacityKbps; i <= kEndingCapacityKbps;
+ i += kStepKbps) {
+ filter.set_capacity_kbps(i);
+ RunFor(kStepTimeMs);
+ }
+}
+
+TEST_P(BweSimulation, LinearDecreasingCapacity) {
+ PeriodicKeyFrameSource source(0, 30, 300, 0, 0, 1000000);
+ PacedVideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter filter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), true, true);
+ filter.set_max_delay_ms(500);
+ const int kStartingCapacityKbps = 1500;
+ const int kEndingCapacityKbps = 150;
+ const int kStepKbps = -5;
+ const int kStepTimeMs = 1000;
+
+ for (int i = kStartingCapacityKbps; i >= kEndingCapacityKbps;
+ i += kStepKbps) {
+ filter.set_capacity_kbps(i);
+ RunFor(kStepTimeMs);
+ }
+}
+
+TEST_P(BweSimulation, PacerGoogleWifiTrace3Mbps) {
+ PeriodicKeyFrameSource source(0, 30, 300, 0, 0, 1000);
+ PacedVideoSender sender(&uplink_, &source, GetParam());
+ RateCounterFilter counter1(&uplink_, 0, "sender_output",
+ bwe_names[GetParam()]);
+ TraceBasedDeliveryFilter filter(&uplink_, 0, "link_capacity");
+ filter.set_max_delay_ms(500);
+ RateCounterFilter counter2(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), true, true);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("google-wifi-3mbps", "rx")));
+ RunFor(300 * 1000);
+}
+
+TEST_P(BweSimulation, SelfFairnessTest) {
+ srand(Clock::GetRealTimeClock()->TimeInMicroseconds());
+ const int kAllFlowIds[] = {0, 1, 2, 3};
+ const size_t kNumFlows = sizeof(kAllFlowIds) / sizeof(kAllFlowIds[0]);
+ rtc::scoped_ptr<VideoSource> sources[kNumFlows];
+ rtc::scoped_ptr<VideoSender> senders[kNumFlows];
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ // Streams started 20 seconds apart to give them different advantage when
+ // competing for the bandwidth.
+ sources[i].reset(new AdaptiveVideoSource(kAllFlowIds[i], 30, 300, 0,
+ i * (rand() % 40000)));
+ senders[i].reset(new VideoSender(&uplink_, sources[i].get(), GetParam()));
+ }
+
+ ChokeFilter choke(&uplink_, CreateFlowIds(kAllFlowIds, kNumFlows));
+ choke.set_capacity_kbps(1000);
+
+ rtc::scoped_ptr<RateCounterFilter> rate_counters[kNumFlows];
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ rate_counters[i].reset(
+ new RateCounterFilter(&uplink_, CreateFlowIds(&kAllFlowIds[i], 1),
+ "Receiver", bwe_names[GetParam()]));
+ }
+
+ RateCounterFilter total_utilization(
+ &uplink_, CreateFlowIds(kAllFlowIds, kNumFlows), "total_utilization",
+ "Total_link_utilization");
+
+ rtc::scoped_ptr<PacketReceiver> receivers[kNumFlows];
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ receivers[i].reset(new PacketReceiver(&uplink_, kAllFlowIds[i], GetParam(),
+ i == 0, false));
+ }
+
+ RunFor(30 * 60 * 1000);
+}
+
+TEST_P(BweSimulation, PacedSelfFairness50msTest) {
+ const int64_t kAverageOffsetMs = 20 * 1000;
+ const int kNumRmcatFlows = 4;
+ int64_t offsets_ms[kNumRmcatFlows];
+ offsets_ms[0] = random_.Rand(0, 2 * kAverageOffsetMs);
+ for (int i = 1; i < kNumRmcatFlows; ++i) {
+ offsets_ms[i] = offsets_ms[i - 1] + random_.Rand(0, 2 * kAverageOffsetMs);
+ }
+ RunFairnessTest(GetParam(), kNumRmcatFlows, 0, 1000, 3000, 50, 50, 0,
+ offsets_ms);
+}
+
+TEST_P(BweSimulation, PacedSelfFairness500msTest) {
+ const int64_t kAverageOffsetMs = 20 * 1000;
+ const int kNumRmcatFlows = 4;
+ int64_t offsets_ms[kNumRmcatFlows];
+ offsets_ms[0] = random_.Rand(0, 2 * kAverageOffsetMs);
+ for (int i = 1; i < kNumRmcatFlows; ++i) {
+ offsets_ms[i] = offsets_ms[i - 1] + random_.Rand(0, 2 * kAverageOffsetMs);
+ }
+ RunFairnessTest(GetParam(), kNumRmcatFlows, 0, 1000, 3000, 500, 50, 0,
+ offsets_ms);
+}
+
+TEST_P(BweSimulation, PacedSelfFairness1000msTest) {
+ const int64_t kAverageOffsetMs = 20 * 1000;
+ const int kNumRmcatFlows = 4;
+ int64_t offsets_ms[kNumRmcatFlows];
+ offsets_ms[0] = random_.Rand(0, 2 * kAverageOffsetMs);
+ for (int i = 1; i < kNumRmcatFlows; ++i) {
+ offsets_ms[i] = offsets_ms[i - 1] + random_.Rand(0, 2 * kAverageOffsetMs);
+ }
+ RunFairnessTest(GetParam(), 4, 0, 1000, 3000, 1000, 50, 0, offsets_ms);
+}
+
+TEST_P(BweSimulation, TcpFairness50msTest) {
+ const int64_t kAverageOffsetMs = 20 * 1000;
+ int64_t offset_ms[] = {random_.Rand(0, 2 * kAverageOffsetMs), 0};
+ RunFairnessTest(GetParam(), 1, 1, 1000, 2000, 50, 50, 0, offset_ms);
+}
+
+TEST_P(BweSimulation, TcpFairness500msTest) {
+ const int64_t kAverageOffsetMs = 20 * 1000;
+ int64_t offset_ms[] = {random_.Rand(0, 2 * kAverageOffsetMs), 0};
+ RunFairnessTest(GetParam(), 1, 1, 1000, 2000, 500, 50, 0, offset_ms);
+}
+
+TEST_P(BweSimulation, TcpFairness1000msTest) {
+ const int kAverageOffsetMs = 20 * 1000;
+ int64_t offset_ms[] = {random_.Rand(0, 2 * kAverageOffsetMs), 0};
+ RunFairnessTest(GetParam(), 1, 1, 1000, 2000, 1000, 50, 0, offset_ms);
+}
+
+// The following test cases begin with "Evaluation" as a referrence to the
+// Internet draft https://tools.ietf.org/html/draft-ietf-rmcat-eval-test-01.
+
+TEST_P(BweSimulation, Evaluation1) {
+ RunVariableCapacity1SingleFlow(GetParam());
+}
+
+TEST_P(BweSimulation, Evaluation2) {
+ const size_t kNumFlows = 2;
+ RunVariableCapacity2MultipleFlows(GetParam(), kNumFlows);
+}
+
+TEST_P(BweSimulation, Evaluation3) {
+ RunBidirectionalFlow(GetParam());
+}
+
+TEST_P(BweSimulation, Evaluation4) {
+ RunSelfFairness(GetParam());
+}
+
+TEST_P(BweSimulation, Evaluation5) {
+ RunRoundTripTimeFairness(GetParam());
+}
+
+TEST_P(BweSimulation, Evaluation6) {
+ RunLongTcpFairness(GetParam());
+}
+
+// Different calls to the Evaluation7 will create the same FileSizes
+// and StartingTimes as long as the seeds remain unchanged. This is essential
+// when calling it with multiple estimators for comparison purposes.
+TEST_P(BweSimulation, Evaluation7) {
+ const int kNumTcpFiles = 10;
+ RunMultipleShortTcpFairness(GetParam(),
+ BweTest::GetFileSizesBytes(kNumTcpFiles),
+ BweTest::GetStartingTimesMs(kNumTcpFiles));
+}
+
+TEST_P(BweSimulation, Evaluation8) {
+ RunPauseResumeFlows(GetParam());
+}
+
+// Following test cases begin with "GccComparison" run the
+// evaluation test cases for both GCC and other calling RMCAT.
+
+TEST_P(BweSimulation, GccComparison1) {
+ RunVariableCapacity1SingleFlow(GetParam());
+ BweTest gcc_test(false);
+ gcc_test.RunVariableCapacity1SingleFlow(kFullSendSideEstimator);
+}
+
+TEST_P(BweSimulation, GccComparison2) {
+ const size_t kNumFlows = 2;
+ RunVariableCapacity2MultipleFlows(GetParam(), kNumFlows);
+ BweTest gcc_test(false);
+ gcc_test.RunVariableCapacity2MultipleFlows(kFullSendSideEstimator, kNumFlows);
+}
+
+TEST_P(BweSimulation, GccComparison3) {
+ RunBidirectionalFlow(GetParam());
+ BweTest gcc_test(false);
+ gcc_test.RunBidirectionalFlow(kFullSendSideEstimator);
+}
+
+TEST_P(BweSimulation, GccComparison4) {
+ RunSelfFairness(GetParam());
+ BweTest gcc_test(false);
+ gcc_test.RunSelfFairness(GetParam());
+}
+
+TEST_P(BweSimulation, GccComparison5) {
+ RunRoundTripTimeFairness(GetParam());
+ BweTest gcc_test(false);
+ gcc_test.RunRoundTripTimeFairness(kFullSendSideEstimator);
+}
+
+TEST_P(BweSimulation, GccComparison6) {
+ RunLongTcpFairness(GetParam());
+ BweTest gcc_test(false);
+ gcc_test.RunLongTcpFairness(kFullSendSideEstimator);
+}
+
+TEST_P(BweSimulation, GccComparison7) {
+ const int kNumTcpFiles = 10;
+
+ std::vector<int> tcp_file_sizes_bytes =
+ BweTest::GetFileSizesBytes(kNumTcpFiles);
+ std::vector<int64_t> tcp_starting_times_ms =
+ BweTest::GetStartingTimesMs(kNumTcpFiles);
+
+ RunMultipleShortTcpFairness(GetParam(), tcp_file_sizes_bytes,
+ tcp_starting_times_ms);
+
+ BweTest gcc_test(false);
+ gcc_test.RunMultipleShortTcpFairness(
+ kFullSendSideEstimator, tcp_file_sizes_bytes, tcp_starting_times_ms);
+}
+
+TEST_P(BweSimulation, GccComparison8) {
+ RunPauseResumeFlows(GetParam());
+ BweTest gcc_test(false);
+ gcc_test.RunPauseResumeFlows(kFullSendSideEstimator);
+}
+
+TEST_P(BweSimulation, GccComparisonChoke) {
+ int array[] = {1000, 500, 1000};
+ std::vector<int> capacities_kbps(array, array + 3);
+ RunChoke(GetParam(), capacities_kbps);
+
+ BweTest gcc_test(false);
+ gcc_test.RunChoke(kFullSendSideEstimator, capacities_kbps);
+}
+
+#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
diff --git a/webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h b/webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h
new file mode 100644
index 0000000000..844fde5b71
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2012 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.
+ */
+
+#ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_BWE_DEFINES_H_
+#define WEBRTC_MODULES_RTP_RTCP_SOURCE_BWE_DEFINES_H_
+
+#include "webrtc/typedefs.h"
+
+#define BWE_MAX(a,b) ((a)>(b)?(a):(b))
+#define BWE_MIN(a,b) ((a)<(b)?(a):(b))
+
+namespace webrtc {
+
+static const int64_t kBitrateWindowMs = 1000;
+
+enum BandwidthUsage
+{
+ kBwNormal = 0,
+ kBwUnderusing = 1,
+ kBwOverusing = 2,
+};
+
+enum RateControlState
+{
+ kRcHold,
+ kRcIncrease,
+ kRcDecrease
+};
+
+enum RateControlRegion
+{
+ kRcNearMax,
+ kRcAboveMax,
+ kRcMaxUnknown
+};
+
+class RateControlInput
+{
+public:
+ RateControlInput(BandwidthUsage bwState,
+ uint32_t incomingBitRate,
+ double noiseVar)
+ : _bwState(bwState),
+ _incomingBitRate(incomingBitRate),
+ _noiseVar(noiseVar) {}
+
+ BandwidthUsage _bwState;
+ uint32_t _incomingBitRate;
+ double _noiseVar;
+};
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_BWE_DEFINES_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/include/mock/mock_remote_bitrate_estimator.h b/webrtc/modules/remote_bitrate_estimator/include/mock/mock_remote_bitrate_estimator.h
new file mode 100644
index 0000000000..91a8ac8707
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/include/mock/mock_remote_bitrate_estimator.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_MOCK_MOCK_REMOTE_BITRATE_ESTIMATOR_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_MOCK_MOCK_REMOTE_BITRATE_ESTIMATOR_H_
+
+#include <vector>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+
+namespace webrtc {
+
+class MockRemoteBitrateEstimator : public RemoteBitrateEstimator {
+ public:
+ MOCK_METHOD1(IncomingPacketFeedbackVector,
+ void(const std::vector<PacketInfo>&));
+ MOCK_METHOD4(IncomingPacket, void(int64_t, size_t, const RTPHeader&, bool));
+ MOCK_METHOD1(RemoveStream, void(unsigned int));
+ MOCK_CONST_METHOD2(LatestEstimate,
+ bool(std::vector<unsigned int>*, unsigned int*));
+ MOCK_CONST_METHOD1(GetStats, bool(ReceiveBandwidthEstimatorStats*));
+
+ // From CallStatsObserver;
+ MOCK_METHOD2(OnRttUpdate, void(int64_t, int64_t));
+
+ // From Module.
+ MOCK_METHOD0(TimeUntilNextProcess, int64_t());
+ MOCK_METHOD0(Process, int32_t());
+ MOCK_METHOD1(SetMinBitrate, void(int));
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_MOCK_MOCK_REMOTE_BITRATE_ESTIMATOR_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/include/mock/mock_remote_bitrate_observer.h b/webrtc/modules/remote_bitrate_estimator/include/mock/mock_remote_bitrate_observer.h
new file mode 100644
index 0000000000..edfac977a2
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/include/mock/mock_remote_bitrate_observer.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_MOCK_MOCK_REMOTE_BITRATE_ESTIMATOR_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_MOCK_MOCK_REMOTE_BITRATE_ESTIMATOR_H_
+
+#include <vector>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+
+namespace webrtc {
+
+class MockRemoteBitrateObserver : public RemoteBitrateObserver {
+ public:
+ MOCK_METHOD2(OnReceiveBitrateChanged,
+ void(const std::vector<unsigned int>& ssrcs, unsigned int bitrate));
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_MOCK_MOCK_REMOTE_BITRATE_ESTIMATOR_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h b/webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h
new file mode 100644
index 0000000000..4bd9d8c7bc
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2012 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.
+ */
+
+// This class estimates the incoming available bandwidth.
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_
+
+#include <map>
+#include <vector>
+
+#include "webrtc/common_types.h"
+#include "webrtc/modules/interface/module.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+class Clock;
+
+// RemoteBitrateObserver is used to signal changes in bitrate estimates for
+// the incoming streams.
+class RemoteBitrateObserver {
+ public:
+ // Called when a receive channel group has a new bitrate estimate for the
+ // incoming streams.
+ virtual void OnReceiveBitrateChanged(const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) = 0;
+
+ virtual ~RemoteBitrateObserver() {}
+};
+
+struct ReceiveBandwidthEstimatorStats {
+ ReceiveBandwidthEstimatorStats() : total_propagation_time_delta_ms(0) {}
+
+ // The "propagation_time_delta" of a frame is defined as (d_arrival - d_sent),
+ // where d_arrival is the delta of the arrival times of the frame and the
+ // previous frame, d_sent is the delta of the sent times of the frame and
+ // the previous frame. The sent time is calculated from the RTP timestamp.
+
+ // |total_propagation_time_delta_ms| is the sum of the propagation_time_deltas
+ // of all received frames, except that it's is adjusted to 0 when it becomes
+ // negative.
+ int total_propagation_time_delta_ms;
+ // The propagation_time_deltas for the frames arrived in the last
+ // kProcessIntervalMs using the clock passed to
+ // RemoteBitrateEstimatorFactory::Create.
+ std::vector<int> recent_propagation_time_delta_ms;
+ // The arrival times for the frames arrived in the last kProcessIntervalMs
+ // using the clock passed to RemoteBitrateEstimatorFactory::Create.
+ std::vector<int64_t> recent_arrival_time_ms;
+};
+
+class RemoteBitrateEstimator : public CallStatsObserver, public Module {
+ public:
+ static const int kDefaultMinBitrateBps = 30000;
+ virtual ~RemoteBitrateEstimator() {}
+
+ virtual void IncomingPacketFeedbackVector(
+ const std::vector<PacketInfo>& packet_feedback_vector) {
+ assert(false);
+ }
+
+ // Called for each incoming packet. Updates the incoming payload bitrate
+ // estimate and the over-use detector. If an over-use is detected the
+ // remote bitrate estimate will be updated. Note that |payload_size| is the
+ // packet size excluding headers.
+ // Note that |arrival_time_ms| can be of an arbitrary time base.
+ virtual void IncomingPacket(int64_t arrival_time_ms,
+ size_t payload_size,
+ const RTPHeader& header,
+ bool was_paced) = 0;
+
+ // Removes all data for |ssrc|.
+ virtual void RemoveStream(unsigned int ssrc) = 0;
+
+ // Returns true if a valid estimate exists and sets |bitrate_bps| to the
+ // estimated payload bitrate in bits per second. |ssrcs| is the list of ssrcs
+ // currently being received and of which the bitrate estimate is based upon.
+ virtual bool LatestEstimate(std::vector<unsigned int>* ssrcs,
+ unsigned int* bitrate_bps) const = 0;
+
+ // Returns true if the statistics are available.
+ virtual bool GetStats(ReceiveBandwidthEstimatorStats* output) const = 0;
+
+ virtual void SetMinBitrate(int min_bitrate_bps) = 0;
+
+ protected:
+ static const int64_t kProcessIntervalMs = 500;
+ static const int64_t kStreamTimeOutMs = 2000;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/include/send_time_history.h b/webrtc/modules/remote_bitrate_estimator/include/send_time_history.h
new file mode 100644
index 0000000000..92d2e28132
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/include/send_time_history.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_BITRATE_CONTROLLER_SEND_TIME_HISTORY_H_
+#define WEBRTC_MODULES_BITRATE_CONTROLLER_SEND_TIME_HISTORY_H_
+
+#include <map>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/basictypes.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+
+namespace webrtc {
+
+class SendTimeHistory {
+ public:
+ SendTimeHistory(Clock* clock, int64_t packet_age_limit);
+ virtual ~SendTimeHistory();
+
+ void AddAndRemoveOld(uint16_t sequence_number, size_t length, bool was_paced);
+ bool OnSentPacket(uint16_t sequence_number, int64_t timestamp);
+ // Look up PacketInfo for a sent packet, based on the sequence number, and
+ // populate all fields except for receive_time. The packet parameter must
+ // thus be non-null and have the sequence_number field set.
+ bool GetInfo(PacketInfo* packet, bool remove);
+ void Clear();
+
+ private:
+ void EraseOld();
+ void UpdateOldestSequenceNumber();
+
+ Clock* const clock_;
+ const int64_t packet_age_limit_;
+ uint16_t oldest_sequence_number_; // Oldest may not be lowest.
+ std::map<uint16_t, PacketInfo> history_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(SendTimeHistory);
+};
+
+} // namespace webrtc
+#endif // WEBRTC_MODULES_BITRATE_CONTROLLER_SEND_TIME_HISTORY_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/inter_arrival.cc b/webrtc/modules/remote_bitrate_estimator/inter_arrival.cc
new file mode 100644
index 0000000000..3dee305bad
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/inter_arrival.cc
@@ -0,0 +1,130 @@
+/*
+ * 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/modules/remote_bitrate_estimator/inter_arrival.h"
+
+#include <algorithm>
+#include <cassert>
+
+#include "webrtc/base/logging.h"
+#include "webrtc/modules/interface/module_common_types.h"
+
+namespace webrtc {
+
+static const int kBurstDeltaThresholdMs = 5;
+
+InterArrival::InterArrival(uint32_t timestamp_group_length_ticks,
+ double timestamp_to_ms_coeff,
+ bool enable_burst_grouping)
+ : kTimestampGroupLengthTicks(timestamp_group_length_ticks),
+ current_timestamp_group_(),
+ prev_timestamp_group_(),
+ timestamp_to_ms_coeff_(timestamp_to_ms_coeff),
+ burst_grouping_(enable_burst_grouping) {}
+
+bool InterArrival::ComputeDeltas(uint32_t timestamp,
+ int64_t arrival_time_ms,
+ size_t packet_size,
+ uint32_t* timestamp_delta,
+ int64_t* arrival_time_delta_ms,
+ int* packet_size_delta) {
+ assert(timestamp_delta != NULL);
+ assert(arrival_time_delta_ms != NULL);
+ assert(packet_size_delta != NULL);
+ bool calculated_deltas = false;
+ if (current_timestamp_group_.IsFirstPacket()) {
+ // We don't have enough data to update the filter, so we store it until we
+ // have two frames of data to process.
+ current_timestamp_group_.timestamp = timestamp;
+ current_timestamp_group_.first_timestamp = timestamp;
+ } else if (!PacketInOrder(timestamp)) {
+ return false;
+ } else if (NewTimestampGroup(arrival_time_ms, timestamp)) {
+ // First packet of a later frame, the previous frame sample is ready.
+ if (prev_timestamp_group_.complete_time_ms >= 0) {
+ *timestamp_delta = current_timestamp_group_.timestamp -
+ prev_timestamp_group_.timestamp;
+ *arrival_time_delta_ms = current_timestamp_group_.complete_time_ms -
+ prev_timestamp_group_.complete_time_ms;
+ if (*arrival_time_delta_ms < 0) {
+ // The group of packets has been reordered since receiving its local
+ // arrival timestamp.
+ LOG(LS_WARNING) << "Packets are being reordered on the path from the "
+ "socket to the bandwidth estimator. Ignoring this "
+ "packet for bandwidth estimation.";
+ return false;
+ }
+ assert(*arrival_time_delta_ms >= 0);
+ *packet_size_delta = static_cast<int>(current_timestamp_group_.size) -
+ static_cast<int>(prev_timestamp_group_.size);
+ calculated_deltas = true;
+ }
+ prev_timestamp_group_ = current_timestamp_group_;
+ // The new timestamp is now the current frame.
+ current_timestamp_group_.first_timestamp = timestamp;
+ current_timestamp_group_.timestamp = timestamp;
+ current_timestamp_group_.size = 0;
+ }
+ else {
+ current_timestamp_group_.timestamp = LatestTimestamp(
+ current_timestamp_group_.timestamp, timestamp);
+ }
+ // Accumulate the frame size.
+ current_timestamp_group_.size += packet_size;
+ current_timestamp_group_.complete_time_ms = arrival_time_ms;
+
+ return calculated_deltas;
+}
+
+bool InterArrival::PacketInOrder(uint32_t timestamp) {
+ if (current_timestamp_group_.IsFirstPacket()) {
+ return true;
+ } else {
+ // Assume that a diff which is bigger than half the timestamp interval
+ // (32 bits) must be due to reordering. This code is almost identical to
+ // that in IsNewerTimestamp() in module_common_types.h.
+ uint32_t timestamp_diff = timestamp -
+ current_timestamp_group_.first_timestamp;
+ return timestamp_diff < 0x80000000;
+ }
+}
+
+// Assumes that |timestamp| is not reordered compared to
+// |current_timestamp_group_|.
+bool InterArrival::NewTimestampGroup(int64_t arrival_time_ms,
+ uint32_t timestamp) const {
+ if (current_timestamp_group_.IsFirstPacket()) {
+ return false;
+ } else if (BelongsToBurst(arrival_time_ms, timestamp)) {
+ return false;
+ } else {
+ uint32_t timestamp_diff = timestamp -
+ current_timestamp_group_.first_timestamp;
+ return timestamp_diff > kTimestampGroupLengthTicks;
+ }
+}
+
+bool InterArrival::BelongsToBurst(int64_t arrival_time_ms,
+ uint32_t timestamp) const {
+ if (!burst_grouping_) {
+ return false;
+ }
+ assert(current_timestamp_group_.complete_time_ms >= 0);
+ int64_t arrival_time_delta_ms = arrival_time_ms -
+ current_timestamp_group_.complete_time_ms;
+ uint32_t timestamp_diff = timestamp - current_timestamp_group_.timestamp;
+ int64_t ts_delta_ms = timestamp_to_ms_coeff_ * timestamp_diff + 0.5;
+ if (ts_delta_ms == 0)
+ return true;
+ int propagation_delta_ms = arrival_time_delta_ms - ts_delta_ms;
+ return propagation_delta_ms < 0 &&
+ arrival_time_delta_ms <= kBurstDeltaThresholdMs;
+}
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/inter_arrival.h b/webrtc/modules/remote_bitrate_estimator/inter_arrival.h
new file mode 100644
index 0000000000..427bafcf96
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/inter_arrival.h
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_
+
+#include <cstddef>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+// Helper class to compute the inter-arrival time delta and the size delta
+// between two timestamp groups. A timestamp is a 32 bit unsigned number with
+// a client defined rate.
+class InterArrival {
+ public:
+ // A timestamp group is defined as all packets with a timestamp which are at
+ // most timestamp_group_length_ticks older than the first timestamp in that
+ // group.
+ InterArrival(uint32_t timestamp_group_length_ticks,
+ double timestamp_to_ms_coeff,
+ bool enable_burst_grouping);
+
+ // This function returns true if a delta was computed, or false if the current
+ // group is still incomplete or if only one group has been completed.
+ // |timestamp| is the timestamp.
+ // |arrival_time_ms| is the local time at which the packet arrived.
+ // |packet_size| is the size of the packet.
+ // |timestamp_delta| (output) is the computed timestamp delta.
+ // |arrival_time_delta_ms| (output) is the computed arrival-time delta.
+ // |packet_size_delta| (output) is the computed size delta.
+ bool ComputeDeltas(uint32_t timestamp,
+ int64_t arrival_time_ms,
+ size_t packet_size,
+ uint32_t* timestamp_delta,
+ int64_t* arrival_time_delta_ms,
+ int* packet_size_delta);
+
+ private:
+ struct TimestampGroup {
+ TimestampGroup()
+ : size(0),
+ first_timestamp(0),
+ timestamp(0),
+ complete_time_ms(-1) {}
+
+ bool IsFirstPacket() const {
+ return complete_time_ms == -1;
+ }
+
+ size_t size;
+ uint32_t first_timestamp;
+ uint32_t timestamp;
+ int64_t complete_time_ms;
+ };
+
+ // Returns true if the packet with timestamp |timestamp| arrived in order.
+ bool PacketInOrder(uint32_t timestamp);
+
+ // Returns true if the last packet was the end of the current batch and the
+ // packet with |timestamp| is the first of a new batch.
+ bool NewTimestampGroup(int64_t arrival_time_ms, uint32_t timestamp) const;
+
+ bool BelongsToBurst(int64_t arrival_time_ms, uint32_t timestamp) const;
+
+ const uint32_t kTimestampGroupLengthTicks;
+ TimestampGroup current_timestamp_group_;
+ TimestampGroup prev_timestamp_group_;
+ double timestamp_to_ms_coeff_;
+ bool burst_grouping_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(InterArrival);
+};
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/inter_arrival_unittest.cc b/webrtc/modules/remote_bitrate_estimator/inter_arrival_unittest.cc
new file mode 100644
index 0000000000..64f7ccae6b
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/inter_arrival_unittest.cc
@@ -0,0 +1,422 @@
+/*
+ * 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 "testing/gtest/include/gtest/gtest.h"
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/inter_arrival.h"
+
+namespace webrtc {
+namespace testing {
+
+enum {
+ kTimestampGroupLengthUs = 5000,
+ kMinStep = 20,
+ kTriggerNewGroupUs = kTimestampGroupLengthUs + kMinStep,
+ kBurstThresholdMs = 5,
+ kAbsSendTimeFraction = 18,
+ kAbsSendTimeInterArrivalUpshift = 8,
+ kInterArrivalShift = kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift,
+};
+
+const double kRtpTimestampToMs = 1.0 / 90.0;
+const double kAstToMs = 1000.0 / static_cast<double>(1 << kInterArrivalShift);
+
+class InterArrivalTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ inter_arrival_rtp_.reset(new InterArrival(
+ MakeRtpTimestamp(kTimestampGroupLengthUs),
+ kRtpTimestampToMs,
+ true));
+ inter_arrival_ast_.reset(new InterArrival(
+ MakeAbsSendTime(kTimestampGroupLengthUs),
+ kAstToMs,
+ true));
+ }
+
+ // Test that neither inter_arrival instance complete the timestamp group from
+ // the given data.
+ void ExpectFalse(int64_t timestamp_us, int64_t arrival_time_ms,
+ size_t packet_size) {
+ InternalExpectFalse(inter_arrival_rtp_.get(),
+ MakeRtpTimestamp(timestamp_us), arrival_time_ms,
+ packet_size);
+ InternalExpectFalse(inter_arrival_ast_.get(), MakeAbsSendTime(timestamp_us),
+ arrival_time_ms, packet_size);
+ }
+
+ // Test that both inter_arrival instances complete the timestamp group from
+ // the given data and that all returned deltas are as expected (except
+ // timestamp delta, which is rounded from us to different ranges and must
+ // match within an interval, given in |timestamp_near].
+ void ExpectTrue(int64_t timestamp_us, int64_t arrival_time_ms,
+ size_t packet_size, int64_t expected_timestamp_delta_us,
+ int64_t expected_arrival_time_delta_ms,
+ int expected_packet_size_delta,
+ uint32_t timestamp_near) {
+ InternalExpectTrue(inter_arrival_rtp_.get(), MakeRtpTimestamp(timestamp_us),
+ arrival_time_ms, packet_size,
+ MakeRtpTimestamp(expected_timestamp_delta_us),
+ expected_arrival_time_delta_ms,
+ expected_packet_size_delta, timestamp_near);
+ InternalExpectTrue(inter_arrival_ast_.get(), MakeAbsSendTime(timestamp_us),
+ arrival_time_ms, packet_size,
+ MakeAbsSendTime(expected_timestamp_delta_us),
+ expected_arrival_time_delta_ms,
+ expected_packet_size_delta, timestamp_near << 8);
+ }
+
+ void WrapTestHelper(int64_t wrap_start_us, uint32_t timestamp_near,
+ bool unorderly_within_group) {
+ // Step through the range of a 32 bit int, 1/4 at a time to not cause
+ // packets close to wraparound to be judged as out of order.
+
+ // G1
+ int64_t arrival_time = 17;
+ ExpectFalse(0, arrival_time, 1);
+
+ // G2
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectFalse(wrap_start_us / 4, arrival_time, 1);
+
+ // G3
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectTrue(wrap_start_us / 2, arrival_time, 1,
+ wrap_start_us / 4, 6, 0, // Delta G2-G1
+ 0);
+
+ // G4
+ arrival_time += kBurstThresholdMs + 1;
+ int64_t g4_arrival_time = arrival_time;
+ ExpectTrue(wrap_start_us / 2 + wrap_start_us / 4, arrival_time, 1,
+ wrap_start_us / 4, 6, 0, // Delta G3-G2
+ timestamp_near);
+
+ // G5
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectTrue(wrap_start_us, arrival_time, 2,
+ wrap_start_us / 4, 6, 0, // Delta G4-G3
+ timestamp_near);
+ for (int i = 0; i < 10; ++i) {
+ // Slowly step across the wrap point.
+ arrival_time += kBurstThresholdMs + 1;
+ if (unorderly_within_group) {
+ // These packets arrive with timestamps in decreasing order but are
+ // nevertheless accumulated to group because their timestamps are higher
+ // than the initial timestamp of the group.
+ ExpectFalse(wrap_start_us + kMinStep * (9 - i), arrival_time, 1);
+ } else {
+ ExpectFalse(wrap_start_us + kMinStep * i, arrival_time, 1);
+ }
+ }
+ int64_t g5_arrival_time = arrival_time;
+
+ // This packet is out of order and should be dropped.
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectFalse(wrap_start_us - 100, arrival_time, 100);
+
+ // G6
+ arrival_time += kBurstThresholdMs + 1;
+ int64_t g6_arrival_time = arrival_time;
+ ExpectTrue(wrap_start_us + kTriggerNewGroupUs, arrival_time, 10,
+ wrap_start_us / 4 + 9 * kMinStep,
+ g5_arrival_time - g4_arrival_time,
+ (2 + 10) - 1, // Delta G5-G4
+ timestamp_near);
+
+ // This packet is out of order and should be dropped.
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectFalse(wrap_start_us + kTimestampGroupLengthUs, arrival_time, 100);
+
+ // G7
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectTrue(wrap_start_us + 2 * kTriggerNewGroupUs,
+ arrival_time, 100,
+ // Delta G6-G5
+ kTriggerNewGroupUs - 9 * kMinStep,
+ g6_arrival_time - g5_arrival_time,
+ 10 - (2 + 10),
+ timestamp_near);
+ }
+
+ private:
+ static uint32_t MakeRtpTimestamp(int64_t us) {
+ return static_cast<uint32_t>(static_cast<uint64_t>(us * 90 + 500) / 1000);
+ }
+
+ static uint32_t MakeAbsSendTime(int64_t us) {
+ uint32_t absolute_send_time = static_cast<uint32_t>(
+ ((static_cast<uint64_t>(us) << 18) + 500000) / 1000000) & 0x00FFFFFFul;
+ return absolute_send_time << 8;
+ }
+
+ static void InternalExpectFalse(InterArrival* inter_arrival,
+ uint32_t timestamp, int64_t arrival_time_ms,
+ size_t packet_size) {
+ uint32_t dummy_timestamp = 101;
+ int64_t dummy_arrival_time_ms = 303;
+ int dummy_packet_size = 909;
+ bool computed = inter_arrival->ComputeDeltas(timestamp,
+ arrival_time_ms,
+ packet_size,
+ &dummy_timestamp,
+ &dummy_arrival_time_ms,
+ &dummy_packet_size);
+ EXPECT_EQ(computed, false);
+ EXPECT_EQ(101ul, dummy_timestamp);
+ EXPECT_EQ(303, dummy_arrival_time_ms);
+ EXPECT_EQ(909, dummy_packet_size);
+ }
+
+ static void InternalExpectTrue(InterArrival* inter_arrival,
+ uint32_t timestamp, int64_t arrival_time_ms,
+ size_t packet_size,
+ uint32_t expected_timestamp_delta,
+ int64_t expected_arrival_time_delta_ms,
+ int expected_packet_size_delta,
+ uint32_t timestamp_near) {
+ uint32_t delta_timestamp = 101;
+ int64_t delta_arrival_time_ms = 303;
+ int delta_packet_size = 909;
+ bool computed = inter_arrival->ComputeDeltas(timestamp,
+ arrival_time_ms,
+ packet_size,
+ &delta_timestamp,
+ &delta_arrival_time_ms,
+ &delta_packet_size);
+ EXPECT_EQ(true, computed);
+ EXPECT_NEAR(expected_timestamp_delta, delta_timestamp, timestamp_near);
+ EXPECT_EQ(expected_arrival_time_delta_ms, delta_arrival_time_ms);
+ EXPECT_EQ(expected_packet_size_delta, delta_packet_size);
+ }
+
+ rtc::scoped_ptr<InterArrival> inter_arrival_rtp_;
+ rtc::scoped_ptr<InterArrival> inter_arrival_ast_;
+};
+
+TEST_F(InterArrivalTest, FirstPacket) {
+ ExpectFalse(0, 17, 1);
+}
+
+TEST_F(InterArrivalTest, FirstGroup) {
+ // G1
+ int64_t arrival_time = 17;
+ int64_t g1_arrival_time = arrival_time;
+ ExpectFalse(0, arrival_time, 1);
+
+ // G2
+ arrival_time += kBurstThresholdMs + 1;
+ int64_t g2_arrival_time = arrival_time;
+ ExpectFalse(kTriggerNewGroupUs, arrival_time, 2);
+
+ // G3
+ // Only once the first packet of the third group arrives, do we see the deltas
+ // between the first two.
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectTrue(2 * kTriggerNewGroupUs, arrival_time, 1,
+ // Delta G2-G1
+ kTriggerNewGroupUs, g2_arrival_time - g1_arrival_time, 1,
+ 0);
+}
+
+TEST_F(InterArrivalTest, SecondGroup) {
+ // G1
+ int64_t arrival_time = 17;
+ int64_t g1_arrival_time = arrival_time;
+ ExpectFalse(0, arrival_time, 1);
+
+ // G2
+ arrival_time += kBurstThresholdMs + 1;
+ int64_t g2_arrival_time = arrival_time;
+ ExpectFalse(kTriggerNewGroupUs, arrival_time, 2);
+
+ // G3
+ arrival_time += kBurstThresholdMs + 1;
+ int64_t g3_arrival_time = arrival_time;
+ ExpectTrue(2 * kTriggerNewGroupUs, arrival_time, 1,
+ // Delta G2-G1
+ kTriggerNewGroupUs, g2_arrival_time - g1_arrival_time, 1,
+ 0);
+
+ // G4
+ // First packet of 4th group yields deltas between group 2 and 3.
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectTrue(3 * kTriggerNewGroupUs, arrival_time, 2,
+ // Delta G3-G2
+ kTriggerNewGroupUs, g3_arrival_time - g2_arrival_time, -1,
+ 0);
+}
+
+TEST_F(InterArrivalTest, AccumulatedGroup) {
+ // G1
+ int64_t arrival_time = 17;
+ int64_t g1_arrival_time = arrival_time;
+ ExpectFalse(0, arrival_time, 1);
+
+ // G2
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectFalse(kTriggerNewGroupUs, 28, 2);
+ int64_t timestamp = kTriggerNewGroupUs;
+ for (int i = 0; i < 10; ++i) {
+ // A bunch of packets arriving within the same group.
+ arrival_time += kBurstThresholdMs + 1;
+ timestamp += kMinStep;
+ ExpectFalse(timestamp, arrival_time, 1);
+ }
+ int64_t g2_arrival_time = arrival_time;
+ int64_t g2_timestamp = timestamp;
+
+ // G3
+ arrival_time = 500;
+ ExpectTrue(2 * kTriggerNewGroupUs, arrival_time, 100,
+ g2_timestamp, g2_arrival_time - g1_arrival_time,
+ (2 + 10) - 1, // Delta G2-G1
+ 0);
+}
+
+TEST_F(InterArrivalTest, OutOfOrderPacket) {
+ // G1
+ int64_t arrival_time = 17;
+ int64_t timestamp = 0;
+ ExpectFalse(timestamp, arrival_time, 1);
+ int64_t g1_timestamp = timestamp;
+ int64_t g1_arrival_time = arrival_time;
+
+ // G2
+ arrival_time += 11;
+ timestamp += kTriggerNewGroupUs;
+ ExpectFalse(timestamp, 28, 2);
+ for (int i = 0; i < 10; ++i) {
+ arrival_time += kBurstThresholdMs + 1;
+ timestamp += kMinStep;
+ ExpectFalse(timestamp, arrival_time, 1);
+ }
+ int64_t g2_timestamp = timestamp;
+ int64_t g2_arrival_time = arrival_time;
+
+ // This packet is out of order and should be dropped.
+ arrival_time = 281;
+ ExpectFalse(g1_timestamp, arrival_time, 100);
+
+ // G3
+ arrival_time = 500;
+ timestamp = 2 * kTriggerNewGroupUs;
+ ExpectTrue(timestamp, arrival_time, 100,
+ // Delta G2-G1
+ g2_timestamp - g1_timestamp, g2_arrival_time - g1_arrival_time,
+ (2 + 10) - 1,
+ 0);
+}
+
+TEST_F(InterArrivalTest, OutOfOrderWithinGroup) {
+ // G1
+ int64_t arrival_time = 17;
+ int64_t timestamp = 0;
+ ExpectFalse(timestamp, arrival_time, 1);
+ int64_t g1_timestamp = timestamp;
+ int64_t g1_arrival_time = arrival_time;
+
+ // G2
+ timestamp += kTriggerNewGroupUs;
+ arrival_time += 11;
+ ExpectFalse(kTriggerNewGroupUs, 28, 2);
+ timestamp += 10 * kMinStep;
+ int64_t g2_timestamp = timestamp;
+ for (int i = 0; i < 10; ++i) {
+ // These packets arrive with timestamps in decreasing order but are
+ // nevertheless accumulated to group because their timestamps are higher
+ // than the initial timestamp of the group.
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectFalse(timestamp, arrival_time, 1);
+ timestamp -= kMinStep;
+ }
+ int64_t g2_arrival_time = arrival_time;
+
+ // However, this packet is deemed out of order and should be dropped.
+ arrival_time = 281;
+ timestamp = g1_timestamp;
+ ExpectFalse(timestamp, arrival_time, 100);
+
+ // G3
+ timestamp = 2 * kTriggerNewGroupUs;
+ arrival_time = 500;
+ ExpectTrue(timestamp, arrival_time, 100,
+ g2_timestamp - g1_timestamp, g2_arrival_time - g1_arrival_time,
+ (2 + 10) - 1,
+ 0);
+}
+
+TEST_F(InterArrivalTest, TwoBursts) {
+ // G1
+ int64_t g1_arrival_time = 17;
+ ExpectFalse(0, g1_arrival_time, 1);
+
+ // G2
+ int64_t timestamp = kTriggerNewGroupUs;
+ int64_t arrival_time = 100; // Simulate no packets arriving for 100 ms.
+ for (int i = 0; i < 10; ++i) {
+ // A bunch of packets arriving in one burst (within 5 ms apart).
+ timestamp += 30000;
+ arrival_time += kBurstThresholdMs;
+ ExpectFalse(timestamp, arrival_time, 1);
+ }
+ int64_t g2_arrival_time = arrival_time;
+ int64_t g2_timestamp = timestamp;
+
+ // G3
+ timestamp += 30000;
+ arrival_time += kBurstThresholdMs + 1;
+ ExpectTrue(timestamp, arrival_time, 100,
+ g2_timestamp, g2_arrival_time - g1_arrival_time,
+ 10 - 1, // Delta G2-G1
+ 0);
+}
+
+
+TEST_F(InterArrivalTest, NoBursts) {
+ // G1
+ ExpectFalse(0, 17, 1);
+
+ // G2
+ int64_t timestamp = kTriggerNewGroupUs;
+ int64_t arrival_time = 28;
+ ExpectFalse(timestamp, arrival_time, 2);
+
+ // G3
+ ExpectTrue(kTriggerNewGroupUs + 30000, arrival_time + kBurstThresholdMs + 1,
+ 100, timestamp - 0, arrival_time - 17,
+ 2 - 1, // Delta G2-G1
+ 0);
+}
+
+// Yields 0xfffffffe when converted to internal representation in
+// inter_arrival_rtp_ and inter_arrival_ast_ respectively.
+static const int64_t kStartRtpTimestampWrapUs = 47721858827;
+static const int64_t kStartAbsSendTimeWrapUs = 63999995;
+
+TEST_F(InterArrivalTest, RtpTimestampWrap) {
+ WrapTestHelper(kStartRtpTimestampWrapUs, 1, false);
+}
+
+TEST_F(InterArrivalTest, AbsSendTimeWrap) {
+ WrapTestHelper(kStartAbsSendTimeWrapUs, 1, false);
+}
+
+TEST_F(InterArrivalTest, RtpTimestampWrapOutOfOrderWithinGroup) {
+ WrapTestHelper(kStartRtpTimestampWrapUs, 1, true);
+}
+
+TEST_F(InterArrivalTest, AbsSendTimeWrapOutOfOrderWithinGroup) {
+ WrapTestHelper(kStartAbsSendTimeWrapUs, 1, true);
+}
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/overuse_detector.cc b/webrtc/modules/remote_bitrate_estimator/overuse_detector.cc
new file mode 100644
index 0000000000..c9340892f2
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/overuse_detector.cc
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2012 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/modules/remote_bitrate_estimator/overuse_detector.h"
+
+#include <algorithm>
+#include <sstream>
+#include <math.h>
+#include <stdlib.h>
+
+#include "webrtc/base/checks.h"
+#include "webrtc/base/common.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/modules/rtp_rtcp/source/rtp_utility.h"
+#include "webrtc/system_wrappers/include/field_trial.h"
+#include "webrtc/system_wrappers/include/trace.h"
+
+namespace webrtc {
+
+const char kAdaptiveThresholdExperiment[] = "WebRTC-AdaptiveBweThreshold";
+const char kEnabledPrefix[] = "Enabled";
+const size_t kEnabledPrefixLength = sizeof(kEnabledPrefix) - 1;
+const size_t kMinExperimentLength = kEnabledPrefixLength + 3;
+
+const double kMaxAdaptOffsetMs = 15.0;
+const double kOverUsingTimeThreshold = 10;
+
+bool AdaptiveThresholdExperimentIsEnabled() {
+ std::string experiment_string =
+ webrtc::field_trial::FindFullName(kAdaptiveThresholdExperiment);
+ if (experiment_string.length() < kMinExperimentLength)
+ return false;
+ return experiment_string.substr(0, kEnabledPrefixLength) == kEnabledPrefix;
+}
+
+// Gets thresholds from the experiment name following the format
+// "WebRTC-AdaptiveBweThreshold/Enabled-0.5,0.002/".
+bool ReadExperimentConstants(double* k_up, double* k_down) {
+ std::string experiment_string =
+ webrtc::field_trial::FindFullName(kAdaptiveThresholdExperiment);
+ return sscanf(experiment_string.substr(kEnabledPrefixLength + 1).c_str(),
+ "%lf,%lf", k_up, k_down) == 2;
+}
+
+OveruseDetector::OveruseDetector(const OverUseDetectorOptions& options)
+ : in_experiment_(AdaptiveThresholdExperimentIsEnabled()),
+ k_up_(0.01),
+ k_down_(0.00018),
+ overusing_time_threshold_(100),
+ options_(options),
+ threshold_(12.5),
+ last_update_ms_(-1),
+ prev_offset_(0.0),
+ time_over_using_(-1),
+ overuse_counter_(0),
+ hypothesis_(kBwNormal) {
+ if (in_experiment_)
+ InitializeExperiment();
+}
+
+OveruseDetector::~OveruseDetector() {}
+
+BandwidthUsage OveruseDetector::State() const {
+ return hypothesis_;
+}
+
+BandwidthUsage OveruseDetector::Detect(double offset,
+ double ts_delta,
+ int num_of_deltas,
+ int64_t now_ms) {
+ if (num_of_deltas < 2) {
+ return kBwNormal;
+ }
+ const double prev_offset = prev_offset_;
+ prev_offset_ = offset;
+ const double T = std::min(num_of_deltas, 60) * offset;
+ BWE_TEST_LOGGING_PLOT(1, "offset", now_ms, T);
+ BWE_TEST_LOGGING_PLOT(1, "threshold", now_ms, threshold_);
+ if (T > threshold_) {
+ if (time_over_using_ == -1) {
+ // Initialize the timer. Assume that we've been
+ // over-using half of the time since the previous
+ // sample.
+ time_over_using_ = ts_delta / 2;
+ } else {
+ // Increment timer
+ time_over_using_ += ts_delta;
+ }
+ overuse_counter_++;
+ if (time_over_using_ > overusing_time_threshold_ && overuse_counter_ > 1) {
+ if (offset >= prev_offset) {
+ time_over_using_ = 0;
+ overuse_counter_ = 0;
+ hypothesis_ = kBwOverusing;
+ }
+ }
+ } else if (T < -threshold_) {
+ time_over_using_ = -1;
+ overuse_counter_ = 0;
+ hypothesis_ = kBwUnderusing;
+ } else {
+ time_over_using_ = -1;
+ overuse_counter_ = 0;
+ hypothesis_ = kBwNormal;
+ }
+
+ UpdateThreshold(T, now_ms);
+
+ return hypothesis_;
+}
+
+void OveruseDetector::UpdateThreshold(double modified_offset, int64_t now_ms) {
+ if (!in_experiment_)
+ return;
+
+ if (last_update_ms_ == -1)
+ last_update_ms_ = now_ms;
+
+ if (fabs(modified_offset) > threshold_ + kMaxAdaptOffsetMs) {
+ // Avoid adapting the threshold to big latency spikes, caused e.g.,
+ // by a sudden capacity drop.
+ last_update_ms_ = now_ms;
+ return;
+ }
+
+ const double k = fabs(modified_offset) < threshold_ ? k_down_ : k_up_;
+ threshold_ +=
+ k * (fabs(modified_offset) - threshold_) * (now_ms - last_update_ms_);
+
+ const double kMinThreshold = 6;
+ const double kMaxThreshold = 600;
+ threshold_ = std::min(std::max(threshold_, kMinThreshold), kMaxThreshold);
+
+ last_update_ms_ = now_ms;
+}
+
+void OveruseDetector::InitializeExperiment() {
+ RTC_DCHECK(in_experiment_);
+ double k_up = 0.0;
+ double k_down = 0.0;
+ overusing_time_threshold_ = kOverUsingTimeThreshold;
+ if (ReadExperimentConstants(&k_up, &k_down)) {
+ k_up_ = k_up;
+ k_down_ = k_down;
+ }
+}
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/overuse_detector.h b/webrtc/modules/remote_bitrate_estimator/overuse_detector.h
new file mode 100644
index 0000000000..bb69a8a0a1
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/overuse_detector.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2012 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.
+ */
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_
+
+#include <list>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+enum RateControlRegion;
+
+bool AdaptiveThresholdExperimentIsEnabled();
+
+class OveruseDetector {
+ public:
+ explicit OveruseDetector(const OverUseDetectorOptions& options);
+ virtual ~OveruseDetector();
+
+ // Update the detection state based on the estimated inter-arrival time delta
+ // offset. |timestamp_delta| is the delta between the last timestamp which the
+ // estimated offset is based on and the last timestamp on which the last
+ // offset was based on, representing the time between detector updates.
+ // |num_of_deltas| is the number of deltas the offset estimate is based on.
+ // Returns the state after the detection update.
+ BandwidthUsage Detect(double offset,
+ double timestamp_delta,
+ int num_of_deltas,
+ int64_t now_ms);
+
+ // Returns the current detector state.
+ BandwidthUsage State() const;
+
+ private:
+ void UpdateThreshold(double modified_offset, int64_t now_ms);
+ void InitializeExperiment();
+
+ const bool in_experiment_;
+ double k_up_;
+ double k_down_;
+ double overusing_time_threshold_;
+ // Must be first member variable. Cannot be const because we need to be
+ // copyable.
+ webrtc::OverUseDetectorOptions options_;
+ double threshold_;
+ int64_t last_update_ms_;
+ double prev_offset_;
+ double time_over_using_;
+ int overuse_counter_;
+ BandwidthUsage hypothesis_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(OveruseDetector);
+};
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/overuse_detector_unittest.cc b/webrtc/modules/remote_bitrate_estimator/overuse_detector_unittest.cc
new file mode 100644
index 0000000000..dcad04b5f6
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/overuse_detector_unittest.cc
@@ -0,0 +1,740 @@
+/*
+ * Copyright (c) 2012 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 <math.h>
+#include <cmath>
+#include <cstdlib>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/inter_arrival.h"
+#include "webrtc/modules/remote_bitrate_estimator/overuse_detector.h"
+#include "webrtc/modules/remote_bitrate_estimator/overuse_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/rate_statistics.h"
+#include "webrtc/test/field_trial.h"
+#include "webrtc/test/random.h"
+#include "webrtc/test/testsupport/gtest_disable.h"
+
+namespace webrtc {
+namespace testing {
+
+const double kRtpTimestampToMs = 1.0 / 90.0;
+
+class OveruseDetectorTest : public ::testing::Test {
+ public:
+ OveruseDetectorTest()
+ : now_ms_(0),
+ receive_time_ms_(0),
+ rtp_timestamp_(10 * 90),
+ overuse_detector_(),
+ overuse_estimator_(new OveruseEstimator(options_)),
+ inter_arrival_(new InterArrival(5 * 90, kRtpTimestampToMs, true)),
+ random_(1234) {}
+
+ protected:
+ void SetUp() override {
+ overuse_detector_.reset(new OveruseDetector(options_));
+ }
+
+ int Run100000Samples(int packets_per_frame, size_t packet_size, int mean_ms,
+ int standard_deviation_ms) {
+ int unique_overuse = 0;
+ int last_overuse = -1;
+ for (int i = 0; i < 100000; ++i) {
+ for (int j = 0; j < packets_per_frame; ++j) {
+ UpdateDetector(rtp_timestamp_, receive_time_ms_, packet_size);
+ }
+ rtp_timestamp_ += mean_ms * 90;
+ now_ms_ += mean_ms;
+ receive_time_ms_ =
+ std::max(receive_time_ms_,
+ now_ms_ + random_.Gaussian(0, standard_deviation_ms));
+ if (kBwOverusing == overuse_detector_->State()) {
+ if (last_overuse + 1 != i) {
+ unique_overuse++;
+ }
+ last_overuse = i;
+ }
+ }
+ return unique_overuse;
+ }
+
+ int RunUntilOveruse(int packets_per_frame, size_t packet_size, int mean_ms,
+ int standard_deviation_ms, int drift_per_frame_ms) {
+ // Simulate a higher send pace, that is too high.
+ for (int i = 0; i < 1000; ++i) {
+ for (int j = 0; j < packets_per_frame; ++j) {
+ UpdateDetector(rtp_timestamp_, receive_time_ms_, packet_size);
+ }
+ rtp_timestamp_ += mean_ms * 90;
+ now_ms_ += mean_ms + drift_per_frame_ms;
+ receive_time_ms_ =
+ std::max(receive_time_ms_,
+ now_ms_ + random_.Gaussian(0, standard_deviation_ms));
+ if (kBwOverusing == overuse_detector_->State()) {
+ return i + 1;
+ }
+ }
+ return -1;
+ }
+
+ void UpdateDetector(uint32_t rtp_timestamp, int64_t receive_time_ms,
+ size_t packet_size) {
+ uint32_t timestamp_delta;
+ int64_t time_delta;
+ int size_delta;
+ if (inter_arrival_->ComputeDeltas(rtp_timestamp,
+ receive_time_ms,
+ packet_size,
+ &timestamp_delta,
+ &time_delta,
+ &size_delta)) {
+ double timestamp_delta_ms = timestamp_delta / 90.0;
+ overuse_estimator_->Update(time_delta, timestamp_delta_ms, size_delta,
+ overuse_detector_->State());
+ overuse_detector_->Detect(
+ overuse_estimator_->offset(), timestamp_delta_ms,
+ overuse_estimator_->num_of_deltas(), receive_time_ms);
+ }
+ }
+
+ int64_t now_ms_;
+ int64_t receive_time_ms_;
+ uint32_t rtp_timestamp_;
+ OverUseDetectorOptions options_;
+ rtc::scoped_ptr<OveruseDetector> overuse_detector_;
+ rtc::scoped_ptr<OveruseEstimator> overuse_estimator_;
+ rtc::scoped_ptr<InterArrival> inter_arrival_;
+ test::Random random_;
+};
+
+TEST_F(OveruseDetectorTest, GaussianRandom) {
+ int buckets[100];
+ memset(buckets, 0, sizeof(buckets));
+ for (int i = 0; i < 100000; ++i) {
+ int index = random_.Gaussian(49, 10);
+ if (index >= 0 && index < 100)
+ buckets[index]++;
+ }
+ for (int n = 0; n < 100; ++n) {
+ printf("Bucket n:%d, %d\n", n, buckets[n]);
+ }
+}
+
+TEST_F(OveruseDetectorTest, SimpleNonOveruse30fps) {
+ size_t packet_size = 1200;
+ uint32_t frame_duration_ms = 33;
+ uint32_t rtp_timestamp = 10 * 90;
+
+ // No variance.
+ for (int i = 0; i < 1000; ++i) {
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ now_ms_ += frame_duration_ms;
+ rtp_timestamp += frame_duration_ms * 90;
+ EXPECT_EQ(kBwNormal, overuse_detector_->State());
+ }
+}
+
+// Roughly 1 Mbit/s
+TEST_F(OveruseDetectorTest, SimpleNonOveruseWithReceiveVariance) {
+ uint32_t frame_duration_ms = 10;
+ uint32_t rtp_timestamp = 10 * 90;
+ size_t packet_size = 1200;
+
+ for (int i = 0; i < 1000; ++i) {
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ rtp_timestamp += frame_duration_ms * 90;
+ if (i % 2) {
+ now_ms_ += frame_duration_ms - 5;
+ } else {
+ now_ms_ += frame_duration_ms + 5;
+ }
+ EXPECT_EQ(kBwNormal, overuse_detector_->State());
+ }
+}
+
+TEST_F(OveruseDetectorTest, SimpleNonOveruseWithRtpTimestampVariance) {
+ // Roughly 1 Mbit/s.
+ uint32_t frame_duration_ms = 10;
+ uint32_t rtp_timestamp = 10 * 90;
+ size_t packet_size = 1200;
+
+ for (int i = 0; i < 1000; ++i) {
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ now_ms_ += frame_duration_ms;
+ if (i % 2) {
+ rtp_timestamp += (frame_duration_ms - 5) * 90;
+ } else {
+ rtp_timestamp += (frame_duration_ms + 5) * 90;
+ }
+ EXPECT_EQ(kBwNormal, overuse_detector_->State());
+ }
+}
+
+TEST_F(OveruseDetectorTest, SimpleOveruse2000Kbit30fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 6;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 0; // No variance.
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(8, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, SimpleOveruse100kbit10fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 100;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 0; // No variance.
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(6, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, DISABLED_OveruseWithHighVariance100Kbit10fps) {
+ uint32_t frame_duration_ms = 100;
+ uint32_t drift_per_frame_ms = 10;
+ uint32_t rtp_timestamp = frame_duration_ms * 90;
+ size_t packet_size = 1200;
+ int offset = 10;
+
+ // Run 1000 samples to reach steady state.
+ for (int i = 0; i < 1000; ++i) {
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ rtp_timestamp += frame_duration_ms * 90;
+ if (i % 2) {
+ offset = rand() % 50;
+ now_ms_ += frame_duration_ms - offset;
+ } else {
+ now_ms_ += frame_duration_ms + offset;
+ }
+ EXPECT_EQ(kBwNormal, overuse_detector_->State());
+ }
+ // Simulate a higher send pace, that is too high.
+ // Above noise generate a standard deviation of approximately 28 ms.
+ // Total build up of 150 ms.
+ for (int j = 0; j < 15; ++j) {
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ now_ms_ += frame_duration_ms + drift_per_frame_ms;
+ rtp_timestamp += frame_duration_ms * 90;
+ EXPECT_EQ(kBwNormal, overuse_detector_->State());
+ }
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ EXPECT_EQ(kBwOverusing, overuse_detector_->State());
+}
+
+TEST_F(OveruseDetectorTest, DISABLED_OveruseWithLowVariance100Kbit10fps) {
+ uint32_t frame_duration_ms = 100;
+ uint32_t drift_per_frame_ms = 1;
+ uint32_t rtp_timestamp = frame_duration_ms * 90;
+ size_t packet_size = 1200;
+ int offset = 10;
+
+ // Run 1000 samples to reach steady state.
+ for (int i = 0; i < 1000; ++i) {
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ rtp_timestamp += frame_duration_ms * 90;
+ if (i % 2) {
+ offset = rand() % 2;
+ now_ms_ += frame_duration_ms - offset;
+ } else {
+ now_ms_ += frame_duration_ms + offset;
+ }
+ EXPECT_EQ(kBwNormal, overuse_detector_->State());
+ }
+ // Simulate a higher send pace, that is too high.
+ // Total build up of 6 ms.
+ for (int j = 0; j < 6; ++j) {
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ now_ms_ += frame_duration_ms + drift_per_frame_ms;
+ rtp_timestamp += frame_duration_ms * 90;
+ EXPECT_EQ(kBwNormal, overuse_detector_->State());
+ }
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ EXPECT_EQ(kBwOverusing, overuse_detector_->State());
+}
+
+TEST_F(OveruseDetectorTest, OveruseWithLowVariance2000Kbit30fps) {
+ uint32_t frame_duration_ms = 33;
+ uint32_t drift_per_frame_ms = 1;
+ uint32_t rtp_timestamp = frame_duration_ms * 90;
+ size_t packet_size = 1200;
+ int offset = 0;
+
+ // Run 1000 samples to reach steady state.
+ for (int i = 0; i < 1000; ++i) {
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ rtp_timestamp += frame_duration_ms * 90;
+ if (i % 2) {
+ offset = rand() % 2;
+ now_ms_ += frame_duration_ms - offset;
+ } else {
+ now_ms_ += frame_duration_ms + offset;
+ }
+ EXPECT_EQ(kBwNormal, overuse_detector_->State());
+ }
+ // Simulate a higher send pace, that is too high.
+ // Total build up of 30 ms.
+ for (int j = 0; j < 5; ++j) {
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ now_ms_ += frame_duration_ms + drift_per_frame_ms * 6;
+ rtp_timestamp += frame_duration_ms * 90;
+ EXPECT_EQ(kBwNormal, overuse_detector_->State());
+ }
+ UpdateDetector(rtp_timestamp, now_ms_, packet_size);
+ EXPECT_EQ(kBwOverusing, overuse_detector_->State());
+}
+
+TEST_F(OveruseDetectorTest,
+ DISABLED_ON_ANDROID(LowGaussianVariance30Kbit3fps)) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 333;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 3;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(13, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(14, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, LowGaussianVarianceFastDrift30Kbit3fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 333;
+ int drift_per_frame_ms = 100;
+ int sigma_ms = 3;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(13, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(4, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, HighGaussianVariance30Kbit3fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 333;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 10;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(46, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(42, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, HighGaussianVarianceFastDrift30Kbit3fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 333;
+ int drift_per_frame_ms = 100;
+ int sigma_ms = 10;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(46, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(4, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest,
+ DISABLED_ON_ANDROID(LowGaussianVariance100Kbit5fps)) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 2;
+ int frame_duration_ms = 200;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 3;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(12, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(12, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest,
+ DISABLED_ON_ANDROID(HighGaussianVariance100Kbit5fps)) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 2;
+ int frame_duration_ms = 200;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 10;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(16, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(37, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest,
+ DISABLED_ON_ANDROID(LowGaussianVariance100Kbit10fps)) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 100;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 3;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(12, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(12, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest,
+ DISABLED_ON_ANDROID(HighGaussianVariance100Kbit10fps)) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 100;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 10;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(12, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(37, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest,
+ DISABLED_ON_ANDROID(LowGaussianVariance300Kbit30fps)) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 3;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(14, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, LowGaussianVarianceFastDrift300Kbit30fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 10;
+ int sigma_ms = 3;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(6, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, HighGaussianVariance300Kbit30fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 10;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(49, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, HighGaussianVarianceFastDrift300Kbit30fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 1;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 10;
+ int sigma_ms = 10;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(8, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest,
+ DISABLED_ON_ANDROID(LowGaussianVariance1000Kbit30fps)) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 3;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 3;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(14, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, LowGaussianVarianceFastDrift1000Kbit30fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 3;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 10;
+ int sigma_ms = 3;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(6, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, HighGaussianVariance1000Kbit30fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 3;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 10;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(49, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, HighGaussianVarianceFastDrift1000Kbit30fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 3;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 10;
+ int sigma_ms = 10;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(8, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest,
+ DISABLED_ON_ANDROID(LowGaussianVariance2000Kbit30fps)) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 6;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 3;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(14, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, LowGaussianVarianceFastDrift2000Kbit30fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 6;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 10;
+ int sigma_ms = 3;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(6, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, HighGaussianVariance2000Kbit30fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 6;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 1;
+ int sigma_ms = 10;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(49, frames_until_overuse);
+}
+
+TEST_F(OveruseDetectorTest, HighGaussianVarianceFastDrift2000Kbit30fps) {
+ size_t packet_size = 1200;
+ int packets_per_frame = 6;
+ int frame_duration_ms = 33;
+ int drift_per_frame_ms = 10;
+ int sigma_ms = 10;
+ int unique_overuse = Run100000Samples(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms);
+ EXPECT_EQ(0, unique_overuse);
+ int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
+ frame_duration_ms, sigma_ms, drift_per_frame_ms);
+ EXPECT_EQ(8, frames_until_overuse);
+}
+
+class OveruseDetectorExperimentTest : public OveruseDetectorTest {
+ public:
+ OveruseDetectorExperimentTest()
+ : override_field_trials_(
+ "WebRTC-AdaptiveBweThreshold/Enabled-0.01,0.00018/") {}
+
+ protected:
+ void SetUp() override {
+ overuse_detector_.reset(new OveruseDetector(options_));
+ }
+
+ test::ScopedFieldTrials override_field_trials_;
+};
+
+TEST_F(OveruseDetectorExperimentTest, ThresholdAdapts) {
+ const double kOffset = 0.21;
+ double kTsDelta = 3000.0;
+ int64_t now_ms = 0;
+ int num_deltas = 60;
+ const int kBatchLength = 10;
+
+ // Pass in a positive offset and verify it triggers overuse.
+ bool overuse_detected = false;
+ for (int i = 0; i < kBatchLength; ++i) {
+ BandwidthUsage overuse_state =
+ overuse_detector_->Detect(kOffset, kTsDelta, num_deltas, now_ms);
+ if (overuse_state == kBwOverusing) {
+ overuse_detected = true;
+ }
+ ++num_deltas;
+ now_ms += 5;
+ }
+ EXPECT_TRUE(overuse_detected);
+
+ // Force the threshold to increase by passing in a higher offset.
+ overuse_detected = false;
+ for (int i = 0; i < kBatchLength; ++i) {
+ BandwidthUsage overuse_state =
+ overuse_detector_->Detect(1.1 * kOffset, kTsDelta, num_deltas, now_ms);
+ if (overuse_state == kBwOverusing) {
+ overuse_detected = true;
+ }
+ ++num_deltas;
+ now_ms += 5;
+ }
+ EXPECT_TRUE(overuse_detected);
+
+ // Verify that the same offset as before no longer triggers overuse.
+ overuse_detected = false;
+ for (int i = 0; i < kBatchLength; ++i) {
+ BandwidthUsage overuse_state =
+ overuse_detector_->Detect(kOffset, kTsDelta, num_deltas, now_ms);
+ if (overuse_state == kBwOverusing) {
+ overuse_detected = true;
+ }
+ ++num_deltas;
+ now_ms += 5;
+ }
+ EXPECT_FALSE(overuse_detected);
+
+ // Pass in a low offset to make the threshold adapt down.
+ for (int i = 0; i < 15 * kBatchLength; ++i) {
+ BandwidthUsage overuse_state =
+ overuse_detector_->Detect(0.7 * kOffset, kTsDelta, num_deltas, now_ms);
+ if (overuse_state == kBwOverusing) {
+ overuse_detected = true;
+ }
+ ++num_deltas;
+ now_ms += 5;
+ }
+ EXPECT_FALSE(overuse_detected);
+
+ // Make sure the original offset now again triggers overuse.
+ for (int i = 0; i < kBatchLength; ++i) {
+ BandwidthUsage overuse_state =
+ overuse_detector_->Detect(kOffset, kTsDelta, num_deltas, now_ms);
+ if (overuse_state == kBwOverusing) {
+ overuse_detected = true;
+ }
+ ++num_deltas;
+ now_ms += 5;
+ }
+ EXPECT_TRUE(overuse_detected);
+}
+
+TEST_F(OveruseDetectorExperimentTest, DoesntAdaptToSpikes) {
+ const double kOffset = 1.0;
+ const double kLargeOffset = 20.0;
+ double kTsDelta = 3000.0;
+ int64_t now_ms = 0;
+ int num_deltas = 60;
+ const int kBatchLength = 10;
+ const int kShortBatchLength = 3;
+
+ // Pass in a positive offset and verify it triggers overuse.
+ bool overuse_detected = false;
+ for (int i = 0; i < kBatchLength; ++i) {
+ BandwidthUsage overuse_state =
+ overuse_detector_->Detect(kOffset, kTsDelta, num_deltas, now_ms);
+ if (overuse_state == kBwOverusing) {
+ overuse_detected = true;
+ }
+ ++num_deltas;
+ now_ms += 5;
+ }
+
+ // Pass in a large offset. This shouldn't have a too big impact on the
+ // threshold, but still trigger an overuse.
+ now_ms += 100;
+ overuse_detected = false;
+ for (int i = 0; i < kShortBatchLength; ++i) {
+ BandwidthUsage overuse_state =
+ overuse_detector_->Detect(kLargeOffset, kTsDelta, num_deltas, now_ms);
+ if (overuse_state == kBwOverusing) {
+ overuse_detected = true;
+ }
+ ++num_deltas;
+ now_ms += 5;
+ }
+ EXPECT_TRUE(overuse_detected);
+
+ // Pass in a positive normal offset and verify it still triggers.
+ overuse_detected = false;
+ for (int i = 0; i < kBatchLength; ++i) {
+ BandwidthUsage overuse_state =
+ overuse_detector_->Detect(kOffset, kTsDelta, num_deltas, now_ms);
+ if (overuse_state == kBwOverusing) {
+ overuse_detected = true;
+ }
+ ++num_deltas;
+ now_ms += 5;
+ }
+ EXPECT_TRUE(overuse_detected);
+}
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/overuse_estimator.cc b/webrtc/modules/remote_bitrate_estimator/overuse_estimator.cc
new file mode 100644
index 0000000000..4be7b7493b
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/overuse_estimator.cc
@@ -0,0 +1,153 @@
+/*
+ * 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/modules/remote_bitrate_estimator/overuse_estimator.h"
+
+#include <algorithm>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h"
+#include "webrtc/system_wrappers/include/logging.h"
+
+namespace webrtc {
+
+enum { kMinFramePeriodHistoryLength = 60 };
+enum { kDeltaCounterMax = 1000 };
+
+OveruseEstimator::OveruseEstimator(const OverUseDetectorOptions& options)
+ : options_(options),
+ num_of_deltas_(0),
+ slope_(options_.initial_slope),
+ offset_(options_.initial_offset),
+ prev_offset_(options_.initial_offset),
+ E_(),
+ process_noise_(),
+ avg_noise_(options_.initial_avg_noise),
+ var_noise_(options_.initial_var_noise),
+ ts_delta_hist_() {
+ memcpy(E_, options_.initial_e, sizeof(E_));
+ memcpy(process_noise_, options_.initial_process_noise,
+ sizeof(process_noise_));
+}
+
+OveruseEstimator::~OveruseEstimator() {
+ ts_delta_hist_.clear();
+}
+
+void OveruseEstimator::Update(int64_t t_delta,
+ double ts_delta,
+ int size_delta,
+ BandwidthUsage current_hypothesis) {
+ const double min_frame_period = UpdateMinFramePeriod(ts_delta);
+ const double t_ts_delta = t_delta - ts_delta;
+ double fs_delta = size_delta;
+
+ ++num_of_deltas_;
+ if (num_of_deltas_ > kDeltaCounterMax) {
+ num_of_deltas_ = kDeltaCounterMax;
+ }
+
+ // Update the Kalman filter.
+ E_[0][0] += process_noise_[0];
+ E_[1][1] += process_noise_[1];
+
+ if ((current_hypothesis == kBwOverusing && offset_ < prev_offset_) ||
+ (current_hypothesis == kBwUnderusing && offset_ > prev_offset_)) {
+ E_[1][1] += 10 * process_noise_[1];
+ }
+
+ const double h[2] = {fs_delta, 1.0};
+ const double Eh[2] = {E_[0][0]*h[0] + E_[0][1]*h[1],
+ E_[1][0]*h[0] + E_[1][1]*h[1]};
+
+ const double residual = t_ts_delta - slope_*h[0] - offset_;
+
+ const bool in_stable_state = (current_hypothesis == kBwNormal);
+ const double max_residual = 3.0 * sqrt(var_noise_);
+ // We try to filter out very late frames. For instance periodic key
+ // frames doesn't fit the Gaussian model well.
+ if (fabs(residual) < max_residual) {
+ UpdateNoiseEstimate(residual, min_frame_period, in_stable_state);
+ } else {
+ UpdateNoiseEstimate(residual < 0 ? -max_residual : max_residual,
+ min_frame_period, in_stable_state);
+ }
+
+ const double denom = var_noise_ + h[0]*Eh[0] + h[1]*Eh[1];
+
+ const double K[2] = {Eh[0] / denom,
+ Eh[1] / denom};
+
+ const double IKh[2][2] = {{1.0 - K[0]*h[0], -K[0]*h[1]},
+ {-K[1]*h[0], 1.0 - K[1]*h[1]}};
+ const double e00 = E_[0][0];
+ const double e01 = E_[0][1];
+
+ // Update state.
+ E_[0][0] = e00 * IKh[0][0] + E_[1][0] * IKh[0][1];
+ E_[0][1] = e01 * IKh[0][0] + E_[1][1] * IKh[0][1];
+ E_[1][0] = e00 * IKh[1][0] + E_[1][0] * IKh[1][1];
+ E_[1][1] = e01 * IKh[1][0] + E_[1][1] * IKh[1][1];
+
+ // The covariance matrix must be positive semi-definite.
+ bool positive_semi_definite = E_[0][0] + E_[1][1] >= 0 &&
+ E_[0][0] * E_[1][1] - E_[0][1] * E_[1][0] >= 0 && E_[0][0] >= 0;
+ assert(positive_semi_definite);
+ if (!positive_semi_definite) {
+ LOG(LS_ERROR) << "The over-use estimator's covariance matrix is no longer "
+ "semi-definite.";
+ }
+
+ slope_ = slope_ + K[0] * residual;
+ prev_offset_ = offset_;
+ offset_ = offset_ + K[1] * residual;
+}
+
+double OveruseEstimator::UpdateMinFramePeriod(double ts_delta) {
+ double min_frame_period = ts_delta;
+ if (ts_delta_hist_.size() >= kMinFramePeriodHistoryLength) {
+ ts_delta_hist_.pop_front();
+ }
+ std::list<double>::iterator it = ts_delta_hist_.begin();
+ for (; it != ts_delta_hist_.end(); it++) {
+ min_frame_period = std::min(*it, min_frame_period);
+ }
+ ts_delta_hist_.push_back(ts_delta);
+ return min_frame_period;
+}
+
+void OveruseEstimator::UpdateNoiseEstimate(double residual,
+ double ts_delta,
+ bool stable_state) {
+ if (!stable_state) {
+ return;
+ }
+ // Faster filter during startup to faster adapt to the jitter level
+ // of the network. |alpha| is tuned for 30 frames per second, but is scaled
+ // according to |ts_delta|.
+ double alpha = 0.01;
+ if (num_of_deltas_ > 10*30) {
+ alpha = 0.002;
+ }
+ // Only update the noise estimate if we're not over-using. |beta| is a
+ // function of alpha and the time delta since the previous update.
+ const double beta = pow(1 - alpha, ts_delta * 30.0 / 1000.0);
+ avg_noise_ = beta * avg_noise_
+ + (1 - beta) * residual;
+ var_noise_ = beta * var_noise_
+ + (1 - beta) * (avg_noise_ - residual) * (avg_noise_ - residual);
+ if (var_noise_ < 1) {
+ var_noise_ = 1;
+ }
+}
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/overuse_estimator.h b/webrtc/modules/remote_bitrate_estimator/overuse_estimator.h
new file mode 100644
index 0000000000..d671f39166
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/overuse_estimator.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_
+
+#include <list>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h"
+
+namespace webrtc {
+
+class OveruseEstimator {
+ public:
+ explicit OveruseEstimator(const OverUseDetectorOptions& options);
+ ~OveruseEstimator();
+
+ // Update the estimator with a new sample. The deltas should represent deltas
+ // between timestamp groups as defined by the InterArrival class.
+ // |current_hypothesis| should be the hypothesis of the over-use detector at
+ // this time.
+ void Update(int64_t t_delta, double ts_delta, int size_delta,
+ BandwidthUsage current_hypothesis);
+
+ // Returns the estimated noise/jitter variance in ms^2.
+ double var_noise() const {
+ return var_noise_;
+ }
+
+ // Returns the estimated inter-arrival time delta offset in ms.
+ double offset() const {
+ return offset_;
+ }
+
+ // Returns the number of deltas which the current over-use estimator state is
+ // based on.
+ unsigned int num_of_deltas() const {
+ return num_of_deltas_;
+ }
+
+ private:
+ double UpdateMinFramePeriod(double ts_delta);
+ void UpdateNoiseEstimate(double residual, double ts_delta, bool stable_state);
+
+ // Must be first member variable. Cannot be const because we need to be
+ // copyable.
+ OverUseDetectorOptions options_;
+ uint16_t num_of_deltas_;
+ double slope_;
+ double offset_;
+ double prev_offset_;
+ double E_[2][2];
+ double process_noise_[2];
+ double avg_noise_;
+ double var_noise_;
+ std::list<double> ts_delta_hist_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(OveruseEstimator);
+};
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/rate_statistics.cc b/webrtc/modules/remote_bitrate_estimator/rate_statistics.cc
new file mode 100644
index 0000000000..c1b4c80cc6
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/rate_statistics.cc
@@ -0,0 +1,85 @@
+/*
+ * 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/modules/remote_bitrate_estimator/rate_statistics.h"
+
+#include <assert.h>
+
+namespace webrtc {
+
+RateStatistics::RateStatistics(uint32_t window_size_ms, float scale)
+ : num_buckets_(window_size_ms + 1), // N ms in (N+1) buckets.
+ buckets_(new size_t[num_buckets_]()),
+ accumulated_count_(0),
+ oldest_time_(0),
+ oldest_index_(0),
+ scale_(scale / (num_buckets_ - 1)) {
+}
+
+RateStatistics::~RateStatistics() {
+}
+
+void RateStatistics::Reset() {
+ accumulated_count_ = 0;
+ oldest_time_ = 0;
+ oldest_index_ = 0;
+ for (int i = 0; i < num_buckets_; i++) {
+ buckets_[i] = 0;
+ }
+}
+
+void RateStatistics::Update(size_t count, int64_t now_ms) {
+ if (now_ms < oldest_time_) {
+ // Too old data is ignored.
+ return;
+ }
+
+ EraseOld(now_ms);
+
+ int now_offset = static_cast<int>(now_ms - oldest_time_);
+ assert(now_offset < num_buckets_);
+ int index = oldest_index_ + now_offset;
+ if (index >= num_buckets_) {
+ index -= num_buckets_;
+ }
+ buckets_[index] += count;
+ accumulated_count_ += count;
+}
+
+uint32_t RateStatistics::Rate(int64_t now_ms) {
+ EraseOld(now_ms);
+ return static_cast<uint32_t>(accumulated_count_ * scale_ + 0.5f);
+}
+
+void RateStatistics::EraseOld(int64_t now_ms) {
+ int64_t new_oldest_time = now_ms - num_buckets_ + 1;
+ if (new_oldest_time <= oldest_time_) {
+ return;
+ }
+
+ while (oldest_time_ < new_oldest_time) {
+ size_t count_in_oldest_bucket = buckets_[oldest_index_];
+ assert(accumulated_count_ >= count_in_oldest_bucket);
+ accumulated_count_ -= count_in_oldest_bucket;
+ buckets_[oldest_index_] = 0;
+ if (++oldest_index_ >= num_buckets_) {
+ oldest_index_ = 0;
+ }
+ ++oldest_time_;
+ if (accumulated_count_ == 0) {
+ // This guarantees we go through all the buckets at most once, even if
+ // |new_oldest_time| is far greater than |oldest_time_|.
+ break;
+ }
+ }
+ oldest_time_ = new_oldest_time;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/rate_statistics.h b/webrtc/modules/remote_bitrate_estimator/rate_statistics.h
new file mode 100644
index 0000000000..fc8ff082d0
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/rate_statistics.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_RATE_STATISTICS_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_RATE_STATISTICS_H_
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+class RateStatistics {
+ public:
+ // window_size = window size in ms for the rate estimation
+ // scale = coefficient to convert counts/ms to desired units,
+ // ex: if counts represents bytes, use 8*1000 to go to bits/s
+ RateStatistics(uint32_t window_size_ms, float scale);
+ ~RateStatistics();
+
+ void Reset();
+ void Update(size_t count, int64_t now_ms);
+ uint32_t Rate(int64_t now_ms);
+
+ private:
+ void EraseOld(int64_t now_ms);
+
+ // Counters are kept in buckets (circular buffer), with one bucket
+ // per millisecond.
+ const int num_buckets_;
+ rtc::scoped_ptr<size_t[]> buckets_;
+
+ // Total count recorded in buckets.
+ size_t accumulated_count_;
+
+ // Oldest time recorded in buckets.
+ int64_t oldest_time_;
+
+ // Bucket index of oldest counter recorded in buckets.
+ int oldest_index_;
+
+ // To convert counts/ms to desired units
+ const float scale_;
+};
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_RATE_STATISTICS_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/rate_statistics_unittest.cc b/webrtc/modules/remote_bitrate_estimator/rate_statistics_unittest.cc
new file mode 100644
index 0000000000..0cbab30bc1
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/rate_statistics_unittest.cc
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2012 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 "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/remote_bitrate_estimator/rate_statistics.h"
+
+namespace {
+
+using webrtc::RateStatistics;
+
+class RateStatisticsTest : public ::testing::Test {
+ protected:
+ RateStatisticsTest() : stats_(500, 8000) {}
+ RateStatistics stats_;
+};
+
+TEST_F(RateStatisticsTest, TestStrictMode) {
+ int64_t now_ms = 0;
+ // Should be initialized to 0.
+ EXPECT_EQ(0u, stats_.Rate(now_ms));
+ stats_.Update(1500, now_ms);
+ // Expecting 24 kbps given a 500 ms window with one 1500 bytes packet.
+ EXPECT_EQ(24000u, stats_.Rate(now_ms));
+ stats_.Reset();
+ // Expecting 0 after init.
+ EXPECT_EQ(0u, stats_.Rate(now_ms));
+ for (int i = 0; i < 100000; ++i) {
+ if (now_ms % 10 == 0) {
+ stats_.Update(1500, now_ms);
+ }
+ // Approximately 1200 kbps expected. Not exact since when packets
+ // are removed we will jump 10 ms to the next packet.
+ if (now_ms > 0 && now_ms % 500 == 0) {
+ EXPECT_NEAR(1200000u, stats_.Rate(now_ms), 24000u);
+ }
+ now_ms += 1;
+ }
+ now_ms += 500;
+ // The window is 2 seconds. If nothing has been received for that time
+ // the estimate should be 0.
+ EXPECT_EQ(0u, stats_.Rate(now_ms));
+}
+
+TEST_F(RateStatisticsTest, IncreasingThenDecreasingBitrate) {
+ int64_t now_ms = 0;
+ stats_.Reset();
+ // Expecting 0 after init.
+ uint32_t bitrate = stats_.Rate(now_ms);
+ EXPECT_EQ(0u, bitrate);
+ // 1000 bytes per millisecond until plateau is reached.
+ while (++now_ms < 10000) {
+ stats_.Update(1000, now_ms);
+ uint32_t new_bitrate = stats_.Rate(now_ms);
+ if (new_bitrate != bitrate) {
+ // New bitrate must be higher than previous one.
+ EXPECT_GT(new_bitrate, bitrate);
+ } else {
+ // Plateau reached, 8000 kbps expected.
+ EXPECT_NEAR(8000000u, bitrate, 80000u);
+ break;
+ }
+ bitrate = new_bitrate;
+ }
+ // 1000 bytes per millisecond until 10-second mark, 8000 kbps expected.
+ while (++now_ms < 10000) {
+ stats_.Update(1000, now_ms);
+ bitrate = stats_.Rate(now_ms);
+ EXPECT_NEAR(8000000u, bitrate, 80000u);
+ }
+ // Zero bytes per millisecond until 0 is reached.
+ while (++now_ms < 20000) {
+ stats_.Update(0, now_ms);
+ uint32_t new_bitrate = stats_.Rate(now_ms);
+ if (new_bitrate != bitrate) {
+ // New bitrate must be lower than previous one.
+ EXPECT_LT(new_bitrate, bitrate);
+ } else {
+ // 0 kbps expected.
+ EXPECT_EQ(0u, bitrate);
+ break;
+ }
+ bitrate = new_bitrate;
+ }
+ // Zero bytes per millisecond until 20-second mark, 0 kbps expected.
+ while (++now_ms < 20000) {
+ stats_.Update(0, now_ms);
+ EXPECT_EQ(0u, stats_.Rate(now_ms));
+ }
+}
+} // namespace
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi
new file mode 100644
index 0000000000..d2af81e2f0
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator.gypi
@@ -0,0 +1,168 @@
+# 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.
+
+{
+ 'includes': [
+ '../../build/common.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'remote_bitrate_estimator',
+ 'type': 'static_library',
+ 'dependencies': [
+ '<(webrtc_root)/common.gyp:webrtc_common',
+ '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers',
+ ],
+ 'sources': [
+ 'include/bwe_defines.h',
+ 'include/remote_bitrate_estimator.h',
+ 'include/send_time_history.h',
+ 'aimd_rate_control.cc',
+ 'aimd_rate_control.h',
+ 'inter_arrival.cc',
+ 'inter_arrival.h',
+ 'overuse_detector.cc',
+ 'overuse_detector.h',
+ 'overuse_estimator.cc',
+ 'overuse_estimator.h',
+ 'rate_statistics.cc',
+ 'rate_statistics.h',
+ 'remote_bitrate_estimator_abs_send_time.cc',
+ 'remote_bitrate_estimator_abs_send_time.h',
+ 'remote_bitrate_estimator_single_stream.cc',
+ 'remote_bitrate_estimator_single_stream.h',
+ 'remote_estimator_proxy.cc',
+ 'remote_estimator_proxy.h',
+ 'send_time_history.cc',
+ 'transport_feedback_adapter.cc',
+ 'transport_feedback_adapter.h',
+ 'test/bwe_test_logging.cc',
+ 'test/bwe_test_logging.h',
+ ], # source
+ 'conditions': [
+ ['enable_bwe_test_logging==1', {
+ 'defines': [ 'BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=1' ],
+ }, {
+ 'defines': [ 'BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0' ],
+ 'sources!': [
+ 'remote_bitrate_estimator/test/bwe_test_logging.cc'
+ ],
+ }],
+ ],
+ },
+ ], # targets
+ 'conditions': [
+ ['include_tests==1', {
+ 'targets': [
+ {
+ 'target_name': 'bwe_simulator',
+ 'type': 'static_library',
+ 'dependencies': [
+ '<(DEPTH)/testing/gtest.gyp:gtest',
+ ],
+ 'sources': [
+ 'test/bwe.cc',
+ 'test/bwe.h',
+ 'test/bwe_test.cc',
+ 'test/bwe_test.h',
+ 'test/bwe_test_baselinefile.cc',
+ 'test/bwe_test_baselinefile.h',
+ 'test/bwe_test_fileutils.cc',
+ 'test/bwe_test_fileutils.h',
+ 'test/bwe_test_framework.cc',
+ 'test/bwe_test_framework.h',
+ 'test/bwe_test_logging.cc',
+ 'test/bwe_test_logging.h',
+ 'test/metric_recorder.cc',
+ 'test/metric_recorder.h',
+ 'test/packet_receiver.cc',
+ 'test/packet_receiver.h',
+ 'test/packet_sender.cc',
+ 'test/packet_sender.h',
+ 'test/packet.h',
+ 'test/estimators/nada.cc',
+ 'test/estimators/nada.h',
+ 'test/estimators/remb.cc',
+ 'test/estimators/remb.h',
+ 'test/estimators/send_side.cc',
+ 'test/estimators/send_side.h',
+ 'test/estimators/tcp.cc',
+ 'test/estimators/tcp.h',
+ ],
+ 'conditions': [
+ ['enable_bwe_test_logging==1', {
+ 'defines': [ 'BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=1' ],
+ }, {
+ 'defines': [ 'BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0' ],
+ 'sources!': [
+ 'remote_bitrate_estimator/test/bwe_test_logging.cc'
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'bwe_tools_util',
+ 'type': 'static_library',
+ 'dependencies': [
+ '<(DEPTH)/third_party/gflags/gflags.gyp:gflags',
+ '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers',
+ 'rtp_rtcp',
+ ],
+ 'sources': [
+ 'tools/bwe_rtp.cc',
+ 'tools/bwe_rtp.h',
+ ],
+ },
+ {
+ 'target_name': 'bwe_rtp_to_text',
+ 'type': 'executable',
+ 'includes': [
+ '../rtp_rtcp/rtp_rtcp.gypi',
+ ],
+ 'dependencies': [
+ '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers',
+ '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers_default',
+ '<(webrtc_root)/test/test.gyp:rtp_test_utils',
+ 'bwe_tools_util',
+ 'rtp_rtcp',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ 'include',
+ ],
+ },
+ 'sources': [
+ 'tools/rtp_to_text.cc',
+ ], # source
+ },
+ {
+ 'target_name': 'bwe_rtp_play',
+ 'type': 'executable',
+ 'includes': [
+ '../rtp_rtcp/rtp_rtcp.gypi',
+ ],
+ 'dependencies': [
+ '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers',
+ '<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers_default',
+ '<(webrtc_root)/test/test.gyp:rtp_test_utils',
+ 'bwe_tools_util',
+ 'rtp_rtcp',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ 'include',
+ ],
+ },
+ 'sources': [
+ 'tools/bwe_rtp_play.cc',
+ ], # source
+ },
+ ],
+ }], # include_tests==1
+ ],
+}
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.cc b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.cc
new file mode 100644
index 0000000000..2dc32a7ee7
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.cc
@@ -0,0 +1,448 @@
+/*
+ * 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/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h"
+
+#include <math.h>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/thread_annotations.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/pacing/include/paced_sender.h"
+#include "webrtc/system_wrappers/include/clock.h"
+#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
+#include "webrtc/system_wrappers/include/logging.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+enum {
+ kTimestampGroupLengthMs = 5,
+ kAbsSendTimeFraction = 18,
+ kAbsSendTimeInterArrivalUpshift = 8,
+ kInterArrivalShift = kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift,
+ kInitialProbingIntervalMs = 2000,
+ kMinClusterSize = 4,
+ kMaxProbePackets = 15,
+ kExpectedNumberOfProbes = 3
+};
+
+static const size_t kPropagationDeltaQueueMaxSize = 1000;
+static const int64_t kPropagationDeltaQueueMaxTimeMs = 1000;
+static const double kTimestampToMs = 1000.0 /
+ static_cast<double>(1 << kInterArrivalShift);
+
+// Removes the entries at |index| of |time| and |value|, if time[index] is
+// smaller than or equal to |deadline|. |time| must be sorted ascendingly.
+static void RemoveStaleEntries(
+ std::vector<int64_t>* time, std::vector<int>* value, int64_t deadline) {
+ assert(time->size() == value->size());
+ std::vector<int64_t>::iterator end_of_removal = std::upper_bound(
+ time->begin(), time->end(), deadline);
+ size_t end_of_removal_index = end_of_removal - time->begin();
+
+ time->erase(time->begin(), end_of_removal);
+ value->erase(value->begin(), value->begin() + end_of_removal_index);
+}
+
+template<typename K, typename V>
+std::vector<K> Keys(const std::map<K, V>& map) {
+ std::vector<K> keys;
+ keys.reserve(map.size());
+ for (typename std::map<K, V>::const_iterator it = map.begin();
+ it != map.end(); ++it) {
+ keys.push_back(it->first);
+ }
+ return keys;
+}
+
+uint32_t ConvertMsTo24Bits(int64_t time_ms) {
+ uint32_t time_24_bits =
+ static_cast<uint32_t>(
+ ((static_cast<uint64_t>(time_ms) << kAbsSendTimeFraction) + 500) /
+ 1000) &
+ 0x00FFFFFF;
+ return time_24_bits;
+}
+
+bool RemoteBitrateEstimatorAbsSendTime::IsWithinClusterBounds(
+ int send_delta_ms,
+ const Cluster& cluster_aggregate) {
+ if (cluster_aggregate.count == 0)
+ return true;
+ float cluster_mean = cluster_aggregate.send_mean_ms /
+ static_cast<float>(cluster_aggregate.count);
+ return fabs(static_cast<float>(send_delta_ms) - cluster_mean) < 2.5f;
+ }
+
+ void RemoteBitrateEstimatorAbsSendTime::AddCluster(
+ std::list<Cluster>* clusters,
+ Cluster* cluster) {
+ cluster->send_mean_ms /= static_cast<float>(cluster->count);
+ cluster->recv_mean_ms /= static_cast<float>(cluster->count);
+ cluster->mean_size /= cluster->count;
+ clusters->push_back(*cluster);
+ }
+
+ int RemoteBitrateEstimatorAbsSendTime::Id() const {
+ return static_cast<int>(reinterpret_cast<uint64_t>(this));
+ }
+
+ RemoteBitrateEstimatorAbsSendTime::RemoteBitrateEstimatorAbsSendTime(
+ RemoteBitrateObserver* observer,
+ Clock* clock)
+ : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
+ observer_(observer),
+ clock_(clock),
+ ssrcs_(),
+ inter_arrival_(),
+ estimator_(OverUseDetectorOptions()),
+ detector_(OverUseDetectorOptions()),
+ incoming_bitrate_(kBitrateWindowMs, 8000),
+ last_process_time_(-1),
+ process_interval_ms_(kProcessIntervalMs),
+ total_propagation_delta_ms_(0),
+ total_probes_received_(0),
+ first_packet_time_ms_(-1) {
+ assert(observer_);
+ assert(clock_);
+ LOG(LS_INFO) << "RemoteBitrateEstimatorAbsSendTime: Instantiating.";
+}
+
+void RemoteBitrateEstimatorAbsSendTime::ComputeClusters(
+ std::list<Cluster>* clusters) const {
+ Cluster current;
+ int64_t prev_send_time = -1;
+ int64_t prev_recv_time = -1;
+ for (std::list<Probe>::const_iterator it = probes_.begin();
+ it != probes_.end();
+ ++it) {
+ if (prev_send_time >= 0) {
+ int send_delta_ms = it->send_time_ms - prev_send_time;
+ int recv_delta_ms = it->recv_time_ms - prev_recv_time;
+ if (send_delta_ms >= 1 && recv_delta_ms >= 1) {
+ ++current.num_above_min_delta;
+ }
+ if (!IsWithinClusterBounds(send_delta_ms, current)) {
+ if (current.count >= kMinClusterSize)
+ AddCluster(clusters, &current);
+ current = Cluster();
+ }
+ current.send_mean_ms += send_delta_ms;
+ current.recv_mean_ms += recv_delta_ms;
+ current.mean_size += it->payload_size;
+ ++current.count;
+ }
+ prev_send_time = it->send_time_ms;
+ prev_recv_time = it->recv_time_ms;
+ }
+ if (current.count >= kMinClusterSize)
+ AddCluster(clusters, &current);
+}
+
+std::list<Cluster>::const_iterator
+RemoteBitrateEstimatorAbsSendTime::FindBestProbe(
+ const std::list<Cluster>& clusters) const {
+ int highest_probe_bitrate_bps = 0;
+ std::list<Cluster>::const_iterator best_it = clusters.end();
+ for (std::list<Cluster>::const_iterator it = clusters.begin();
+ it != clusters.end();
+ ++it) {
+ if (it->send_mean_ms == 0 || it->recv_mean_ms == 0)
+ continue;
+ int send_bitrate_bps = it->mean_size * 8 * 1000 / it->send_mean_ms;
+ int recv_bitrate_bps = it->mean_size * 8 * 1000 / it->recv_mean_ms;
+ if (it->num_above_min_delta > it->count / 2 &&
+ (it->recv_mean_ms - it->send_mean_ms <= 2.0f &&
+ it->send_mean_ms - it->recv_mean_ms <= 5.0f)) {
+ int probe_bitrate_bps =
+ std::min(it->GetSendBitrateBps(), it->GetRecvBitrateBps());
+ if (probe_bitrate_bps > highest_probe_bitrate_bps) {
+ highest_probe_bitrate_bps = probe_bitrate_bps;
+ best_it = it;
+ }
+ } else {
+ LOG(LS_INFO) << "Probe failed, sent at " << send_bitrate_bps
+ << " bps, received at " << recv_bitrate_bps
+ << " bps. Mean send delta: " << it->send_mean_ms
+ << " ms, mean recv delta: " << it->recv_mean_ms
+ << " ms, num probes: " << it->count;
+ break;
+ }
+ }
+ return best_it;
+}
+
+void RemoteBitrateEstimatorAbsSendTime::ProcessClusters(int64_t now_ms) {
+ std::list<Cluster> clusters;
+ ComputeClusters(&clusters);
+ if (clusters.empty()) {
+ // If we reach the max number of probe packets and still have no clusters,
+ // we will remove the oldest one.
+ if (probes_.size() >= kMaxProbePackets)
+ probes_.pop_front();
+ return;
+ }
+
+ std::list<Cluster>::const_iterator best_it = FindBestProbe(clusters);
+ if (best_it != clusters.end()) {
+ int probe_bitrate_bps =
+ std::min(best_it->GetSendBitrateBps(), best_it->GetRecvBitrateBps());
+ // Make sure that a probe sent on a lower bitrate than our estimate can't
+ // reduce the estimate.
+ if (IsBitrateImproving(probe_bitrate_bps) &&
+ probe_bitrate_bps > static_cast<int>(incoming_bitrate_.Rate(now_ms))) {
+ LOG(LS_INFO) << "Probe successful, sent at "
+ << best_it->GetSendBitrateBps() << " bps, received at "
+ << best_it->GetRecvBitrateBps()
+ << " bps. Mean send delta: " << best_it->send_mean_ms
+ << " ms, mean recv delta: " << best_it->recv_mean_ms
+ << " ms, num probes: " << best_it->count;
+ remote_rate_.SetEstimate(probe_bitrate_bps, now_ms);
+ }
+ }
+
+ // Not probing and received non-probe packet, or finished with current set
+ // of probes.
+ if (clusters.size() >= kExpectedNumberOfProbes)
+ probes_.clear();
+}
+
+bool RemoteBitrateEstimatorAbsSendTime::IsBitrateImproving(
+ int new_bitrate_bps) const {
+ bool initial_probe = !remote_rate_.ValidEstimate() && new_bitrate_bps > 0;
+ bool bitrate_above_estimate =
+ remote_rate_.ValidEstimate() &&
+ new_bitrate_bps > static_cast<int>(remote_rate_.LatestEstimate());
+ return initial_probe || bitrate_above_estimate;
+}
+
+void RemoteBitrateEstimatorAbsSendTime::IncomingPacketFeedbackVector(
+ const std::vector<PacketInfo>& packet_feedback_vector) {
+ for (const auto& packet_info : packet_feedback_vector) {
+ IncomingPacketInfo(packet_info.arrival_time_ms,
+ ConvertMsTo24Bits(packet_info.send_time_ms),
+ packet_info.payload_size, 0, packet_info.was_paced);
+ }
+}
+
+void RemoteBitrateEstimatorAbsSendTime::IncomingPacket(int64_t arrival_time_ms,
+ size_t payload_size,
+ const RTPHeader& header,
+ bool was_paced) {
+ if (!header.extension.hasAbsoluteSendTime) {
+ LOG(LS_WARNING) << "RemoteBitrateEstimatorAbsSendTimeImpl: Incoming packet "
+ "is missing absolute send time extension!";
+ }
+ IncomingPacketInfo(arrival_time_ms, header.extension.absoluteSendTime,
+ payload_size, header.ssrc, was_paced);
+}
+
+void RemoteBitrateEstimatorAbsSendTime::IncomingPacketInfo(
+ int64_t arrival_time_ms,
+ uint32_t send_time_24bits,
+ size_t payload_size,
+ uint32_t ssrc,
+ bool was_paced) {
+ assert(send_time_24bits < (1ul << 24));
+ // Shift up send time to use the full 32 bits that inter_arrival works with,
+ // so wrapping works properly.
+ uint32_t timestamp = send_time_24bits << kAbsSendTimeInterArrivalUpshift;
+ int64_t send_time_ms = static_cast<int64_t>(timestamp) * kTimestampToMs;
+
+ CriticalSectionScoped cs(crit_sect_.get());
+ int64_t now_ms = clock_->TimeInMilliseconds();
+ // TODO(holmer): SSRCs are only needed for REMB, should be broken out from
+ // here.
+ ssrcs_[ssrc] = now_ms;
+ incoming_bitrate_.Update(payload_size, now_ms);
+ const BandwidthUsage prior_state = detector_.State();
+
+ if (first_packet_time_ms_ == -1)
+ first_packet_time_ms_ = clock_->TimeInMilliseconds();
+
+ uint32_t ts_delta = 0;
+ int64_t t_delta = 0;
+ int size_delta = 0;
+ // For now only try to detect probes while we don't have a valid estimate, and
+ // make sure the packet was paced. We currently assume that only packets
+ // larger than 200 bytes are paced by the sender.
+ was_paced = was_paced && payload_size > PacedSender::kMinProbePacketSize;
+ if (was_paced &&
+ (!remote_rate_.ValidEstimate() ||
+ now_ms - first_packet_time_ms_ < kInitialProbingIntervalMs)) {
+ // TODO(holmer): Use a map instead to get correct order?
+ if (total_probes_received_ < kMaxProbePackets) {
+ int send_delta_ms = -1;
+ int recv_delta_ms = -1;
+ if (!probes_.empty()) {
+ send_delta_ms = send_time_ms - probes_.back().send_time_ms;
+ recv_delta_ms = arrival_time_ms - probes_.back().recv_time_ms;
+ }
+ LOG(LS_INFO) << "Probe packet received: send time=" << send_time_ms
+ << " ms, recv time=" << arrival_time_ms
+ << " ms, send delta=" << send_delta_ms
+ << " ms, recv delta=" << recv_delta_ms << " ms.";
+ }
+ probes_.push_back(Probe(send_time_ms, arrival_time_ms, payload_size));
+ ++total_probes_received_;
+ ProcessClusters(now_ms);
+ }
+ if (!inter_arrival_.get()) {
+ inter_arrival_.reset(
+ new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000,
+ kTimestampToMs, true));
+ }
+ if (inter_arrival_->ComputeDeltas(timestamp, arrival_time_ms, payload_size,
+ &ts_delta, &t_delta, &size_delta)) {
+ double ts_delta_ms = (1000.0 * ts_delta) / (1 << kInterArrivalShift);
+ estimator_.Update(t_delta, ts_delta_ms, size_delta, detector_.State());
+ detector_.Detect(estimator_.offset(), ts_delta_ms,
+ estimator_.num_of_deltas(), arrival_time_ms);
+ UpdateStats(static_cast<int>(t_delta - ts_delta_ms), now_ms);
+ }
+ if (detector_.State() == kBwOverusing) {
+ uint32_t incoming_bitrate_bps = incoming_bitrate_.Rate(now_ms);
+ if (prior_state != kBwOverusing ||
+ remote_rate_.TimeToReduceFurther(now_ms, incoming_bitrate_bps)) {
+ // The first overuse should immediately trigger a new estimate.
+ // We also have to update the estimate immediately if we are overusing
+ // and the target bitrate is too high compared to what we are receiving.
+ UpdateEstimate(now_ms);
+ }
+ }
+}
+
+int32_t RemoteBitrateEstimatorAbsSendTime::Process() {
+ if (TimeUntilNextProcess() > 0) {
+ return 0;
+ }
+ {
+ CriticalSectionScoped cs(crit_sect_.get());
+ UpdateEstimate(clock_->TimeInMilliseconds());
+ }
+ last_process_time_ = clock_->TimeInMilliseconds();
+ return 0;
+}
+
+int64_t RemoteBitrateEstimatorAbsSendTime::TimeUntilNextProcess() {
+ if (last_process_time_ < 0) {
+ return 0;
+ }
+ {
+ CriticalSectionScoped cs(crit_sect_.get());
+ return last_process_time_ + process_interval_ms_ -
+ clock_->TimeInMilliseconds();
+ }
+}
+
+void RemoteBitrateEstimatorAbsSendTime::UpdateEstimate(int64_t now_ms) {
+ if (!inter_arrival_.get()) {
+ // No packets have been received on the active streams.
+ return;
+ }
+ for (Ssrcs::iterator it = ssrcs_.begin(); it != ssrcs_.end();) {
+ if ((now_ms - it->second) > kStreamTimeOutMs) {
+ ssrcs_.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+ if (ssrcs_.empty()) {
+ // We can't update the estimate if we don't have any active streams.
+ inter_arrival_.reset();
+ // We deliberately don't reset the first_packet_time_ms_ here for now since
+ // we only probe for bandwidth in the beginning of a call right now.
+ return;
+ }
+
+ const RateControlInput input(detector_.State(),
+ incoming_bitrate_.Rate(now_ms),
+ estimator_.var_noise());
+ remote_rate_.Update(&input, now_ms);
+ unsigned int target_bitrate = remote_rate_.UpdateBandwidthEstimate(now_ms);
+ if (remote_rate_.ValidEstimate()) {
+ process_interval_ms_ = remote_rate_.GetFeedbackInterval();
+ observer_->OnReceiveBitrateChanged(Keys(ssrcs_), target_bitrate);
+ }
+}
+
+void RemoteBitrateEstimatorAbsSendTime::OnRttUpdate(int64_t avg_rtt_ms,
+ int64_t max_rtt_ms) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ remote_rate_.SetRtt(avg_rtt_ms);
+}
+
+void RemoteBitrateEstimatorAbsSendTime::RemoveStream(unsigned int ssrc) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ssrcs_.erase(ssrc);
+}
+
+bool RemoteBitrateEstimatorAbsSendTime::LatestEstimate(
+ std::vector<unsigned int>* ssrcs,
+ unsigned int* bitrate_bps) const {
+ CriticalSectionScoped cs(crit_sect_.get());
+ assert(ssrcs);
+ assert(bitrate_bps);
+ if (!remote_rate_.ValidEstimate()) {
+ return false;
+ }
+ *ssrcs = Keys(ssrcs_);
+ if (ssrcs_.empty()) {
+ *bitrate_bps = 0;
+ } else {
+ *bitrate_bps = remote_rate_.LatestEstimate();
+ }
+ return true;
+}
+
+bool RemoteBitrateEstimatorAbsSendTime::GetStats(
+ ReceiveBandwidthEstimatorStats* output) const {
+ {
+ CriticalSectionScoped cs(crit_sect_.get());
+ output->recent_propagation_time_delta_ms = recent_propagation_delta_ms_;
+ output->recent_arrival_time_ms = recent_update_time_ms_;
+ output->total_propagation_time_delta_ms = total_propagation_delta_ms_;
+ }
+ RemoveStaleEntries(
+ &output->recent_arrival_time_ms,
+ &output->recent_propagation_time_delta_ms,
+ clock_->TimeInMilliseconds() - kPropagationDeltaQueueMaxTimeMs);
+ return true;
+}
+
+void RemoteBitrateEstimatorAbsSendTime::UpdateStats(int propagation_delta_ms,
+ int64_t now_ms) {
+ // The caller must enter crit_sect_ before the call.
+
+ // Remove the oldest entry if the size limit is reached.
+ if (recent_update_time_ms_.size() == kPropagationDeltaQueueMaxSize) {
+ recent_update_time_ms_.erase(recent_update_time_ms_.begin());
+ recent_propagation_delta_ms_.erase(recent_propagation_delta_ms_.begin());
+ }
+
+ recent_propagation_delta_ms_.push_back(propagation_delta_ms);
+ recent_update_time_ms_.push_back(now_ms);
+
+ RemoveStaleEntries(
+ &recent_update_time_ms_,
+ &recent_propagation_delta_ms_,
+ now_ms - kPropagationDeltaQueueMaxTimeMs);
+
+ total_propagation_delta_ms_ =
+ std::max(total_propagation_delta_ms_ + propagation_delta_ms, 0);
+}
+
+void RemoteBitrateEstimatorAbsSendTime::SetMinBitrate(int min_bitrate_bps) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ remote_rate_.SetMinBitrate(min_bitrate_bps);
+}
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h
new file mode 100644
index 0000000000..549c437faf
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_
+
+#include <list>
+#include <map>
+#include <vector>
+
+#include "webrtc/base/checks.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/aimd_rate_control.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/inter_arrival.h"
+#include "webrtc/modules/remote_bitrate_estimator/overuse_detector.h"
+#include "webrtc/modules/remote_bitrate_estimator/overuse_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/rate_statistics.h"
+#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
+
+namespace webrtc {
+
+struct Probe {
+ Probe(int64_t send_time_ms, int64_t recv_time_ms, size_t payload_size)
+ : send_time_ms(send_time_ms),
+ recv_time_ms(recv_time_ms),
+ payload_size(payload_size) {}
+ int64_t send_time_ms;
+ int64_t recv_time_ms;
+ size_t payload_size;
+};
+
+struct Cluster {
+ Cluster()
+ : send_mean_ms(0.0f),
+ recv_mean_ms(0.0f),
+ mean_size(0),
+ count(0),
+ num_above_min_delta(0) {}
+
+ int GetSendBitrateBps() const {
+ RTC_CHECK_GT(send_mean_ms, 0.0f);
+ return mean_size * 8 * 1000 / send_mean_ms;
+ }
+
+ int GetRecvBitrateBps() const {
+ RTC_CHECK_GT(recv_mean_ms, 0.0f);
+ return mean_size * 8 * 1000 / recv_mean_ms;
+ }
+
+ float send_mean_ms;
+ float recv_mean_ms;
+ // TODO(holmer): Add some variance metric as well?
+ size_t mean_size;
+ int count;
+ int num_above_min_delta;
+};
+
+class RemoteBitrateEstimatorAbsSendTime : public RemoteBitrateEstimator {
+ public:
+ RemoteBitrateEstimatorAbsSendTime(RemoteBitrateObserver* observer,
+ Clock* clock);
+ virtual ~RemoteBitrateEstimatorAbsSendTime() {}
+
+ void IncomingPacketFeedbackVector(
+ const std::vector<PacketInfo>& packet_feedback_vector) override;
+
+ void IncomingPacket(int64_t arrival_time_ms,
+ size_t payload_size,
+ const RTPHeader& header,
+ bool was_paced) override;
+ // This class relies on Process() being called periodically (at least once
+ // every other second) for streams to be timed out properly. Therefore it
+ // shouldn't be detached from the ProcessThread except if it's about to be
+ // deleted.
+ int32_t Process() override;
+ int64_t TimeUntilNextProcess() override;
+ void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override;
+ void RemoveStream(unsigned int ssrc) override;
+ bool LatestEstimate(std::vector<unsigned int>* ssrcs,
+ unsigned int* bitrate_bps) const override;
+ bool GetStats(ReceiveBandwidthEstimatorStats* output) const override;
+ void SetMinBitrate(int min_bitrate_bps) override;
+
+ private:
+ typedef std::map<unsigned int, int64_t> Ssrcs;
+
+ static bool IsWithinClusterBounds(int send_delta_ms,
+ const Cluster& cluster_aggregate);
+
+ static void AddCluster(std::list<Cluster>* clusters, Cluster* cluster);
+
+ int Id() const;
+
+ void IncomingPacketInfo(int64_t arrival_time_ms,
+ uint32_t send_time_24bits,
+ size_t payload_size,
+ uint32_t ssrc,
+ bool was_paced);
+
+ bool IsProbe(int64_t send_time_ms, int payload_size) const
+ EXCLUSIVE_LOCKS_REQUIRED(crit_sect_.get());
+
+ // Triggers a new estimate calculation.
+ void UpdateEstimate(int64_t now_ms)
+ EXCLUSIVE_LOCKS_REQUIRED(crit_sect_.get());
+
+ void UpdateStats(int propagation_delta_ms, int64_t now_ms)
+ EXCLUSIVE_LOCKS_REQUIRED(crit_sect_.get());
+
+ void ComputeClusters(std::list<Cluster>* clusters) const;
+
+ std::list<Cluster>::const_iterator FindBestProbe(
+ const std::list<Cluster>& clusters) const
+ EXCLUSIVE_LOCKS_REQUIRED(crit_sect_.get());
+
+ void ProcessClusters(int64_t now_ms)
+ EXCLUSIVE_LOCKS_REQUIRED(crit_sect_.get());
+
+ bool IsBitrateImproving(int probe_bitrate_bps) const
+ EXCLUSIVE_LOCKS_REQUIRED(crit_sect_.get());
+
+ rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_;
+ RemoteBitrateObserver* observer_ GUARDED_BY(crit_sect_.get());
+ Clock* clock_;
+ Ssrcs ssrcs_ GUARDED_BY(crit_sect_.get());
+ rtc::scoped_ptr<InterArrival> inter_arrival_ GUARDED_BY(crit_sect_.get());
+ OveruseEstimator estimator_ GUARDED_BY(crit_sect_.get());
+ OveruseDetector detector_ GUARDED_BY(crit_sect_.get());
+ RateStatistics incoming_bitrate_ GUARDED_BY(crit_sect_.get());
+ AimdRateControl remote_rate_ GUARDED_BY(crit_sect_.get());
+ int64_t last_process_time_;
+ std::vector<int> recent_propagation_delta_ms_ GUARDED_BY(crit_sect_.get());
+ std::vector<int64_t> recent_update_time_ms_ GUARDED_BY(crit_sect_.get());
+ int64_t process_interval_ms_ GUARDED_BY(crit_sect_.get());
+ int total_propagation_delta_ms_ GUARDED_BY(crit_sect_.get());
+
+ std::list<Probe> probes_;
+ size_t total_probes_received_;
+ int64_t first_packet_time_ms_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RemoteBitrateEstimatorAbsSendTime);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time_unittest.cc b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time_unittest.cc
new file mode 100644
index 0000000000..195c95aacb
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time_unittest.cc
@@ -0,0 +1,292 @@
+/*
+ * 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/base/constructormagic.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h"
+
+namespace webrtc {
+
+class RemoteBitrateEstimatorAbsSendTimeTest :
+ public RemoteBitrateEstimatorTest {
+ public:
+
+ RemoteBitrateEstimatorAbsSendTimeTest() {}
+ virtual void SetUp() {
+ bitrate_estimator_.reset(new RemoteBitrateEstimatorAbsSendTime(
+ bitrate_observer_.get(), &clock_));
+ }
+ protected:
+ RTC_DISALLOW_COPY_AND_ASSIGN(RemoteBitrateEstimatorAbsSendTimeTest);
+};
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, InitialBehavior) {
+ InitialBehaviorTestHelper(508017);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, RateIncreaseReordering) {
+ RateIncreaseReorderingTestHelper(506422);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, RateIncreaseRtpTimestamps) {
+ RateIncreaseRtpTimestampsTestHelper(1240);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropOneStream) {
+ CapacityDropTestHelper(1, false, 600);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropOneStreamWrap) {
+ CapacityDropTestHelper(1, true, 600);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropTwoStreamsWrap) {
+ CapacityDropTestHelper(2, true, 533);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropThreeStreamsWrap) {
+ CapacityDropTestHelper(3, true, 700);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropThirteenStreamsWrap) {
+ CapacityDropTestHelper(13, true, 700);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropNineteenStreamsWrap) {
+ CapacityDropTestHelper(19, true, 700);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropThirtyStreamsWrap) {
+ CapacityDropTestHelper(30, true, 700);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, TestTimestampGrouping) {
+ TestTimestampGroupingTestHelper();
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, TestGetStats) {
+ TestGetStatsHelper();
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, TestShortTimeoutAndWrap) {
+ // Simulate a client leaving and rejoining the call after 35 seconds. This
+ // will make abs send time wrap, so if streams aren't timed out properly
+ // the next 30 seconds of packets will be out of order.
+ TestWrappingHelper(35);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, TestLongTimeoutAndWrap) {
+ // Simulate a client leaving and rejoining the call after some multiple of
+ // 64 seconds later. This will cause a zero difference in abs send times due
+ // to the wrap, but a big difference in arrival time, if streams aren't
+ // properly timed out.
+ TestWrappingHelper(10 * 64);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, TestProcessAfterTimeout) {
+ // This time constant must be equal to the ones defined for the
+ // RemoteBitrateEstimator.
+ const int64_t kStreamTimeOutMs = 2000;
+ const int64_t kProcessIntervalMs = 1000;
+ IncomingPacket(0, 1000, clock_.TimeInMilliseconds(), 0, 0, true);
+ clock_.AdvanceTimeMilliseconds(kStreamTimeOutMs + 1);
+ // Trigger timeout.
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+ clock_.AdvanceTimeMilliseconds(kProcessIntervalMs);
+ // This shouldn't crash.
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, TestProbeDetection) {
+ const int kProbeLength = 5;
+ int64_t now_ms = clock_.TimeInMilliseconds();
+ // First burst sent at 8 * 1000 / 10 = 800 kbps.
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(10);
+ now_ms = clock_.TimeInMilliseconds();
+ IncomingPacket(0, 1000, now_ms, 90 * now_ms, AbsSendTime(now_ms, 1000),
+ true);
+ }
+
+ // Second burst sent at 8 * 1000 / 5 = 1600 kbps.
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(5);
+ now_ms = clock_.TimeInMilliseconds();
+ IncomingPacket(0, 1000, now_ms, 90 * now_ms, AbsSendTime(now_ms, 1000),
+ true);
+ }
+
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+ EXPECT_TRUE(bitrate_observer_->updated());
+ EXPECT_GT(bitrate_observer_->latest_bitrate(), 1500000u);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest,
+ TestProbeDetectionNonPacedPackets) {
+ const int kProbeLength = 5;
+ int64_t now_ms = clock_.TimeInMilliseconds();
+ // First burst sent at 8 * 1000 / 10 = 800 kbps, but with every other packet
+ // not being paced which could mess things up.
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(5);
+ now_ms = clock_.TimeInMilliseconds();
+ IncomingPacket(0, 1000, now_ms, 90 * now_ms, AbsSendTime(now_ms, 1000),
+ true);
+ // Non-paced packet, arriving 5 ms after.
+ clock_.AdvanceTimeMilliseconds(5);
+ IncomingPacket(0, 100, now_ms, 90 * now_ms, AbsSendTime(now_ms, 1000),
+ false);
+ }
+
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+ EXPECT_TRUE(bitrate_observer_->updated());
+ EXPECT_GT(bitrate_observer_->latest_bitrate(), 800000u);
+}
+
+// Packets will require 5 ms to be transmitted to the receiver, causing packets
+// of the second probe to be dispersed.
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest,
+ TestProbeDetectionTooHighBitrate) {
+ const int kProbeLength = 5;
+ int64_t now_ms = clock_.TimeInMilliseconds();
+ int64_t send_time_ms = 0;
+ // First burst sent at 8 * 1000 / 10 = 800 kbps.
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(10);
+ now_ms = clock_.TimeInMilliseconds();
+ send_time_ms += 10;
+ IncomingPacket(0, 1000, now_ms, 90 * send_time_ms,
+ AbsSendTime(send_time_ms, 1000), true);
+ }
+
+ // Second burst sent at 8 * 1000 / 5 = 1600 kbps, arriving at 8 * 1000 / 8 =
+ // 1000 kbps.
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(8);
+ now_ms = clock_.TimeInMilliseconds();
+ send_time_ms += 5;
+ IncomingPacket(0, 1000, now_ms, send_time_ms,
+ AbsSendTime(send_time_ms, 1000), true);
+ }
+
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+ EXPECT_TRUE(bitrate_observer_->updated());
+ EXPECT_NEAR(bitrate_observer_->latest_bitrate(), 800000u, 10000);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest,
+ TestProbeDetectionSlightlyFasterArrival) {
+ const int kProbeLength = 5;
+ int64_t now_ms = clock_.TimeInMilliseconds();
+ // First burst sent at 8 * 1000 / 10 = 800 kbps.
+ // Arriving at 8 * 1000 / 5 = 1600 kbps.
+ int64_t send_time_ms = 0;
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(5);
+ send_time_ms += 10;
+ now_ms = clock_.TimeInMilliseconds();
+ IncomingPacket(0, 1000, now_ms, 90 * send_time_ms,
+ AbsSendTime(send_time_ms, 1000), true);
+ }
+
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+ EXPECT_TRUE(bitrate_observer_->updated());
+ EXPECT_GT(bitrate_observer_->latest_bitrate(), 800000u);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, TestProbeDetectionFasterArrival) {
+ const int kProbeLength = 5;
+ int64_t now_ms = clock_.TimeInMilliseconds();
+ // First burst sent at 8 * 1000 / 10 = 800 kbps.
+ // Arriving at 8 * 1000 / 5 = 1600 kbps.
+ int64_t send_time_ms = 0;
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(1);
+ send_time_ms += 10;
+ now_ms = clock_.TimeInMilliseconds();
+ IncomingPacket(0, 1000, now_ms, 90 * send_time_ms,
+ AbsSendTime(send_time_ms, 1000), true);
+ }
+
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+ EXPECT_FALSE(bitrate_observer_->updated());
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, TestProbeDetectionSlowerArrival) {
+ const int kProbeLength = 5;
+ int64_t now_ms = clock_.TimeInMilliseconds();
+ // First burst sent at 8 * 1000 / 5 = 1600 kbps.
+ // Arriving at 8 * 1000 / 7 = 1142 kbps.
+ int64_t send_time_ms = 0;
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(7);
+ send_time_ms += 5;
+ now_ms = clock_.TimeInMilliseconds();
+ IncomingPacket(0, 1000, now_ms, 90 * send_time_ms,
+ AbsSendTime(send_time_ms, 1000), true);
+ }
+
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+ EXPECT_TRUE(bitrate_observer_->updated());
+ EXPECT_NEAR(bitrate_observer_->latest_bitrate(), 1140000, 10000);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest,
+ TestProbeDetectionSlowerArrivalHighBitrate) {
+ const int kProbeLength = 5;
+ int64_t now_ms = clock_.TimeInMilliseconds();
+ // Burst sent at 8 * 1000 / 1 = 8000 kbps.
+ // Arriving at 8 * 1000 / 2 = 4000 kbps.
+ int64_t send_time_ms = 0;
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(2);
+ send_time_ms += 1;
+ now_ms = clock_.TimeInMilliseconds();
+ IncomingPacket(0, 1000, now_ms, 90 * send_time_ms,
+ AbsSendTime(send_time_ms, 1000), true);
+ }
+
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+ EXPECT_TRUE(bitrate_observer_->updated());
+ EXPECT_NEAR(bitrate_observer_->latest_bitrate(), 4000000u, 10000);
+}
+
+TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, ProbingIgnoresSmallPackets) {
+ const int kProbeLength = 5;
+ int64_t now_ms = clock_.TimeInMilliseconds();
+ // Probing with 200 bytes every 10 ms, should be ignored by the probe
+ // detection.
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(10);
+ now_ms = clock_.TimeInMilliseconds();
+ IncomingPacket(0, 200, now_ms, 90 * now_ms, AbsSendTime(now_ms, 1000),
+ true);
+ }
+
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+ EXPECT_FALSE(bitrate_observer_->updated());
+
+ // Followed by a probe with 1000 bytes packets, should be detected as a
+ // probe.
+ for (int i = 0; i < kProbeLength; ++i) {
+ clock_.AdvanceTimeMilliseconds(10);
+ now_ms = clock_.TimeInMilliseconds();
+ IncomingPacket(0, 1000, now_ms, 90 * now_ms, AbsSendTime(now_ms, 1000),
+ true);
+ }
+
+ // Wait long enough so that we can call Process again.
+ clock_.AdvanceTimeMilliseconds(1000);
+
+ EXPECT_EQ(0, bitrate_estimator_->Process());
+ EXPECT_TRUE(bitrate_observer_->updated());
+ EXPECT_NEAR(bitrate_observer_->latest_bitrate(), 800000u, 10000);
+}
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.cc b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.cc
new file mode 100644
index 0000000000..f1a1cb6602
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.cc
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2012 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/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.h"
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/thread_annotations.h"
+#include "webrtc/modules/remote_bitrate_estimator/inter_arrival.h"
+#include "webrtc/modules/remote_bitrate_estimator/overuse_detector.h"
+#include "webrtc/modules/remote_bitrate_estimator/overuse_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/aimd_rate_control.h"
+#include "webrtc/system_wrappers/include/clock.h"
+#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
+#include "webrtc/system_wrappers/include/logging.h"
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+
+enum { kTimestampGroupLengthMs = 5 };
+static const double kTimestampToMs = 1.0 / 90.0;
+
+struct RemoteBitrateEstimatorSingleStream::Detector {
+ explicit Detector(int64_t last_packet_time_ms,
+ const OverUseDetectorOptions& options,
+ bool enable_burst_grouping)
+ : last_packet_time_ms(last_packet_time_ms),
+ inter_arrival(90 * kTimestampGroupLengthMs, kTimestampToMs,
+ enable_burst_grouping),
+ estimator(options),
+ detector(options) {}
+ int64_t last_packet_time_ms;
+ InterArrival inter_arrival;
+ OveruseEstimator estimator;
+ OveruseDetector detector;
+ };
+
+ RemoteBitrateEstimatorSingleStream::RemoteBitrateEstimatorSingleStream(
+ RemoteBitrateObserver* observer,
+ Clock* clock)
+ : clock_(clock),
+ incoming_bitrate_(kBitrateWindowMs, 8000),
+ remote_rate_(new AimdRateControl()),
+ observer_(observer),
+ crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
+ last_process_time_(-1),
+ process_interval_ms_(kProcessIntervalMs) {
+ assert(observer_);
+ LOG(LS_INFO) << "RemoteBitrateEstimatorSingleStream: Instantiating.";
+}
+
+RemoteBitrateEstimatorSingleStream::~RemoteBitrateEstimatorSingleStream() {
+ while (!overuse_detectors_.empty()) {
+ SsrcOveruseEstimatorMap::iterator it = overuse_detectors_.begin();
+ delete it->second;
+ overuse_detectors_.erase(it);
+ }
+}
+
+void RemoteBitrateEstimatorSingleStream::IncomingPacket(int64_t arrival_time_ms,
+ size_t payload_size,
+ const RTPHeader& header,
+ bool was_paced) {
+ uint32_t ssrc = header.ssrc;
+ uint32_t rtp_timestamp = header.timestamp +
+ header.extension.transmissionTimeOffset;
+ int64_t now_ms = clock_->TimeInMilliseconds();
+ CriticalSectionScoped cs(crit_sect_.get());
+ SsrcOveruseEstimatorMap::iterator it = overuse_detectors_.find(ssrc);
+ if (it == overuse_detectors_.end()) {
+ // This is a new SSRC. Adding to map.
+ // TODO(holmer): If the channel changes SSRC the old SSRC will still be
+ // around in this map until the channel is deleted. This is OK since the
+ // callback will no longer be called for the old SSRC. This will be
+ // automatically cleaned up when we have one RemoteBitrateEstimator per REMB
+ // group.
+ std::pair<SsrcOveruseEstimatorMap::iterator, bool> insert_result =
+ overuse_detectors_.insert(std::make_pair(
+ ssrc, new Detector(now_ms, OverUseDetectorOptions(), true)));
+ it = insert_result.first;
+ }
+ Detector* estimator = it->second;
+ estimator->last_packet_time_ms = now_ms;
+ incoming_bitrate_.Update(payload_size, now_ms);
+ const BandwidthUsage prior_state = estimator->detector.State();
+ uint32_t timestamp_delta = 0;
+ int64_t time_delta = 0;
+ int size_delta = 0;
+ if (estimator->inter_arrival.ComputeDeltas(rtp_timestamp, arrival_time_ms,
+ payload_size, &timestamp_delta,
+ &time_delta, &size_delta)) {
+ double timestamp_delta_ms = timestamp_delta * kTimestampToMs;
+ estimator->estimator.Update(time_delta, timestamp_delta_ms, size_delta,
+ estimator->detector.State());
+ estimator->detector.Detect(estimator->estimator.offset(),
+ timestamp_delta_ms,
+ estimator->estimator.num_of_deltas(), now_ms);
+ }
+ if (estimator->detector.State() == kBwOverusing) {
+ uint32_t incoming_bitrate_bps = incoming_bitrate_.Rate(now_ms);
+ if (prior_state != kBwOverusing ||
+ remote_rate_->TimeToReduceFurther(now_ms, incoming_bitrate_bps)) {
+ // The first overuse should immediately trigger a new estimate.
+ // We also have to update the estimate immediately if we are overusing
+ // and the target bitrate is too high compared to what we are receiving.
+ UpdateEstimate(now_ms);
+ }
+ }
+}
+
+int32_t RemoteBitrateEstimatorSingleStream::Process() {
+ if (TimeUntilNextProcess() > 0) {
+ return 0;
+ }
+ {
+ CriticalSectionScoped cs(crit_sect_.get());
+ UpdateEstimate(clock_->TimeInMilliseconds());
+ }
+ last_process_time_ = clock_->TimeInMilliseconds();
+ return 0;
+}
+
+int64_t RemoteBitrateEstimatorSingleStream::TimeUntilNextProcess() {
+ if (last_process_time_ < 0) {
+ return 0;
+ }
+ {
+ CriticalSectionScoped cs_(crit_sect_.get());
+ return last_process_time_ + process_interval_ms_ -
+ clock_->TimeInMilliseconds();
+ }
+}
+
+void RemoteBitrateEstimatorSingleStream::UpdateEstimate(int64_t now_ms) {
+ BandwidthUsage bw_state = kBwNormal;
+ double sum_var_noise = 0.0;
+ SsrcOveruseEstimatorMap::iterator it = overuse_detectors_.begin();
+ while (it != overuse_detectors_.end()) {
+ const int64_t time_of_last_received_packet =
+ it->second->last_packet_time_ms;
+ if (time_of_last_received_packet >= 0 &&
+ now_ms - time_of_last_received_packet > kStreamTimeOutMs) {
+ // This over-use detector hasn't received packets for |kStreamTimeOutMs|
+ // milliseconds and is considered stale.
+ delete it->second;
+ overuse_detectors_.erase(it++);
+ } else {
+ sum_var_noise += it->second->estimator.var_noise();
+ // Make sure that we trigger an over-use if any of the over-use detectors
+ // is detecting over-use.
+ if (it->second->detector.State() > bw_state) {
+ bw_state = it->second->detector.State();
+ }
+ ++it;
+ }
+ }
+ // We can't update the estimate if we don't have any active streams.
+ if (overuse_detectors_.empty()) {
+ remote_rate_.reset(new AimdRateControl());
+ return;
+ }
+ double mean_noise_var = sum_var_noise /
+ static_cast<double>(overuse_detectors_.size());
+ const RateControlInput input(bw_state,
+ incoming_bitrate_.Rate(now_ms),
+ mean_noise_var);
+ remote_rate_->Update(&input, now_ms);
+ unsigned int target_bitrate = remote_rate_->UpdateBandwidthEstimate(now_ms);
+ if (remote_rate_->ValidEstimate()) {
+ process_interval_ms_ = remote_rate_->GetFeedbackInterval();
+ std::vector<unsigned int> ssrcs;
+ GetSsrcs(&ssrcs);
+ observer_->OnReceiveBitrateChanged(ssrcs, target_bitrate);
+ }
+}
+
+void RemoteBitrateEstimatorSingleStream::OnRttUpdate(int64_t avg_rtt_ms,
+ int64_t max_rtt_ms) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ remote_rate_->SetRtt(avg_rtt_ms);
+}
+
+void RemoteBitrateEstimatorSingleStream::RemoveStream(unsigned int ssrc) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ SsrcOveruseEstimatorMap::iterator it = overuse_detectors_.find(ssrc);
+ if (it != overuse_detectors_.end()) {
+ delete it->second;
+ overuse_detectors_.erase(it);
+ }
+}
+
+bool RemoteBitrateEstimatorSingleStream::LatestEstimate(
+ std::vector<unsigned int>* ssrcs,
+ unsigned int* bitrate_bps) const {
+ CriticalSectionScoped cs(crit_sect_.get());
+ assert(bitrate_bps);
+ if (!remote_rate_->ValidEstimate()) {
+ return false;
+ }
+ GetSsrcs(ssrcs);
+ if (ssrcs->empty())
+ *bitrate_bps = 0;
+ else
+ *bitrate_bps = remote_rate_->LatestEstimate();
+ return true;
+}
+
+bool RemoteBitrateEstimatorSingleStream::GetStats(
+ ReceiveBandwidthEstimatorStats* output) const {
+ // Not implemented.
+ return false;
+}
+
+void RemoteBitrateEstimatorSingleStream::GetSsrcs(
+ std::vector<unsigned int>* ssrcs) const {
+ assert(ssrcs);
+ ssrcs->resize(overuse_detectors_.size());
+ int i = 0;
+ for (SsrcOveruseEstimatorMap::const_iterator it = overuse_detectors_.begin();
+ it != overuse_detectors_.end(); ++it, ++i) {
+ (*ssrcs)[i] = it->first;
+ }
+}
+
+void RemoteBitrateEstimatorSingleStream::SetMinBitrate(int min_bitrate_bps) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ remote_rate_->SetMinBitrate(min_bitrate_bps);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.h b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.h
new file mode 100644
index 0000000000..35fe7216a5
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_SINGLE_STREAM_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_SINGLE_STREAM_H_
+
+#include <map>
+#include <vector>
+
+#include "webrtc/modules/remote_bitrate_estimator/aimd_rate_control.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/rate_statistics.h"
+#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
+
+namespace webrtc {
+
+class RemoteBitrateEstimatorSingleStream : public RemoteBitrateEstimator {
+ public:
+ RemoteBitrateEstimatorSingleStream(RemoteBitrateObserver* observer,
+ Clock* clock);
+ virtual ~RemoteBitrateEstimatorSingleStream();
+
+ void IncomingPacket(int64_t arrival_time_ms,
+ size_t payload_size,
+ const RTPHeader& header,
+ bool was_paced) override;
+ int32_t Process() override;
+ int64_t TimeUntilNextProcess() override;
+ void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override;
+ void RemoveStream(unsigned int ssrc) override;
+ bool LatestEstimate(std::vector<unsigned int>* ssrcs,
+ unsigned int* bitrate_bps) const override;
+ bool GetStats(ReceiveBandwidthEstimatorStats* output) const override;
+ void SetMinBitrate(int min_bitrate_bps) override;
+
+ private:
+ struct Detector;
+
+ typedef std::map<unsigned int, Detector*> SsrcOveruseEstimatorMap;
+
+ // Triggers a new estimate calculation.
+ void UpdateEstimate(int64_t time_now)
+ EXCLUSIVE_LOCKS_REQUIRED(crit_sect_.get());
+
+ void GetSsrcs(std::vector<unsigned int>* ssrcs) const
+ SHARED_LOCKS_REQUIRED(crit_sect_.get());
+
+ Clock* clock_;
+ SsrcOveruseEstimatorMap overuse_detectors_ GUARDED_BY(crit_sect_.get());
+ RateStatistics incoming_bitrate_ GUARDED_BY(crit_sect_.get());
+ rtc::scoped_ptr<AimdRateControl> remote_rate_ GUARDED_BY(crit_sect_.get());
+ RemoteBitrateObserver* observer_ GUARDED_BY(crit_sect_.get());
+ rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_;
+ int64_t last_process_time_;
+ int64_t process_interval_ms_ GUARDED_BY(crit_sect_.get());
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RemoteBitrateEstimatorSingleStream);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_SINGLE_STREAM_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream_unittest.cc b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream_unittest.cc
new file mode 100644
index 0000000000..a6c182a7bc
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream_unittest.cc
@@ -0,0 +1,73 @@
+/*
+ * 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/base/constructormagic.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h"
+
+namespace webrtc {
+
+class RemoteBitrateEstimatorSingleTest :
+ public RemoteBitrateEstimatorTest {
+ public:
+
+ RemoteBitrateEstimatorSingleTest() {}
+ virtual void SetUp() {
+ bitrate_estimator_.reset(new RemoteBitrateEstimatorSingleStream(
+ bitrate_observer_.get(), &clock_));
+ }
+ protected:
+ RTC_DISALLOW_COPY_AND_ASSIGN(RemoteBitrateEstimatorSingleTest);
+};
+
+TEST_F(RemoteBitrateEstimatorSingleTest, InitialBehavior) {
+ InitialBehaviorTestHelper(508017);
+}
+
+TEST_F(RemoteBitrateEstimatorSingleTest, RateIncreaseReordering) {
+ RateIncreaseReorderingTestHelper(506422);
+}
+
+TEST_F(RemoteBitrateEstimatorSingleTest, RateIncreaseRtpTimestamps) {
+ RateIncreaseRtpTimestampsTestHelper(1240);
+}
+
+TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropOneStream) {
+ CapacityDropTestHelper(1, false, 600);
+}
+
+TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropOneStreamWrap) {
+ CapacityDropTestHelper(1, true, 600);
+}
+
+TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropTwoStreamsWrap) {
+ CapacityDropTestHelper(2, true, 700);
+}
+
+TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropThreeStreamsWrap) {
+ CapacityDropTestHelper(3, true, 734);
+}
+
+TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropThirteenStreamsWrap) {
+ CapacityDropTestHelper(13, true, 700);
+}
+
+TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropNineteenStreamsWrap) {
+ CapacityDropTestHelper(19, true, 700);
+}
+
+TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropThirtyStreamsWrap) {
+ CapacityDropTestHelper(30, true, 700);
+}
+
+TEST_F(RemoteBitrateEstimatorSingleTest, TestTimestampGrouping) {
+ TestTimestampGroupingTestHelper();
+}
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.cc b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.cc
new file mode 100644
index 0000000000..315f5422d9
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.cc
@@ -0,0 +1,667 @@
+/*
+ * Copyright (c) 2012 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/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h"
+
+#include <algorithm>
+#include <utility>
+
+namespace webrtc {
+
+const size_t kMtu = 1200;
+const unsigned int kAcceptedBitrateErrorBps = 50000;
+
+namespace testing {
+
+void TestBitrateObserver::OnReceiveBitrateChanged(
+ const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) {
+ latest_bitrate_ = bitrate;
+ updated_ = true;
+}
+
+RtpStream::RtpStream(int fps,
+ int bitrate_bps,
+ unsigned int ssrc,
+ unsigned int frequency,
+ uint32_t timestamp_offset,
+ int64_t rtcp_receive_time)
+ : fps_(fps),
+ bitrate_bps_(bitrate_bps),
+ ssrc_(ssrc),
+ frequency_(frequency),
+ next_rtp_time_(0),
+ next_rtcp_time_(rtcp_receive_time),
+ rtp_timestamp_offset_(timestamp_offset),
+ kNtpFracPerMs(4.294967296E6) {
+ assert(fps_ > 0);
+}
+
+void RtpStream::set_rtp_timestamp_offset(uint32_t offset) {
+ rtp_timestamp_offset_ = offset;
+}
+
+// Generates a new frame for this stream. If called too soon after the
+// previous frame, no frame will be generated. The frame is split into
+// packets.
+int64_t RtpStream::GenerateFrame(int64_t time_now_us, PacketList* packets) {
+ if (time_now_us < next_rtp_time_) {
+ return next_rtp_time_;
+ }
+ assert(packets != NULL);
+ size_t bits_per_frame = (bitrate_bps_ + fps_ / 2) / fps_;
+ size_t n_packets =
+ std::max<size_t>((bits_per_frame + 4 * kMtu) / (8 * kMtu), 1u);
+ size_t packet_size = (bits_per_frame + 4 * n_packets) / (8 * n_packets);
+ for (size_t i = 0; i < n_packets; ++i) {
+ RtpPacket* packet = new RtpPacket;
+ packet->send_time = time_now_us + kSendSideOffsetUs;
+ packet->size = packet_size;
+ packet->rtp_timestamp = rtp_timestamp_offset_ + static_cast<uint32_t>(
+ ((frequency_ / 1000) * packet->send_time + 500) / 1000);
+ packet->ssrc = ssrc_;
+ packets->push_back(packet);
+ }
+ next_rtp_time_ = time_now_us + (1000000 + fps_ / 2) / fps_;
+ return next_rtp_time_;
+}
+
+// The send-side time when the next frame can be generated.
+double RtpStream::next_rtp_time() const {
+ return next_rtp_time_;
+}
+
+// Generates an RTCP packet.
+RtpStream::RtcpPacket* RtpStream::Rtcp(int64_t time_now_us) {
+ if (time_now_us < next_rtcp_time_) {
+ return NULL;
+ }
+ RtcpPacket* rtcp = new RtcpPacket;
+ int64_t send_time_us = time_now_us + kSendSideOffsetUs;
+ rtcp->timestamp = rtp_timestamp_offset_ + static_cast<uint32_t>(
+ ((frequency_ / 1000) * send_time_us + 500) / 1000);
+ rtcp->ntp_secs = send_time_us / 1000000;
+ rtcp->ntp_frac = static_cast<int64_t>((send_time_us % 1000000) *
+ kNtpFracPerMs);
+ rtcp->ssrc = ssrc_;
+ next_rtcp_time_ = time_now_us + kRtcpIntervalUs;
+ return rtcp;
+}
+
+void RtpStream::set_bitrate_bps(int bitrate_bps) {
+ ASSERT_GE(bitrate_bps, 0);
+ bitrate_bps_ = bitrate_bps;
+}
+
+int RtpStream::bitrate_bps() const {
+ return bitrate_bps_;
+}
+
+unsigned int RtpStream::ssrc() const {
+ return ssrc_;
+}
+
+bool RtpStream::Compare(const std::pair<unsigned int, RtpStream*>& left,
+ const std::pair<unsigned int, RtpStream*>& right) {
+ return left.second->next_rtp_time_ < right.second->next_rtp_time_;
+}
+
+StreamGenerator::StreamGenerator(int capacity, double time_now)
+ : capacity_(capacity),
+ prev_arrival_time_us_(time_now) {}
+
+StreamGenerator::~StreamGenerator() {
+ for (StreamMap::iterator it = streams_.begin(); it != streams_.end();
+ ++it) {
+ delete it->second;
+ }
+ streams_.clear();
+}
+
+// Add a new stream.
+void StreamGenerator::AddStream(RtpStream* stream) {
+ streams_[stream->ssrc()] = stream;
+}
+
+// Set the link capacity.
+void StreamGenerator::set_capacity_bps(int capacity_bps) {
+ ASSERT_GT(capacity_bps, 0);
+ capacity_ = capacity_bps;
+}
+
+// Divides |bitrate_bps| among all streams. The allocated bitrate per stream
+// is decided by the current allocation ratios.
+void StreamGenerator::SetBitrateBps(int bitrate_bps) {
+ ASSERT_GE(streams_.size(), 0u);
+ int total_bitrate_before = 0;
+ for (StreamMap::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+ total_bitrate_before += it->second->bitrate_bps();
+ }
+ int64_t bitrate_before = 0;
+ int total_bitrate_after = 0;
+ for (StreamMap::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+ bitrate_before += it->second->bitrate_bps();
+ int64_t bitrate_after = (bitrate_before * bitrate_bps +
+ total_bitrate_before / 2) / total_bitrate_before;
+ it->second->set_bitrate_bps(bitrate_after - total_bitrate_after);
+ total_bitrate_after += it->second->bitrate_bps();
+ }
+ ASSERT_EQ(bitrate_before, total_bitrate_before);
+ EXPECT_EQ(total_bitrate_after, bitrate_bps);
+}
+
+// Set the RTP timestamp offset for the stream identified by |ssrc|.
+void StreamGenerator::set_rtp_timestamp_offset(unsigned int ssrc,
+ uint32_t offset) {
+ streams_[ssrc]->set_rtp_timestamp_offset(offset);
+}
+
+// TODO(holmer): Break out the channel simulation part from this class to make
+// it possible to simulate different types of channels.
+int64_t StreamGenerator::GenerateFrame(RtpStream::PacketList* packets,
+ int64_t time_now_us) {
+ assert(packets != NULL);
+ assert(packets->empty());
+ assert(capacity_ > 0);
+ StreamMap::iterator it = std::min_element(streams_.begin(), streams_.end(),
+ RtpStream::Compare);
+ (*it).second->GenerateFrame(time_now_us, packets);
+ int i = 0;
+ for (RtpStream::PacketList::iterator packet_it = packets->begin();
+ packet_it != packets->end(); ++packet_it) {
+ int capacity_bpus = capacity_ / 1000;
+ int64_t required_network_time_us =
+ (8 * 1000 * (*packet_it)->size + capacity_bpus / 2) / capacity_bpus;
+ prev_arrival_time_us_ = std::max(time_now_us + required_network_time_us,
+ prev_arrival_time_us_ + required_network_time_us);
+ (*packet_it)->arrival_time = prev_arrival_time_us_;
+ ++i;
+ }
+ it = std::min_element(streams_.begin(), streams_.end(), RtpStream::Compare);
+ return (*it).second->next_rtp_time();
+}
+} // namespace testing
+
+RemoteBitrateEstimatorTest::RemoteBitrateEstimatorTest()
+ : clock_(0),
+ bitrate_observer_(new testing::TestBitrateObserver),
+ stream_generator_(new testing::StreamGenerator(
+ 1e6, // Capacity.
+ clock_.TimeInMicroseconds())) {}
+
+RemoteBitrateEstimatorTest::~RemoteBitrateEstimatorTest() {}
+
+void RemoteBitrateEstimatorTest::AddDefaultStream() {
+ stream_generator_->AddStream(new testing::RtpStream(
+ 30, // Frames per second.
+ 3e5, // Bitrate.
+ 1, // SSRC.
+ 90000, // RTP frequency.
+ 0xFFFFF000, // Timestamp offset.
+ 0)); // RTCP receive time.
+}
+
+uint32_t RemoteBitrateEstimatorTest::AbsSendTime(int64_t t, int64_t denom) {
+ return (((t << 18) + (denom >> 1)) / denom) & 0x00fffffful;
+}
+
+uint32_t RemoteBitrateEstimatorTest::AddAbsSendTime(uint32_t t1, uint32_t t2) {
+ return (t1 + t2) & 0x00fffffful;
+}
+
+const unsigned int RemoteBitrateEstimatorTest::kDefaultSsrc = 1;
+
+void RemoteBitrateEstimatorTest::IncomingPacket(uint32_t ssrc,
+ size_t payload_size,
+ int64_t arrival_time,
+ uint32_t rtp_timestamp,
+ uint32_t absolute_send_time,
+ bool was_paced) {
+ RTPHeader header;
+ memset(&header, 0, sizeof(header));
+ header.ssrc = ssrc;
+ header.timestamp = rtp_timestamp;
+ header.extension.hasAbsoluteSendTime = true;
+ header.extension.absoluteSendTime = absolute_send_time;
+ bitrate_estimator_->IncomingPacket(arrival_time + kArrivalTimeClockOffsetMs,
+ payload_size, header, was_paced);
+}
+
+// Generates a frame of packets belonging to a stream at a given bitrate and
+// with a given ssrc. The stream is pushed through a very simple simulated
+// network, and is then given to the receive-side bandwidth estimator.
+// Returns true if an over-use was seen, false otherwise.
+// The StreamGenerator::updated() should be used to check for any changes in
+// target bitrate after the call to this function.
+bool RemoteBitrateEstimatorTest::GenerateAndProcessFrame(unsigned int ssrc,
+ unsigned int bitrate_bps) {
+ stream_generator_->SetBitrateBps(bitrate_bps);
+ testing::RtpStream::PacketList packets;
+ int64_t next_time_us = stream_generator_->GenerateFrame(
+ &packets, clock_.TimeInMicroseconds());
+ bool overuse = false;
+ while (!packets.empty()) {
+ testing::RtpStream::RtpPacket* packet = packets.front();
+ bitrate_observer_->Reset();
+ // The simulated clock should match the time of packet->arrival_time
+ // since both are used in IncomingPacket().
+ clock_.AdvanceTimeMicroseconds(packet->arrival_time -
+ clock_.TimeInMicroseconds());
+ IncomingPacket(packet->ssrc, packet->size,
+ (packet->arrival_time + 500) / 1000, packet->rtp_timestamp,
+ AbsSendTime(packet->send_time, 1000000), true);
+ if (bitrate_observer_->updated()) {
+ // Verify that new estimates only are triggered by an overuse and a
+ // rate decrease.
+ overuse = true;
+ EXPECT_LE(bitrate_observer_->latest_bitrate(), bitrate_bps);
+ }
+ delete packet;
+ packets.pop_front();
+ }
+ bitrate_estimator_->Process();
+ clock_.AdvanceTimeMicroseconds(next_time_us - clock_.TimeInMicroseconds());
+ return overuse;
+}
+
+// Run the bandwidth estimator with a stream of |number_of_frames| frames, or
+// until it reaches |target_bitrate|.
+// Can for instance be used to run the estimator for some time to get it
+// into a steady state.
+unsigned int RemoteBitrateEstimatorTest::SteadyStateRun(
+ unsigned int ssrc,
+ int max_number_of_frames,
+ unsigned int start_bitrate,
+ unsigned int min_bitrate,
+ unsigned int max_bitrate,
+ unsigned int target_bitrate) {
+ unsigned int bitrate_bps = start_bitrate;
+ bool bitrate_update_seen = false;
+ // Produce |number_of_frames| frames and give them to the estimator.
+ for (int i = 0; i < max_number_of_frames; ++i) {
+ bool overuse = GenerateAndProcessFrame(ssrc, bitrate_bps);
+ if (overuse) {
+ EXPECT_LT(bitrate_observer_->latest_bitrate(), max_bitrate);
+ EXPECT_GT(bitrate_observer_->latest_bitrate(), min_bitrate);
+ bitrate_bps = bitrate_observer_->latest_bitrate();
+ bitrate_update_seen = true;
+ } else if (bitrate_observer_->updated()) {
+ bitrate_bps = bitrate_observer_->latest_bitrate();
+ bitrate_observer_->Reset();
+ }
+ if (bitrate_update_seen && bitrate_bps > target_bitrate) {
+ break;
+ }
+ }
+ EXPECT_TRUE(bitrate_update_seen);
+ return bitrate_bps;
+}
+
+void RemoteBitrateEstimatorTest::InitialBehaviorTestHelper(
+ unsigned int expected_converge_bitrate) {
+ const int kFramerate = 50; // 50 fps to avoid rounding errors.
+ const int kFrameIntervalMs = 1000 / kFramerate;
+ const uint32_t kFrameIntervalAbsSendTime = AbsSendTime(1, kFramerate);
+ unsigned int bitrate_bps = 0;
+ uint32_t timestamp = 0;
+ uint32_t absolute_send_time = 0;
+ std::vector<unsigned int> ssrcs;
+ EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps));
+ EXPECT_EQ(0u, ssrcs.size());
+ clock_.AdvanceTimeMilliseconds(1000);
+ bitrate_estimator_->Process();
+ EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps));
+ EXPECT_FALSE(bitrate_observer_->updated());
+ bitrate_observer_->Reset();
+ clock_.AdvanceTimeMilliseconds(1000);
+ // Inserting a packet. Still no valid estimate. We need to wait 5 seconds.
+ IncomingPacket(kDefaultSsrc, kMtu, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ bitrate_estimator_->Process();
+ EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps));
+ EXPECT_EQ(0u, ssrcs.size());
+ EXPECT_FALSE(bitrate_observer_->updated());
+ bitrate_observer_->Reset();
+ // Inserting packets for 5 seconds to get a valid estimate.
+ for (int i = 0; i < 5 * kFramerate + 1; ++i) {
+ IncomingPacket(kDefaultSsrc, kMtu, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ clock_.AdvanceTimeMilliseconds(1000 / kFramerate);
+ timestamp += 90 * kFrameIntervalMs;
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ kFrameIntervalAbsSendTime);
+ }
+ bitrate_estimator_->Process();
+ EXPECT_TRUE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps));
+ ASSERT_EQ(1u, ssrcs.size());
+ EXPECT_EQ(kDefaultSsrc, ssrcs.front());
+ EXPECT_NEAR(expected_converge_bitrate, bitrate_bps, kAcceptedBitrateErrorBps);
+ EXPECT_TRUE(bitrate_observer_->updated());
+ bitrate_observer_->Reset();
+ EXPECT_EQ(bitrate_observer_->latest_bitrate(), bitrate_bps);
+ bitrate_estimator_->RemoveStream(kDefaultSsrc);
+ EXPECT_TRUE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps));
+ ASSERT_EQ(0u, ssrcs.size());
+ EXPECT_EQ(0u, bitrate_bps);
+}
+
+void RemoteBitrateEstimatorTest::RateIncreaseReorderingTestHelper(
+ uint32_t expected_bitrate_bps) {
+ const int kFramerate = 50; // 50 fps to avoid rounding errors.
+ const int kFrameIntervalMs = 1000 / kFramerate;
+ const uint32_t kFrameIntervalAbsSendTime = AbsSendTime(1, kFramerate);
+ uint32_t timestamp = 0;
+ uint32_t absolute_send_time = 0;
+ IncomingPacket(kDefaultSsrc, 1000, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ bitrate_estimator_->Process();
+ EXPECT_FALSE(bitrate_observer_->updated()); // No valid estimate.
+ // Inserting packets for one second to get a valid estimate.
+ for (int i = 0; i < 5 * kFramerate + 1; ++i) {
+ IncomingPacket(kDefaultSsrc, kMtu, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ clock_.AdvanceTimeMilliseconds(kFrameIntervalMs);
+ timestamp += 90 * kFrameIntervalMs;
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ kFrameIntervalAbsSendTime);
+ }
+ bitrate_estimator_->Process();
+ EXPECT_TRUE(bitrate_observer_->updated());
+ EXPECT_NEAR(expected_bitrate_bps,
+ bitrate_observer_->latest_bitrate(),
+ kAcceptedBitrateErrorBps);
+ for (int i = 0; i < 10; ++i) {
+ clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs);
+ timestamp += 2 * 90 * kFrameIntervalMs;
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ 2 * kFrameIntervalAbsSendTime);
+ IncomingPacket(kDefaultSsrc, 1000, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ IncomingPacket(
+ kDefaultSsrc, 1000, clock_.TimeInMilliseconds(),
+ timestamp - 90 * kFrameIntervalMs,
+ AddAbsSendTime(absolute_send_time, -int(kFrameIntervalAbsSendTime)),
+ true);
+ }
+ bitrate_estimator_->Process();
+ EXPECT_TRUE(bitrate_observer_->updated());
+ EXPECT_NEAR(expected_bitrate_bps,
+ bitrate_observer_->latest_bitrate(),
+ kAcceptedBitrateErrorBps);
+}
+
+// Make sure we initially increase the bitrate as expected.
+void RemoteBitrateEstimatorTest::RateIncreaseRtpTimestampsTestHelper(
+ int expected_iterations) {
+ // This threshold corresponds approximately to increasing linearly with
+ // bitrate(i) = 1.04 * bitrate(i-1) + 1000
+ // until bitrate(i) > 500000, with bitrate(1) ~= 30000.
+ unsigned int bitrate_bps = 30000;
+ int iterations = 0;
+ AddDefaultStream();
+ // Feed the estimator with a stream of packets and verify that it reaches
+ // 500 kbps at the expected time.
+ while (bitrate_bps < 5e5) {
+ bool overuse = GenerateAndProcessFrame(kDefaultSsrc, bitrate_bps);
+ if (overuse) {
+ EXPECT_GT(bitrate_observer_->latest_bitrate(), bitrate_bps);
+ bitrate_bps = bitrate_observer_->latest_bitrate();
+ bitrate_observer_->Reset();
+ } else if (bitrate_observer_->updated()) {
+ bitrate_bps = bitrate_observer_->latest_bitrate();
+ bitrate_observer_->Reset();
+ }
+ ++iterations;
+ ASSERT_LE(iterations, expected_iterations);
+ }
+ ASSERT_EQ(expected_iterations, iterations);
+}
+
+void RemoteBitrateEstimatorTest::CapacityDropTestHelper(
+ int number_of_streams,
+ bool wrap_time_stamp,
+ unsigned int expected_bitrate_drop_delta) {
+ const int kFramerate = 30;
+ const int kStartBitrate = 900e3;
+ const int kMinExpectedBitrate = 800e3;
+ const int kMaxExpectedBitrate = 1100e3;
+ const unsigned int kInitialCapacityBps = 1000e3;
+ const unsigned int kReducedCapacityBps = 500e3;
+
+ int steady_state_time = 0;
+ if (number_of_streams <= 1) {
+ steady_state_time = 10;
+ AddDefaultStream();
+ } else {
+ steady_state_time = 10 * number_of_streams;
+ int bitrate_sum = 0;
+ int kBitrateDenom = number_of_streams * (number_of_streams - 1);
+ for (int i = 0; i < number_of_streams; i++) {
+ // First stream gets half available bitrate, while the rest share the
+ // remaining half i.e.: 1/2 = Sum[n/(N*(N-1))] for n=1..N-1 (rounded up)
+ int bitrate = kStartBitrate / 2;
+ if (i > 0) {
+ bitrate = (kStartBitrate * i + kBitrateDenom / 2) / kBitrateDenom;
+ }
+ stream_generator_->AddStream(new testing::RtpStream(
+ kFramerate, // Frames per second.
+ bitrate, // Bitrate.
+ kDefaultSsrc + i, // SSRC.
+ 90000, // RTP frequency.
+ 0xFFFFF000 ^ (~0 << (32 - i)), // Timestamp offset.
+ 0)); // RTCP receive time.
+ bitrate_sum += bitrate;
+ }
+ ASSERT_EQ(bitrate_sum, kStartBitrate);
+ }
+ if (wrap_time_stamp) {
+ stream_generator_->set_rtp_timestamp_offset(kDefaultSsrc,
+ std::numeric_limits<uint32_t>::max() - steady_state_time * 90000);
+ }
+
+ // Run in steady state to make the estimator converge.
+ stream_generator_->set_capacity_bps(kInitialCapacityBps);
+ unsigned int bitrate_bps = SteadyStateRun(kDefaultSsrc,
+ steady_state_time * kFramerate,
+ kStartBitrate,
+ kMinExpectedBitrate,
+ kMaxExpectedBitrate,
+ kInitialCapacityBps);
+ EXPECT_NEAR(kInitialCapacityBps, bitrate_bps, 110000u);
+ bitrate_observer_->Reset();
+
+ // Reduce the capacity and verify the decrease time.
+ stream_generator_->set_capacity_bps(kReducedCapacityBps);
+ int64_t overuse_start_time = clock_.TimeInMilliseconds();
+ int64_t bitrate_drop_time = -1;
+ for (int i = 0; i < 100 * number_of_streams; ++i) {
+ GenerateAndProcessFrame(kDefaultSsrc, bitrate_bps);
+ // Check for either increase or decrease.
+ if (bitrate_observer_->updated()) {
+ if (bitrate_drop_time == -1 &&
+ bitrate_observer_->latest_bitrate() <= kReducedCapacityBps) {
+ bitrate_drop_time = clock_.TimeInMilliseconds();
+ }
+ bitrate_bps = bitrate_observer_->latest_bitrate();
+ bitrate_observer_->Reset();
+ }
+ }
+
+ EXPECT_NEAR(expected_bitrate_drop_delta,
+ bitrate_drop_time - overuse_start_time, 33);
+
+ // Remove stream one by one.
+ unsigned int latest_bps = 0;
+ std::vector<unsigned int> ssrcs;
+ for (int i = 0; i < number_of_streams; i++) {
+ EXPECT_TRUE(bitrate_estimator_->LatestEstimate(&ssrcs, &latest_bps));
+ EXPECT_EQ(number_of_streams - i, static_cast<int>(ssrcs.size()));
+ EXPECT_EQ(bitrate_bps, latest_bps);
+ for (int j = i; j < number_of_streams; j++) {
+ EXPECT_EQ(kDefaultSsrc + j, ssrcs[j - i]);
+ }
+ bitrate_estimator_->RemoveStream(kDefaultSsrc + i);
+ }
+ EXPECT_TRUE(bitrate_estimator_->LatestEstimate(&ssrcs, &latest_bps));
+ EXPECT_EQ(0u, ssrcs.size());
+ EXPECT_EQ(0u, latest_bps);
+}
+
+void RemoteBitrateEstimatorTest::TestTimestampGroupingTestHelper() {
+ const int kFramerate = 50; // 50 fps to avoid rounding errors.
+ const int kFrameIntervalMs = 1000 / kFramerate;
+ const uint32_t kFrameIntervalAbsSendTime = AbsSendTime(1, kFramerate);
+ uint32_t timestamp = 0;
+ // Initialize absolute_send_time (24 bits) so that it will definitely wrap
+ // during the test.
+ uint32_t absolute_send_time =
+ AddAbsSendTime((1 << 24), -int(50 * kFrameIntervalAbsSendTime));
+ // Initial set of frames to increase the bitrate. 6 seconds to have enough
+ // time for the first estimate to be generated and for Process() to be called.
+ for (int i = 0; i <= 6 * kFramerate; ++i) {
+ IncomingPacket(kDefaultSsrc, 1000, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ bitrate_estimator_->Process();
+ clock_.AdvanceTimeMilliseconds(kFrameIntervalMs);
+ timestamp += 90 * kFrameIntervalMs;
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ kFrameIntervalAbsSendTime);
+ }
+ EXPECT_TRUE(bitrate_observer_->updated());
+ EXPECT_GE(bitrate_observer_->latest_bitrate(), 400000u);
+
+ // Insert batches of frames which were sent very close in time. Also simulate
+ // capacity over-use to see that we back off correctly.
+ const int kTimestampGroupLength = 15;
+ const uint32_t kTimestampGroupLengthAbsSendTime =
+ AbsSendTime(kTimestampGroupLength, 90000);
+ const uint32_t kSingleRtpTickAbsSendTime = AbsSendTime(1, 90000);
+ for (int i = 0; i < 100; ++i) {
+ for (int j = 0; j < kTimestampGroupLength; ++j) {
+ // Insert |kTimestampGroupLength| frames with just 1 timestamp ticks in
+ // between. Should be treated as part of the same group by the estimator.
+ IncomingPacket(kDefaultSsrc, 100, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ clock_.AdvanceTimeMilliseconds(kFrameIntervalMs / kTimestampGroupLength);
+ timestamp += 1;
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ kSingleRtpTickAbsSendTime);
+ }
+ // Increase time until next batch to simulate over-use.
+ clock_.AdvanceTimeMilliseconds(10);
+ timestamp += 90 * kFrameIntervalMs - kTimestampGroupLength;
+ absolute_send_time = AddAbsSendTime(absolute_send_time, AddAbsSendTime(
+ kFrameIntervalAbsSendTime, -int(kTimestampGroupLengthAbsSendTime)));
+ bitrate_estimator_->Process();
+ }
+ EXPECT_TRUE(bitrate_observer_->updated());
+ // Should have reduced the estimate.
+ EXPECT_LT(bitrate_observer_->latest_bitrate(), 400000u);
+}
+
+void RemoteBitrateEstimatorTest::TestGetStatsHelper() {
+ const int kFramerate = 100;
+ const int kFrameIntervalMs = 1000 / kFramerate;
+ const int kBurstThresholdMs = 5;
+ const uint32_t kFrameIntervalAbsSendTime = AbsSendTime(1, kFramerate);
+ uint32_t timestamp = 0;
+ // Initialize absolute_send_time (24 bits) so that it will definitely wrap
+ // during the test.
+ uint32_t absolute_send_time =
+ AddAbsSendTime((1 << 24),
+ -(50 * static_cast<int>(kFrameIntervalAbsSendTime)));
+
+ // Inject propagation_time_delta of kFrameIntervalMs.
+ for (size_t i = 0; i < 3; ++i) {
+ IncomingPacket(kDefaultSsrc, 1000, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ timestamp += kFrameIntervalMs;
+ // Insert a kFrameIntervalMs propagation_time_delta.
+ clock_.AdvanceTimeMilliseconds(kFrameIntervalMs * 2);
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ kFrameIntervalAbsSendTime);
+ }
+ ReceiveBandwidthEstimatorStats stats;
+ EXPECT_TRUE(bitrate_estimator_->GetStats(&stats));
+ EXPECT_EQ(1U, stats.recent_propagation_time_delta_ms.size());
+ EXPECT_EQ(kFrameIntervalMs, stats.recent_propagation_time_delta_ms[0]);
+ EXPECT_EQ(1U, stats.recent_arrival_time_ms.size());
+ EXPECT_EQ(kFrameIntervalMs, stats.total_propagation_time_delta_ms);
+
+ // Inject negative propagation_time_deltas. The total propagation_time_delta
+ // should be adjusted to 0.
+ for (size_t i = 0; i < 3; ++i) {
+ IncomingPacket(kDefaultSsrc, 1000, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ timestamp += 10 * kFrameIntervalMs;
+ clock_.AdvanceTimeMilliseconds(kBurstThresholdMs + 1);
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ 10 * kFrameIntervalAbsSendTime);
+ }
+ EXPECT_TRUE(bitrate_estimator_->GetStats(&stats));
+ EXPECT_EQ(0, stats.total_propagation_time_delta_ms);
+
+ // Send more than 1000 frames and make sure the stats queues stays within
+ // limits.
+ for (size_t i = 0; i < 1001; ++i) {
+ IncomingPacket(kDefaultSsrc, 1000, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ timestamp += kFrameIntervalMs;
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ kFrameIntervalAbsSendTime);
+ }
+ EXPECT_TRUE(bitrate_estimator_->GetStats(&stats));
+ EXPECT_LE(stats.recent_propagation_time_delta_ms.size(), 1000U);
+ EXPECT_LE(stats.recent_arrival_time_ms.size(), 1000U);
+
+ // Move the clock over the 1000ms limit.
+ clock_.AdvanceTimeMilliseconds(2000);
+ EXPECT_TRUE(bitrate_estimator_->GetStats(&stats));
+ EXPECT_EQ(0U, stats.recent_propagation_time_delta_ms.size());
+}
+
+void RemoteBitrateEstimatorTest::TestWrappingHelper(
+ int silence_time_s) {
+ const int kFramerate = 100;
+ const int kFrameIntervalMs = 1000 / kFramerate;
+ const uint32_t kFrameIntervalAbsSendTime = AbsSendTime(1, kFramerate);
+ uint32_t absolute_send_time = 0;
+ uint32_t timestamp = 0;
+
+ for (size_t i = 0; i < 3000; ++i) {
+ IncomingPacket(kDefaultSsrc, 1000, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ timestamp += kFrameIntervalMs;
+ clock_.AdvanceTimeMilliseconds(kFrameIntervalMs);
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ kFrameIntervalAbsSendTime);
+ bitrate_estimator_->Process();
+ }
+ unsigned int bitrate_before = 0;
+ std::vector<unsigned int> ssrcs;
+ bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_before);
+
+ clock_.AdvanceTimeMilliseconds(silence_time_s * 1000);
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ AbsSendTime(silence_time_s, 1));
+ bitrate_estimator_->Process();
+ for (size_t i = 0; i < 100; ++i) {
+ IncomingPacket(kDefaultSsrc, 1000, clock_.TimeInMilliseconds(), timestamp,
+ absolute_send_time, true);
+ timestamp += kFrameIntervalMs;
+ clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs);
+ absolute_send_time = AddAbsSendTime(absolute_send_time,
+ kFrameIntervalAbsSendTime);
+ bitrate_estimator_->Process();
+ }
+ unsigned int bitrate_after = 0;
+ bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_after);
+ EXPECT_LT(bitrate_after, bitrate_before);
+}
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h
new file mode 100644
index 0000000000..606bb6c4e6
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2012 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_UNITTEST_HELPER_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_UNITTEST_HELPER_H_
+
+#include <list>
+#include <map>
+#include <utility>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/system_wrappers/include/clock.h"
+
+namespace webrtc {
+namespace testing {
+
+class TestBitrateObserver : public RemoteBitrateObserver {
+ public:
+ TestBitrateObserver() : updated_(false), latest_bitrate_(0) {}
+ virtual ~TestBitrateObserver() {}
+
+ void OnReceiveBitrateChanged(const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) override;
+
+ void Reset() { updated_ = false; }
+
+ bool updated() const { return updated_; }
+
+ unsigned int latest_bitrate() const { return latest_bitrate_; }
+
+ private:
+ bool updated_;
+ unsigned int latest_bitrate_;
+};
+
+class RtpStream {
+ public:
+ struct RtpPacket {
+ int64_t send_time;
+ int64_t arrival_time;
+ uint32_t rtp_timestamp;
+ size_t size;
+ unsigned int ssrc;
+ };
+
+ struct RtcpPacket {
+ uint32_t ntp_secs;
+ uint32_t ntp_frac;
+ uint32_t timestamp;
+ unsigned int ssrc;
+ };
+
+ typedef std::list<RtpPacket*> PacketList;
+
+ enum { kSendSideOffsetUs = 1000000 };
+
+ RtpStream(int fps, int bitrate_bps, unsigned int ssrc, unsigned int frequency,
+ uint32_t timestamp_offset, int64_t rtcp_receive_time);
+ void set_rtp_timestamp_offset(uint32_t offset);
+
+ // Generates a new frame for this stream. If called too soon after the
+ // previous frame, no frame will be generated. The frame is split into
+ // packets.
+ int64_t GenerateFrame(int64_t time_now_us, PacketList* packets);
+
+ // The send-side time when the next frame can be generated.
+ double next_rtp_time() const;
+
+ // Generates an RTCP packet.
+ RtcpPacket* Rtcp(int64_t time_now_us);
+
+ void set_bitrate_bps(int bitrate_bps);
+
+ int bitrate_bps() const;
+
+ unsigned int ssrc() const;
+
+ static bool Compare(const std::pair<unsigned int, RtpStream*>& left,
+ const std::pair<unsigned int, RtpStream*>& right);
+
+ private:
+ enum { kRtcpIntervalUs = 1000000 };
+
+ int fps_;
+ int bitrate_bps_;
+ unsigned int ssrc_;
+ unsigned int frequency_;
+ int64_t next_rtp_time_;
+ int64_t next_rtcp_time_;
+ uint32_t rtp_timestamp_offset_;
+ const double kNtpFracPerMs;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(RtpStream);
+};
+
+class StreamGenerator {
+ public:
+ typedef std::list<RtpStream::RtcpPacket*> RtcpList;
+
+ StreamGenerator(int capacity, double time_now);
+
+ ~StreamGenerator();
+
+ // Add a new stream.
+ void AddStream(RtpStream* stream);
+
+ // Set the link capacity.
+ void set_capacity_bps(int capacity_bps);
+
+ // Divides |bitrate_bps| among all streams. The allocated bitrate per stream
+ // is decided by the initial allocation ratios.
+ void SetBitrateBps(int bitrate_bps);
+
+ // Set the RTP timestamp offset for the stream identified by |ssrc|.
+ void set_rtp_timestamp_offset(unsigned int ssrc, uint32_t offset);
+
+ // TODO(holmer): Break out the channel simulation part from this class to make
+ // it possible to simulate different types of channels.
+ int64_t GenerateFrame(RtpStream::PacketList* packets, int64_t time_now_us);
+
+ private:
+ typedef std::map<unsigned int, RtpStream*> StreamMap;
+
+ // Capacity of the simulated channel in bits per second.
+ int capacity_;
+ // The time when the last packet arrived.
+ int64_t prev_arrival_time_us_;
+ // All streams being transmitted on this simulated channel.
+ StreamMap streams_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(StreamGenerator);
+};
+} // namespace testing
+
+class RemoteBitrateEstimatorTest : public ::testing::Test {
+ public:
+ RemoteBitrateEstimatorTest();
+ virtual ~RemoteBitrateEstimatorTest();
+
+ protected:
+ virtual void SetUp() = 0;
+
+ void AddDefaultStream();
+
+ // Helper to convert some time format to resolution used in absolute send time
+ // header extension, rounded upwards. |t| is the time to convert, in some
+ // resolution. |denom| is the value to divide |t| by to get whole seconds,
+ // e.g. |denom| = 1000 if |t| is in milliseconds.
+ static uint32_t AbsSendTime(int64_t t, int64_t denom);
+
+ // Helper to add two absolute send time values and keep it less than 1<<24.
+ static uint32_t AddAbsSendTime(uint32_t t1, uint32_t t2);
+
+ // Helper to create a WebRtcRTPHeader containing the relevant data for the
+ // estimator (all other fields are cleared) and call IncomingPacket on the
+ // estimator.
+ void IncomingPacket(uint32_t ssrc,
+ size_t payload_size,
+ int64_t arrival_time,
+ uint32_t rtp_timestamp,
+ uint32_t absolute_send_time,
+ bool was_paced);
+
+ // Generates a frame of packets belonging to a stream at a given bitrate and
+ // with a given ssrc. The stream is pushed through a very simple simulated
+ // network, and is then given to the receive-side bandwidth estimator.
+ // Returns true if an over-use was seen, false otherwise.
+ // The StreamGenerator::updated() should be used to check for any changes in
+ // target bitrate after the call to this function.
+ bool GenerateAndProcessFrame(unsigned int ssrc, unsigned int bitrate_bps);
+
+ // Run the bandwidth estimator with a stream of |number_of_frames| frames, or
+ // until it reaches |target_bitrate|.
+ // Can for instance be used to run the estimator for some time to get it
+ // into a steady state.
+ unsigned int SteadyStateRun(unsigned int ssrc,
+ int number_of_frames,
+ unsigned int start_bitrate,
+ unsigned int min_bitrate,
+ unsigned int max_bitrate,
+ unsigned int target_bitrate);
+
+ void TestTimestampGroupingTestHelper();
+
+ void TestGetStatsHelper();
+
+ void TestWrappingHelper(int silence_time_s);
+
+ void InitialBehaviorTestHelper(unsigned int expected_converge_bitrate);
+ void RateIncreaseReorderingTestHelper(unsigned int expected_bitrate);
+ void RateIncreaseRtpTimestampsTestHelper(int expected_iterations);
+ void CapacityDropTestHelper(int number_of_streams,
+ bool wrap_time_stamp,
+ unsigned int expected_bitrate_drop_delta);
+
+ static const unsigned int kDefaultSsrc;
+ static const int kArrivalTimeClockOffsetMs = 60000;
+
+ SimulatedClock clock_; // Time at the receiver.
+ rtc::scoped_ptr<testing::TestBitrateObserver> bitrate_observer_;
+ rtc::scoped_ptr<RemoteBitrateEstimator> bitrate_estimator_;
+ rtc::scoped_ptr<testing::StreamGenerator> stream_generator_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(RemoteBitrateEstimatorTest);
+};
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_UNITTEST_HELPER_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimators_test.cc b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimators_test.cc
new file mode 100644
index 0000000000..d6f049f6ac
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimators_test.cc
@@ -0,0 +1,434 @@
+/*
+ * 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.
+ */
+
+#ifndef WEBRTC_WIN
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include <sstream>
+
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
+#include "webrtc/test/testsupport/fileutils.h"
+
+using std::string;
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class DefaultBweTest : public BweTest,
+ public ::testing::TestWithParam<BandwidthEstimatorType> {
+ public:
+ virtual ~DefaultBweTest() {}
+};
+
+INSTANTIATE_TEST_CASE_P(VideoSendersTest,
+ DefaultBweTest,
+ ::testing::Values(kRembEstimator,
+ kFullSendSideEstimator));
+
+TEST_P(DefaultBweTest, UnlimitedSpeed) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ RunFor(10 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, SteadyLoss) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ LossFilter loss(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ loss.SetLoss(20.0);
+ RunFor(10 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, IncreasingLoss1) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ LossFilter loss(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ for (int i = 0; i < 76; ++i) {
+ loss.SetLoss(i);
+ RunFor(5000);
+ }
+}
+
+TEST_P(DefaultBweTest, SteadyDelay) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ DelayFilter delay(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ delay.SetOneWayDelayMs(1000);
+ RunFor(10 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, IncreasingDelay1) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ DelayFilter delay(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ RunFor(10 * 60 * 1000);
+ for (int i = 0; i < 30 * 2; ++i) {
+ delay.SetOneWayDelayMs(i);
+ RunFor(10 * 1000);
+ }
+ RunFor(10 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, IncreasingDelay2) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ DelayFilter delay(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "", "");
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ RunFor(1 * 60 * 1000);
+ for (int i = 1; i < 51; ++i) {
+ delay.SetOneWayDelayMs(10.0f * i);
+ RunFor(10 * 1000);
+ }
+ delay.SetOneWayDelayMs(0.0f);
+ RunFor(10 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, JumpyDelay1) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ DelayFilter delay(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ RunFor(10 * 60 * 1000);
+ for (int i = 1; i < 200; ++i) {
+ delay.SetOneWayDelayMs((10 * i) % 500);
+ RunFor(1000);
+ delay.SetOneWayDelayMs(1.0f);
+ RunFor(1000);
+ }
+ delay.SetOneWayDelayMs(0.0f);
+ RunFor(10 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, SteadyJitter) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ JitterFilter jitter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "", "");
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ jitter.SetMaxJitter(20);
+ RunFor(2 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, IncreasingJitter1) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ JitterFilter jitter(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ for (int i = 0; i < 2 * 60 * 2; ++i) {
+ jitter.SetMaxJitter(i);
+ RunFor(10 * 1000);
+ }
+ RunFor(10 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, IncreasingJitter2) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ JitterFilter jitter(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ RunFor(30 * 1000);
+ for (int i = 1; i < 51; ++i) {
+ jitter.SetMaxJitter(10.0f * i);
+ RunFor(10 * 1000);
+ }
+ jitter.SetMaxJitter(0.0f);
+ RunFor(10 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, SteadyReorder) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ ReorderFilter reorder(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ reorder.SetReorder(20.0);
+ RunFor(10 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, IncreasingReorder1) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ ReorderFilter reorder(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ for (int i = 0; i < 76; ++i) {
+ reorder.SetReorder(i);
+ RunFor(5000);
+ }
+}
+
+TEST_P(DefaultBweTest, SteadyChoke) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter choke(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ choke.set_capacity_kbps(140);
+ RunFor(10 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, IncreasingChoke1) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter choke(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ for (int i = 1200; i >= 100; i -= 100) {
+ choke.set_capacity_kbps(i);
+ RunFor(5000);
+ }
+}
+
+TEST_P(DefaultBweTest, IncreasingChoke2) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter choke(&uplink_, 0);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ RunFor(60 * 1000);
+ for (int i = 1200; i >= 100; i -= 20) {
+ choke.set_capacity_kbps(i);
+ RunFor(1000);
+ }
+}
+
+TEST_P(DefaultBweTest, Multi1) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ DelayFilter delay(&uplink_, 0);
+ ChokeFilter choke(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "", "");
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ choke.set_capacity_kbps(1000);
+ RunFor(1 * 60 * 1000);
+ for (int i = 1; i < 51; ++i) {
+ delay.SetOneWayDelayMs(100.0f * i);
+ RunFor(10 * 1000);
+ }
+ RunFor(500 * 1000);
+ delay.SetOneWayDelayMs(0.0f);
+ RunFor(5 * 60 * 1000);
+}
+
+TEST_P(DefaultBweTest, Multi2) {
+ VideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter choke(&uplink_, 0);
+ JitterFilter jitter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "", "");
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ choke.set_capacity_kbps(2000);
+ jitter.SetMaxJitter(120);
+ RunFor(5 * 60 * 1000);
+}
+
+// This test fixture is used to instantiate tests running with adaptive video
+// senders.
+class BweFeedbackTest
+ : public BweTest,
+ public ::testing::TestWithParam<BandwidthEstimatorType> {
+ public:
+ BweFeedbackTest() : BweTest() {}
+ virtual ~BweFeedbackTest() {}
+
+ protected:
+ void SetUp() override {
+ unsigned int seed = Clock::GetRealTimeClock()->TimeInMicroseconds();
+#ifndef WEBRTC_WIN
+ seed *= getpid();
+#endif
+ srand(seed);
+ BweTest::SetUp();
+ }
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweFeedbackTest);
+};
+
+INSTANTIATE_TEST_CASE_P(VideoSendersTest,
+ BweFeedbackTest,
+ ::testing::Values(kRembEstimator,
+ kFullSendSideEstimator));
+
+TEST_P(BweFeedbackTest, ConstantCapacity) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ PacedVideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter filter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ const int kCapacityKbps = 1000;
+ filter.set_capacity_kbps(kCapacityKbps);
+ filter.set_max_delay_ms(500);
+ RunFor(180 * 1000);
+ PrintResults(kCapacityKbps, counter.GetBitrateStats(), 0,
+ receiver.GetDelayStats(), counter.GetBitrateStats());
+}
+
+TEST_P(BweFeedbackTest, Choke1000kbps500kbps1000kbps) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ PacedVideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter filter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ const int kHighCapacityKbps = 1000;
+ const int kLowCapacityKbps = 500;
+ filter.set_capacity_kbps(kHighCapacityKbps);
+ filter.set_max_delay_ms(500);
+ RunFor(60 * 1000);
+ filter.set_capacity_kbps(kLowCapacityKbps);
+ RunFor(60 * 1000);
+ filter.set_capacity_kbps(kHighCapacityKbps);
+ RunFor(60 * 1000);
+ PrintResults((2 * kHighCapacityKbps + kLowCapacityKbps) / 3.0,
+ counter.GetBitrateStats(), 0, receiver.GetDelayStats(),
+ counter.GetBitrateStats());
+}
+
+TEST_P(BweFeedbackTest, Choke200kbps30kbps200kbps) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ PacedVideoSender sender(&uplink_, &source, GetParam());
+ ChokeFilter filter(&uplink_, 0);
+ RateCounterFilter counter(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ const int kHighCapacityKbps = 200;
+ const int kLowCapacityKbps = 30;
+ filter.set_capacity_kbps(kHighCapacityKbps);
+ filter.set_max_delay_ms(500);
+ RunFor(60 * 1000);
+ filter.set_capacity_kbps(kLowCapacityKbps);
+ RunFor(60 * 1000);
+ filter.set_capacity_kbps(kHighCapacityKbps);
+ RunFor(60 * 1000);
+
+ PrintResults((2 * kHighCapacityKbps + kLowCapacityKbps) / 3.0,
+ counter.GetBitrateStats(), 0, receiver.GetDelayStats(),
+ counter.GetBitrateStats());
+}
+
+TEST_P(BweFeedbackTest, Verizon4gDownlinkTest) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ RateCounterFilter counter1(&uplink_, 0, "sender_output",
+ bwe_names[GetParam()]);
+ TraceBasedDeliveryFilter filter(&uplink_, 0, "link_capacity");
+ RateCounterFilter counter2(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("verizon4g-downlink", "rx")));
+ RunFor(22 * 60 * 1000);
+ PrintResults(filter.GetBitrateStats().GetMean(), counter2.GetBitrateStats(),
+ 0, receiver.GetDelayStats(), counter2.GetBitrateStats());
+}
+
+// webrtc:3277
+TEST_P(BweFeedbackTest, GoogleWifiTrace3Mbps) {
+ AdaptiveVideoSource source(0, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, GetParam());
+ RateCounterFilter counter1(&uplink_, 0, "sender_output",
+ bwe_names[GetParam()]);
+ TraceBasedDeliveryFilter filter(&uplink_, 0, "link_capacity");
+ filter.set_max_delay_ms(500);
+ RateCounterFilter counter2(&uplink_, 0, "Receiver", bwe_names[GetParam()]);
+ PacketReceiver receiver(&uplink_, 0, GetParam(), false, false);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("google-wifi-3mbps", "rx")));
+ RunFor(300 * 1000);
+ PrintResults(filter.GetBitrateStats().GetMean(), counter2.GetBitrateStats(),
+ 0, receiver.GetDelayStats(), counter2.GetBitrateStats());
+}
+
+TEST_P(BweFeedbackTest, PacedSelfFairness50msTest) {
+ int64_t kRttMs = 100;
+ int64_t kMaxJitterMs = 15;
+
+ const int kNumRmcatFlows = 4;
+ int64_t offset_ms[kNumRmcatFlows];
+ for (int i = 0; i < kNumRmcatFlows; ++i) {
+ offset_ms[i] = std::max(0, 5000 * i + rand() % 2001 - 1000);
+ }
+
+ RunFairnessTest(GetParam(), kNumRmcatFlows, 0, 300, 3000, 50, kRttMs,
+ kMaxJitterMs, offset_ms);
+}
+
+TEST_P(BweFeedbackTest, PacedSelfFairness500msTest) {
+ int64_t kRttMs = 100;
+ int64_t kMaxJitterMs = 15;
+
+ const int kNumRmcatFlows = 4;
+ int64_t offset_ms[kNumRmcatFlows];
+ for (int i = 0; i < kNumRmcatFlows; ++i) {
+ offset_ms[i] = std::max(0, 5000 * i + rand() % 2001 - 1000);
+ }
+
+ RunFairnessTest(GetParam(), kNumRmcatFlows, 0, 300, 3000, 500, kRttMs,
+ kMaxJitterMs, offset_ms);
+}
+
+TEST_P(BweFeedbackTest, PacedSelfFairness1000msTest) {
+ int64_t kRttMs = 100;
+ int64_t kMaxJitterMs = 15;
+
+ const int kNumRmcatFlows = 4;
+ int64_t offset_ms[kNumRmcatFlows];
+ for (int i = 0; i < kNumRmcatFlows; ++i) {
+ offset_ms[i] = std::max(0, 5000 * i + rand() % 2001 - 1000);
+ }
+
+ RunFairnessTest(GetParam(), kNumRmcatFlows, 0, 300, 3000, 1000, kRttMs,
+ kMaxJitterMs, offset_ms);
+}
+
+TEST_P(BweFeedbackTest, TcpFairness50msTest) {
+ int64_t kRttMs = 100;
+ int64_t kMaxJitterMs = 15;
+
+ int64_t offset_ms[2]; // One TCP, one RMCAT flow.
+ for (int i = 0; i < 2; ++i) {
+ offset_ms[i] = std::max(0, 5000 * i + rand() % 2001 - 1000);
+ }
+
+ RunFairnessTest(GetParam(), 1, 1, 300, 2000, 50, kRttMs, kMaxJitterMs,
+ offset_ms);
+}
+
+TEST_P(BweFeedbackTest, TcpFairness500msTest) {
+ int64_t kRttMs = 100;
+ int64_t kMaxJitterMs = 15;
+
+ int64_t offset_ms[2]; // One TCP, one RMCAT flow.
+ for (int i = 0; i < 2; ++i) {
+ offset_ms[i] = std::max(0, 5000 * i + rand() % 2001 - 1000);
+ }
+
+ RunFairnessTest(GetParam(), 1, 1, 300, 2000, 500, kRttMs, kMaxJitterMs,
+ offset_ms);
+}
+
+TEST_P(BweFeedbackTest, TcpFairness1000msTest) {
+ int64_t kRttMs = 100;
+ int64_t kMaxJitterMs = 15;
+
+ int64_t offset_ms[2]; // One TCP, one RMCAT flow.
+ for (int i = 0; i < 2; ++i) {
+ offset_ms[i] = std::max(0, 5000 * i + rand() % 2001 - 1000);
+ }
+
+ RunFairnessTest(GetParam(), 1, 1, 300, 2000, 1000, kRttMs, kMaxJitterMs,
+ offset_ms);
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc b/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc
new file mode 100644
index 0000000000..b7f9f65dbc
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2015 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/modules/remote_bitrate_estimator/remote_estimator_proxy.h"
+
+#include "webrtc/base/checks.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/system_wrappers/include/clock.h"
+#include "webrtc/modules/pacing/include/packet_router.h"
+#include "webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h"
+
+namespace webrtc {
+
+// TODO(sprang): Tune these!
+const int RemoteEstimatorProxy::kDefaultProcessIntervalMs = 200;
+const int RemoteEstimatorProxy::kBackWindowMs = 500;
+
+RemoteEstimatorProxy::RemoteEstimatorProxy(Clock* clock,
+ PacketRouter* packet_router)
+ : clock_(clock),
+ packet_router_(packet_router),
+ last_process_time_ms_(-1),
+ media_ssrc_(0),
+ feedback_sequence_(0),
+ window_start_seq_(-1) {}
+
+RemoteEstimatorProxy::~RemoteEstimatorProxy() {}
+
+void RemoteEstimatorProxy::IncomingPacketFeedbackVector(
+ const std::vector<PacketInfo>& packet_feedback_vector) {
+ rtc::CritScope cs(&lock_);
+ for (PacketInfo info : packet_feedback_vector)
+ OnPacketArrival(info.sequence_number, info.arrival_time_ms);
+}
+
+void RemoteEstimatorProxy::IncomingPacket(int64_t arrival_time_ms,
+ size_t payload_size,
+ const RTPHeader& header,
+ bool was_paced) {
+ if (!header.extension.hasTransportSequenceNumber) {
+ LOG(LS_WARNING) << "RemoteEstimatorProxy: Incoming packet "
+ "is missing the transport sequence number extension!";
+ return;
+ }
+ rtc::CritScope cs(&lock_);
+ media_ssrc_ = header.ssrc;
+ OnPacketArrival(header.extension.transportSequenceNumber, arrival_time_ms);
+}
+
+void RemoteEstimatorProxy::RemoveStream(unsigned int ssrc) {}
+
+bool RemoteEstimatorProxy::LatestEstimate(std::vector<unsigned int>* ssrcs,
+ unsigned int* bitrate_bps) const {
+ return false;
+}
+
+bool RemoteEstimatorProxy::GetStats(
+ ReceiveBandwidthEstimatorStats* output) const {
+ return false;
+}
+
+
+int64_t RemoteEstimatorProxy::TimeUntilNextProcess() {
+ int64_t now = clock_->TimeInMilliseconds();
+ int64_t time_until_next = 0;
+ if (last_process_time_ms_ != -1 &&
+ now - last_process_time_ms_ < kDefaultProcessIntervalMs) {
+ time_until_next = (last_process_time_ms_ + kDefaultProcessIntervalMs - now);
+ }
+ return time_until_next;
+}
+
+int32_t RemoteEstimatorProxy::Process() {
+ // TODO(sprang): Perhaps we need a dedicated thread here instead?
+
+ if (TimeUntilNextProcess() > 0)
+ return 0;
+ last_process_time_ms_ = clock_->TimeInMilliseconds();
+
+ bool more_to_build = true;
+ while (more_to_build) {
+ rtcp::TransportFeedback feedback_packet;
+ if (BuildFeedbackPacket(&feedback_packet)) {
+ RTC_DCHECK(packet_router_ != nullptr);
+ packet_router_->SendFeedback(&feedback_packet);
+ } else {
+ more_to_build = false;
+ }
+ }
+
+ return 0;
+}
+
+void RemoteEstimatorProxy::OnPacketArrival(uint16_t sequence_number,
+ int64_t arrival_time) {
+ int64_t seq = unwrapper_.Unwrap(sequence_number);
+
+ if (window_start_seq_ == -1) {
+ window_start_seq_ = seq;
+ // Start new feedback packet, cull old packets.
+ for (auto it = packet_arrival_times_.begin();
+ it != packet_arrival_times_.end() && it->first < seq &&
+ arrival_time - it->second >= kBackWindowMs;) {
+ auto delete_it = it;
+ ++it;
+ packet_arrival_times_.erase(delete_it);
+ }
+ } else if (seq < window_start_seq_) {
+ window_start_seq_ = seq;
+ }
+
+ RTC_DCHECK(packet_arrival_times_.end() == packet_arrival_times_.find(seq));
+ packet_arrival_times_[seq] = arrival_time;
+}
+
+bool RemoteEstimatorProxy::BuildFeedbackPacket(
+ rtcp::TransportFeedback* feedback_packet) {
+ rtc::CritScope cs(&lock_);
+ if (window_start_seq_ == -1)
+ return false;
+
+ // window_start_seq_ is the first sequence number to include in the current
+ // feedback packet. Some older may still be in the map, in case a reordering
+ // happens and we need to retransmit them.
+ auto it = packet_arrival_times_.find(window_start_seq_);
+ RTC_DCHECK(it != packet_arrival_times_.end());
+
+ // TODO(sprang): Measure receive times in microseconds and remove the
+ // conversions below.
+ feedback_packet->WithMediaSourceSsrc(media_ssrc_);
+ feedback_packet->WithBase(static_cast<uint16_t>(it->first & 0xFFFF),
+ it->second * 1000);
+ feedback_packet->WithFeedbackSequenceNumber(feedback_sequence_++);
+ for (; it != packet_arrival_times_.end(); ++it) {
+ if (!feedback_packet->WithReceivedPacket(
+ static_cast<uint16_t>(it->first & 0xFFFF), it->second * 1000)) {
+ // If we can't even add the first seq to the feedback packet, we won't be
+ // able to build it at all.
+ RTC_CHECK_NE(window_start_seq_, it->first);
+
+ // Could not add timestamp, feedback packet might be full. Return and
+ // try again with a fresh packet.
+ window_start_seq_ = it->first;
+ break;
+ }
+ // Note: Don't erase items from packet_arrival_times_ after sending, in case
+ // they need to be re-sent after a reordering. Removal will be handled
+ // by OnPacketArrival once packets are too old.
+ }
+ if (it == packet_arrival_times_.end())
+ window_start_seq_ = -1;
+
+ return true;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.h b/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.h
new file mode 100644
index 0000000000..e867ff77a4
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_ESTIMATOR_PROXY_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_ESTIMATOR_PROXY_H_
+
+#include <map>
+#include <vector>
+
+#include "webrtc/base/criticalsection.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+
+namespace webrtc {
+
+class Clock;
+class PacketRouter;
+namespace rtcp {
+class TransportFeedback;
+}
+
+// Class used when send-side BWE is enabled: This proxy is instantiated on the
+// receive side. It buffers a number of receive timestamps and then sends
+// transport feedback messages back too the send side.
+
+class RemoteEstimatorProxy : public RemoteBitrateEstimator {
+ public:
+ RemoteEstimatorProxy(Clock* clock, PacketRouter* packet_router);
+ virtual ~RemoteEstimatorProxy();
+
+ void IncomingPacketFeedbackVector(
+ const std::vector<PacketInfo>& packet_feedback_vector) override;
+ void IncomingPacket(int64_t arrival_time_ms,
+ size_t payload_size,
+ const RTPHeader& header,
+ bool was_paced) override;
+ void RemoveStream(unsigned int ssrc) override;
+ bool LatestEstimate(std::vector<unsigned int>* ssrcs,
+ unsigned int* bitrate_bps) const override;
+ bool GetStats(ReceiveBandwidthEstimatorStats* output) const override;
+ void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override {}
+ void SetMinBitrate(int min_bitrate_bps) override {}
+ int64_t TimeUntilNextProcess() override;
+ int32_t Process() override;
+
+ static const int kDefaultProcessIntervalMs;
+ static const int kBackWindowMs;
+
+ private:
+ void OnPacketArrival(uint16_t sequence_number, int64_t arrival_time)
+ EXCLUSIVE_LOCKS_REQUIRED(&lock_);
+ bool BuildFeedbackPacket(rtcp::TransportFeedback* feedback_packetket);
+
+ Clock* const clock_;
+ PacketRouter* const packet_router_;
+ int64_t last_process_time_ms_;
+
+ rtc::CriticalSection lock_;
+
+ uint32_t media_ssrc_ GUARDED_BY(&lock_);
+ uint8_t feedback_sequence_ GUARDED_BY(&lock_);
+ SequenceNumberUnwrapper unwrapper_ GUARDED_BY(&lock_);
+ int64_t window_start_seq_ GUARDED_BY(&lock_);
+ // Map unwrapped seq -> time.
+ std::map<int64_t, int64_t> packet_arrival_times_ GUARDED_BY(&lock_);
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_ESTIMATOR_PROXY_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy_unittest.cc b/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy_unittest.cc
new file mode 100644
index 0000000000..826a724e33
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy_unittest.cc
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2015 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 "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "webrtc/modules/pacing/include/packet_router.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.h"
+#include "webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
+#include "webrtc/system_wrappers/include/clock.h"
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+
+namespace webrtc {
+
+class MockPacketRouter : public PacketRouter {
+ public:
+ MOCK_METHOD1(SendFeedback, bool(rtcp::TransportFeedback* packet));
+};
+
+class RemoteEstimatorProxyTest : public ::testing::Test {
+ public:
+ RemoteEstimatorProxyTest() : clock_(0), proxy_(&clock_, &router_) {}
+
+ protected:
+ void IncomingPacket(uint16_t seq, int64_t time_ms) {
+ RTPHeader header;
+ header.extension.hasTransportSequenceNumber = true;
+ header.extension.transportSequenceNumber = seq;
+ header.ssrc = kMediaSsrc;
+ proxy_.IncomingPacket(time_ms, kDefaultPacketSize, header, true);
+ }
+
+ void Process() {
+ clock_.AdvanceTimeMilliseconds(
+ RemoteEstimatorProxy::kDefaultProcessIntervalMs);
+ proxy_.Process();
+ }
+
+ SimulatedClock clock_;
+ MockPacketRouter router_;
+ RemoteEstimatorProxy proxy_;
+
+ const size_t kDefaultPacketSize = 100;
+ const uint32_t kMediaSsrc = 456;
+ const uint16_t kBaseSeq = 10;
+ const int64_t kBaseTimeMs = 123;
+ const int64_t kMaxSmallDeltaMs =
+ (rtcp::TransportFeedback::kDeltaScaleFactor * 0xFF) / 1000;
+};
+
+TEST_F(RemoteEstimatorProxyTest, SendsSinglePacketFeedback) {
+ IncomingPacket(kBaseSeq, kBaseTimeMs);
+
+ EXPECT_CALL(router_, SendFeedback(_))
+ .Times(1)
+ .WillOnce(Invoke([this](rtcp::TransportFeedback* packet) {
+ packet->Build();
+ EXPECT_EQ(kBaseSeq, packet->GetBaseSequence());
+ EXPECT_EQ(kMediaSsrc, packet->GetMediaSourceSsrc());
+
+ std::vector<rtcp::TransportFeedback::StatusSymbol> status_vec =
+ packet->GetStatusVector();
+ EXPECT_EQ(1u, status_vec.size());
+ EXPECT_EQ(rtcp::TransportFeedback::StatusSymbol::kReceivedSmallDelta,
+ status_vec[0]);
+ std::vector<int64_t> delta_vec = packet->GetReceiveDeltasUs();
+ EXPECT_EQ(1u, delta_vec.size());
+ EXPECT_EQ(kBaseTimeMs, (packet->GetBaseTimeUs() + delta_vec[0]) / 1000);
+ return true;
+ }));
+
+ Process();
+}
+
+TEST_F(RemoteEstimatorProxyTest, SendsFeedbackWithVaryingDeltas) {
+ IncomingPacket(kBaseSeq, kBaseTimeMs);
+ IncomingPacket(kBaseSeq + 1, kBaseTimeMs + kMaxSmallDeltaMs);
+ IncomingPacket(kBaseSeq + 2, kBaseTimeMs + (2 * kMaxSmallDeltaMs) + 1);
+
+ EXPECT_CALL(router_, SendFeedback(_))
+ .Times(1)
+ .WillOnce(Invoke([this](rtcp::TransportFeedback* packet) {
+ packet->Build();
+ EXPECT_EQ(kBaseSeq, packet->GetBaseSequence());
+ EXPECT_EQ(kMediaSsrc, packet->GetMediaSourceSsrc());
+
+ std::vector<rtcp::TransportFeedback::StatusSymbol> status_vec =
+ packet->GetStatusVector();
+ EXPECT_EQ(3u, status_vec.size());
+ EXPECT_EQ(rtcp::TransportFeedback::StatusSymbol::kReceivedSmallDelta,
+ status_vec[0]);
+ EXPECT_EQ(rtcp::TransportFeedback::StatusSymbol::kReceivedSmallDelta,
+ status_vec[1]);
+ EXPECT_EQ(rtcp::TransportFeedback::StatusSymbol::kReceivedLargeDelta,
+ status_vec[2]);
+
+ std::vector<int64_t> delta_vec = packet->GetReceiveDeltasUs();
+ EXPECT_EQ(3u, delta_vec.size());
+ EXPECT_EQ(kBaseTimeMs, (packet->GetBaseTimeUs() + delta_vec[0]) / 1000);
+ EXPECT_EQ(kMaxSmallDeltaMs, delta_vec[1] / 1000);
+ EXPECT_EQ(kMaxSmallDeltaMs + 1, delta_vec[2] / 1000);
+ return true;
+ }));
+
+ Process();
+}
+
+TEST_F(RemoteEstimatorProxyTest, SendsFragmentedFeedback) {
+ const int64_t kTooLargeDelta =
+ rtcp::TransportFeedback::kDeltaScaleFactor * (1 << 16);
+
+ IncomingPacket(kBaseSeq, kBaseTimeMs);
+ IncomingPacket(kBaseSeq + 1, kBaseTimeMs + kTooLargeDelta);
+
+ InSequence s;
+ EXPECT_CALL(router_, SendFeedback(_))
+ .Times(1)
+ .WillOnce(Invoke([kTooLargeDelta, this](rtcp::TransportFeedback* packet) {
+ packet->Build();
+ EXPECT_EQ(kBaseSeq, packet->GetBaseSequence());
+ EXPECT_EQ(kMediaSsrc, packet->GetMediaSourceSsrc());
+
+ std::vector<rtcp::TransportFeedback::StatusSymbol> status_vec =
+ packet->GetStatusVector();
+ EXPECT_EQ(1u, status_vec.size());
+ EXPECT_EQ(rtcp::TransportFeedback::StatusSymbol::kReceivedSmallDelta,
+ status_vec[0]);
+ std::vector<int64_t> delta_vec = packet->GetReceiveDeltasUs();
+ EXPECT_EQ(1u, delta_vec.size());
+ EXPECT_EQ(kBaseTimeMs, (packet->GetBaseTimeUs() + delta_vec[0]) / 1000);
+ return true;
+ }))
+ .RetiresOnSaturation();
+
+ EXPECT_CALL(router_, SendFeedback(_))
+ .Times(1)
+ .WillOnce(Invoke([kTooLargeDelta, this](rtcp::TransportFeedback* packet) {
+ packet->Build();
+ EXPECT_EQ(kBaseSeq + 1, packet->GetBaseSequence());
+ EXPECT_EQ(kMediaSsrc, packet->GetMediaSourceSsrc());
+
+ std::vector<rtcp::TransportFeedback::StatusSymbol> status_vec =
+ packet->GetStatusVector();
+ EXPECT_EQ(1u, status_vec.size());
+ EXPECT_EQ(rtcp::TransportFeedback::StatusSymbol::kReceivedSmallDelta,
+ status_vec[0]);
+ std::vector<int64_t> delta_vec = packet->GetReceiveDeltasUs();
+ EXPECT_EQ(1u, delta_vec.size());
+ EXPECT_EQ(kBaseTimeMs + kTooLargeDelta,
+ (packet->GetBaseTimeUs() + delta_vec[0]) / 1000);
+ return true;
+ }))
+ .RetiresOnSaturation();
+
+ Process();
+}
+
+TEST_F(RemoteEstimatorProxyTest, ResendsTimestampsOnReordering) {
+ IncomingPacket(kBaseSeq, kBaseTimeMs);
+ IncomingPacket(kBaseSeq + 2, kBaseTimeMs + 2);
+
+ EXPECT_CALL(router_, SendFeedback(_))
+ .Times(1)
+ .WillOnce(Invoke([this](rtcp::TransportFeedback* packet) {
+ packet->Build();
+ EXPECT_EQ(kBaseSeq, packet->GetBaseSequence());
+ EXPECT_EQ(kMediaSsrc, packet->GetMediaSourceSsrc());
+
+ std::vector<int64_t> delta_vec = packet->GetReceiveDeltasUs();
+ EXPECT_EQ(2u, delta_vec.size());
+ EXPECT_EQ(kBaseTimeMs, (packet->GetBaseTimeUs() + delta_vec[0]) / 1000);
+ EXPECT_EQ(2, delta_vec[1] / 1000);
+ return true;
+ }));
+
+ Process();
+
+ IncomingPacket(kBaseSeq + 1, kBaseTimeMs + 1);
+
+ EXPECT_CALL(router_, SendFeedback(_))
+ .Times(1)
+ .WillOnce(Invoke([this](rtcp::TransportFeedback* packet) {
+ packet->Build();
+ EXPECT_EQ(kBaseSeq + 1, packet->GetBaseSequence());
+ EXPECT_EQ(kMediaSsrc, packet->GetMediaSourceSsrc());
+
+ std::vector<int64_t> delta_vec = packet->GetReceiveDeltasUs();
+ EXPECT_EQ(2u, delta_vec.size());
+ EXPECT_EQ(kBaseTimeMs + 1,
+ (packet->GetBaseTimeUs() + delta_vec[0]) / 1000);
+ EXPECT_EQ(1, delta_vec[1] / 1000);
+ return true;
+ }));
+
+ Process();
+}
+
+TEST_F(RemoteEstimatorProxyTest, RemovesTimestampsOutOfScope) {
+ const int64_t kTimeoutTimeMs =
+ kBaseTimeMs + RemoteEstimatorProxy::kBackWindowMs;
+
+ IncomingPacket(kBaseSeq + 2, kBaseTimeMs);
+
+ EXPECT_CALL(router_, SendFeedback(_))
+ .Times(1)
+ .WillOnce(Invoke([kTimeoutTimeMs, this](rtcp::TransportFeedback* packet) {
+ packet->Build();
+ EXPECT_EQ(kBaseSeq + 2, packet->GetBaseSequence());
+
+ std::vector<int64_t> delta_vec = packet->GetReceiveDeltasUs();
+ EXPECT_EQ(1u, delta_vec.size());
+ EXPECT_EQ(kBaseTimeMs, (packet->GetBaseTimeUs() + delta_vec[0]) / 1000);
+ return true;
+ }));
+
+ Process();
+
+ IncomingPacket(kBaseSeq + 3, kTimeoutTimeMs); // kBaseSeq + 2 times out here.
+
+ EXPECT_CALL(router_, SendFeedback(_))
+ .Times(1)
+ .WillOnce(Invoke([kTimeoutTimeMs, this](rtcp::TransportFeedback* packet) {
+ packet->Build();
+ EXPECT_EQ(kBaseSeq + 3, packet->GetBaseSequence());
+
+ std::vector<int64_t> delta_vec = packet->GetReceiveDeltasUs();
+ EXPECT_EQ(1u, delta_vec.size());
+ EXPECT_EQ(kTimeoutTimeMs,
+ (packet->GetBaseTimeUs() + delta_vec[0]) / 1000);
+ return true;
+ }));
+
+ Process();
+
+ // New group, with sequence starting below the first so that they may be
+ // retransmitted.
+ IncomingPacket(kBaseSeq, kBaseTimeMs - 1);
+ IncomingPacket(kBaseSeq + 1, kTimeoutTimeMs - 1);
+
+ EXPECT_CALL(router_, SendFeedback(_))
+ .Times(1)
+ .WillOnce(Invoke([kTimeoutTimeMs, this](rtcp::TransportFeedback* packet) {
+ packet->Build();
+ EXPECT_EQ(kBaseSeq, packet->GetBaseSequence());
+
+ // Four status entries (kBaseSeq + 3 missing).
+ EXPECT_EQ(4u, packet->GetStatusVector().size());
+
+ // Only three actual timestamps.
+ std::vector<int64_t> delta_vec = packet->GetReceiveDeltasUs();
+ EXPECT_EQ(3u, delta_vec.size());
+ EXPECT_EQ(kBaseTimeMs - 1,
+ (packet->GetBaseTimeUs() + delta_vec[0]) / 1000);
+ EXPECT_EQ(kTimeoutTimeMs - kBaseTimeMs, delta_vec[1] / 1000);
+ EXPECT_EQ(1, delta_vec[2] / 1000);
+ return true;
+ }));
+
+ Process();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/send_time_history.cc b/webrtc/modules/remote_bitrate_estimator/send_time_history.cc
new file mode 100644
index 0000000000..a58d12a160
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/send_time_history.cc
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2015 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 <assert.h>
+
+#include "webrtc/modules/remote_bitrate_estimator/include/send_time_history.h"
+
+namespace webrtc {
+
+SendTimeHistory::SendTimeHistory(Clock* clock, int64_t packet_age_limit)
+ : clock_(clock),
+ packet_age_limit_(packet_age_limit),
+ oldest_sequence_number_(0) {}
+
+SendTimeHistory::~SendTimeHistory() {
+}
+
+void SendTimeHistory::Clear() {
+ history_.clear();
+}
+
+void SendTimeHistory::AddAndRemoveOld(uint16_t sequence_number,
+ size_t length,
+ bool was_paced) {
+ EraseOld();
+
+ if (history_.empty())
+ oldest_sequence_number_ = sequence_number;
+
+ history_.insert(std::pair<uint16_t, PacketInfo>(
+ sequence_number, PacketInfo(clock_->TimeInMilliseconds(), 0, -1,
+ sequence_number, length, was_paced)));
+}
+
+bool SendTimeHistory::OnSentPacket(uint16_t sequence_number,
+ int64_t send_time_ms) {
+ auto it = history_.find(sequence_number);
+ if (it == history_.end())
+ return false;
+ it->second.send_time_ms = send_time_ms;
+ return true;
+}
+
+void SendTimeHistory::EraseOld() {
+ while (!history_.empty()) {
+ auto it = history_.find(oldest_sequence_number_);
+ assert(it != history_.end());
+
+ if (clock_->TimeInMilliseconds() - it->second.creation_time_ms <=
+ packet_age_limit_) {
+ return; // Oldest packet within age limit, return.
+ }
+
+ // TODO(sprang): Warn if erasing (too many) old items?
+ history_.erase(it);
+ UpdateOldestSequenceNumber();
+ }
+}
+
+void SendTimeHistory::UpdateOldestSequenceNumber() {
+ // After removing an element from the map, update oldest_sequence_number_ to
+ // the element with the lowest sequence number higher than the previous
+ // value (there might be gaps).
+ if (history_.empty())
+ return;
+ auto it = history_.upper_bound(oldest_sequence_number_);
+ if (it == history_.end()) {
+ // No element with higher sequence number than oldest_sequence_number_
+ // found, check wrap around. Note that history_.upper_bound(0) will not
+ // find 0 even if it is there, need to explicitly check for 0.
+ it = history_.find(0);
+ if (it == history_.end())
+ it = history_.upper_bound(0);
+ }
+ assert(it != history_.end());
+ oldest_sequence_number_ = it->first;
+}
+
+bool SendTimeHistory::GetInfo(PacketInfo* packet, bool remove) {
+ auto it = history_.find(packet->sequence_number);
+ if (it == history_.end())
+ return false;
+ int64_t receive_time = packet->arrival_time_ms;
+ *packet = it->second;
+ packet->arrival_time_ms = receive_time;
+ if (remove) {
+ history_.erase(it);
+ if (packet->sequence_number == oldest_sequence_number_)
+ UpdateOldestSequenceNumber();
+ }
+ return true;
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/send_time_history_unittest.cc b/webrtc/modules/remote_bitrate_estimator/send_time_history_unittest.cc
new file mode 100644
index 0000000000..b525813cdc
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/send_time_history_unittest.cc
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2015 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 <algorithm>
+#include <limits>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/send_time_history.h"
+#include "webrtc/system_wrappers/include/clock.h"
+
+namespace webrtc {
+namespace test {
+
+static const int kDefaultHistoryLengthMs = 1000;
+
+class SendTimeHistoryTest : public ::testing::Test {
+ protected:
+ SendTimeHistoryTest()
+ : clock_(0), history_(&clock_, kDefaultHistoryLengthMs) {}
+ ~SendTimeHistoryTest() {}
+
+ virtual void SetUp() {}
+
+ virtual void TearDown() {}
+
+ void AddPacketWithSendTime(uint16_t sequence_number,
+ size_t length,
+ bool was_paced,
+ int64_t send_time_ms) {
+ history_.AddAndRemoveOld(sequence_number, length, was_paced);
+ history_.OnSentPacket(sequence_number, send_time_ms);
+ }
+
+ webrtc::SimulatedClock clock_;
+ SendTimeHistory history_;
+};
+
+// Help class extended so we can do EXPECT_EQ and collections.
+class PacketInfo : public webrtc::PacketInfo {
+ public:
+ PacketInfo() : webrtc::PacketInfo(-1, 0, 0, 0, 0, false) {}
+ PacketInfo(int64_t arrival_time_ms, uint16_t sequence_number)
+ : PacketInfo(arrival_time_ms, 0, sequence_number, 0, false) {}
+ PacketInfo(int64_t arrival_time_ms,
+ int64_t send_time_ms,
+ uint16_t sequence_number,
+ size_t payload_size,
+ bool was_paced)
+ : webrtc::PacketInfo(-1,
+ arrival_time_ms,
+ send_time_ms,
+ sequence_number,
+ payload_size,
+ was_paced) {}
+ bool operator==(const PacketInfo& other) const {
+ return arrival_time_ms == other.arrival_time_ms &&
+ send_time_ms == other.send_time_ms &&
+ sequence_number == other.sequence_number &&
+ payload_size == other.payload_size && was_paced == other.was_paced;
+ }
+};
+
+TEST_F(SendTimeHistoryTest, AddRemoveOne) {
+ const uint16_t kSeqNo = 10;
+ const PacketInfo kSentPacket(0, 1, kSeqNo, 1, true);
+ AddPacketWithSendTime(kSeqNo, 1, true, 1);
+
+ PacketInfo received_packet(0, 0, kSeqNo, 0, false);
+ EXPECT_TRUE(history_.GetInfo(&received_packet, false));
+ EXPECT_EQ(kSentPacket, received_packet);
+
+ PacketInfo received_packet2(0, 0, kSeqNo, 0, false);
+ EXPECT_TRUE(history_.GetInfo(&received_packet2, true));
+ EXPECT_EQ(kSentPacket, received_packet2);
+
+ PacketInfo received_packet3(0, 0, kSeqNo, 0, false);
+ EXPECT_FALSE(history_.GetInfo(&received_packet3, true));
+}
+
+TEST_F(SendTimeHistoryTest, PopulatesExpectedFields) {
+ const uint16_t kSeqNo = 10;
+ const int64_t kSendTime = 1000;
+ const int64_t kReceiveTime = 2000;
+ const size_t kPayloadSize = 42;
+ const bool kPaced = true;
+
+ AddPacketWithSendTime(kSeqNo, kPayloadSize, kPaced, kSendTime);
+
+ PacketInfo info(kReceiveTime, kSeqNo);
+ EXPECT_TRUE(history_.GetInfo(&info, true));
+ EXPECT_EQ(kReceiveTime, info.arrival_time_ms);
+ EXPECT_EQ(kSendTime, info.send_time_ms);
+ EXPECT_EQ(kSeqNo, info.sequence_number);
+ EXPECT_EQ(kPayloadSize, info.payload_size);
+ EXPECT_EQ(kPaced, info.was_paced);
+}
+
+TEST_F(SendTimeHistoryTest, AddThenRemoveOutOfOrder) {
+ std::vector<PacketInfo> sent_packets;
+ std::vector<PacketInfo> received_packets;
+ const size_t num_items = 100;
+ const size_t kPacketSize = 400;
+ const size_t kTransmissionTime = 1234;
+ const bool kPaced = true;
+ for (size_t i = 0; i < num_items; ++i) {
+ sent_packets.push_back(PacketInfo(0, static_cast<int64_t>(i),
+ static_cast<uint16_t>(i), kPacketSize,
+ kPaced));
+ received_packets.push_back(
+ PacketInfo(static_cast<int64_t>(i) + kTransmissionTime, 0,
+ static_cast<uint16_t>(i), kPacketSize, false));
+ }
+ for (size_t i = 0; i < num_items; ++i) {
+ history_.AddAndRemoveOld(sent_packets[i].sequence_number,
+ sent_packets[i].payload_size,
+ sent_packets[i].was_paced);
+ }
+ for (size_t i = 0; i < num_items; ++i)
+ history_.OnSentPacket(sent_packets[i].sequence_number,
+ sent_packets[i].send_time_ms);
+ std::random_shuffle(received_packets.begin(), received_packets.end());
+ for (size_t i = 0; i < num_items; ++i) {
+ PacketInfo packet = received_packets[i];
+ EXPECT_TRUE(history_.GetInfo(&packet, false));
+ PacketInfo sent_packet = sent_packets[packet.sequence_number];
+ sent_packet.arrival_time_ms = packet.arrival_time_ms;
+ EXPECT_EQ(sent_packet, packet);
+ EXPECT_TRUE(history_.GetInfo(&packet, true));
+ }
+ for (PacketInfo packet : sent_packets)
+ EXPECT_FALSE(history_.GetInfo(&packet, false));
+}
+
+TEST_F(SendTimeHistoryTest, HistorySize) {
+ const int kItems = kDefaultHistoryLengthMs / 100;
+ for (int i = 0; i < kItems; ++i) {
+ clock_.AdvanceTimeMilliseconds(100);
+ AddPacketWithSendTime(i, 0, false, i * 100);
+ }
+ for (int i = 0; i < kItems; ++i) {
+ PacketInfo info(0, 0, static_cast<uint16_t>(i), 0, false);
+ EXPECT_TRUE(history_.GetInfo(&info, false));
+ EXPECT_EQ(i * 100, info.send_time_ms);
+ }
+ clock_.AdvanceTimeMilliseconds(101);
+ AddPacketWithSendTime(kItems, 0, false, kItems * 101);
+ PacketInfo info(0, 0, 0, 0, false);
+ EXPECT_FALSE(history_.GetInfo(&info, false));
+ for (int i = 1; i < (kItems + 1); ++i) {
+ PacketInfo info2(0, 0, static_cast<uint16_t>(i), 0, false);
+ EXPECT_TRUE(history_.GetInfo(&info2, false));
+ int64_t expected_time_ms = (i == kItems) ? i * 101 : i * 100;
+ EXPECT_EQ(expected_time_ms, info2.send_time_ms);
+ }
+}
+
+TEST_F(SendTimeHistoryTest, HistorySizeWithWraparound) {
+ const uint16_t kMaxSeqNo = std::numeric_limits<uint16_t>::max();
+ AddPacketWithSendTime(kMaxSeqNo - 2, 0, false, 0);
+
+ clock_.AdvanceTimeMilliseconds(100);
+ AddPacketWithSendTime(kMaxSeqNo - 1, 1, false, 100);
+
+ clock_.AdvanceTimeMilliseconds(100);
+ AddPacketWithSendTime(kMaxSeqNo, 0, false, 200);
+
+ clock_.AdvanceTimeMilliseconds(kDefaultHistoryLengthMs - 200 + 1);
+ AddPacketWithSendTime(0, 0, false, kDefaultHistoryLengthMs);
+
+ PacketInfo info(0, static_cast<uint16_t>(kMaxSeqNo - 2));
+ EXPECT_FALSE(history_.GetInfo(&info, false));
+ PacketInfo info2(0, static_cast<uint16_t>(kMaxSeqNo - 1));
+ EXPECT_TRUE(history_.GetInfo(&info2, false));
+ PacketInfo info3(0, static_cast<uint16_t>(kMaxSeqNo));
+ EXPECT_TRUE(history_.GetInfo(&info3, false));
+ PacketInfo info4(0, 0);
+ EXPECT_TRUE(history_.GetInfo(&info4, false));
+
+ // Create a gap (kMaxSeqNo - 1) -> 0.
+ PacketInfo info5(0, kMaxSeqNo);
+ EXPECT_TRUE(history_.GetInfo(&info5, true));
+
+ clock_.AdvanceTimeMilliseconds(100);
+ AddPacketWithSendTime(1, 0, false, 1100);
+
+ PacketInfo info6(0, static_cast<uint16_t>(kMaxSeqNo - 2));
+ EXPECT_FALSE(history_.GetInfo(&info6, false));
+ PacketInfo info7(0, static_cast<uint16_t>(kMaxSeqNo - 1));
+ EXPECT_FALSE(history_.GetInfo(&info7, false));
+ PacketInfo info8(0, kMaxSeqNo);
+ EXPECT_FALSE(history_.GetInfo(&info8, false));
+ PacketInfo info9(0, 0);
+ EXPECT_TRUE(history_.GetInfo(&info9, false));
+ PacketInfo info10(0, 1);
+ EXPECT_TRUE(history_.GetInfo(&info10, false));
+}
+
+TEST_F(SendTimeHistoryTest, InterlievedGetAndRemove) {
+ const uint16_t kSeqNo = 1;
+ const int64_t kTimestamp = 2;
+ PacketInfo packets[3] = {{0, kTimestamp, kSeqNo, 0, false},
+ {0, kTimestamp + 1, kSeqNo + 1, 0, false},
+ {0, kTimestamp + 2, kSeqNo + 2, 0, false}};
+
+ AddPacketWithSendTime(packets[0].sequence_number, packets[0].payload_size,
+ packets[0].was_paced, packets[0].send_time_ms);
+ AddPacketWithSendTime(packets[1].sequence_number, packets[1].payload_size,
+ packets[1].was_paced, packets[1].send_time_ms);
+ PacketInfo info(0, 0, packets[0].sequence_number, 0, false);
+ EXPECT_TRUE(history_.GetInfo(&info, true));
+ EXPECT_EQ(packets[0], info);
+
+ AddPacketWithSendTime(packets[2].sequence_number, packets[2].payload_size,
+ packets[2].was_paced, packets[2].send_time_ms);
+
+ PacketInfo info2(0, 0, packets[1].sequence_number, 0, false);
+ EXPECT_TRUE(history_.GetInfo(&info2, true));
+ EXPECT_EQ(packets[1], info2);
+
+ PacketInfo info3(0, 0, packets[2].sequence_number, 0, false);
+ EXPECT_TRUE(history_.GetInfo(&info3, true));
+ EXPECT_EQ(packets[2], info3);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe.cc
new file mode 100644
index 0000000000..c667b6864e
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe.cc
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2015 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/modules/remote_bitrate_estimator/test/bwe.h"
+
+#include <limits>
+
+#include "webrtc/base/common.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+// With the assumption that packet loss is lower than 97%, the max gap
+// between elements in the set is lower than 0x8000, hence we have a
+// total order in the set. For (x,y,z) subset of the LinkedSet,
+// (x<=y and y<=z) ==> x<=z so the set can be sorted.
+const int kSetCapacity = 1000;
+
+BweReceiver::BweReceiver(int flow_id)
+ : flow_id_(flow_id),
+ received_packets_(kSetCapacity),
+ rate_counter_(),
+ loss_account_() {
+}
+
+BweReceiver::BweReceiver(int flow_id, int64_t window_size_ms)
+ : flow_id_(flow_id),
+ received_packets_(kSetCapacity),
+ rate_counter_(window_size_ms),
+ loss_account_() {
+}
+
+void BweReceiver::ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) {
+ if (received_packets_.size() == kSetCapacity) {
+ RelieveSetAndUpdateLoss();
+ }
+
+ received_packets_.Insert(media_packet.sequence_number(),
+ media_packet.send_time_ms(), arrival_time_ms,
+ media_packet.payload_size());
+
+ rate_counter_.UpdateRates(media_packet.send_time_ms() * 1000,
+ static_cast<uint32_t>(media_packet.payload_size()));
+}
+
+class NullBweSender : public BweSender {
+ public:
+ NullBweSender() {}
+ virtual ~NullBweSender() {}
+
+ int GetFeedbackIntervalMs() const override { return 1000; }
+ void GiveFeedback(const FeedbackPacket& feedback) override {}
+ void OnPacketsSent(const Packets& packets) override {}
+ int64_t TimeUntilNextProcess() override {
+ return std::numeric_limits<int64_t>::max();
+ }
+ int Process() override { return 0; }
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(NullBweSender);
+};
+
+int64_t GetAbsSendTimeInMs(uint32_t abs_send_time) {
+ const int kInterArrivalShift = 26;
+ const int kAbsSendTimeInterArrivalUpshift = 8;
+ const double kTimestampToMs =
+ 1000.0 / static_cast<double>(1 << kInterArrivalShift);
+ uint32_t timestamp = abs_send_time << kAbsSendTimeInterArrivalUpshift;
+ return static_cast<int64_t>(timestamp) * kTimestampToMs;
+}
+
+BweSender* CreateBweSender(BandwidthEstimatorType estimator,
+ int kbps,
+ BitrateObserver* observer,
+ Clock* clock) {
+ switch (estimator) {
+ case kRembEstimator:
+ return new RembBweSender(kbps, observer, clock);
+ case kFullSendSideEstimator:
+ return new FullBweSender(kbps, observer, clock);
+ case kNadaEstimator:
+ return new NadaBweSender(kbps, observer, clock);
+ case kTcpEstimator:
+ FALLTHROUGH();
+ case kNullEstimator:
+ return new NullBweSender();
+ }
+ assert(false);
+ return NULL;
+}
+
+BweReceiver* CreateBweReceiver(BandwidthEstimatorType type,
+ int flow_id,
+ bool plot) {
+ switch (type) {
+ case kRembEstimator:
+ return new RembReceiver(flow_id, plot);
+ case kFullSendSideEstimator:
+ return new SendSideBweReceiver(flow_id);
+ case kNadaEstimator:
+ return new NadaBweReceiver(flow_id);
+ case kTcpEstimator:
+ return new TcpBweReceiver(flow_id);
+ case kNullEstimator:
+ return new BweReceiver(flow_id);
+ }
+ assert(false);
+ return NULL;
+}
+
+// Take into account all LinkedSet content.
+void BweReceiver::UpdateLoss() {
+ loss_account_.Add(LinkedSetPacketLossRatio());
+}
+
+// Preserve 10% latest packets and update packet loss based on the oldest
+// 90%, that will be removed.
+void BweReceiver::RelieveSetAndUpdateLoss() {
+ // Compute Loss for the whole LinkedSet and updates loss_account_.
+ UpdateLoss();
+
+ size_t num_preserved_elements = received_packets_.size() / 10;
+ PacketNodeIt it = received_packets_.begin();
+ std::advance(it, num_preserved_elements);
+
+ while (it != received_packets_.end()) {
+ received_packets_.Erase(it++);
+ }
+
+ // Compute Loss for the preserved elements
+ loss_account_.Subtract(LinkedSetPacketLossRatio());
+}
+
+float BweReceiver::GlobalReceiverPacketLossRatio() {
+ UpdateLoss();
+ return loss_account_.LossRatio();
+}
+
+// This function considers at most kSetCapacity = 1000 packets.
+LossAccount BweReceiver::LinkedSetPacketLossRatio() {
+ if (received_packets_.empty()) {
+ return LossAccount();
+ }
+
+ uint16_t oldest_seq_num = received_packets_.OldestSeqNumber();
+ uint16_t newest_seq_num = received_packets_.NewestSeqNumber();
+
+ size_t set_total_packets =
+ static_cast<uint16_t>(newest_seq_num - oldest_seq_num + 1);
+
+ size_t set_received_packets = received_packets_.size();
+ size_t set_lost_packets = set_total_packets - set_received_packets;
+
+ return LossAccount(set_total_packets, set_lost_packets);
+}
+
+uint32_t BweReceiver::RecentKbps() const {
+ return (rate_counter_.bits_per_second() + 500) / 1000;
+}
+
+// Go through a fixed time window of most recent packets received and
+// counts packets missing to obtain the packet loss ratio. If an unordered
+// packet falls out of the timewindow it will be counted as missing.
+// E.g.: for a timewindow covering 5 packets of the following arrival sequence
+// {10 7 9 5 6} 8 3 2 4 1, the output will be 1/6 (#8 is considered as missing).
+float BweReceiver::RecentPacketLossRatio() {
+ if (received_packets_.empty()) {
+ return 0.0f;
+ }
+ int number_packets_received = 0;
+
+ PacketNodeIt node_it = received_packets_.begin(); // Latest.
+
+ // Lowest timestamp limit, oldest one that should be checked.
+ int64_t time_limit_ms = (*node_it)->arrival_time_ms - kPacketLossTimeWindowMs;
+ // Oldest and newest values found within the given time window.
+ uint16_t oldest_seq_num = (*node_it)->sequence_number;
+ uint16_t newest_seq_num = oldest_seq_num;
+
+ while (node_it != received_packets_.end()) {
+ if ((*node_it)->arrival_time_ms < time_limit_ms) {
+ break;
+ }
+ uint16_t seq_num = (*node_it)->sequence_number;
+ if (IsNewerSequenceNumber(seq_num, newest_seq_num)) {
+ newest_seq_num = seq_num;
+ }
+ if (IsNewerSequenceNumber(oldest_seq_num, seq_num)) {
+ oldest_seq_num = seq_num;
+ }
+ ++node_it;
+ ++number_packets_received;
+ }
+ // Interval width between oldest and newest sequence number.
+ // There was an overflow if newest_seq_num < oldest_seq_num.
+ int gap = static_cast<uint16_t>(newest_seq_num - oldest_seq_num + 1);
+
+ return static_cast<float>(gap - number_packets_received) / gap;
+}
+
+LinkedSet::~LinkedSet() {
+ while (!empty())
+ RemoveTail();
+}
+
+void LinkedSet::Insert(uint16_t sequence_number,
+ int64_t send_time_ms,
+ int64_t arrival_time_ms,
+ size_t payload_size) {
+ auto it = map_.find(sequence_number);
+ if (it != map_.end()) {
+ PacketNodeIt node_it = it->second;
+ PacketIdentifierNode* node = *node_it;
+ node->arrival_time_ms = arrival_time_ms;
+ if (node_it != list_.begin()) {
+ list_.erase(node_it);
+ list_.push_front(node);
+ map_[sequence_number] = list_.begin();
+ }
+ } else {
+ if (size() == capacity_) {
+ RemoveTail();
+ }
+ UpdateHead(new PacketIdentifierNode(sequence_number, send_time_ms,
+ arrival_time_ms, payload_size));
+ }
+}
+
+void LinkedSet::Insert(PacketIdentifierNode packet_identifier) {
+ Insert(packet_identifier.sequence_number, packet_identifier.send_time_ms,
+ packet_identifier.arrival_time_ms, packet_identifier.payload_size);
+}
+
+void LinkedSet::RemoveTail() {
+ map_.erase(list_.back()->sequence_number);
+ delete list_.back();
+ list_.pop_back();
+}
+void LinkedSet::UpdateHead(PacketIdentifierNode* new_head) {
+ list_.push_front(new_head);
+ map_[new_head->sequence_number] = list_.begin();
+}
+
+void LinkedSet::Erase(PacketNodeIt node_it) {
+ map_.erase((*node_it)->sequence_number);
+ delete (*node_it);
+ list_.erase(node_it);
+}
+
+void LossAccount::Add(LossAccount rhs) {
+ num_total += rhs.num_total;
+ num_lost += rhs.num_lost;
+}
+void LossAccount::Subtract(LossAccount rhs) {
+ num_total -= rhs.num_total;
+ num_lost -= rhs.num_lost;
+}
+
+float LossAccount::LossRatio() {
+ if (num_total == 0)
+ return 0.0f;
+ return static_cast<float>(num_lost) / num_total;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe.h b/webrtc/modules/remote_bitrate_estimator/test/bwe.h
new file mode 100644
index 0000000000..ef9b3149d7
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_H_
+
+#include <sstream>
+
+#include "webrtc/test/testsupport/gtest_prod_util.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet.h"
+#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+// Overload map comparator.
+class SequenceNumberOlderThan {
+ public:
+ bool operator()(uint16_t seq_num_1, uint16_t seq_num_2) const {
+ return IsNewerSequenceNumber(seq_num_2, seq_num_1);
+ }
+};
+
+// Holds information for computing global packet loss.
+struct LossAccount {
+ LossAccount() : num_total(0), num_lost(0) {}
+ LossAccount(size_t num_total, size_t num_lost)
+ : num_total(num_total), num_lost(num_lost) {}
+ void Add(LossAccount rhs);
+ void Subtract(LossAccount rhs);
+ float LossRatio();
+ size_t num_total;
+ size_t num_lost;
+};
+
+// Holds only essential information about packets to be saved for
+// further use, e.g. for calculating packet loss and receiving rate.
+struct PacketIdentifierNode {
+ PacketIdentifierNode(uint16_t sequence_number,
+ int64_t send_time_ms,
+ int64_t arrival_time_ms,
+ size_t payload_size)
+ : sequence_number(sequence_number),
+ send_time_ms(send_time_ms),
+ arrival_time_ms(arrival_time_ms),
+ payload_size(payload_size) {}
+
+ uint16_t sequence_number;
+ int64_t send_time_ms;
+ int64_t arrival_time_ms;
+ size_t payload_size;
+};
+
+typedef std::list<PacketIdentifierNode*>::iterator PacketNodeIt;
+
+// FIFO implementation for a limited capacity set.
+// Used for keeping the latest arrived packets while avoiding duplicates.
+// Allows efficient insertion, deletion and search.
+class LinkedSet {
+ public:
+ explicit LinkedSet(int capacity) : capacity_(capacity) {}
+ ~LinkedSet();
+
+ // If the arriving packet (identified by its sequence number) is already
+ // in the LinkedSet, move its Node to the head of the list. Else, create
+ // a PacketIdentifierNode n_ and then UpdateHead(n_), calling RemoveTail()
+ // if the LinkedSet reached its maximum capacity.
+ void Insert(uint16_t sequence_number,
+ int64_t send_time_ms,
+ int64_t arrival_time_ms,
+ size_t payload_size);
+
+ void Insert(PacketIdentifierNode packet_identifier);
+
+ PacketNodeIt begin() { return list_.begin(); }
+ PacketNodeIt end() { return list_.end(); }
+
+ bool empty() const { return list_.empty(); }
+ size_t size() const { return list_.size(); }
+ size_t capacity() const { return capacity_; }
+
+ uint16_t OldestSeqNumber() const { return empty() ? 0 : map_.begin()->first; }
+ uint16_t NewestSeqNumber() const {
+ return empty() ? 0 : map_.rbegin()->first;
+ }
+
+ void Erase(PacketNodeIt node_it);
+
+ private:
+ // Pop oldest element from the back of the list and remove it from the map.
+ void RemoveTail();
+ // Add new element to the front of the list and insert it in the map.
+ void UpdateHead(PacketIdentifierNode* new_head);
+ size_t capacity_;
+ std::map<uint16_t, PacketNodeIt, SequenceNumberOlderThan> map_;
+ std::list<PacketIdentifierNode*> list_;
+};
+
+const int kMinBitrateKbps = 50;
+const int kMaxBitrateKbps = 2500;
+
+class BweSender : public Module {
+ public:
+ BweSender() {}
+ explicit BweSender(int bitrate_kbps) : bitrate_kbps_(bitrate_kbps) {}
+ virtual ~BweSender() {}
+
+ virtual int GetFeedbackIntervalMs() const = 0;
+ virtual void GiveFeedback(const FeedbackPacket& feedback) = 0;
+ virtual void OnPacketsSent(const Packets& packets) = 0;
+
+ protected:
+ int bitrate_kbps_;
+
+ private:
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweSender);
+};
+
+class BweReceiver {
+ public:
+ explicit BweReceiver(int flow_id);
+ BweReceiver(int flow_id, int64_t window_size_ms);
+
+ virtual ~BweReceiver() {}
+
+ virtual void ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet);
+ virtual FeedbackPacket* GetFeedback(int64_t now_ms) { return NULL; }
+
+ size_t GetSetCapacity() { return received_packets_.capacity(); }
+ double BitrateWindowS() const { return rate_counter_.BitrateWindowS(); }
+ uint32_t RecentKbps() const; // Receiving Rate.
+
+ // Computes packet loss during an entire simulation, up to 4 billion packets.
+ float GlobalReceiverPacketLossRatio(); // Plot histogram.
+ float RecentPacketLossRatio(); // Plot dynamics.
+
+ static const int64_t kPacketLossTimeWindowMs = 500;
+ static const int64_t kReceivingRateTimeWindowMs = 1000;
+
+ protected:
+ int flow_id_;
+ // Deals with packets sent more than once.
+ LinkedSet received_packets_;
+ // Used for calculating recent receiving rate.
+ RateCounter rate_counter_;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(BweReceiverTest, RecentKbps);
+ FRIEND_TEST_ALL_PREFIXES(BweReceiverTest, Loss);
+
+ void UpdateLoss();
+ void RelieveSetAndUpdateLoss();
+ // Packet loss for packets stored in the LinkedSet, up to 1000 packets.
+ // Used to update global loss account whenever the set is filled and cleared.
+ LossAccount LinkedSetPacketLossRatio();
+
+ // Used for calculating global packet loss ratio.
+ LossAccount loss_account_;
+};
+
+enum BandwidthEstimatorType {
+ kNullEstimator,
+ kNadaEstimator,
+ kRembEstimator,
+ kFullSendSideEstimator,
+ kTcpEstimator
+};
+
+const std::string bwe_names[] = {"Null", "NADA", "REMB", "GCC", "TCP"};
+
+int64_t GetAbsSendTimeInMs(uint32_t abs_send_time);
+
+BweSender* CreateBweSender(BandwidthEstimatorType estimator,
+ int kbps,
+ BitrateObserver* observer,
+ Clock* clock);
+
+BweReceiver* CreateBweReceiver(BandwidthEstimatorType type,
+ int flow_id,
+ bool plot);
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc
new file mode 100644
index 0000000000..f837638474
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.cc
@@ -0,0 +1,983 @@
+/*
+ * 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/modules/remote_bitrate_estimator/test/bwe_test.h"
+
+#include <sstream>
+
+#include "webrtc/base/common.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
+#include "webrtc/system_wrappers/include/clock.h"
+#include "webrtc/test/testsupport/perf_test.h"
+
+using std::string;
+using std::vector;
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+PacketProcessorRunner::PacketProcessorRunner(PacketProcessor* processor)
+ : processor_(processor) {
+}
+
+PacketProcessorRunner::~PacketProcessorRunner() {
+ for (Packet* packet : queue_)
+ delete packet;
+}
+
+bool PacketProcessorRunner::RunsProcessor(
+ const PacketProcessor* processor) const {
+ return processor == processor_;
+}
+
+void PacketProcessorRunner::RunFor(int64_t time_ms,
+ int64_t time_now_ms,
+ Packets* in_out) {
+ Packets to_process;
+ FindPacketsToProcess(processor_->flow_ids(), in_out, &to_process);
+ processor_->RunFor(time_ms, &to_process);
+ QueuePackets(&to_process, time_now_ms * 1000);
+ if (!to_process.empty()) {
+ processor_->Plot(to_process.back()->send_time_ms());
+ }
+ in_out->merge(to_process, DereferencingComparator<Packet>);
+}
+
+void PacketProcessorRunner::FindPacketsToProcess(const FlowIds& flow_ids,
+ Packets* in,
+ Packets* out) {
+ assert(out->empty());
+ for (Packets::iterator it = in->begin(); it != in->end();) {
+ // TODO(holmer): Further optimize this by looking for consecutive flow ids
+ // in the packet list and only doing the binary search + splice once for a
+ // sequence.
+ if (flow_ids.find((*it)->flow_id()) != flow_ids.end()) {
+ Packets::iterator next = it;
+ ++next;
+ out->splice(out->end(), *in, it);
+ it = next;
+ } else {
+ ++it;
+ }
+ }
+}
+
+void PacketProcessorRunner::QueuePackets(Packets* batch,
+ int64_t end_of_batch_time_us) {
+ queue_.merge(*batch, DereferencingComparator<Packet>);
+ if (queue_.empty()) {
+ return;
+ }
+ Packets::iterator it = queue_.begin();
+ for (; it != queue_.end(); ++it) {
+ if ((*it)->send_time_us() > end_of_batch_time_us) {
+ break;
+ }
+ }
+ Packets to_transfer;
+ to_transfer.splice(to_transfer.begin(), queue_, queue_.begin(), it);
+ batch->merge(to_transfer, DereferencingComparator<Packet>);
+}
+
+// Plot link capacity by default.
+BweTest::BweTest() : BweTest(true) {
+}
+
+BweTest::BweTest(bool plot_capacity)
+ : run_time_ms_(0),
+ time_now_ms_(-1),
+ simulation_interval_ms_(-1),
+ plot_total_available_capacity_(plot_capacity) {
+ links_.push_back(&uplink_);
+ links_.push_back(&downlink_);
+}
+
+BweTest::~BweTest() {
+ for (Packet* packet : packets_)
+ delete packet;
+}
+
+void BweTest::SetUp() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ string test_name =
+ string(test_info->test_case_name()) + "_" + string(test_info->name());
+ BWE_TEST_LOGGING_GLOBAL_CONTEXT(test_name);
+ BWE_TEST_LOGGING_GLOBAL_ENABLE(false);
+}
+
+void Link::AddPacketProcessor(PacketProcessor* processor,
+ ProcessorType processor_type) {
+ assert(processor);
+ switch (processor_type) {
+ case kSender:
+ senders_.push_back(static_cast<PacketSender*>(processor));
+ break;
+ case kReceiver:
+ receivers_.push_back(static_cast<PacketReceiver*>(processor));
+ break;
+ case kRegular:
+ break;
+ }
+ processors_.push_back(PacketProcessorRunner(processor));
+}
+
+void Link::RemovePacketProcessor(PacketProcessor* processor) {
+ for (vector<PacketProcessorRunner>::iterator it = processors_.begin();
+ it != processors_.end(); ++it) {
+ if (it->RunsProcessor(processor)) {
+ processors_.erase(it);
+ return;
+ }
+ }
+ assert(false);
+}
+
+// Ownership of the created packets is handed over to the caller.
+void Link::Run(int64_t run_for_ms, int64_t now_ms, Packets* packets) {
+ for (auto& processor : processors_) {
+ processor.RunFor(run_for_ms, now_ms, packets);
+ }
+}
+
+void BweTest::VerboseLogging(bool enable) {
+ BWE_TEST_LOGGING_GLOBAL_ENABLE(enable);
+}
+
+void BweTest::RunFor(int64_t time_ms) {
+ // Set simulation interval from first packet sender.
+ // TODO(holmer): Support different feedback intervals for different flows.
+ if (!uplink_.senders().empty()) {
+ simulation_interval_ms_ = uplink_.senders()[0]->GetFeedbackIntervalMs();
+ } else if (!downlink_.senders().empty()) {
+ simulation_interval_ms_ = downlink_.senders()[0]->GetFeedbackIntervalMs();
+ }
+ assert(simulation_interval_ms_ > 0);
+ if (time_now_ms_ == -1) {
+ time_now_ms_ = simulation_interval_ms_;
+ }
+ for (run_time_ms_ += time_ms;
+ time_now_ms_ <= run_time_ms_ - simulation_interval_ms_;
+ time_now_ms_ += simulation_interval_ms_) {
+ // Packets are first generated on the first link, passed through all the
+ // PacketProcessors and PacketReceivers. The PacketReceivers produces
+ // FeedbackPackets which are then processed by the next link, where they
+ // at some point will be consumed by a PacketSender.
+ for (Link* link : links_)
+ link->Run(simulation_interval_ms_, time_now_ms_, &packets_);
+ }
+}
+
+string BweTest::GetTestName() const {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ return string(test_info->name());
+}
+
+void BweTest::PrintResults(double max_throughput_kbps,
+ Stats<double> throughput_kbps,
+ int flow_id,
+ Stats<double> flow_delay_ms,
+ Stats<double> flow_throughput_kbps) {
+ std::map<int, Stats<double>> flow_delays_ms;
+ flow_delays_ms[flow_id] = flow_delay_ms;
+ std::map<int, Stats<double>> flow_throughputs_kbps;
+ flow_throughputs_kbps[flow_id] = flow_throughput_kbps;
+ PrintResults(max_throughput_kbps, throughput_kbps, flow_delays_ms,
+ flow_throughputs_kbps);
+}
+
+void BweTest::PrintResults(double max_throughput_kbps,
+ Stats<double> throughput_kbps,
+ std::map<int, Stats<double>> flow_delay_ms,
+ std::map<int, Stats<double>> flow_throughput_kbps) {
+ double utilization = throughput_kbps.GetMean() / max_throughput_kbps;
+ webrtc::test::PrintResult("BwePerformance", GetTestName(), "Utilization",
+ utilization * 100.0, "%", false);
+ std::stringstream ss;
+ ss << throughput_kbps.GetStdDev() / throughput_kbps.GetMean();
+ webrtc::test::PrintResult("BwePerformance", GetTestName(),
+ "Utilization var coeff", ss.str(), "", false);
+ for (auto& kv : flow_throughput_kbps) {
+ ss.str("");
+ ss << "Throughput flow " << kv.first;
+ webrtc::test::PrintResultMeanAndError("BwePerformance", GetTestName(),
+ ss.str(), kv.second.AsString(),
+ "kbps", false);
+ }
+ for (auto& kv : flow_delay_ms) {
+ ss.str("");
+ ss << "Delay flow " << kv.first;
+ webrtc::test::PrintResultMeanAndError("BwePerformance", GetTestName(),
+ ss.str(), kv.second.AsString(), "ms",
+ false);
+ }
+ double fairness_index = 1.0;
+ if (!flow_throughput_kbps.empty()) {
+ double squared_bitrate_sum = 0.0;
+ fairness_index = 0.0;
+ for (auto kv : flow_throughput_kbps) {
+ squared_bitrate_sum += kv.second.GetMean() * kv.second.GetMean();
+ fairness_index += kv.second.GetMean();
+ }
+ fairness_index *= fairness_index;
+ fairness_index /= flow_throughput_kbps.size() * squared_bitrate_sum;
+ }
+ webrtc::test::PrintResult("BwePerformance", GetTestName(), "Fairness",
+ fairness_index * 100, "%", false);
+}
+
+void BweTest::RunFairnessTest(BandwidthEstimatorType bwe_type,
+ size_t num_media_flows,
+ size_t num_tcp_flows,
+ int64_t run_time_seconds,
+ uint32_t capacity_kbps,
+ int64_t max_delay_ms,
+ int64_t rtt_ms,
+ int64_t max_jitter_ms,
+ const int64_t* offsets_ms) {
+ RunFairnessTest(bwe_type, num_media_flows, num_tcp_flows, run_time_seconds,
+ capacity_kbps, max_delay_ms, rtt_ms, max_jitter_ms,
+ offsets_ms, "Fairness_test", bwe_names[bwe_type]);
+}
+
+void BweTest::RunFairnessTest(BandwidthEstimatorType bwe_type,
+ size_t num_media_flows,
+ size_t num_tcp_flows,
+ int64_t run_time_seconds,
+ uint32_t capacity_kbps,
+ int64_t max_delay_ms,
+ int64_t rtt_ms,
+ int64_t max_jitter_ms,
+ const int64_t* offsets_ms,
+ const std::string& title,
+ const std::string& flow_name) {
+ std::set<int> all_flow_ids;
+ std::set<int> media_flow_ids;
+ std::set<int> tcp_flow_ids;
+ int next_flow_id = 0;
+ for (size_t i = 0; i < num_media_flows; ++i) {
+ media_flow_ids.insert(next_flow_id);
+ all_flow_ids.insert(next_flow_id);
+ ++next_flow_id;
+ }
+ for (size_t i = 0; i < num_tcp_flows; ++i) {
+ tcp_flow_ids.insert(next_flow_id);
+ all_flow_ids.insert(next_flow_id);
+ ++next_flow_id;
+ }
+
+ std::vector<VideoSource*> sources;
+ std::vector<PacketSender*> senders;
+ std::vector<MetricRecorder*> metric_recorders;
+
+ int64_t max_offset_ms = 0;
+
+ for (int media_flow : media_flow_ids) {
+ sources.push_back(new AdaptiveVideoSource(media_flow, 30, 300, 0,
+ offsets_ms[media_flow]));
+ senders.push_back(new PacedVideoSender(&uplink_, sources.back(), bwe_type));
+ max_offset_ms = std::max(max_offset_ms, offsets_ms[media_flow]);
+ }
+
+ for (int tcp_flow : tcp_flow_ids) {
+ senders.push_back(new TcpSender(&uplink_, tcp_flow, offsets_ms[tcp_flow]));
+ max_offset_ms = std::max(max_offset_ms, offsets_ms[tcp_flow]);
+ }
+
+ ChokeFilter choke(&uplink_, all_flow_ids);
+ choke.set_capacity_kbps(capacity_kbps);
+ choke.set_max_delay_ms(max_delay_ms);
+ LinkShare link_share(&choke);
+
+ int64_t one_way_delay_ms = rtt_ms / 2;
+ DelayFilter delay_uplink(&uplink_, all_flow_ids);
+ delay_uplink.SetOneWayDelayMs(one_way_delay_ms);
+
+ JitterFilter jitter(&uplink_, all_flow_ids);
+ jitter.SetMaxJitter(max_jitter_ms);
+
+ std::vector<RateCounterFilter*> rate_counters;
+ for (int flow : media_flow_ids) {
+ rate_counters.push_back(
+ new RateCounterFilter(&uplink_, flow, "Receiver", bwe_names[bwe_type]));
+ }
+ for (int flow : tcp_flow_ids) {
+ rate_counters.push_back(new RateCounterFilter(&uplink_, flow, "Receiver",
+ bwe_names[kTcpEstimator]));
+ }
+
+ RateCounterFilter total_utilization(
+ &uplink_, all_flow_ids, "total_utilization", "Total_link_utilization");
+
+ std::vector<PacketReceiver*> receivers;
+ // Delays is being plotted only for the first flow.
+ // To plot all of them, replace "i == 0" with "true" on new PacketReceiver().
+ for (int media_flow : media_flow_ids) {
+ metric_recorders.push_back(
+ new MetricRecorder(bwe_names[bwe_type], static_cast<int>(media_flow),
+ senders[media_flow], &link_share));
+ receivers.push_back(new PacketReceiver(&uplink_, media_flow, bwe_type,
+ media_flow == 0, false,
+ metric_recorders[media_flow]));
+ metric_recorders[media_flow]->set_plot_available_capacity(
+ media_flow == 0 && plot_total_available_capacity_);
+ metric_recorders[media_flow]->set_start_computing_metrics_ms(max_offset_ms);
+ }
+ // Delays is not being plotted only for TCP flows. To plot all of them,
+ // replace first "false" occurence with "true" on new PacketReceiver().
+ for (int tcp_flow : tcp_flow_ids) {
+ metric_recorders.push_back(
+ new MetricRecorder(bwe_names[kTcpEstimator], static_cast<int>(tcp_flow),
+ senders[tcp_flow], &link_share));
+ receivers.push_back(new PacketReceiver(&uplink_, tcp_flow, kTcpEstimator,
+ false, false,
+ metric_recorders[tcp_flow]));
+ metric_recorders[tcp_flow]->set_plot_available_capacity(
+ tcp_flow == 0 && plot_total_available_capacity_);
+ }
+
+ DelayFilter delay_downlink(&downlink_, all_flow_ids);
+ delay_downlink.SetOneWayDelayMs(one_way_delay_ms);
+
+ RunFor(run_time_seconds * 1000);
+
+ std::map<int, Stats<double>> flow_throughput_kbps;
+ for (RateCounterFilter* rate_counter : rate_counters) {
+ int flow_id = *rate_counter->flow_ids().begin();
+ flow_throughput_kbps[flow_id] = rate_counter->GetBitrateStats();
+ }
+
+ std::map<int, Stats<double>> flow_delay_ms;
+ for (PacketReceiver* receiver : receivers) {
+ int flow_id = *receiver->flow_ids().begin();
+ flow_delay_ms[flow_id] = receiver->GetDelayStats();
+ }
+
+ PrintResults(capacity_kbps, total_utilization.GetBitrateStats(),
+ flow_delay_ms, flow_throughput_kbps);
+
+ for (int i : all_flow_ids) {
+ metric_recorders[i]->PlotThroughputHistogram(
+ title, flow_name, static_cast<int>(num_media_flows), 0);
+
+ metric_recorders[i]->PlotLossHistogram(title, flow_name,
+ static_cast<int>(num_media_flows),
+ receivers[i]->GlobalPacketLoss());
+ }
+
+ // Pointless to show delay histogram for TCP flow.
+ for (int i : media_flow_ids) {
+ metric_recorders[i]->PlotDelayHistogram(title, bwe_names[bwe_type],
+ static_cast<int>(num_media_flows),
+ one_way_delay_ms);
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], one_way_delay_ms, i);
+ }
+
+ for (VideoSource* source : sources)
+ delete source;
+ for (PacketSender* sender : senders)
+ delete sender;
+ for (RateCounterFilter* rate_counter : rate_counters)
+ delete rate_counter;
+ for (PacketReceiver* receiver : receivers)
+ delete receiver;
+ for (MetricRecorder* recorder : metric_recorders)
+ delete recorder;
+}
+
+void BweTest::RunChoke(BandwidthEstimatorType bwe_type,
+ std::vector<int> capacities_kbps) {
+ int flow_id = bwe_type;
+ AdaptiveVideoSource source(flow_id, 30, 300, 0, 0);
+ VideoSender sender(&uplink_, &source, bwe_type);
+ ChokeFilter choke(&uplink_, flow_id);
+ LinkShare link_share(&choke);
+ MetricRecorder metric_recorder(bwe_names[bwe_type], flow_id, &sender,
+ &link_share);
+ PacketReceiver receiver(&uplink_, flow_id, bwe_type, true, false,
+ &metric_recorder);
+ metric_recorder.set_plot_available_capacity(plot_total_available_capacity_);
+
+ choke.set_max_delay_ms(500);
+ const int64_t kRunTimeMs = 60 * 1000;
+
+ std::stringstream title("Choke");
+ char delimiter = '_';
+
+ for (auto it = capacities_kbps.begin(); it != capacities_kbps.end(); ++it) {
+ choke.set_capacity_kbps(*it);
+ RunFor(kRunTimeMs);
+ title << delimiter << (*it);
+ delimiter = '-';
+ }
+
+ title << "_kbps,_" << (kRunTimeMs / 1000) << "s_each";
+ metric_recorder.PlotThroughputHistogram(title.str(), bwe_names[bwe_type], 1,
+ 0);
+ metric_recorder.PlotDelayHistogram(title.str(), bwe_names[bwe_type], 1, 0);
+ // receiver.PlotLossHistogram(title, bwe_names[bwe_type], 1);
+ // receiver.PlotObjectiveHistogram(title, bwe_names[bwe_type], 1);
+}
+
+// 5.1. Single Video and Audio media traffic, forward direction.
+void BweTest::RunVariableCapacity1SingleFlow(BandwidthEstimatorType bwe_type) {
+ const int kFlowId = 0; // Arbitrary value.
+ AdaptiveVideoSource source(kFlowId, 30, 300, 0, 0);
+ PacedVideoSender sender(&uplink_, &source, bwe_type);
+
+ DefaultEvaluationFilter up_filter(&uplink_, kFlowId);
+ LinkShare link_share(&(up_filter.choke));
+ MetricRecorder metric_recorder(bwe_names[bwe_type], kFlowId, &sender,
+ &link_share);
+
+ PacketReceiver receiver(&uplink_, kFlowId, bwe_type, true, true,
+ &metric_recorder);
+
+ metric_recorder.set_plot_available_capacity(plot_total_available_capacity_);
+
+ DelayFilter down_filter(&downlink_, kFlowId);
+ down_filter.SetOneWayDelayMs(kOneWayDelayMs);
+
+ // Test also with one way propagation delay = 100ms.
+ // up_filter.delay.SetOneWayDelayMs(100);
+ // down_filter.SetOneWayDelayMs(100);
+
+ up_filter.choke.set_capacity_kbps(1000);
+ RunFor(40 * 1000); // 0-40s.
+ up_filter.choke.set_capacity_kbps(2500);
+ RunFor(20 * 1000); // 40-60s.
+ up_filter.choke.set_capacity_kbps(600);
+ RunFor(20 * 1000); // 60-80s.
+ up_filter.choke.set_capacity_kbps(1000);
+ RunFor(20 * 1000); // 80-100s.
+
+ std::string title("5.1_Variable_capacity_single_flow");
+ metric_recorder.PlotThroughputHistogram(title, bwe_names[bwe_type], 1, 0);
+ metric_recorder.PlotDelayHistogram(title, bwe_names[bwe_type], 1,
+ kOneWayDelayMs);
+ metric_recorder.PlotLossHistogram(title, bwe_names[bwe_type], 1,
+ receiver.GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kOneWayDelayMs, kFlowId);
+}
+
+// 5.2. Two forward direction competing flows, variable capacity.
+void BweTest::RunVariableCapacity2MultipleFlows(BandwidthEstimatorType bwe_type,
+ size_t num_flows) {
+ std::vector<VideoSource*> sources;
+ std::vector<PacketSender*> senders;
+ std::vector<MetricRecorder*> metric_recorders;
+ std::vector<PacketReceiver*> receivers;
+
+ const int64_t kStartingApartMs = 0; // Flows initialized simultaneously.
+
+ for (size_t i = 0; i < num_flows; ++i) {
+ sources.push_back(new AdaptiveVideoSource(static_cast<int>(i), 30, 300, 0,
+ i * kStartingApartMs));
+ senders.push_back(new VideoSender(&uplink_, sources[i], bwe_type));
+ }
+
+ FlowIds flow_ids = CreateFlowIdRange(0, static_cast<int>(num_flows - 1));
+
+ DefaultEvaluationFilter up_filter(&uplink_, flow_ids);
+ LinkShare link_share(&(up_filter.choke));
+
+ RateCounterFilter total_utilization(&uplink_, flow_ids, "Total_utilization",
+ "Total_link_utilization");
+
+ // Delays is being plotted only for the first flow.
+ // To plot all of them, replace "i == 0" with "true" on new PacketReceiver().
+ for (size_t i = 0; i < num_flows; ++i) {
+ metric_recorders.push_back(new MetricRecorder(
+ bwe_names[bwe_type], static_cast<int>(i), senders[i], &link_share));
+
+ receivers.push_back(new PacketReceiver(&uplink_, static_cast<int>(i),
+ bwe_type, i == 0, false,
+ metric_recorders[i]));
+ metric_recorders[i]->set_plot_available_capacity(
+ i == 0 && plot_total_available_capacity_);
+ }
+
+ DelayFilter down_filter(&downlink_, flow_ids);
+ down_filter.SetOneWayDelayMs(kOneWayDelayMs);
+ // Test also with one way propagation delay = 100ms.
+ // up_filter.delay.SetOneWayDelayMs(100);
+ // down_filter.SetOneWayDelayMs(100);
+
+ up_filter.choke.set_capacity_kbps(4000);
+ RunFor(25 * 1000); // 0-25s.
+ up_filter.choke.set_capacity_kbps(2000);
+ RunFor(25 * 1000); // 25-50s.
+ up_filter.choke.set_capacity_kbps(3500);
+ RunFor(25 * 1000); // 50-75s.
+ up_filter.choke.set_capacity_kbps(1000);
+ RunFor(25 * 1000); // 75-100s.
+ up_filter.choke.set_capacity_kbps(2000);
+ RunFor(25 * 1000); // 100-125s.
+
+ std::string title("5.2_Variable_capacity_two_flows");
+ for (size_t i = 0; i < num_flows; ++i) {
+ metric_recorders[i]->PlotThroughputHistogram(title, bwe_names[bwe_type],
+ num_flows, 0);
+ metric_recorders[i]->PlotDelayHistogram(title, bwe_names[bwe_type],
+ num_flows, kOneWayDelayMs);
+ metric_recorders[i]->PlotLossHistogram(title, bwe_names[bwe_type],
+ num_flows,
+ receivers[i]->GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kOneWayDelayMs, i);
+ }
+
+ for (VideoSource* source : sources)
+ delete source;
+ for (PacketSender* sender : senders)
+ delete sender;
+ for (MetricRecorder* recorder : metric_recorders)
+ delete recorder;
+ for (PacketReceiver* receiver : receivers)
+ delete receiver;
+}
+
+// 5.3. Bi-directional RMCAT flows.
+void BweTest::RunBidirectionalFlow(BandwidthEstimatorType bwe_type) {
+ enum direction { kForward = 0, kBackward };
+ const size_t kNumFlows = 2;
+ rtc::scoped_ptr<AdaptiveVideoSource> sources[kNumFlows];
+ rtc::scoped_ptr<VideoSender> senders[kNumFlows];
+ rtc::scoped_ptr<MetricRecorder> metric_recorders[kNumFlows];
+ rtc::scoped_ptr<PacketReceiver> receivers[kNumFlows];
+
+ sources[kForward].reset(new AdaptiveVideoSource(kForward, 30, 300, 0, 0));
+ senders[kForward].reset(
+ new VideoSender(&uplink_, sources[kForward].get(), bwe_type));
+
+ sources[kBackward].reset(new AdaptiveVideoSource(kBackward, 30, 300, 0, 0));
+ senders[kBackward].reset(
+ new VideoSender(&downlink_, sources[kBackward].get(), bwe_type));
+
+ DefaultEvaluationFilter up_filter(&uplink_, kForward);
+ LinkShare up_link_share(&(up_filter.choke));
+
+ metric_recorders[kForward].reset(new MetricRecorder(
+ bwe_names[bwe_type], kForward, senders[kForward].get(), &up_link_share));
+ receivers[kForward].reset(
+ new PacketReceiver(&uplink_, kForward, bwe_type, true, false,
+ metric_recorders[kForward].get()));
+
+ metric_recorders[kForward].get()->set_plot_available_capacity(
+ plot_total_available_capacity_);
+
+ DefaultEvaluationFilter down_filter(&downlink_, kBackward);
+ LinkShare down_link_share(&(down_filter.choke));
+
+ metric_recorders[kBackward].reset(
+ new MetricRecorder(bwe_names[bwe_type], kBackward,
+ senders[kBackward].get(), &down_link_share));
+ receivers[kBackward].reset(
+ new PacketReceiver(&downlink_, kBackward, bwe_type, true, false,
+ metric_recorders[kBackward].get()));
+
+ metric_recorders[kBackward].get()->set_plot_available_capacity(
+ plot_total_available_capacity_);
+
+ // Test also with one way propagation delay = 100ms.
+ // up_filter.delay.SetOneWayDelayMs(100);
+ // down_filter.delay.SetOneWayDelayMs(100);
+
+ up_filter.choke.set_capacity_kbps(2000);
+ down_filter.choke.set_capacity_kbps(2000);
+ RunFor(20 * 1000); // 0-20s.
+
+ up_filter.choke.set_capacity_kbps(1000);
+ RunFor(15 * 1000); // 20-35s.
+
+ down_filter.choke.set_capacity_kbps(800);
+ RunFor(5 * 1000); // 35-40s.
+
+ up_filter.choke.set_capacity_kbps(500);
+ RunFor(20 * 1000); // 40-60s.
+
+ up_filter.choke.set_capacity_kbps(2000);
+ RunFor(10 * 1000); // 60-70s.
+
+ down_filter.choke.set_capacity_kbps(2000);
+ RunFor(30 * 1000); // 70-100s.
+
+ std::string title("5.3_Bidirectional_flows");
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ metric_recorders[i].get()->PlotThroughputHistogram(
+ title, bwe_names[bwe_type], kNumFlows, 0);
+ metric_recorders[i].get()->PlotDelayHistogram(title, bwe_names[bwe_type],
+ kNumFlows, kOneWayDelayMs);
+ metric_recorders[i].get()->PlotLossHistogram(
+ title, bwe_names[bwe_type], kNumFlows,
+ receivers[i].get()->GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kOneWayDelayMs, i);
+ }
+}
+
+// 5.4. Three forward direction competing flows, constant capacity.
+void BweTest::RunSelfFairness(BandwidthEstimatorType bwe_type) {
+ const int kNumRmcatFlows = 3;
+ const int kNumTcpFlows = 0;
+ const int64_t kRunTimeS = 120;
+ const int kLinkCapacity = 3500;
+
+ int64_t max_delay_ms = kMaxQueueingDelayMs;
+ int64_t rtt_ms = 2 * kOneWayDelayMs;
+
+ const int64_t kStartingApartMs = 20 * 1000;
+ int64_t offsets_ms[kNumRmcatFlows];
+ for (int i = 0; i < kNumRmcatFlows; ++i) {
+ offsets_ms[i] = kStartingApartMs * i;
+ }
+
+ // Test also with one way propagation delay = 100ms.
+ // rtt_ms = 2 * 100;
+ // Test also with bottleneck queue size = 20ms and 1000ms.
+ // max_delay_ms = 20;
+ // max_delay_ms = 1000;
+
+ std::string title("5.4_Self_fairness_test");
+
+ // Test also with one way propagation delay = 100ms.
+ RunFairnessTest(bwe_type, kNumRmcatFlows, kNumTcpFlows, kRunTimeS,
+ kLinkCapacity, max_delay_ms, rtt_ms, kMaxJitterMs, offsets_ms,
+ title, bwe_names[bwe_type]);
+}
+
+// 5.5. Five competing RMCAT flows under different RTTs.
+void BweTest::RunRoundTripTimeFairness(BandwidthEstimatorType bwe_type) {
+ const int kAllFlowIds[] = {0, 1, 2, 3, 4}; // Five RMCAT flows.
+ const int64_t kAllOneWayDelayMs[] = {10, 25, 50, 100, 150};
+ const size_t kNumFlows = ARRAY_SIZE(kAllFlowIds);
+ rtc::scoped_ptr<AdaptiveVideoSource> sources[kNumFlows];
+ rtc::scoped_ptr<VideoSender> senders[kNumFlows];
+ rtc::scoped_ptr<MetricRecorder> metric_recorders[kNumFlows];
+
+ // Flows initialized 10 seconds apart.
+ const int64_t kStartingApartMs = 10 * 1000;
+
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ sources[i].reset(new AdaptiveVideoSource(kAllFlowIds[i], 30, 300, 0,
+ i * kStartingApartMs));
+ senders[i].reset(new VideoSender(&uplink_, sources[i].get(), bwe_type));
+ }
+
+ ChokeFilter choke_filter(&uplink_, CreateFlowIds(kAllFlowIds, kNumFlows));
+ LinkShare link_share(&choke_filter);
+
+ JitterFilter jitter_filter(&uplink_, CreateFlowIds(kAllFlowIds, kNumFlows));
+
+ rtc::scoped_ptr<DelayFilter> up_delay_filters[kNumFlows];
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ up_delay_filters[i].reset(new DelayFilter(&uplink_, kAllFlowIds[i]));
+ }
+
+ RateCounterFilter total_utilization(
+ &uplink_, CreateFlowIds(kAllFlowIds, kNumFlows), "Total_utilization",
+ "Total_link_utilization");
+
+ // Delays is being plotted only for the first flow.
+ // To plot all of them, replace "i == 0" with "true" on new PacketReceiver().
+ rtc::scoped_ptr<PacketReceiver> receivers[kNumFlows];
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ metric_recorders[i].reset(
+ new MetricRecorder(bwe_names[bwe_type], static_cast<int>(i),
+ senders[i].get(), &link_share));
+
+ receivers[i].reset(new PacketReceiver(&uplink_, kAllFlowIds[i], bwe_type,
+ i == 0, false,
+ metric_recorders[i].get()));
+ metric_recorders[i].get()->set_start_computing_metrics_ms(kStartingApartMs *
+ (kNumFlows - 1));
+ metric_recorders[i].get()->set_plot_available_capacity(
+ i == 0 && plot_total_available_capacity_);
+ }
+
+ rtc::scoped_ptr<DelayFilter> down_delay_filters[kNumFlows];
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ down_delay_filters[i].reset(new DelayFilter(&downlink_, kAllFlowIds[i]));
+ }
+
+ jitter_filter.SetMaxJitter(kMaxJitterMs);
+ choke_filter.set_max_delay_ms(kMaxQueueingDelayMs);
+
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ up_delay_filters[i]->SetOneWayDelayMs(kAllOneWayDelayMs[i]);
+ down_delay_filters[i]->SetOneWayDelayMs(kAllOneWayDelayMs[i]);
+ }
+
+ choke_filter.set_capacity_kbps(3500);
+
+ RunFor(300 * 1000); // 0-300s.
+
+ std::string title("5.5_Round_Trip_Time_Fairness");
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ metric_recorders[i].get()->PlotThroughputHistogram(
+ title, bwe_names[bwe_type], kNumFlows, 0);
+ metric_recorders[i].get()->PlotDelayHistogram(title, bwe_names[bwe_type],
+ kNumFlows, kOneWayDelayMs);
+ metric_recorders[i].get()->PlotLossHistogram(
+ title, bwe_names[bwe_type], kNumFlows,
+ receivers[i].get()->GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kAllOneWayDelayMs[i],
+ i);
+ }
+}
+
+// 5.6. RMCAT Flow competing with a long TCP Flow.
+void BweTest::RunLongTcpFairness(BandwidthEstimatorType bwe_type) {
+ const size_t kNumRmcatFlows = 1;
+ const size_t kNumTcpFlows = 1;
+ const int64_t kRunTimeS = 120;
+ const int kCapacityKbps = 2000;
+ // Tcp starts at t = 0, media flow at t = 5s.
+ const int64_t kOffSetsMs[] = {5000, 0};
+
+ int64_t max_delay_ms = kMaxQueueingDelayMs;
+ int64_t rtt_ms = 2 * kOneWayDelayMs;
+
+ // Test also with one way propagation delay = 100ms.
+ // rtt_ms = 2 * 100;
+ // Test also with bottleneck queue size = 20ms and 1000ms.
+ // max_delay_ms = 20;
+ // max_delay_ms = 1000;
+
+ std::string title("5.6_Long_TCP_Fairness");
+ std::string flow_name(bwe_names[bwe_type] + 'x' + bwe_names[kTcpEstimator]);
+
+ RunFairnessTest(bwe_type, kNumRmcatFlows, kNumTcpFlows, kRunTimeS,
+ kCapacityKbps, max_delay_ms, rtt_ms, kMaxJitterMs, kOffSetsMs,
+ title, flow_name);
+}
+
+// 5.7. RMCAT Flows competing with multiple short TCP Flows.
+void BweTest::RunMultipleShortTcpFairness(
+ BandwidthEstimatorType bwe_type,
+ std::vector<int> tcp_file_sizes_bytes,
+ std::vector<int64_t> tcp_starting_times_ms) {
+ // Two RMCAT flows and ten TCP flows.
+ const int kAllRmcatFlowIds[] = {0, 1};
+ const int kAllTcpFlowIds[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+
+ assert(tcp_starting_times_ms.size() == tcp_file_sizes_bytes.size() &&
+ tcp_starting_times_ms.size() == ARRAY_SIZE(kAllTcpFlowIds));
+
+ const size_t kNumRmcatFlows = ARRAY_SIZE(kAllRmcatFlowIds);
+ const size_t kNumTotalFlows = kNumRmcatFlows + ARRAY_SIZE(kAllTcpFlowIds);
+
+ rtc::scoped_ptr<AdaptiveVideoSource> sources[kNumRmcatFlows];
+ rtc::scoped_ptr<PacketSender> senders[kNumTotalFlows];
+ rtc::scoped_ptr<MetricRecorder> metric_recorders[kNumTotalFlows];
+ rtc::scoped_ptr<PacketReceiver> receivers[kNumTotalFlows];
+
+ // RMCAT Flows are initialized simultaneosly at t=5 seconds.
+ const int64_t kRmcatStartingTimeMs = 5 * 1000;
+ for (size_t id : kAllRmcatFlowIds) {
+ sources[id].reset(new AdaptiveVideoSource(static_cast<int>(id), 30, 300, 0,
+ kRmcatStartingTimeMs));
+ senders[id].reset(new VideoSender(&uplink_, sources[id].get(), bwe_type));
+ }
+
+ for (size_t id : kAllTcpFlowIds) {
+ senders[id].reset(new TcpSender(&uplink_, static_cast<int>(id),
+ tcp_starting_times_ms[id - kNumRmcatFlows],
+ tcp_file_sizes_bytes[id - kNumRmcatFlows]));
+ }
+
+ FlowIds flow_ids = CreateFlowIdRange(0, static_cast<int>(kNumTotalFlows - 1));
+ DefaultEvaluationFilter up_filter(&uplink_, flow_ids);
+
+ LinkShare link_share(&(up_filter.choke));
+
+ RateCounterFilter total_utilization(&uplink_, flow_ids, "Total_utilization",
+ "Total_link_utilization");
+
+ // Delays is being plotted only for the first flow.
+ // To plot all of them, replace "i == 0" with "true" on new PacketReceiver().
+ for (size_t id : kAllRmcatFlowIds) {
+ metric_recorders[id].reset(
+ new MetricRecorder(bwe_names[bwe_type], static_cast<int>(id),
+ senders[id].get(), &link_share));
+ receivers[id].reset(new PacketReceiver(&uplink_, static_cast<int>(id),
+ bwe_type, id == 0, false,
+ metric_recorders[id].get()));
+ metric_recorders[id].get()->set_start_computing_metrics_ms(
+ kRmcatStartingTimeMs);
+ metric_recorders[id].get()->set_plot_available_capacity(
+ id == 0 && plot_total_available_capacity_);
+ }
+
+ // Delays is not being plotted only for TCP flows. To plot all of them,
+ // replace first "false" occurence with "true" on new PacketReceiver().
+ for (size_t id : kAllTcpFlowIds) {
+ metric_recorders[id].reset(
+ new MetricRecorder(bwe_names[kTcpEstimator], static_cast<int>(id),
+ senders[id].get(), &link_share));
+ receivers[id].reset(new PacketReceiver(&uplink_, static_cast<int>(id),
+ kTcpEstimator, false, false,
+ metric_recorders[id].get()));
+ metric_recorders[id].get()->set_plot_available_capacity(
+ id == 0 && plot_total_available_capacity_);
+ }
+
+ DelayFilter down_filter(&downlink_, flow_ids);
+ down_filter.SetOneWayDelayMs(kOneWayDelayMs);
+
+ // Test also with one way propagation delay = 100ms.
+ // up_filter.delay.SetOneWayDelayMs(100);
+ // down_filter.SetOneWayDelayms(100);
+
+ // Test also with bottleneck queue size = 20ms and 1000ms.
+ // up_filter.choke.set_max_delay_ms(20);
+ // up_filter.choke.set_max_delay_ms(1000);
+
+ // Test also with no Jitter:
+ // up_filter.jitter.SetMaxJitter(0);
+
+ up_filter.choke.set_capacity_kbps(2000);
+
+ RunFor(300 * 1000); // 0-300s.
+
+ std::string title("5.7_Multiple_short_TCP_flows");
+ for (size_t id : kAllRmcatFlowIds) {
+ metric_recorders[id].get()->PlotThroughputHistogram(
+ title, bwe_names[bwe_type], kNumRmcatFlows, 0);
+ metric_recorders[id].get()->PlotDelayHistogram(
+ title, bwe_names[bwe_type], kNumRmcatFlows, kOneWayDelayMs);
+ metric_recorders[id].get()->PlotLossHistogram(
+ title, bwe_names[bwe_type], kNumRmcatFlows,
+ receivers[id].get()->GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kOneWayDelayMs, id);
+ }
+}
+
+// 5.8. Three forward direction competing flows, constant capacity.
+// During the test, one of the flows is paused and later resumed.
+void BweTest::RunPauseResumeFlows(BandwidthEstimatorType bwe_type) {
+ const int kAllFlowIds[] = {0, 1, 2}; // Three RMCAT flows.
+ const size_t kNumFlows = ARRAY_SIZE(kAllFlowIds);
+
+ rtc::scoped_ptr<AdaptiveVideoSource> sources[kNumFlows];
+ rtc::scoped_ptr<VideoSender> senders[kNumFlows];
+ rtc::scoped_ptr<MetricRecorder> metric_recorders[kNumFlows];
+ rtc::scoped_ptr<PacketReceiver> receivers[kNumFlows];
+
+ // Flows initialized simultaneously.
+ const int64_t kStartingApartMs = 0;
+
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ sources[i].reset(new AdaptiveVideoSource(kAllFlowIds[i], 30, 300, 0,
+ i * kStartingApartMs));
+ senders[i].reset(new VideoSender(&uplink_, sources[i].get(), bwe_type));
+ }
+
+ DefaultEvaluationFilter filter(&uplink_,
+ CreateFlowIds(kAllFlowIds, kNumFlows));
+
+ LinkShare link_share(&(filter.choke));
+
+ RateCounterFilter total_utilization(
+ &uplink_, CreateFlowIds(kAllFlowIds, kNumFlows), "Total_utilization",
+ "Total_link_utilization");
+
+ // Delays is being plotted only for the first flow.
+ // To plot all of them, replace "i == 0" with "true" on new PacketReceiver().
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ metric_recorders[i].reset(
+ new MetricRecorder(bwe_names[bwe_type], static_cast<int>(i),
+ senders[i].get(), &link_share));
+ receivers[i].reset(new PacketReceiver(&uplink_, kAllFlowIds[i], bwe_type,
+ i == 0, false,
+ metric_recorders[i].get()));
+ metric_recorders[i].get()->set_start_computing_metrics_ms(kStartingApartMs *
+ (kNumFlows - 1));
+ metric_recorders[i].get()->set_plot_available_capacity(
+ i == 0 && plot_total_available_capacity_);
+ }
+
+ // Test also with one way propagation delay = 100ms.
+ // filter.delay.SetOneWayDelayMs(100);
+ filter.choke.set_capacity_kbps(3500);
+
+ RunFor(40 * 1000); // 0-40s.
+ senders[0].get()->Pause();
+ RunFor(20 * 1000); // 40-60s.
+ senders[0].get()->Resume(20 * 1000);
+ RunFor(60 * 1000); // 60-120s.
+
+ int64_t paused[] = {20 * 1000, 0, 0};
+
+ // First flow is being paused, hence having a different optimum.
+ const std::string optima_lines[] = {"1", "2", "2"};
+
+ std::string title("5.8_Pause_and_resume_media_flow");
+ for (size_t i = 0; i < kNumFlows; ++i) {
+ metric_recorders[i].get()->PlotThroughputHistogram(
+ title, bwe_names[bwe_type], kNumFlows, paused[i], optima_lines[i]);
+ metric_recorders[i].get()->PlotDelayHistogram(title, bwe_names[bwe_type],
+ kNumFlows, kOneWayDelayMs);
+ metric_recorders[i].get()->PlotLossHistogram(
+ title, bwe_names[bwe_type], kNumFlows,
+ receivers[i].get()->GlobalPacketLoss());
+ BWE_TEST_LOGGING_BASELINEBAR(5, bwe_names[bwe_type], kOneWayDelayMs, i);
+ }
+}
+
+// Following functions are used for randomizing TCP file size and
+// starting time, used on 5.7 RunMultipleShortTcpFairness.
+// They are pseudo-random generators, creating always the same
+// value sequence for a given Random seed.
+
+std::vector<int> BweTest::GetFileSizesBytes(int num_files) {
+ // File size chosen from uniform distribution between [100,1000] kB.
+ const int kMinKbytes = 100;
+ const int kMaxKbytes = 1000;
+
+ test::Random random(0x12345678);
+ std::vector<int> tcp_file_sizes_bytes;
+
+ while (num_files-- > 0) {
+ tcp_file_sizes_bytes.push_back(random.Rand(kMinKbytes, kMaxKbytes) * 1000);
+ }
+
+ return tcp_file_sizes_bytes;
+}
+
+std::vector<int64_t> BweTest::GetStartingTimesMs(int num_files) {
+ // OFF state behaves as an exp. distribution with mean = 10 seconds.
+ const float kMeanMs = 10000.0f;
+ test::Random random(0x12345678);
+
+ std::vector<int64_t> tcp_starting_times_ms;
+
+ // Two TCP Flows are initialized simultaneosly at t=0 seconds.
+ for (int i = 0; i < 2; ++i, --num_files) {
+ tcp_starting_times_ms.push_back(0);
+ }
+
+ // Other TCP Flows are initialized in an OFF state.
+ while (num_files-- > 0) {
+ tcp_starting_times_ms.push_back(
+ static_cast<int64_t>(random.Exponential(1.0f / kMeanMs)));
+ }
+
+ return tcp_starting_times_ms;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.h
new file mode 100644
index 0000000000..5fb3252195
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test.h
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_H_
+
+#include <map>
+#include <string>
+#include <vector>
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+
+namespace webrtc {
+
+namespace testing {
+namespace bwe {
+
+class BweReceiver;
+class PacketReceiver;
+class PacketSender;
+
+class PacketProcessorRunner {
+ public:
+ explicit PacketProcessorRunner(PacketProcessor* processor);
+ ~PacketProcessorRunner();
+
+ bool RunsProcessor(const PacketProcessor* processor) const;
+ void RunFor(int64_t time_ms, int64_t time_now_ms, Packets* in_out);
+
+ private:
+ void FindPacketsToProcess(const FlowIds& flow_ids, Packets* in, Packets* out);
+ void QueuePackets(Packets* batch, int64_t end_of_batch_time_us);
+
+ PacketProcessor* processor_;
+ Packets queue_;
+};
+
+class Link : public PacketProcessorListener {
+ public:
+ virtual ~Link() {}
+
+ virtual void AddPacketProcessor(PacketProcessor* processor,
+ ProcessorType type);
+ virtual void RemovePacketProcessor(PacketProcessor* processor);
+
+ void Run(int64_t run_for_ms, int64_t now_ms, Packets* packets);
+
+ const std::vector<PacketSender*>& senders() { return senders_; }
+ const std::vector<PacketProcessorRunner>& processors() { return processors_; }
+
+ private:
+ std::vector<PacketSender*> senders_;
+ std::vector<PacketReceiver*> receivers_;
+ std::vector<PacketProcessorRunner> processors_;
+};
+
+class BweTest {
+ public:
+ BweTest();
+ explicit BweTest(bool plot_capacity);
+ ~BweTest();
+
+ void RunChoke(BandwidthEstimatorType bwe_type,
+ std::vector<int> capacities_kbps);
+
+ void RunVariableCapacity1SingleFlow(BandwidthEstimatorType bwe_type);
+ void RunVariableCapacity2MultipleFlows(BandwidthEstimatorType bwe_type,
+ size_t num_flows);
+ void RunBidirectionalFlow(BandwidthEstimatorType bwe_type);
+ void RunSelfFairness(BandwidthEstimatorType bwe_type);
+ void RunRoundTripTimeFairness(BandwidthEstimatorType bwe_type);
+ void RunLongTcpFairness(BandwidthEstimatorType bwe_type);
+ void RunMultipleShortTcpFairness(BandwidthEstimatorType bwe_type,
+ std::vector<int> tcp_file_sizes_bytes,
+ std::vector<int64_t> tcp_starting_times_ms);
+ void RunPauseResumeFlows(BandwidthEstimatorType bwe_type);
+
+ void RunFairnessTest(BandwidthEstimatorType bwe_type,
+ size_t num_media_flows,
+ size_t num_tcp_flows,
+ int64_t run_time_seconds,
+ uint32_t capacity_kbps,
+ int64_t max_delay_ms,
+ int64_t rtt_ms,
+ int64_t max_jitter_ms,
+ const int64_t* offsets_ms);
+
+ void RunFairnessTest(BandwidthEstimatorType bwe_type,
+ size_t num_media_flows,
+ size_t num_tcp_flows,
+ int64_t run_time_seconds,
+ uint32_t capacity_kbps,
+ int64_t max_delay_ms,
+ int64_t rtt_ms,
+ int64_t max_jitter_ms,
+ const int64_t* offsets_ms,
+ const std::string& title,
+ const std::string& flow_name);
+
+ static std::vector<int> GetFileSizesBytes(int num_files);
+ static std::vector<int64_t> GetStartingTimesMs(int num_files);
+
+ protected:
+ void SetUp();
+
+ void VerboseLogging(bool enable);
+ void RunFor(int64_t time_ms);
+ std::string GetTestName() const;
+
+ void PrintResults(double max_throughput_kbps,
+ Stats<double> throughput_kbps,
+ int flow_id,
+ Stats<double> flow_delay_ms,
+ Stats<double> flow_throughput_kbps);
+
+ void PrintResults(double max_throughput_kbps,
+ Stats<double> throughput_kbps,
+ std::map<int, Stats<double>> flow_delay_ms,
+ std::map<int, Stats<double>> flow_throughput_kbps);
+
+ Link downlink_;
+ Link uplink_;
+
+ private:
+ void FindPacketsToProcess(const FlowIds& flow_ids, Packets* in,
+ Packets* out);
+ void GiveFeedbackToAffectedSenders(PacketReceiver* receiver);
+
+ int64_t run_time_ms_;
+ int64_t time_now_ms_;
+ int64_t simulation_interval_ms_;
+ std::vector<Link*> links_;
+ Packets packets_;
+ bool plot_total_available_capacity_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweTest);
+};
+
+// Default Evaluation parameters:
+// Link capacity: 4000ms;
+// Queueing delay capacity: 300ms.
+// One-Way propagation delay: 50ms.
+// Jitter model: Truncated gaussian.
+// Maximum end-to-end jitter: 30ms = 2*standard_deviation.
+// Bottleneck queue type: Drop tail.
+// Path loss ratio: 0%.
+
+const int kOneWayDelayMs = 50;
+const int kMaxQueueingDelayMs = 300;
+const int kMaxCapacityKbps = 4000;
+const int kMaxJitterMs = 15;
+
+struct DefaultEvaluationFilter {
+ DefaultEvaluationFilter(PacketProcessorListener* listener, int flow_id)
+ : choke(listener, flow_id),
+ delay(listener, flow_id),
+ jitter(listener, flow_id) {
+ SetDefaultParameters();
+ }
+
+ DefaultEvaluationFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : choke(listener, flow_ids),
+ delay(listener, flow_ids),
+ jitter(listener, flow_ids) {
+ SetDefaultParameters();
+ }
+
+ void SetDefaultParameters() {
+ delay.SetOneWayDelayMs(kOneWayDelayMs);
+ choke.set_max_delay_ms(kMaxQueueingDelayMs);
+ choke.set_capacity_kbps(kMaxCapacityKbps);
+ jitter.SetMaxJitter(kMaxJitterMs);
+ }
+
+ ChokeFilter choke;
+ DelayFilter delay;
+ JitterFilter jitter;
+};
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc
new file mode 100644
index 0000000000..d7abede707
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc
@@ -0,0 +1,168 @@
+/*
+ * 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/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.h"
+
+#include <stdio.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/test/testsupport/fileutils.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+// The format of BWE test baseline files is extremely simple:
+// 1. All read/written entities are 32-bit unsigned integers in network byte
+// order (Big Endian).
+// 2. Files beging with a 2 word header containing a magic marker and file
+// format version indicator. The Magic marker reads "BWE!" in a hex dump.
+// 3. Each estimate is logged as a pair of words: time in milliseconds and
+// estimated bit rate, in bits per second.
+const uint32_t kMagicMarker = 0x42574521;
+const uint32_t kFileVersion1 = 0x00000001;
+const char kResourceSubDir[] = "remote_bitrate_estimator";
+
+class BaseLineFileVerify : public BaseLineFileInterface {
+ public:
+ // If |allow_missing_file| is set, VerifyOrWrite() will return true even if
+ // the baseline file is missing. This is the default when verifying files, but
+ // not when updating (i.e. we always write it out if missing).
+ BaseLineFileVerify(const std::string& filepath, bool allow_missing_file)
+ : reader_(),
+ fail_to_read_response_(false) {
+ rtc::scoped_ptr<ResourceFileReader> reader;
+ reader.reset(ResourceFileReader::Create(filepath, "bin"));
+ if (!reader.get()) {
+ printf("WARNING: Missing baseline file for BWE test: %s.bin\n",
+ filepath.c_str());
+ fail_to_read_response_ = allow_missing_file;
+ } else {
+ uint32_t magic_marker = 0;
+ uint32_t file_version = 0;
+ if (reader->Read(&magic_marker) && magic_marker == kMagicMarker &&
+ reader->Read(&file_version) && file_version == kFileVersion1) {
+ reader_.swap(reader);
+ } else {
+ printf("WARNING: Bad baseline file header for BWE test: %s.bin\n",
+ filepath.c_str());
+ }
+ }
+ }
+ virtual ~BaseLineFileVerify() {}
+
+ virtual void Estimate(int64_t time_ms, uint32_t estimate_bps) {
+ if (reader_.get()) {
+ uint32_t read_ms = 0;
+ uint32_t read_bps = 0;
+ if (reader_->Read(&read_ms) && read_ms == time_ms &&
+ reader_->Read(&read_bps) && read_bps == estimate_bps) {
+ } else {
+ printf("ERROR: Baseline differs starting at: %d ms (%d vs %d)!\n",
+ static_cast<uint32_t>(time_ms), estimate_bps, read_bps);
+ reader_.reset(NULL);
+ }
+ }
+ }
+
+ virtual bool VerifyOrWrite() {
+ if (reader_.get()) {
+ if (reader_->IsAtEnd()) {
+ return true;
+ } else {
+ printf("ERROR: Baseline file contains more data!\n");
+ return false;
+ }
+ }
+ return fail_to_read_response_;
+ }
+
+ private:
+ rtc::scoped_ptr<ResourceFileReader> reader_;
+ bool fail_to_read_response_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BaseLineFileVerify);
+};
+
+class BaseLineFileUpdate : public BaseLineFileInterface {
+ public:
+ BaseLineFileUpdate(const std::string& filepath,
+ BaseLineFileInterface* verifier)
+ : verifier_(verifier),
+ output_content_(),
+ filepath_(filepath) {
+ output_content_.push_back(kMagicMarker);
+ output_content_.push_back(kFileVersion1);
+ }
+ virtual ~BaseLineFileUpdate() {}
+
+ virtual void Estimate(int64_t time_ms, uint32_t estimate_bps) {
+ verifier_->Estimate(time_ms, estimate_bps);
+ output_content_.push_back(static_cast<uint32_t>(time_ms));
+ output_content_.push_back(estimate_bps);
+ }
+
+ virtual bool VerifyOrWrite() {
+ if (!verifier_->VerifyOrWrite()) {
+ std::string dir_path = webrtc::test::OutputPath() + kResourceSubDir;
+ if (!webrtc::test::CreateDir(dir_path)) {
+ printf("WARNING: Cannot create output dir: %s\n", dir_path.c_str());
+ return false;
+ }
+ rtc::scoped_ptr<OutputFileWriter> writer;
+ writer.reset(OutputFileWriter::Create(filepath_, "bin"));
+ if (!writer.get()) {
+ printf("WARNING: Cannot create output file: %s.bin\n",
+ filepath_.c_str());
+ return false;
+ }
+ printf("NOTE: Writing baseline file for BWE test: %s.bin\n",
+ filepath_.c_str());
+ for (std::vector<uint32_t>::iterator it = output_content_.begin();
+ it != output_content_.end(); ++it) {
+ writer->Write(*it);
+ }
+ return true;
+ }
+ printf("NOTE: No change, not writing: %s\n", filepath_.c_str());
+ return true;
+ }
+
+ private:
+ rtc::scoped_ptr<BaseLineFileInterface> verifier_;
+ std::vector<uint32_t> output_content_;
+ std::string filepath_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BaseLineFileUpdate);
+};
+
+BaseLineFileInterface* BaseLineFileInterface::Create(
+ const std::string& filename, bool write_output_file) {
+ std::string filepath = filename;
+ std::replace(filepath.begin(), filepath.end(), '/', '_');
+ filepath = std::string(kResourceSubDir) + "/" + filepath;
+
+ rtc::scoped_ptr<BaseLineFileInterface> result;
+ result.reset(new BaseLineFileVerify(filepath, !write_output_file));
+ if (write_output_file) {
+ // Takes ownership of the |verifier| instance.
+ result.reset(new BaseLineFileUpdate(filepath, result.release()));
+ }
+ return result.release();
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.h
new file mode 100644
index 0000000000..64dfa85535
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_baselinefile.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_BASELINEFILE_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_BASELINEFILE_H_
+
+#include <string>
+#include "webrtc/modules/interface/module_common_types.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class BaseLineFileInterface {
+ public:
+ virtual ~BaseLineFileInterface() {}
+
+ // Compare, or log, one estimate against the baseline file.
+ virtual void Estimate(int64_t time_ms, uint32_t estimate_bps) = 0;
+
+ // Verify whether there are any differences between the logged estimates and
+ // those read from the baseline file. If updating the baseline file, write out
+ // new file if there were differences. Return true if logged estimates are
+ // identical, or if output file was updated successfully.
+ virtual bool VerifyOrWrite() = 0;
+
+ // Create an instance for either verifying estimates against a baseline file
+ // with name |filename|, living in the resources/ directory or, if the flag
+ // |write_updated_file| is set, write logged estimates to a file with the same
+ // name, living in the out/ directory.
+ static BaseLineFileInterface* Create(const std::string& filename,
+ bool write_updated_file);
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_BASELINEFILE_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.cc
new file mode 100644
index 0000000000..5744c9c320
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.cc
@@ -0,0 +1,97 @@
+/*
+ * 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/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h"
+
+#ifdef WIN32
+#include <Winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+#include <assert.h>
+
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/test/testsupport/fileutils.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+ResourceFileReader::~ResourceFileReader() {
+ if (file_ != NULL) {
+ fclose(file_);
+ file_ = NULL;
+ }
+}
+
+bool ResourceFileReader::IsAtEnd() {
+ int32_t current_pos = ftell(file_);
+ fseek(file_, 0, SEEK_END);
+ int32_t end_pos = ftell(file_);
+ fseek(file_, current_pos, SEEK_SET);
+ return current_pos == end_pos;
+}
+
+bool ResourceFileReader::Read(uint32_t* out) {
+ assert(out);
+ uint32_t tmp = 0;
+ if (fread(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) {
+ printf("Error reading!\n");
+ return false;
+ }
+ *out = ntohl(tmp);
+ return true;
+}
+
+ResourceFileReader* ResourceFileReader::Create(const std::string& filename,
+ const std::string& extension) {
+ std::string filepath = webrtc::test::ResourcePath(filename, extension);
+ FILE* file = fopen(filepath.c_str(), "rb");
+ if (file == NULL) {
+ BWE_TEST_LOGGING_CONTEXT("ResourceFileReader");
+ BWE_TEST_LOGGING_LOG1("Create", "Can't read file: %s", filepath.c_str());
+ return 0;
+ } else {
+ return new ResourceFileReader(file);
+ }
+}
+
+OutputFileWriter::~OutputFileWriter() {
+ if (file_ != NULL) {
+ fclose(file_);
+ file_ = NULL;
+ }
+}
+
+bool OutputFileWriter::Write(uint32_t value) {
+ uint32_t tmp = htonl(value);
+ if (fwrite(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) {
+ return false;
+ }
+ return true;
+}
+
+OutputFileWriter* OutputFileWriter::Create(const std::string& filename,
+ const std::string& extension) {
+ std::string filepath = webrtc::test::OutputPath() + filename + "." +
+ extension;
+ FILE* file = fopen(filepath.c_str(), "wb");
+ if (file == NULL) {
+ BWE_TEST_LOGGING_CONTEXT("OutputFileWriter");
+ BWE_TEST_LOGGING_LOG1("Create", "Can't write file: %s", filepath.c_str());
+ return NULL;
+ } else {
+ return new OutputFileWriter(file);
+ }
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h
new file mode 100644
index 0000000000..2881eba424
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FILEUTILS_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FILEUTILS_H_
+
+#include <stdio.h>
+
+#include <string>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/interface/module_common_types.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class ResourceFileReader {
+ public:
+ ~ResourceFileReader();
+
+ bool IsAtEnd();
+ bool Read(uint32_t* out);
+
+ static ResourceFileReader* Create(const std::string& filename,
+ const std::string& extension);
+
+ private:
+ explicit ResourceFileReader(FILE* file) : file_(file) {}
+ FILE* file_;
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ResourceFileReader);
+};
+
+class OutputFileWriter {
+ public:
+ ~OutputFileWriter();
+
+ bool Write(uint32_t value);
+
+ static OutputFileWriter* Create(const std::string& filename,
+ const std::string& extension);
+
+ private:
+ explicit OutputFileWriter(FILE* file) : file_(file) {}
+ FILE* file_;
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(OutputFileWriter);
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FILEUTILS_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc
new file mode 100644
index 0000000000..4574d3d8a1
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.cc
@@ -0,0 +1,817 @@
+/*
+ * 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/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+
+#include <stdio.h>
+
+#include <sstream>
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class DelayCapHelper {
+ public:
+ // Max delay = 0 stands for +infinite.
+ DelayCapHelper() : max_delay_us_(0), delay_stats_() {}
+
+ void set_max_delay_ms(int64_t max_delay_ms) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("Max Delay", "%d ms", static_cast<int>(max_delay_ms));
+ assert(max_delay_ms >= 0);
+ max_delay_us_ = max_delay_ms * 1000;
+ }
+
+ bool ShouldSendPacket(int64_t send_time_us, int64_t arrival_time_us) {
+ int64_t packet_delay_us = send_time_us - arrival_time_us;
+ delay_stats_.Push((std::min(packet_delay_us, max_delay_us_) + 500) / 1000);
+ return (max_delay_us_ == 0 || max_delay_us_ >= packet_delay_us);
+ }
+
+ const Stats<double>& delay_stats() const {
+ return delay_stats_;
+ }
+
+ private:
+ int64_t max_delay_us_;
+ Stats<double> delay_stats_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(DelayCapHelper);
+};
+
+const FlowIds CreateFlowIds(const int *flow_ids_array, size_t num_flow_ids) {
+ FlowIds flow_ids(&flow_ids_array[0], flow_ids_array + num_flow_ids);
+ return flow_ids;
+}
+
+const FlowIds CreateFlowIdRange(int initial_value, int last_value) {
+ int size = last_value - initial_value + 1;
+ assert(size > 0);
+ int* flow_ids_array = new int[size];
+ for (int i = initial_value; i <= last_value; ++i) {
+ flow_ids_array[i - initial_value] = i;
+ }
+ return CreateFlowIds(flow_ids_array, size);
+}
+
+void RateCounter::UpdateRates(int64_t send_time_us, uint32_t payload_size) {
+ ++recently_received_packets_;
+ recently_received_bytes_ += payload_size;
+ last_accumulated_us_ = send_time_us;
+ window_.push_back(std::make_pair(send_time_us, payload_size));
+ while (!window_.empty()) {
+ const TimeSizePair& packet = window_.front();
+ if (packet.first > (last_accumulated_us_ - window_size_us_)) {
+ break;
+ }
+ assert(recently_received_packets_ >= 1);
+ assert(recently_received_bytes_ >= packet.second);
+ --recently_received_packets_;
+ recently_received_bytes_ -= packet.second;
+ window_.pop_front();
+ }
+}
+
+uint32_t RateCounter::bits_per_second() const {
+ return (8 * recently_received_bytes_) / BitrateWindowS();
+}
+
+uint32_t RateCounter::packets_per_second() const {
+ return recently_received_packets_ / BitrateWindowS();
+}
+
+double RateCounter::BitrateWindowS() const {
+ return static_cast<double>(window_size_us_) / (1000 * 1000);
+}
+
+Packet::Packet()
+ : flow_id_(0),
+ creation_time_us_(-1),
+ send_time_us_(-1),
+ sender_timestamp_us_(-1),
+ payload_size_(0),
+ paced_(false) {
+}
+
+Packet::Packet(int flow_id, int64_t send_time_us, size_t payload_size)
+ : flow_id_(flow_id),
+ creation_time_us_(send_time_us),
+ send_time_us_(send_time_us),
+ sender_timestamp_us_(send_time_us),
+ payload_size_(payload_size),
+ paced_(false) {
+}
+
+Packet::~Packet() {
+}
+
+bool Packet::operator<(const Packet& rhs) const {
+ return send_time_us_ < rhs.send_time_us_;
+}
+
+void Packet::set_send_time_us(int64_t send_time_us) {
+ assert(send_time_us >= 0);
+ send_time_us_ = send_time_us;
+}
+
+MediaPacket::MediaPacket() {
+ memset(&header_, 0, sizeof(header_));
+}
+
+MediaPacket::MediaPacket(int flow_id,
+ int64_t send_time_us,
+ size_t payload_size,
+ uint16_t sequence_number)
+ : Packet(flow_id, send_time_us, payload_size) {
+ header_ = RTPHeader();
+ header_.sequenceNumber = sequence_number;
+}
+
+MediaPacket::MediaPacket(int flow_id,
+ int64_t send_time_us,
+ size_t payload_size,
+ const RTPHeader& header)
+ : Packet(flow_id, send_time_us, payload_size), header_(header) {
+}
+
+MediaPacket::MediaPacket(int64_t send_time_us, uint16_t sequence_number)
+ : Packet(0, send_time_us, 0) {
+ header_ = RTPHeader();
+ header_.sequenceNumber = sequence_number;
+}
+
+void MediaPacket::SetAbsSendTimeMs(int64_t abs_send_time_ms) {
+ header_.extension.hasAbsoluteSendTime = true;
+ header_.extension.absoluteSendTime = ((static_cast<int64_t>(abs_send_time_ms *
+ (1 << 18)) + 500) / 1000) & 0x00fffffful;
+}
+
+RembFeedback::RembFeedback(int flow_id,
+ int64_t send_time_us,
+ int64_t last_send_time_ms,
+ uint32_t estimated_bps,
+ RTCPReportBlock report_block)
+ : FeedbackPacket(flow_id, send_time_us, last_send_time_ms),
+ estimated_bps_(estimated_bps),
+ report_block_(report_block) {
+}
+
+SendSideBweFeedback::SendSideBweFeedback(
+ int flow_id,
+ int64_t send_time_us,
+ int64_t last_send_time_ms,
+ const std::vector<PacketInfo>& packet_feedback_vector)
+ : FeedbackPacket(flow_id, send_time_us, last_send_time_ms),
+ packet_feedback_vector_(packet_feedback_vector) {
+}
+
+bool IsTimeSorted(const Packets& packets) {
+ PacketsConstIt last_it = packets.begin();
+ for (PacketsConstIt it = last_it; it != packets.end(); ++it) {
+ if (it != last_it && **it < **last_it) {
+ return false;
+ }
+ last_it = it;
+ }
+ return true;
+}
+
+PacketProcessor::PacketProcessor(PacketProcessorListener* listener,
+ int flow_id,
+ ProcessorType type)
+ : listener_(listener), flow_ids_(&flow_id, &flow_id + 1) {
+ if (listener_) {
+ listener_->AddPacketProcessor(this, type);
+ }
+}
+
+PacketProcessor::PacketProcessor(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ ProcessorType type)
+ : listener_(listener), flow_ids_(flow_ids) {
+ if (listener_) {
+ listener_->AddPacketProcessor(this, type);
+ }
+}
+
+PacketProcessor::~PacketProcessor() {
+ if (listener_) {
+ listener_->RemovePacketProcessor(this);
+ }
+}
+
+uint32_t PacketProcessor::packets_per_second() const {
+ return rate_counter_.packets_per_second();
+}
+
+uint32_t PacketProcessor::bits_per_second() const {
+ return rate_counter_.bits_per_second();
+}
+
+RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
+ int flow_id,
+ const char* name,
+ const std::string& plot_name)
+ : PacketProcessor(listener, flow_id, kRegular),
+ packets_per_second_stats_(),
+ kbps_stats_(),
+ start_plotting_time_ms_(0),
+ plot_name_(plot_name) {
+ std::stringstream ss;
+ ss << name << "_" << flow_id;
+ name_ = ss.str();
+}
+
+RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ const char* name,
+ const std::string& plot_name)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ packets_per_second_stats_(),
+ kbps_stats_(),
+ start_plotting_time_ms_(0),
+ plot_name_(plot_name) {
+ std::stringstream ss;
+ ss << name;
+ char delimiter = '_';
+ for (int flow_id : flow_ids) {
+ ss << delimiter << flow_id;
+ delimiter = ',';
+ }
+ name_ = ss.str();
+}
+
+RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ const char* name,
+ int64_t start_plotting_time_ms,
+ const std::string& plot_name)
+ : RateCounterFilter(listener, flow_ids, name, plot_name) {
+ start_plotting_time_ms_ = start_plotting_time_ms;
+}
+
+RateCounterFilter::~RateCounterFilter() {
+ LogStats();
+}
+
+
+void RateCounterFilter::LogStats() {
+ BWE_TEST_LOGGING_CONTEXT("RateCounterFilter");
+ packets_per_second_stats_.Log("pps");
+ kbps_stats_.Log("kbps");
+}
+
+Stats<double> RateCounterFilter::GetBitrateStats() const {
+ return kbps_stats_;
+}
+
+void RateCounterFilter::Plot(int64_t timestamp_ms) {
+ uint32_t plot_kbps = 0;
+ if (timestamp_ms >= start_plotting_time_ms_) {
+ plot_kbps = rate_counter_.bits_per_second() / 1000.0;
+ }
+ BWE_TEST_LOGGING_CONTEXT(name_.c_str());
+ if (plot_name_.empty()) {
+ BWE_TEST_LOGGING_PLOT(0, "Throughput_kbps#1", timestamp_ms, plot_kbps);
+ } else {
+ BWE_TEST_LOGGING_PLOT_WITH_NAME(0, "Throughput_kbps#1", timestamp_ms,
+ plot_kbps, plot_name_);
+ }
+
+ RTC_UNUSED(plot_kbps);
+}
+
+void RateCounterFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ for (const Packet* packet : *in_out) {
+ rate_counter_.UpdateRates(packet->send_time_us(),
+ static_cast<int>(packet->payload_size()));
+ }
+ packets_per_second_stats_.Push(rate_counter_.packets_per_second());
+ kbps_stats_.Push(rate_counter_.bits_per_second() / 1000.0);
+}
+
+LossFilter::LossFilter(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ random_(0x12345678),
+ loss_fraction_(0.0f) {
+}
+
+LossFilter::LossFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ random_(0x12345678),
+ loss_fraction_(0.0f) {
+}
+
+void LossFilter::SetLoss(float loss_percent) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("Loss", "%f%%", loss_percent);
+ assert(loss_percent >= 0.0f);
+ assert(loss_percent <= 100.0f);
+ loss_fraction_ = loss_percent * 0.01f;
+}
+
+void LossFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ for (PacketsIt it = in_out->begin(); it != in_out->end(); ) {
+ if (random_.Rand() < loss_fraction_) {
+ delete *it;
+ it = in_out->erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+const int64_t kDefaultOneWayDelayUs = 0;
+
+DelayFilter::DelayFilter(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ one_way_delay_us_(kDefaultOneWayDelayUs),
+ last_send_time_us_(0) {
+}
+
+DelayFilter::DelayFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ one_way_delay_us_(kDefaultOneWayDelayUs),
+ last_send_time_us_(0) {
+}
+
+void DelayFilter::SetOneWayDelayMs(int64_t one_way_delay_ms) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("Delay", "%d ms", static_cast<int>(one_way_delay_ms));
+ assert(one_way_delay_ms >= 0);
+ one_way_delay_us_ = one_way_delay_ms * 1000;
+}
+
+void DelayFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ for (Packet* packet : *in_out) {
+ int64_t new_send_time_us = packet->send_time_us() + one_way_delay_us_;
+ last_send_time_us_ = std::max(last_send_time_us_, new_send_time_us);
+ packet->set_send_time_us(last_send_time_us_);
+ }
+}
+
+JitterFilter::JitterFilter(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ random_(0x89674523),
+ stddev_jitter_us_(0),
+ last_send_time_us_(0),
+ reordering_(false) {
+}
+
+JitterFilter::JitterFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ random_(0x89674523),
+ stddev_jitter_us_(0),
+ last_send_time_us_(0),
+ reordering_(false) {
+}
+
+const int kN = 3; // Truncated N sigma gaussian.
+
+void JitterFilter::SetMaxJitter(int64_t max_jitter_ms) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("Max Jitter", "%d ms", static_cast<int>(max_jitter_ms));
+ assert(max_jitter_ms >= 0);
+ // Truncated gaussian, Max jitter = kN*sigma.
+ stddev_jitter_us_ = (max_jitter_ms * 1000 + kN / 2) / kN;
+}
+
+namespace {
+inline int64_t TruncatedNSigmaGaussian(test::Random* const random,
+ int64_t mean,
+ int64_t std_dev) {
+ int64_t gaussian_random = random->Gaussian(mean, std_dev);
+ return std::max(std::min(gaussian_random, kN * std_dev), -kN * std_dev);
+}
+}
+
+void JitterFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ for (Packet* packet : *in_out) {
+ int64_t jitter_us =
+ std::abs(TruncatedNSigmaGaussian(&random_, 0, stddev_jitter_us_));
+ int64_t new_send_time_us = packet->send_time_us() + jitter_us;
+
+ if (!reordering_) {
+ new_send_time_us = std::max(last_send_time_us_, new_send_time_us);
+ }
+
+ // Receiver timestamp cannot be lower than sender timestamp.
+ assert(new_send_time_us >= packet->sender_timestamp_us());
+
+ packet->set_send_time_us(new_send_time_us);
+ last_send_time_us_ = new_send_time_us;
+ }
+}
+
+// Computes the expected value for a right sided (abs) truncated gaussian.
+// Does not take into account possible reoerdering updates.
+int64_t JitterFilter::MeanUs() {
+ const double kPi = 3.1415926535897932;
+ double max_jitter_us = static_cast<double>(kN * stddev_jitter_us_);
+ double right_sided_mean_us =
+ static_cast<double>(stddev_jitter_us_) / sqrt(kPi / 2.0);
+ double truncated_mean_us =
+ right_sided_mean_us *
+ (1.0 - exp(-pow(static_cast<double>(kN), 2.0) / 2.0)) +
+ max_jitter_us * erfc(static_cast<double>(kN));
+ return static_cast<int64_t>(truncated_mean_us + 0.5);
+}
+
+ReorderFilter::ReorderFilter(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ random_(0x27452389),
+ reorder_fraction_(0.0f) {
+}
+
+ReorderFilter::ReorderFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ random_(0x27452389),
+ reorder_fraction_(0.0f) {
+}
+
+void ReorderFilter::SetReorder(float reorder_percent) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("Reordering", "%f%%", reorder_percent);
+ assert(reorder_percent >= 0.0f);
+ assert(reorder_percent <= 100.0f);
+ reorder_fraction_ = reorder_percent * 0.01f;
+}
+
+void ReorderFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ if (in_out->size() >= 2) {
+ PacketsIt last_it = in_out->begin();
+ PacketsIt it = last_it;
+ while (++it != in_out->end()) {
+ if (random_.Rand() < reorder_fraction_) {
+ int64_t t1 = (*last_it)->send_time_us();
+ int64_t t2 = (*it)->send_time_us();
+ std::swap(*last_it, *it);
+ (*last_it)->set_send_time_us(t1);
+ (*it)->set_send_time_us(t2);
+ }
+ last_it = it;
+ }
+ }
+}
+
+const uint32_t kDefaultKbps = 1200;
+
+ChokeFilter::ChokeFilter(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ capacity_kbps_(kDefaultKbps),
+ last_send_time_us_(0),
+ delay_cap_helper_(new DelayCapHelper()) {
+}
+
+ChokeFilter::ChokeFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ capacity_kbps_(kDefaultKbps),
+ last_send_time_us_(0),
+ delay_cap_helper_(new DelayCapHelper()) {
+}
+
+ChokeFilter::~ChokeFilter() {}
+
+void ChokeFilter::set_capacity_kbps(uint32_t kbps) {
+ BWE_TEST_LOGGING_ENABLE(false);
+ BWE_TEST_LOGGING_LOG1("BitrateChoke", "%d kbps", kbps);
+ capacity_kbps_ = kbps;
+}
+
+uint32_t ChokeFilter::capacity_kbps() {
+ return capacity_kbps_;
+}
+
+void ChokeFilter::RunFor(int64_t /*time_ms*/, Packets* in_out) {
+ assert(in_out);
+ for (PacketsIt it = in_out->begin(); it != in_out->end(); ) {
+ int64_t earliest_send_time_us =
+ std::max(last_send_time_us_, (*it)->send_time_us());
+
+ int64_t new_send_time_us =
+ earliest_send_time_us +
+ ((*it)->payload_size() * 8 * 1000 + capacity_kbps_ / 2) /
+ capacity_kbps_;
+
+ if (delay_cap_helper_->ShouldSendPacket(new_send_time_us,
+ (*it)->send_time_us())) {
+ (*it)->set_send_time_us(new_send_time_us);
+ last_send_time_us_ = new_send_time_us;
+ ++it;
+ } else {
+ delete *it;
+ it = in_out->erase(it);
+ }
+ }
+}
+
+void ChokeFilter::set_max_delay_ms(int64_t max_delay_ms) {
+ delay_cap_helper_->set_max_delay_ms(max_delay_ms);
+}
+
+Stats<double> ChokeFilter::GetDelayStats() const {
+ return delay_cap_helper_->delay_stats();
+}
+
+TraceBasedDeliveryFilter::TraceBasedDeliveryFilter(
+ PacketProcessorListener* listener,
+ int flow_id)
+ : PacketProcessor(listener, flow_id, kRegular),
+ current_offset_us_(0),
+ delivery_times_us_(),
+ next_delivery_it_(),
+ local_time_us_(-1),
+ rate_counter_(new RateCounter),
+ name_(""),
+ delay_cap_helper_(new DelayCapHelper()),
+ packets_per_second_stats_(),
+ kbps_stats_() {
+}
+
+TraceBasedDeliveryFilter::TraceBasedDeliveryFilter(
+ PacketProcessorListener* listener,
+ const FlowIds& flow_ids)
+ : PacketProcessor(listener, flow_ids, kRegular),
+ current_offset_us_(0),
+ delivery_times_us_(),
+ next_delivery_it_(),
+ local_time_us_(-1),
+ rate_counter_(new RateCounter),
+ name_(""),
+ delay_cap_helper_(new DelayCapHelper()),
+ packets_per_second_stats_(),
+ kbps_stats_() {
+}
+
+TraceBasedDeliveryFilter::TraceBasedDeliveryFilter(
+ PacketProcessorListener* listener,
+ int flow_id,
+ const char* name)
+ : PacketProcessor(listener, flow_id, kRegular),
+ current_offset_us_(0),
+ delivery_times_us_(),
+ next_delivery_it_(),
+ local_time_us_(-1),
+ rate_counter_(new RateCounter),
+ name_(name),
+ delay_cap_helper_(new DelayCapHelper()),
+ packets_per_second_stats_(),
+ kbps_stats_() {
+}
+
+TraceBasedDeliveryFilter::~TraceBasedDeliveryFilter() {
+}
+
+bool TraceBasedDeliveryFilter::Init(const std::string& filename) {
+ FILE* trace_file = fopen(filename.c_str(), "r");
+ if (!trace_file) {
+ return false;
+ }
+ int64_t first_timestamp = -1;
+ while(!feof(trace_file)) {
+ const size_t kMaxLineLength = 100;
+ char line[kMaxLineLength];
+ if (fgets(line, kMaxLineLength, trace_file)) {
+ std::string line_string(line);
+ std::istringstream buffer(line_string);
+ int64_t timestamp;
+ buffer >> timestamp;
+ timestamp /= 1000; // Convert to microseconds.
+ if (first_timestamp == -1)
+ first_timestamp = timestamp;
+ assert(delivery_times_us_.empty() ||
+ timestamp - first_timestamp - delivery_times_us_.back() >= 0);
+ delivery_times_us_.push_back(timestamp - first_timestamp);
+ }
+ }
+ assert(!delivery_times_us_.empty());
+ next_delivery_it_ = delivery_times_us_.begin();
+ fclose(trace_file);
+ return true;
+}
+
+void TraceBasedDeliveryFilter::Plot(int64_t timestamp_ms) {
+ BWE_TEST_LOGGING_CONTEXT(name_.c_str());
+ // This plots the max possible throughput of the trace-based delivery filter,
+ // which will be reached if a packet sent on every packet slot of the trace.
+ BWE_TEST_LOGGING_PLOT(0, "MaxThroughput_#1", timestamp_ms,
+ rate_counter_->bits_per_second() / 1000.0);
+}
+
+void TraceBasedDeliveryFilter::RunFor(int64_t time_ms, Packets* in_out) {
+ assert(in_out);
+ for (PacketsIt it = in_out->begin(); it != in_out->end();) {
+ while (local_time_us_ < (*it)->send_time_us()) {
+ ProceedToNextSlot();
+ }
+ // Drop any packets that have been queued for too long.
+ while (!delay_cap_helper_->ShouldSendPacket(local_time_us_,
+ (*it)->send_time_us())) {
+ delete *it;
+ it = in_out->erase(it);
+ if (it == in_out->end()) {
+ return;
+ }
+ }
+ if (local_time_us_ >= (*it)->send_time_us()) {
+ (*it)->set_send_time_us(local_time_us_);
+ ProceedToNextSlot();
+ }
+ ++it;
+ }
+ packets_per_second_stats_.Push(rate_counter_->packets_per_second());
+ kbps_stats_.Push(rate_counter_->bits_per_second() / 1000.0);
+}
+
+void TraceBasedDeliveryFilter::set_max_delay_ms(int64_t max_delay_ms) {
+ delay_cap_helper_->set_max_delay_ms(max_delay_ms);
+}
+
+Stats<double> TraceBasedDeliveryFilter::GetDelayStats() const {
+ return delay_cap_helper_->delay_stats();
+}
+
+Stats<double> TraceBasedDeliveryFilter::GetBitrateStats() const {
+ return kbps_stats_;
+}
+
+void TraceBasedDeliveryFilter::ProceedToNextSlot() {
+ if (*next_delivery_it_ <= local_time_us_) {
+ ++next_delivery_it_;
+ if (next_delivery_it_ == delivery_times_us_.end()) {
+ // When the trace wraps we allow two packets to be sent back-to-back.
+ for (int64_t& delivery_time_us : delivery_times_us_) {
+ delivery_time_us += local_time_us_ - current_offset_us_;
+ }
+ current_offset_us_ += local_time_us_ - current_offset_us_;
+ next_delivery_it_ = delivery_times_us_.begin();
+ }
+ }
+ local_time_us_ = *next_delivery_it_;
+ const int kPayloadSize = 1200;
+ rate_counter_->UpdateRates(local_time_us_, kPayloadSize);
+}
+
+VideoSource::VideoSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms)
+ : kMaxPayloadSizeBytes(1200),
+ kTimestampBase(0xff80ff00ul),
+ frame_period_ms_(1000.0 / fps),
+ bits_per_second_(1000 * kbps),
+ frame_size_bytes_(bits_per_second_ / 8 / fps),
+ flow_id_(flow_id),
+ next_frame_ms_(first_frame_offset_ms),
+ next_frame_rand_ms_(0),
+ now_ms_(0),
+ prototype_header_() {
+ memset(&prototype_header_, 0, sizeof(prototype_header_));
+ prototype_header_.ssrc = ssrc;
+ prototype_header_.sequenceNumber = 0xf000u;
+}
+
+uint32_t VideoSource::NextFrameSize() {
+ return frame_size_bytes_;
+}
+
+int64_t VideoSource::GetTimeUntilNextFrameMs() const {
+ return next_frame_ms_ + next_frame_rand_ms_ - now_ms_;
+}
+
+uint32_t VideoSource::NextPacketSize(uint32_t frame_size,
+ uint32_t remaining_payload) {
+ return std::min(kMaxPayloadSizeBytes, remaining_payload);
+}
+
+void VideoSource::RunFor(int64_t time_ms, Packets* in_out) {
+ assert(in_out);
+
+ now_ms_ += time_ms;
+ Packets new_packets;
+
+ while (now_ms_ >= next_frame_ms_) {
+ const int64_t kRandAmplitude = 2;
+ // A variance picked uniformly from {-1, 0, 1} ms is added to the frame
+ // timestamp.
+ next_frame_rand_ms_ =
+ kRandAmplitude * static_cast<float>(rand()) / RAND_MAX -
+ kRandAmplitude / 2;
+
+ // Ensure frame will not have a negative timestamp.
+ int64_t next_frame_ms =
+ std::max<int64_t>(next_frame_ms_ + next_frame_rand_ms_, 0);
+
+ prototype_header_.timestamp =
+ kTimestampBase + static_cast<uint32_t>(next_frame_ms * 90.0);
+ prototype_header_.extension.transmissionTimeOffset = 0;
+
+ // Generate new packets for this frame, all with the same timestamp,
+ // but the payload size is capped, so if the whole frame doesn't fit in
+ // one packet, we will see a number of equally sized packets followed by
+ // one smaller at the tail.
+
+ int64_t send_time_us = next_frame_ms * 1000.0;
+
+ uint32_t frame_size = NextFrameSize();
+ uint32_t payload_size = frame_size;
+
+ while (payload_size > 0) {
+ ++prototype_header_.sequenceNumber;
+ uint32_t size = NextPacketSize(frame_size, payload_size);
+ MediaPacket* new_packet =
+ new MediaPacket(flow_id_, send_time_us, size, prototype_header_);
+ new_packets.push_back(new_packet);
+ new_packet->SetAbsSendTimeMs(next_frame_ms);
+ new_packet->set_sender_timestamp_us(send_time_us);
+ payload_size -= size;
+ }
+
+ next_frame_ms_ += frame_period_ms_;
+ }
+
+ in_out->merge(new_packets, DereferencingComparator<Packet>);
+}
+
+AdaptiveVideoSource::AdaptiveVideoSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms)
+ : VideoSource(flow_id, fps, kbps, ssrc, first_frame_offset_ms) {
+}
+
+void AdaptiveVideoSource::SetBitrateBps(int bitrate_bps) {
+ bits_per_second_ = std::min(bitrate_bps, 2500000);
+ frame_size_bytes_ = (bits_per_second_ / 8 * frame_period_ms_ + 500) / 1000;
+}
+
+PeriodicKeyFrameSource::PeriodicKeyFrameSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms,
+ int key_frame_interval)
+ : AdaptiveVideoSource(flow_id, fps, kbps, ssrc, first_frame_offset_ms),
+ key_frame_interval_(key_frame_interval),
+ frame_counter_(0),
+ compensation_bytes_(0),
+ compensation_per_frame_(0) {
+}
+
+uint32_t PeriodicKeyFrameSource::NextFrameSize() {
+ uint32_t payload_size = frame_size_bytes_;
+ if (frame_counter_ == 0) {
+ payload_size = kMaxPayloadSizeBytes * 12;
+ compensation_bytes_ = 4 * frame_size_bytes_;
+ compensation_per_frame_ = compensation_bytes_ / 30;
+ } else if (key_frame_interval_ > 0 &&
+ (frame_counter_ % key_frame_interval_ == 0)) {
+ payload_size *= 5;
+ compensation_bytes_ = payload_size - frame_size_bytes_;
+ compensation_per_frame_ = compensation_bytes_ / 30;
+ } else if (compensation_bytes_ > 0) {
+ if (compensation_per_frame_ > static_cast<int>(payload_size)) {
+ // Skip this frame.
+ compensation_bytes_ -= payload_size;
+ payload_size = 0;
+ } else {
+ payload_size -= compensation_per_frame_;
+ compensation_bytes_ -= compensation_per_frame_;
+ }
+ }
+ if (compensation_bytes_ < 0)
+ compensation_bytes_ = 0;
+ ++frame_counter_;
+ return payload_size;
+}
+
+uint32_t PeriodicKeyFrameSource::NextPacketSize(uint32_t frame_size,
+ uint32_t remaining_payload) {
+ uint32_t fragments =
+ (frame_size + (kMaxPayloadSizeBytes - 1)) / kMaxPayloadSizeBytes;
+ uint32_t avg_size = (frame_size + fragments - 1) / fragments;
+ return std::min(avg_size, remaining_payload);
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h
new file mode 100644
index 0000000000..6b24cf30a6
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h
@@ -0,0 +1,468 @@
+/*
+ * 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FRAMEWORK_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FRAMEWORK_H_
+
+#include <assert.h>
+#include <math.h>
+
+#include <algorithm>
+#include <list>
+#include <numeric>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "webrtc/base/common.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/pacing/include/paced_sender.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
+#include "webrtc/system_wrappers/include/clock.h"
+#include "webrtc/test/random.h"
+
+namespace webrtc {
+
+class RtcpBandwidthObserver;
+
+namespace testing {
+namespace bwe {
+
+class DelayCapHelper;
+
+class RateCounter {
+ public:
+ RateCounter(int64_t window_size_ms)
+ : window_size_us_(1000 * window_size_ms),
+ recently_received_packets_(0),
+ recently_received_bytes_(0),
+ last_accumulated_us_(0),
+ window_() {}
+
+ RateCounter() : RateCounter(1000) {}
+
+ void UpdateRates(int64_t send_time_us, uint32_t payload_size);
+
+ int64_t window_size_ms() const { return (window_size_us_ + 500) / 1000; }
+ uint32_t packets_per_second() const;
+ uint32_t bits_per_second() const;
+
+ double BitrateWindowS() const;
+
+ private:
+ typedef std::pair<int64_t, uint32_t> TimeSizePair;
+
+ int64_t window_size_us_;
+ uint32_t recently_received_packets_;
+ uint32_t recently_received_bytes_;
+ int64_t last_accumulated_us_;
+ std::list<TimeSizePair> window_;
+};
+
+typedef std::set<int> FlowIds;
+const FlowIds CreateFlowIds(const int *flow_ids_array, size_t num_flow_ids);
+const FlowIds CreateFlowIdRange(int initial_value, int last_value);
+
+template <typename T>
+bool DereferencingComparator(const T* const& a, const T* const& b) {
+ assert(a != NULL);
+ assert(b != NULL);
+ return *a < *b;
+}
+
+template<typename T> class Stats {
+ public:
+ Stats()
+ : data_(),
+ last_mean_count_(0),
+ last_variance_count_(0),
+ last_minmax_count_(0),
+ mean_(0),
+ variance_(0),
+ min_(0),
+ max_(0) {
+ }
+
+ void Push(T data_point) {
+ data_.push_back(data_point);
+ }
+
+ T GetMean() {
+ if (last_mean_count_ != data_.size()) {
+ last_mean_count_ = data_.size();
+ mean_ = std::accumulate(data_.begin(), data_.end(), static_cast<T>(0));
+ assert(last_mean_count_ != 0);
+ mean_ /= static_cast<T>(last_mean_count_);
+ }
+ return mean_;
+ }
+ T GetVariance() {
+ if (last_variance_count_ != data_.size()) {
+ last_variance_count_ = data_.size();
+ T mean = GetMean();
+ variance_ = 0;
+ for (const auto& sample : data_) {
+ T diff = (sample - mean);
+ variance_ += diff * diff;
+ }
+ assert(last_variance_count_ != 0);
+ variance_ /= static_cast<T>(last_variance_count_);
+ }
+ return variance_;
+ }
+ T GetStdDev() {
+ return sqrt(static_cast<double>(GetVariance()));
+ }
+ T GetMin() {
+ RefreshMinMax();
+ return min_;
+ }
+ T GetMax() {
+ RefreshMinMax();
+ return max_;
+ }
+
+ std::string AsString() {
+ std::stringstream ss;
+ ss << (GetMean() >= 0 ? GetMean() : -1) << ", " <<
+ (GetStdDev() >= 0 ? GetStdDev() : -1);
+ return ss.str();
+ }
+
+ void Log(const std::string& units) {
+ BWE_TEST_LOGGING_LOG5("", "%f %s\t+/-%f\t[%f,%f]",
+ GetMean(), units.c_str(), GetStdDev(), GetMin(), GetMax());
+ }
+
+ private:
+ void RefreshMinMax() {
+ if (last_minmax_count_ != data_.size()) {
+ last_minmax_count_ = data_.size();
+ min_ = max_ = 0;
+ if (data_.empty()) {
+ return;
+ }
+ typename std::vector<T>::const_iterator it = data_.begin();
+ min_ = max_ = *it;
+ while (++it != data_.end()) {
+ min_ = std::min(min_, *it);
+ max_ = std::max(max_, *it);
+ }
+ }
+ }
+
+ std::vector<T> data_;
+ typename std::vector<T>::size_type last_mean_count_;
+ typename std::vector<T>::size_type last_variance_count_;
+ typename std::vector<T>::size_type last_minmax_count_;
+ T mean_;
+ T variance_;
+ T min_;
+ T max_;
+};
+
+bool IsTimeSorted(const Packets& packets);
+
+class PacketProcessor;
+
+enum ProcessorType { kSender, kReceiver, kRegular };
+
+class PacketProcessorListener {
+ public:
+ virtual ~PacketProcessorListener() {}
+
+ virtual void AddPacketProcessor(PacketProcessor* processor,
+ ProcessorType type) = 0;
+ virtual void RemovePacketProcessor(PacketProcessor* processor) = 0;
+};
+
+class PacketProcessor {
+ public:
+ PacketProcessor(PacketProcessorListener* listener,
+ int flow_id,
+ ProcessorType type);
+ PacketProcessor(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ ProcessorType type);
+ virtual ~PacketProcessor();
+
+ // Called after each simulation batch to allow the processor to plot any
+ // internal data.
+ virtual void Plot(int64_t timestamp_ms) {}
+
+ // Run simulation for |time_ms| milliseconds, consuming packets from, and
+ // producing packets into in_out. The outgoing packet list must be sorted on
+ // |send_time_us_|. The simulation time |time_ms| is optional to use.
+ virtual void RunFor(int64_t time_ms, Packets* in_out) = 0;
+
+ const FlowIds& flow_ids() const { return flow_ids_; }
+
+ uint32_t packets_per_second() const;
+ uint32_t bits_per_second() const;
+
+ protected:
+ RateCounter rate_counter_;
+
+ private:
+ PacketProcessorListener* listener_;
+ const FlowIds flow_ids_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(PacketProcessor);
+};
+
+class RateCounterFilter : public PacketProcessor {
+ public:
+ RateCounterFilter(PacketProcessorListener* listener,
+ int flow_id,
+ const char* name,
+ const std::string& plot_name);
+ RateCounterFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ const char* name,
+ const std::string& plot_name);
+ RateCounterFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids,
+ const char* name,
+ int64_t start_plotting_time_ms,
+ const std::string& plot_name);
+ virtual ~RateCounterFilter();
+
+ void LogStats();
+ Stats<double> GetBitrateStats() const;
+ virtual void Plot(int64_t timestamp_ms);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ private:
+ Stats<double> packets_per_second_stats_;
+ Stats<double> kbps_stats_;
+ std::string name_;
+ int64_t start_plotting_time_ms_;
+ // Algorithm name if single flow, Total link utilization if all flows.
+ std::string plot_name_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RateCounterFilter);
+};
+
+class LossFilter : public PacketProcessor {
+ public:
+ LossFilter(PacketProcessorListener* listener, int flow_id);
+ LossFilter(PacketProcessorListener* listener, const FlowIds& flow_ids);
+ virtual ~LossFilter() {}
+
+ void SetLoss(float loss_percent);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ private:
+ test::Random random_;
+ float loss_fraction_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(LossFilter);
+};
+
+class DelayFilter : public PacketProcessor {
+ public:
+ DelayFilter(PacketProcessorListener* listener, int flow_id);
+ DelayFilter(PacketProcessorListener* listener, const FlowIds& flow_ids);
+ virtual ~DelayFilter() {}
+
+ void SetOneWayDelayMs(int64_t one_way_delay_ms);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ private:
+ int64_t one_way_delay_us_;
+ int64_t last_send_time_us_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DelayFilter);
+};
+
+class JitterFilter : public PacketProcessor {
+ public:
+ JitterFilter(PacketProcessorListener* listener, int flow_id);
+ JitterFilter(PacketProcessorListener* listener, const FlowIds& flow_ids);
+ virtual ~JitterFilter() {}
+
+ void SetMaxJitter(int64_t stddev_jitter_ms);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+ void set_reorderdering(bool reordering) { reordering_ = reordering; }
+ int64_t MeanUs();
+
+ private:
+ test::Random random_;
+ int64_t stddev_jitter_us_;
+ int64_t last_send_time_us_;
+ bool reordering_; // False by default.
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(JitterFilter);
+};
+
+// Reorders two consecutive packets with a probability of reorder_percent.
+class ReorderFilter : public PacketProcessor {
+ public:
+ ReorderFilter(PacketProcessorListener* listener, int flow_id);
+ ReorderFilter(PacketProcessorListener* listener, const FlowIds& flow_ids);
+ virtual ~ReorderFilter() {}
+
+ void SetReorder(float reorder_percent);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ private:
+ test::Random random_;
+ float reorder_fraction_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ReorderFilter);
+};
+
+// Apply a bitrate choke with an infinite queue on the packet stream.
+class ChokeFilter : public PacketProcessor {
+ public:
+ ChokeFilter(PacketProcessorListener* listener, int flow_id);
+ ChokeFilter(PacketProcessorListener* listener, const FlowIds& flow_ids);
+ virtual ~ChokeFilter();
+
+ void set_capacity_kbps(uint32_t kbps);
+ void set_max_delay_ms(int64_t max_queueing_delay_ms);
+
+ uint32_t capacity_kbps();
+
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ Stats<double> GetDelayStats() const;
+
+ private:
+ uint32_t capacity_kbps_;
+ int64_t last_send_time_us_;
+ rtc::scoped_ptr<DelayCapHelper> delay_cap_helper_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ChokeFilter);
+};
+
+class TraceBasedDeliveryFilter : public PacketProcessor {
+ public:
+ TraceBasedDeliveryFilter(PacketProcessorListener* listener, int flow_id);
+ TraceBasedDeliveryFilter(PacketProcessorListener* listener,
+ const FlowIds& flow_ids);
+ TraceBasedDeliveryFilter(PacketProcessorListener* listener,
+ int flow_id,
+ const char* name);
+ virtual ~TraceBasedDeliveryFilter();
+
+ // The file should contain nanosecond timestamps corresponding to the time
+ // when the network can accept another packet. The timestamps should be
+ // separated by new lines, e.g., "100000000\n125000000\n321000000\n..."
+ bool Init(const std::string& filename);
+ virtual void Plot(int64_t timestamp_ms);
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ void set_max_delay_ms(int64_t max_delay_ms);
+ Stats<double> GetDelayStats() const;
+ Stats<double> GetBitrateStats() const;
+
+ private:
+ void ProceedToNextSlot();
+
+ typedef std::vector<int64_t> TimeList;
+ int64_t current_offset_us_;
+ TimeList delivery_times_us_;
+ TimeList::const_iterator next_delivery_it_;
+ int64_t local_time_us_;
+ rtc::scoped_ptr<RateCounter> rate_counter_;
+ std::string name_;
+ rtc::scoped_ptr<DelayCapHelper> delay_cap_helper_;
+ Stats<double> packets_per_second_stats_;
+ Stats<double> kbps_stats_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(TraceBasedDeliveryFilter);
+};
+
+class VideoSource {
+ public:
+ VideoSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms);
+ virtual ~VideoSource() {}
+
+ virtual void RunFor(int64_t time_ms, Packets* in_out);
+
+ virtual int flow_id() const { return flow_id_; }
+ virtual void SetBitrateBps(int bitrate_bps) {}
+ uint32_t bits_per_second() const { return bits_per_second_; }
+ uint32_t max_payload_size_bytes() const { return kMaxPayloadSizeBytes; }
+ int64_t GetTimeUntilNextFrameMs() const;
+
+ protected:
+ virtual uint32_t NextFrameSize();
+ virtual uint32_t NextPacketSize(uint32_t frame_size,
+ uint32_t remaining_payload);
+
+ const uint32_t kMaxPayloadSizeBytes;
+ const uint32_t kTimestampBase;
+ const double frame_period_ms_;
+ uint32_t bits_per_second_;
+ uint32_t frame_size_bytes_;
+
+ private:
+ const int flow_id_;
+ int64_t next_frame_ms_;
+ int64_t next_frame_rand_ms_;
+ int64_t now_ms_;
+ RTPHeader prototype_header_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(VideoSource);
+};
+
+class AdaptiveVideoSource : public VideoSource {
+ public:
+ AdaptiveVideoSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms);
+ virtual ~AdaptiveVideoSource() {}
+
+ void SetBitrateBps(int bitrate_bps) override;
+
+ private:
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AdaptiveVideoSource);
+};
+
+class PeriodicKeyFrameSource : public AdaptiveVideoSource {
+ public:
+ PeriodicKeyFrameSource(int flow_id,
+ float fps,
+ uint32_t kbps,
+ uint32_t ssrc,
+ int64_t first_frame_offset_ms,
+ int key_frame_interval);
+ virtual ~PeriodicKeyFrameSource() {}
+
+ protected:
+ uint32_t NextFrameSize() override;
+ uint32_t NextPacketSize(uint32_t frame_size,
+ uint32_t remaining_payload) override;
+
+ private:
+ int key_frame_interval_;
+ uint32_t frame_counter_;
+ int compensation_bytes_;
+ int compensation_per_frame_;
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(PeriodicKeyFrameSource);
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_FRAMEWORK_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc
new file mode 100644
index 0000000000..627260678b
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework_unittest.cc
@@ -0,0 +1,1051 @@
+/*
+ * 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/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+
+#include <numeric>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
+#include "webrtc/test/testsupport/fileutils.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+TEST(BweTestFramework_RandomTest, Gaussian) {
+ enum {
+ kN = 100000,
+ kBuckets = 100,
+ kMean = 49,
+ kStddev = 10
+ };
+
+ test::Random random(0x12345678);
+
+ int buckets[kBuckets] = {0};
+ for (int i = 0; i < kN; ++i) {
+ int index = random.Gaussian(kMean, kStddev);
+ if (index >= 0 && index < kBuckets) {
+ buckets[index]++;
+ }
+ }
+
+ const double kPi = 3.14159265358979323846;
+ const double kScale = kN / (kStddev * sqrt(2.0 * kPi));
+ const double kDiv = -2.0 * kStddev * kStddev;
+ double self_corr = 0.0;
+ double bucket_corr = 0.0;
+ for (int n = 0; n < kBuckets; ++n) {
+ double normal_dist = kScale * exp((n - kMean) * (n - kMean) / kDiv);
+ self_corr += normal_dist * normal_dist;
+ bucket_corr += normal_dist * buckets[n];
+ }
+ printf("Correlation: %f (random sample), %f (self), %f (quotient)\n",
+ bucket_corr, self_corr, bucket_corr / self_corr);
+ EXPECT_NEAR(1.0, bucket_corr / self_corr, 0.0004);
+}
+
+static bool IsSequenceNumberSorted(const Packets& packets) {
+ PacketsConstIt last_it = packets.begin();
+ for (PacketsConstIt it = last_it; it != packets.end(); ++it) {
+ const MediaPacket* packet = static_cast<const MediaPacket*>(*it);
+ const MediaPacket* last_packet = static_cast<const MediaPacket*>(*last_it);
+ if (IsNewerSequenceNumber(last_packet->header().sequenceNumber,
+ packet->header().sequenceNumber)) {
+ return false;
+ }
+ last_it = it;
+ }
+ return true;
+}
+
+TEST(BweTestFramework_PacketTest, IsTimeSorted) {
+ Packets packets;
+ // Insert some packets in order...
+ EXPECT_TRUE(IsTimeSorted(packets));
+
+ packets.push_back(new MediaPacket(100, 0));
+ EXPECT_TRUE(IsTimeSorted(packets));
+
+ packets.push_back(new MediaPacket(110, 0));
+ EXPECT_TRUE(IsTimeSorted(packets));
+
+ // ...and one out-of-order...
+ packets.push_back(new MediaPacket(100, 0));
+ EXPECT_FALSE(IsTimeSorted(packets));
+
+ // ...remove the out-of-order packet, insert another in-order packet.
+ delete packets.back();
+ packets.pop_back();
+ packets.push_back(new MediaPacket(120, 0));
+ EXPECT_TRUE(IsTimeSorted(packets));
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+TEST(BweTestFramework_PacketTest, IsSequenceNumberSorted) {
+ Packets packets;
+ // Insert some packets in order...
+ EXPECT_TRUE(IsSequenceNumberSorted(packets));
+
+ packets.push_back(new MediaPacket(0, 100));
+ EXPECT_TRUE(IsSequenceNumberSorted(packets));
+
+ packets.push_back(new MediaPacket(0, 110));
+ EXPECT_TRUE(IsSequenceNumberSorted(packets));
+
+ // ...and one out-of-order...
+ packets.push_back(new MediaPacket(0, 100));
+ EXPECT_FALSE(IsSequenceNumberSorted(packets));
+
+ // ...remove the out-of-order packet, insert another in-order packet.
+ delete packets.back();
+ packets.pop_back();
+ packets.push_back(new MediaPacket(0, 120));
+ EXPECT_TRUE(IsSequenceNumberSorted(packets));
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+TEST(BweTestFramework_StatsTest, Mean) {
+ Stats<int32_t> stats;
+ EXPECT_EQ(0, stats.GetMean());
+
+ stats.Push(1);
+ stats.Push(3);
+ EXPECT_EQ(2, stats.GetMean());
+
+ // Integer division rounds (1+3-3)/3 to 0.
+ stats.Push(-3);
+ EXPECT_EQ(0, stats.GetMean());
+}
+
+TEST(BweTestFramework_StatsTest, Variance) {
+ Stats<int32_t> stats;
+ EXPECT_EQ(0, stats.GetVariance());
+
+ // Mean is 2 ; ((1-2)*(1-2)+(3-2)*(3-2))/2 = (1+1)/2 = 1
+ stats.Push(1);
+ stats.Push(3);
+ EXPECT_EQ(1, stats.GetVariance());
+
+ // Integer division rounds 26/3 to 8
+ // Mean is 0 ; (1*1+3*3+(-4)*(-4))/3 = (1+9+16)/3 = 8
+ stats.Push(-4);
+ EXPECT_EQ(8, stats.GetVariance());
+}
+
+TEST(BweTestFramework_StatsTest, StdDev) {
+ Stats<int32_t> stats;
+ EXPECT_EQ(0, stats.GetStdDev());
+
+ // Variance is 1 ; sqrt(1) = 1
+ stats.Push(1);
+ stats.Push(3);
+ EXPECT_EQ(1, stats.GetStdDev());
+
+ // Variance is 8 ; sqrt(8) = 2 with integers.
+ stats.Push(-4);
+ EXPECT_EQ(2, stats.GetStdDev());
+}
+
+TEST(BweTestFramework_StatsTest, MinMax) {
+ Stats<int32_t> stats;
+ EXPECT_EQ(0, stats.GetMin());
+ EXPECT_EQ(0, stats.GetMax());
+
+ stats.Push(1);
+ EXPECT_EQ(1, stats.GetMin());
+ EXPECT_EQ(1, stats.GetMax());
+
+ stats.Push(3);
+ EXPECT_EQ(1, stats.GetMin());
+ EXPECT_EQ(3, stats.GetMax());
+
+ stats.Push(-4);
+ EXPECT_EQ(-4, stats.GetMin());
+ EXPECT_EQ(3, stats.GetMax());
+}
+
+class BweTestFramework_RateCounterFilterTest : public ::testing::Test {
+ public:
+ BweTestFramework_RateCounterFilterTest()
+ : filter_(NULL, 0, "", ""), now_ms_(0) {}
+ virtual ~BweTestFramework_RateCounterFilterTest() {}
+
+ protected:
+ void TestRateCounter(int64_t run_for_ms, uint32_t payload_bits,
+ uint32_t expected_pps, uint32_t expected_bps) {
+ Packets packets;
+ RTPHeader header;
+ // "Send" a packet every 10 ms.
+ for (int64_t i = 0; i < run_for_ms; i += 10, now_ms_ += 10) {
+ packets.push_back(
+ new MediaPacket(0, now_ms_ * 1000, payload_bits / 8, header));
+ }
+ filter_.RunFor(run_for_ms, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ EXPECT_EQ(expected_pps, filter_.packets_per_second());
+ EXPECT_EQ(expected_bps, filter_.bits_per_second());
+
+ for (auto* packet : packets)
+ delete packet;
+ }
+
+ private:
+ RateCounterFilter filter_;
+ int64_t now_ms_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweTestFramework_RateCounterFilterTest);
+};
+
+TEST_F(BweTestFramework_RateCounterFilterTest, Short) {
+ // 100ms, 100 bytes per packet, should result in 10 pps and 8 kbps. We're
+ // generating one packet every 10 ms ; 10 * 800 = 8k
+ TestRateCounter(100, 800, 10, 8000);
+}
+
+TEST_F(BweTestFramework_RateCounterFilterTest, Medium) {
+ // 100ms, like above.
+ TestRateCounter(100, 800, 10, 8000);
+ // 1000ms, 100 bpp, should result in 100 pps and 80 kbps. We're still
+ // generating packets every 10 ms.
+ TestRateCounter(900, 800, 100, 80000);
+}
+
+TEST_F(BweTestFramework_RateCounterFilterTest, Long) {
+ // 100ms, 1000ms, like above.
+ TestRateCounter(100, 800, 10, 8000);
+ TestRateCounter(900, 800, 100, 80000);
+ // 2000ms, should only see rate of last second, so 100 pps, and 40 kbps now.
+ TestRateCounter(1000, 400, 100, 40000);
+ // 2500ms, half a second with zero payload size. We should get same pps as
+ // before, but kbps should drop to half of previous rate.
+ TestRateCounter(500, 0, 100, 20000);
+ // Another half second with zero payload size. Now the kbps rate should drop
+ // to zero.
+ TestRateCounter(500, 0, 100, 0);
+ // Increate payload size again. 200 * 100 * 0.5 = 10 kbps.
+ TestRateCounter(500, 200, 100, 10000);
+}
+
+static void TestLossFilter(float loss_percent, bool zero_tolerance) {
+ LossFilter filter(NULL, 0);
+ filter.SetLoss(loss_percent);
+ Packets::size_type sent_packets = 0;
+ Packets::size_type remaining_packets = 0;
+
+ // No input should yield no output
+ {
+ Packets packets;
+ sent_packets += packets.size();
+ filter.RunFor(0, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ remaining_packets += packets.size();
+ EXPECT_EQ(0u, sent_packets);
+ EXPECT_EQ(0u, remaining_packets);
+ for (auto* packet : packets)
+ delete packet;
+ }
+
+ // Generate and process 10000 packets in different batch sizes (some empty)
+ for (int i = 0; i < 2225; ++i) {
+ Packets packets;
+ for (int j = 0; j < i % 10; ++j)
+ packets.push_back(new MediaPacket(i, i));
+ sent_packets += packets.size();
+ filter.RunFor(0, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ remaining_packets += packets.size();
+ for (auto* packet : packets)
+ delete packet;
+ }
+
+ float loss_fraction = 0.01f * (100.0f - loss_percent);
+ Packets::size_type expected_packets = loss_fraction * sent_packets;
+ if (zero_tolerance) {
+ EXPECT_EQ(expected_packets, remaining_packets);
+ } else {
+ // Require within 1% of expected
+ EXPECT_NEAR(expected_packets, remaining_packets, 100);
+ }
+}
+
+TEST(BweTestFramework_LossFilterTest, Loss0) {
+ // With 0% loss, the result should be exact (no loss).
+ TestLossFilter(0.0f, true);
+}
+
+TEST(BweTestFramework_LossFilterTest, Loss10) {
+ TestLossFilter(10.0f, false);
+}
+
+TEST(BweTestFramework_LossFilterTest, Loss50) {
+ TestLossFilter(50.0f, false);
+}
+
+TEST(BweTestFramework_LossFilterTest, Loss100) {
+ // With 100% loss, the result should be exact (no packets out).
+ TestLossFilter(100.0f, true);
+}
+
+class BweTestFramework_DelayFilterTest : public ::testing::Test {
+ public:
+ BweTestFramework_DelayFilterTest()
+ : filter_(NULL, 0), now_ms_(0), sequence_number_(0) {}
+ virtual ~BweTestFramework_DelayFilterTest() {
+ for (auto* packet : accumulated_packets_)
+ delete packet;
+ }
+
+ protected:
+ void TestDelayFilter(int64_t run_for_ms, uint32_t in_packets,
+ uint32_t out_packets) {
+ Packets packets;
+ for (uint32_t i = 0; i < in_packets; ++i) {
+ packets.push_back(new MediaPacket(
+ now_ms_ * 1000 + (sequence_number_ >> 4), sequence_number_));
+ sequence_number_++;
+ }
+ filter_.RunFor(run_for_ms, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ for (PacketsConstIt it = packets.begin(); it != packets.end(); ++it) {
+ EXPECT_LE(now_ms_ * 1000, (*it)->send_time_us());
+ }
+ EXPECT_EQ(out_packets, packets.size());
+ accumulated_packets_.splice(accumulated_packets_.end(), packets);
+ now_ms_ += run_for_ms;
+ }
+
+ void TestDelayFilter(int64_t delay_ms) {
+ filter_.SetOneWayDelayMs(delay_ms);
+ TestDelayFilter(1, 0, 0); // No input should yield no output
+
+ // Single packet
+ TestDelayFilter(0, 1, 1);
+ TestDelayFilter(delay_ms, 0, 0);
+
+ for (int i = 0; i < delay_ms; ++i) {
+ filter_.SetOneWayDelayMs(i);
+ TestDelayFilter(1, 10, 10);
+ }
+ TestDelayFilter(0, 0, 0);
+ TestDelayFilter(delay_ms, 0, 0);
+
+ // Wait a little longer - should still see no output
+ TestDelayFilter(delay_ms, 0, 0);
+
+ for (int i = 1; i < delay_ms + 1; ++i) {
+ filter_.SetOneWayDelayMs(i);
+ TestDelayFilter(1, 5, 5);
+ }
+ TestDelayFilter(0, 0, 0);
+ filter_.SetOneWayDelayMs(2 * delay_ms);
+ TestDelayFilter(1, 0, 0);
+ TestDelayFilter(delay_ms, 13, 13);
+ TestDelayFilter(delay_ms, 0, 0);
+
+ // Wait a little longer - should still see no output
+ TestDelayFilter(delay_ms, 0, 0);
+
+ for (int i = 0; i < 2 * delay_ms; ++i) {
+ filter_.SetOneWayDelayMs(2 * delay_ms - i - 1);
+ TestDelayFilter(1, 5, 5);
+ }
+ TestDelayFilter(0, 0, 0);
+ filter_.SetOneWayDelayMs(0);
+ TestDelayFilter(0, 7, 7);
+
+ ASSERT_TRUE(IsTimeSorted(accumulated_packets_));
+ ASSERT_TRUE(IsSequenceNumberSorted(accumulated_packets_));
+ }
+
+ DelayFilter filter_;
+ Packets accumulated_packets_;
+
+ private:
+ int64_t now_ms_;
+ uint16_t sequence_number_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweTestFramework_DelayFilterTest);
+};
+
+TEST_F(BweTestFramework_DelayFilterTest, Delay0) {
+ TestDelayFilter(1, 0, 0); // No input should yield no output
+ TestDelayFilter(1, 10, 10); // Expect no delay (delay time is zero)
+ TestDelayFilter(1, 0, 0); // Check no packets are still in buffer
+ filter_.SetOneWayDelayMs(0);
+ TestDelayFilter(1, 5, 5); // Expect no delay (delay time is zero)
+ TestDelayFilter(1, 0, 0); // Check no packets are still in buffer
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, Delay1) {
+ TestDelayFilter(1);
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, Delay2) {
+ TestDelayFilter(2);
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, Delay20) {
+ TestDelayFilter(20);
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, Delay100) {
+ TestDelayFilter(100);
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, JumpToZeroDelay) {
+ DelayFilter delay(NULL, 0);
+ Packets acc;
+ Packets packets;
+
+ // Delay a bunch of packets, accumulate them to the 'acc' list.
+ delay.SetOneWayDelayMs(100.0f);
+ for (uint32_t i = 0; i < 10; ++i) {
+ packets.push_back(new MediaPacket(i * 100, i));
+ }
+ delay.RunFor(1000, &packets);
+ acc.splice(acc.end(), packets);
+ ASSERT_TRUE(IsTimeSorted(acc));
+ ASSERT_TRUE(IsSequenceNumberSorted(acc));
+
+ // Drop delay to zero, send a few more packets through the delay, append them
+ // to the 'acc' list and verify that it is all sorted.
+ delay.SetOneWayDelayMs(0.0f);
+ for (uint32_t i = 10; i < 50; ++i) {
+ packets.push_back(new MediaPacket(i * 100, i));
+ }
+ delay.RunFor(1000, &packets);
+ acc.splice(acc.end(), packets);
+ ASSERT_TRUE(IsTimeSorted(acc));
+ ASSERT_TRUE(IsSequenceNumberSorted(acc));
+
+ for (auto* packet : acc)
+ delete packet;
+}
+
+TEST_F(BweTestFramework_DelayFilterTest, IncreasingDelay) {
+ // Gradually increase delay.
+ for (int i = 1; i < 50; i += 4) {
+ TestDelayFilter(i);
+ }
+ // Reach a steady state.
+ filter_.SetOneWayDelayMs(100);
+ TestDelayFilter(1, 20, 20);
+ TestDelayFilter(2, 0, 0);
+ TestDelayFilter(99, 20, 20);
+ // Drop delay back down to zero.
+ filter_.SetOneWayDelayMs(0);
+ TestDelayFilter(1, 100, 100);
+ TestDelayFilter(23010, 0, 0);
+ ASSERT_TRUE(IsTimeSorted(accumulated_packets_));
+ ASSERT_TRUE(IsSequenceNumberSorted(accumulated_packets_));
+}
+
+static void TestJitterFilter(int64_t max_jitter_ms) {
+ JitterFilter filter(NULL, 0);
+ filter.SetMaxJitter(max_jitter_ms);
+
+ int64_t now_ms = 0;
+ uint16_t sequence_number = 0;
+
+ // Generate packets, add jitter to them, accumulate the altered packets.
+ Packets original;
+ Packets jittered;
+ for (uint32_t i = 0; i < 1000; ++i) {
+ Packets packets;
+ for (uint32_t j = 0; j < i % 100; ++j) {
+ packets.push_back(new MediaPacket(now_ms * 1000, sequence_number));
+ original.push_back(new MediaPacket(now_ms * 1000, sequence_number));
+ ++sequence_number;
+ now_ms += 5 * max_jitter_ms;
+ }
+ filter.RunFor(max_jitter_ms, &packets);
+ jittered.splice(jittered.end(), packets);
+ }
+
+ // Jittered packets should still be in order.
+ ASSERT_TRUE(IsTimeSorted(original));
+ ASSERT_TRUE(IsTimeSorted(jittered));
+ ASSERT_TRUE(IsSequenceNumberSorted(original));
+ ASSERT_TRUE(IsSequenceNumberSorted(jittered));
+ EXPECT_EQ(original.size(), jittered.size());
+
+ // Make sure jittered and original packets are in same order. Collect time
+ // difference (jitter) in stats, then check that mean jitter is close to zero
+ // and standard deviation of jitter is what we set it to.
+ Stats<double> jitter_us;
+ int64_t max_jitter_obtained_us = 0;
+ for (PacketsIt it1 = original.begin(), it2 = jittered.begin();
+ it1 != original.end() && it2 != jittered.end(); ++it1, ++it2) {
+ const MediaPacket* packet1 = static_cast<const MediaPacket*>(*it1);
+ const MediaPacket* packet2 = static_cast<const MediaPacket*>(*it2);
+ EXPECT_EQ(packet1->header().sequenceNumber,
+ packet2->header().sequenceNumber);
+ max_jitter_obtained_us =
+ std::max(max_jitter_obtained_us,
+ packet2->send_time_us() - packet1->send_time_us());
+ jitter_us.Push(packet2->send_time_us() - packet1->send_time_us());
+ }
+ EXPECT_NEAR(filter.MeanUs(), jitter_us.GetMean(),
+ max_jitter_ms * 1000.0 * 0.01);
+ EXPECT_NEAR(max_jitter_ms * 1000.0, max_jitter_obtained_us,
+ max_jitter_ms * 1000.0 * 0.01);
+ for (auto* packet : original)
+ delete packet;
+ for (auto* packet : jittered)
+ delete packet;
+}
+
+TEST(BweTestFramework_JitterFilterTest, Jitter0) {
+ TestJitterFilter(0);
+}
+
+TEST(BweTestFramework_JitterFilterTest, Jitter1) {
+ TestJitterFilter(1);
+}
+
+TEST(BweTestFramework_JitterFilterTest, Jitter5) {
+ TestJitterFilter(5);
+}
+
+TEST(BweTestFramework_JitterFilterTest, Jitter10) {
+ TestJitterFilter(10);
+}
+
+TEST(BweTestFramework_JitterFilterTest, Jitter1031) {
+ TestJitterFilter(1031);
+}
+
+static void TestReorderFilter(uint16_t reorder_percent, uint16_t near_value) {
+ const uint16_t kPacketCount = 10000;
+
+ // Generate packets with 10 ms interval.
+ Packets packets;
+ int64_t now_ms = 0;
+ uint16_t sequence_number = 1;
+ for (uint16_t i = 0; i < kPacketCount; ++i, now_ms += 10) {
+ packets.push_back(new MediaPacket(now_ms * 1000, sequence_number++));
+ }
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+
+ // Reorder packets, verify that send times are still in order.
+ ReorderFilter filter(NULL, 0);
+ filter.SetReorder(reorder_percent);
+ filter.RunFor(now_ms, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+
+ // We measure the amount of reordering by summing the distance by which out-
+ // of-order packets have been moved in the stream.
+ uint16_t distance = 0;
+ uint16_t last_sequence_number = 0;
+ for (auto* packet : packets) {
+ const MediaPacket* media_packet = static_cast<const MediaPacket*>(packet);
+ uint16_t sequence_number = media_packet->header().sequenceNumber;
+ if (sequence_number < last_sequence_number) {
+ distance += last_sequence_number - sequence_number;
+ }
+ last_sequence_number = sequence_number;
+ }
+
+ // Because reordering is random, we allow a threshold when comparing. The
+ // maximum distance a packet can be moved is PacketCount - 1.
+ EXPECT_NEAR(
+ ((kPacketCount - 1) * reorder_percent) / 100, distance, near_value);
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder0) {
+ // For 0% reordering, no packets should have been moved, so result is exact.
+ TestReorderFilter(0, 0);
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder10) {
+ TestReorderFilter(10, 30);
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder20) {
+ TestReorderFilter(20, 20);
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder50) {
+ TestReorderFilter(50, 20);
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder70) {
+ TestReorderFilter(70, 20);
+}
+
+TEST(BweTestFramework_ReorderFilterTest, Reorder100) {
+ // Note that because the implementation works by optionally swapping two
+ // adjacent packets, when the likelihood of a swap is 1.0, a swap will always
+ // occur, so the stream will be in order except for the first packet, which
+ // has been moved to the end. Therefore we expect the result to be exact here.
+ TestReorderFilter(100.0, 0);
+}
+
+class BweTestFramework_ChokeFilterTest : public ::testing::Test {
+ public:
+ BweTestFramework_ChokeFilterTest()
+ : now_ms_(0),
+ sequence_number_(0),
+ output_packets_(),
+ send_times_us_() {
+ }
+ virtual ~BweTestFramework_ChokeFilterTest() {
+ for (auto* packet : output_packets_)
+ delete packet;
+ }
+
+ protected:
+ void TestChoke(PacketProcessor* filter,
+ int64_t run_for_ms,
+ uint32_t packets_to_generate,
+ size_t expected_kbit_transmitted) {
+ // Generate a bunch of packets, apply choke, verify output is ordered.
+ Packets packets;
+ RTPHeader header;
+ for (uint32_t i = 0; i < packets_to_generate; ++i) {
+ int64_t send_time_ms = now_ms_ + (i * run_for_ms) / packets_to_generate;
+ header.sequenceNumber = sequence_number_++;
+ // Payload is 1000 bits.
+ packets.push_back(new MediaPacket(0, send_time_ms * 1000, 125, header));
+ send_times_us_.push_back(send_time_ms * 1000);
+ }
+ ASSERT_TRUE(IsTimeSorted(packets));
+ filter->RunFor(run_for_ms, &packets);
+ now_ms_ += run_for_ms;
+ output_packets_.splice(output_packets_.end(), packets);
+ ASSERT_TRUE(IsTimeSorted(output_packets_));
+ ASSERT_TRUE(IsSequenceNumberSorted(output_packets_));
+
+ // Sum up the transmitted bytes up until the current time.
+ size_t bytes_transmitted = 0;
+ while (!output_packets_.empty()) {
+ const Packet* packet = output_packets_.front();
+ if (packet->send_time_us() > now_ms_ * 1000) {
+ break;
+ }
+ bytes_transmitted += packet->payload_size();
+ delete output_packets_.front();
+ output_packets_.pop_front();
+ }
+ EXPECT_EQ(expected_kbit_transmitted, (bytes_transmitted * 8 + 500) / 1000);
+ }
+
+ void CheckMaxDelay(int64_t max_delay_ms) {
+ for (const auto* packet : output_packets_) {
+ const MediaPacket* media_packet = static_cast<const MediaPacket*>(packet);
+ int64_t delay_us = media_packet->send_time_us() -
+ send_times_us_[media_packet->header().sequenceNumber];
+ EXPECT_GE(max_delay_ms * 1000, delay_us);
+ }
+ }
+
+ private:
+ int64_t now_ms_;
+ uint16_t sequence_number_;
+ Packets output_packets_;
+ std::vector<int64_t> send_times_us_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(BweTestFramework_ChokeFilterTest);
+};
+
+TEST_F(BweTestFramework_ChokeFilterTest, NoQueue) {
+ const int kCapacityKbps = 10;
+ const size_t kPacketSizeBytes = 125;
+ const int64_t kExpectedSendTimeUs =
+ (kPacketSizeBytes * 8 * 1000 + kCapacityKbps / 2) / kCapacityKbps;
+ uint16_t sequence_number = 0;
+ int64_t send_time_us = 0;
+ ChokeFilter filter(NULL, 0);
+ filter.set_capacity_kbps(10);
+ Packets packets;
+ RTPHeader header;
+ for (int i = 0; i < 2; ++i) {
+ header.sequenceNumber = sequence_number++;
+ // Payload is 1000 bits.
+ packets.push_back(
+ new MediaPacket(0, send_time_us, kPacketSizeBytes, header));
+ // Packets are sent far enough a part plus an extra millisecond so that they
+ // will never be in the choke queue at the same time.
+ send_time_us += kExpectedSendTimeUs + 1000;
+ }
+ ASSERT_TRUE(IsTimeSorted(packets));
+ filter.RunFor(2 * kExpectedSendTimeUs + 1000, &packets);
+ EXPECT_EQ(kExpectedSendTimeUs, packets.front()->send_time_us());
+ delete packets.front();
+ packets.pop_front();
+ EXPECT_EQ(2 * kExpectedSendTimeUs + 1000, packets.front()->send_time_us());
+ delete packets.front();
+ packets.pop_front();
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, Short) {
+ // 100ms, 100 packets, 10 kbps choke -> 1 kbit of data should have propagated.
+ // That is actually just a single packet, since each packet has 1000 bits of
+ // payload.
+ ChokeFilter filter(NULL, 0);
+ filter.set_capacity_kbps(10);
+ TestChoke(&filter, 100, 100, 1);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, Medium) {
+ // 100ms, 10 packets, 10 kbps choke -> 1 packet through, or 1 kbit.
+ ChokeFilter filter(NULL, 0);
+ filter.set_capacity_kbps(10);
+ TestChoke(&filter, 100, 10, 1);
+ // 200ms, no new packets -> another packet through.
+ TestChoke(&filter, 100, 0, 1);
+ // 1000ms, no new packets -> 8 more packets.
+ TestChoke(&filter, 800, 0, 8);
+ // 2000ms, no new packets -> queue is empty so no output.
+ TestChoke(&filter, 1000, 0, 0);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, Long) {
+ // 100ms, 100 packets in queue, 10 kbps choke -> 1 packet through, or 1 kbit.
+ ChokeFilter filter(NULL, 0);
+ filter.set_capacity_kbps(10);
+ TestChoke(&filter, 100, 100, 1);
+ // 200ms, no input, another packet through.
+ TestChoke(&filter, 100, 0, 1);
+ // 1000ms, no input, 8 packets through.
+ TestChoke(&filter, 800, 0, 8);
+ // 10000ms, no input, raise choke to 100 kbps. Remaining 90 packets in queue
+ // should be propagated, for a total of 90 kbps.
+ filter.set_capacity_kbps(100);
+ TestChoke(&filter, 9000, 0, 90);
+ // 10100ms, 20 more packets -> 10 packets or 10 kbit through.
+ TestChoke(&filter, 100, 20, 10);
+ // 10300ms, 10 more packets -> 20 packets out.
+ TestChoke(&filter, 200, 10, 20);
+ // 11300ms, no input, queue should be empty.
+ filter.set_capacity_kbps(10);
+ TestChoke(&filter, 1000, 0, 0);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, MaxDelay) {
+ // 10 kbps choke, 500 ms delay cap
+ ChokeFilter filter(NULL, 0);
+ filter.set_capacity_kbps(10);
+ filter.set_max_delay_ms(500);
+ // 100ms, 100 packets in queue, 10 kbps choke -> 1 packet through, or 1 kbit.
+ TestChoke(&filter, 100, 100, 1);
+ CheckMaxDelay(500);
+ // 500ms, no input, 4 more packets through.
+ TestChoke(&filter, 400, 0, 4);
+ // 10000ms, no input, remaining packets should have been dropped.
+ TestChoke(&filter, 9500, 0, 0);
+
+ // 100 ms delay cap
+ filter.set_max_delay_ms(100);
+ // 10100ms, 50 more packets -> 1 packets or 1 kbit through.
+ TestChoke(&filter, 100, 50, 1);
+ CheckMaxDelay(100);
+ // 20000ms, no input, remaining packets in queue should have been dropped.
+ TestChoke(&filter, 9900, 0, 0);
+
+ // Reset delay cap (0 is no cap) and verify no packets are dropped.
+ filter.set_capacity_kbps(10);
+ filter.set_max_delay_ms(0);
+ TestChoke(&filter, 100, 100, 1);
+ TestChoke(&filter, 9900, 0, 99);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, ShortTrace) {
+ // According to the input file 6 packets should be transmitted within
+ // 100 milliseconds.
+ TraceBasedDeliveryFilter filter(NULL, 0);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("synthetic-trace", "rx")));
+ TestChoke(&filter, 100, 100, 6);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, ShortTraceTwoWraps) {
+ // According to the input file 19 packets should be transmitted within
+ // 280 milliseconds (at the wrapping point two packets are sent back to back).
+ TraceBasedDeliveryFilter filter(NULL, 0);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("synthetic-trace", "rx")));
+ TestChoke(&filter, 280, 100, 19);
+}
+
+TEST_F(BweTestFramework_ChokeFilterTest, ShortTraceMaxDelay) {
+ TraceBasedDeliveryFilter filter(NULL, 0);
+ filter.set_max_delay_ms(25);
+ ASSERT_TRUE(filter.Init(test::ResourcePath("synthetic-trace", "rx")));
+ // Uses all slots up to 110 ms. Several packets are being dropped.
+ TestChoke(&filter, 110, 20, 9);
+ CheckMaxDelay(25);
+ // Simulate enough time for the next slot (at 135 ms) to be used. This makes
+ // sure that a slot isn't missed between runs.
+ TestChoke(&filter, 25, 1, 1);
+}
+
+void TestVideoSender(VideoSender* sender,
+ int64_t run_for_ms,
+ uint32_t expected_packets,
+ uint32_t expected_payload_size,
+ size_t expected_total_payload_size) {
+ assert(sender);
+ Packets packets;
+ sender->RunFor(run_for_ms, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ EXPECT_EQ(expected_packets, packets.size());
+
+ int64_t send_time_us = -1;
+ size_t total_payload_size = 0;
+ uint32_t absolute_send_time = 0;
+ uint32_t absolute_send_time_wraps = 0;
+ uint32_t rtp_timestamp = 0;
+ uint32_t rtp_timestamp_wraps = 0;
+
+ for (const auto* packet : packets) {
+ const MediaPacket* media_packet = static_cast<const MediaPacket*>(packet);
+ EXPECT_LE(send_time_us, media_packet->send_time_us());
+ send_time_us = media_packet->send_time_us();
+ if (sender->source()->max_payload_size_bytes() !=
+ media_packet->payload_size()) {
+ EXPECT_EQ(expected_payload_size, media_packet->payload_size());
+ }
+ total_payload_size += media_packet->payload_size();
+ if (absolute_send_time >
+ media_packet->header().extension.absoluteSendTime) {
+ absolute_send_time_wraps++;
+ }
+ absolute_send_time = media_packet->header().extension.absoluteSendTime;
+ if (rtp_timestamp > media_packet->header().timestamp) {
+ rtp_timestamp_wraps++;
+ }
+ rtp_timestamp = media_packet->header().timestamp;
+ }
+
+ EXPECT_EQ(expected_total_payload_size, total_payload_size);
+ EXPECT_GE(1u, absolute_send_time_wraps);
+ EXPECT_GE(1u, rtp_timestamp_wraps);
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+// Random {-1, 0, +1} ms was added to frame timestamps.
+
+TEST(BweTestFramework_VideoSenderTest, Fps1Kbps80_1s) {
+ // 1 fps, 80 kbps
+ VideoSource source(0, 1.0f, 80, 0x1234, 0);
+ VideoSender sender(NULL, &source, kNullEstimator);
+ EXPECT_EQ(80000u, source.bits_per_second());
+ // We're at 1 fps, so all packets should be generated on first call, giving 10
+ // packets of each 1000 bytes, total 10000 bytes.
+ TestVideoSender(&sender, 1, 9, 400, 10000);
+ // 998ms, should see no output here.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+ // 1001ms, should get data for one more frame.
+ TestVideoSender(&sender, 3, 9, 400, 10000);
+ // 1998ms, should see no output here.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+ // 2001ms, one more frame.
+ TestVideoSender(&sender, 3, 9, 400, 10000);
+ // 2998ms, should see nothing.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+}
+
+TEST(BweTestFramework_VideoSenderTest, Fps1Kbps80_1s_Offset) {
+ // 1 fps, 80 kbps, offset 0.5 of a frame period, ==0.5s in this case.
+ VideoSource source(0, 1.0f, 80, 0x1234, 500);
+ VideoSender sender(NULL, &source, kNullEstimator);
+ EXPECT_EQ(80000u, source.bits_per_second());
+ // 498ms, no output.
+ TestVideoSender(&sender, 498, 0, 0, 0);
+ // 501ms, first frame (this is the offset we set), 10 packets of 1000 bytes.
+ TestVideoSender(&sender, 3, 9, 400, 10000);
+ // 1498ms, nothing.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+ // 1501ms, second frame.
+ TestVideoSender(&sender, 3, 9, 400, 10000);
+ // 2498ms, nothing.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+ // 2501ms, third frame.
+ TestVideoSender(&sender, 3, 9, 400, 10000);
+ // 3498ms, nothing.
+ TestVideoSender(&sender, 997, 0, 0, 0);
+}
+
+TEST(BweTestFramework_VideoSenderTest, Fps50Kpbs80_11s) {
+ // 50 fps, 80 kbps.
+ VideoSource source(0, 50.0f, 80, 0x1234, 0);
+ VideoSender sender(NULL, &source, kNullEstimator);
+ EXPECT_EQ(80000u, source.bits_per_second());
+ // 9981, should see 500 frames, 200 byte payloads, total 100000 bytes.
+ TestVideoSender(&sender, 9981, 500, 200, 100000);
+ // 9998ms, nothing.
+ TestVideoSender(&sender, 17, 0, 0, 0);
+ // 10001ms, 501st frame as a single packet.
+ TestVideoSender(&sender, 3, 1, 200, 200);
+ // 10981ms, 49 more frames.
+ TestVideoSender(&sender, 981, 49, 200, 9800);
+ // 10998ms, nothing.
+ TestVideoSender(&sender, 17, 0, 0, 0);
+}
+
+TEST(BweTestFramework_VideoSenderTest, Fps20Kpbs120_1s) {
+ // 20 fps, 120 kbps.
+ VideoSource source(0, 20.0f, 120, 0x1234, 0);
+ VideoSender sender(NULL, &source, kNullEstimator);
+ EXPECT_EQ(120000u, source.bits_per_second());
+ // 451ms, 10 frames with 750 byte payloads, total 7500 bytes.
+ TestVideoSender(&sender, 451, 10, 750, 7500);
+ // 498ms, nothing.
+ TestVideoSender(&sender, 47, 0, 0, 0);
+ // 501ms, one more frame.
+ TestVideoSender(&sender, 3, 1, 750, 750);
+ // 951ms, 9 more frames.
+ TestVideoSender(&sender, 450, 9, 750, 6750);
+ // 998ms, nothing.
+ TestVideoSender(&sender, 47, 0, 0, 0);
+}
+
+TEST(BweTestFramework_VideoSenderTest, Fps25Kbps820_20s) {
+ // 25 fps, 820 kbps.
+ VideoSource source(0, 25.0f, 820, 0x1234, 0);
+ VideoSender sender(NULL, &source, kNullEstimator);
+ EXPECT_EQ(820000u, source.bits_per_second());
+ // 9961ms, 250 frames. 820 kbps = 102500 bytes/s, so total should be 1025000.
+ // Each frame is 102500/25=4100 bytes, or 5 packets (4 @1000 bytes, 1 @100),
+ // so packet count should be 5*250=1250 and last packet of each frame has
+ // 100 bytes of payload.
+ TestVideoSender(&sender, 9961, 1000, 500, 1025000);
+ // 9998ms, nothing.
+ TestVideoSender(&sender, 37, 0, 0, 0);
+ // 19961ms, 250 more frames.
+ TestVideoSender(&sender, 9963, 1000, 500, 1025000);
+ // 19998ms, nothing.
+ TestVideoSender(&sender, 37, 0, 0, 0);
+ // 20001ms, one more frame, as described above (25fps == 40ms/frame).
+ TestVideoSender(&sender, 3, 4, 500, 4100);
+ // 20038ms, nothing.
+ TestVideoSender(&sender, 37, 0, 0, 0);
+}
+
+TEST(BweTestFramework_VideoSenderTest, TestAppendInOrder) {
+ // 1 fps, 80 kbps, 250ms offset.
+ VideoSource source1(0, 1.0f, 80, 0x1234, 250);
+ VideoSender sender1(NULL, &source1, kNullEstimator);
+ EXPECT_EQ(80000u, source1.bits_per_second());
+ Packets packets;
+ // Generate some packets, verify they are sorted.
+ sender1.RunFor(999, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ EXPECT_EQ(9u, packets.size());
+ // Generate some more packets and verify they are appended to end of list.
+ sender1.RunFor(1000, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ ASSERT_TRUE(IsSequenceNumberSorted(packets));
+ EXPECT_EQ(18u, packets.size());
+
+ // Another sender, 2 fps, 160 kbps, 150ms offset
+ VideoSource source2(0, 2.0f, 160, 0x2234, 150);
+ VideoSender sender2(NULL, &source2, kNullEstimator);
+ EXPECT_EQ(160000u, source2.bits_per_second());
+ // Generate some packets, verify that they are merged with the packets already
+ // on the list.
+ sender2.RunFor(999, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ EXPECT_EQ(36u, packets.size());
+ // Generate some more.
+ sender2.RunFor(1000, &packets);
+ ASSERT_TRUE(IsTimeSorted(packets));
+ EXPECT_EQ(54u, packets.size());
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+TEST(BweTestFramework_VideoSenderTest, FeedbackIneffective) {
+ VideoSource source(0, 25.0f, 820, 0x1234, 0);
+ VideoSender sender(NULL, &source, kNullEstimator);
+
+ EXPECT_EQ(820000u, source.bits_per_second());
+ TestVideoSender(&sender, 9961, 1000, 500, 1025000);
+
+ // Make sure feedback has no effect on a regular video sender.
+ RembFeedback* feedback = new RembFeedback(0, 0, 0, 512000, RTCPReportBlock());
+ Packets packets;
+ packets.push_back(feedback);
+ sender.RunFor(0, &packets);
+ EXPECT_EQ(820000u, source.bits_per_second());
+ TestVideoSender(&sender, 10000, 1000, 500, 1025000);
+}
+
+TEST(BweTestFramework_AdaptiveVideoSenderTest, FeedbackChangesBitrate) {
+ AdaptiveVideoSource source(0, 25.0f, 820, 0x1234, 0);
+ VideoSender sender(NULL, &source, kRembEstimator);
+ EXPECT_EQ(820000u, source.bits_per_second());
+ TestVideoSender(&sender, 9961, 1000, 500, 1025000);
+
+ // Make sure we can reduce the bitrate.
+ RembFeedback* feedback = new RembFeedback(0, 0, 0, 512000, RTCPReportBlock());
+ Packets packets;
+ packets.push_back(feedback);
+ sender.RunFor(0, &packets);
+ EXPECT_EQ(512000u, source.bits_per_second());
+ TestVideoSender(&sender, 10000, 750, 160, 640000);
+
+ // Increase the bitrate to the initial bitrate and verify that the output is
+ // the same.
+ feedback = new RembFeedback(0, 0, 0, 820000, RTCPReportBlock());
+ packets.push_back(feedback);
+ sender.RunFor(10000, &packets);
+ EXPECT_EQ(820000u, source.bits_per_second());
+
+ for (auto* packet : packets)
+ delete packet;
+}
+
+TEST(BweTestFramework_AdaptiveVideoSenderTest, Paced_FeedbackChangesBitrate) {
+ AdaptiveVideoSource source(0, 25.0f, 820, 0x1234, 0);
+ PacedVideoSender sender(NULL, &source, kRembEstimator);
+ EXPECT_EQ(820000u, source.bits_per_second());
+ TestVideoSender(&sender, 9998, 1000, 500, 1025000);
+
+ // Make sure we can reduce the bitrate.
+ RembFeedback* feedback = new RembFeedback(0, 1, 0, 512000, RTCPReportBlock());
+ Packets packets;
+ packets.push_back(feedback);
+ sender.RunFor(10000, &packets);
+ ASSERT_EQ(512000u, source.bits_per_second());
+ TestVideoSender(&sender, 10000, 750, 160, 640000);
+
+ // Increase the bitrate to the initial bitrate and verify that the output is
+ // the same.
+ feedback = new RembFeedback(0, 0, 0, 820000, RTCPReportBlock());
+ packets.push_back(feedback);
+ sender.RunFor(10000, &packets);
+ EXPECT_EQ(820000u, source.bits_per_second());
+
+ for (auto* packet : packets)
+ delete packet;
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.cc
new file mode 100644
index 0000000000..dcc08d8dde
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.cc
@@ -0,0 +1,250 @@
+/*
+ * 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/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+
+#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <algorithm>
+#include <sstream>
+
+#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
+#include "webrtc/system_wrappers/include/thread_wrapper.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+Logging Logging::g_Logging;
+
+static std::string ToString(uint32_t v) {
+ std::stringstream ss;
+ ss << v;
+ return ss.str();
+}
+
+Logging::Context::Context(uint32_t name, int64_t timestamp_ms, bool enabled) {
+ Logging::GetInstance()->PushState(ToString(name), timestamp_ms, enabled);
+}
+
+Logging::Context::Context(const std::string& name, int64_t timestamp_ms,
+ bool enabled) {
+ Logging::GetInstance()->PushState(name, timestamp_ms, enabled);
+}
+
+Logging::Context::Context(const char* name, int64_t timestamp_ms,
+ bool enabled) {
+ Logging::GetInstance()->PushState(name, timestamp_ms, enabled);
+}
+
+Logging::Context::~Context() {
+ Logging::GetInstance()->PopState();
+}
+
+Logging* Logging::GetInstance() {
+ return &g_Logging;
+}
+
+void Logging::SetGlobalContext(uint32_t name) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ thread_map_[ThreadWrapper::GetThreadId()].global_state.tag = ToString(name);
+}
+
+void Logging::SetGlobalContext(const std::string& name) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ thread_map_[ThreadWrapper::GetThreadId()].global_state.tag = name;
+}
+
+void Logging::SetGlobalContext(const char* name) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ thread_map_[ThreadWrapper::GetThreadId()].global_state.tag = name;
+}
+
+void Logging::SetGlobalEnable(bool enabled) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ thread_map_[ThreadWrapper::GetThreadId()].global_state.enabled = enabled;
+}
+
+void Logging::Log(const char format[], ...) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("%s\t", state.tag.c_str());
+ va_list args;
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+ }
+}
+
+void Logging::Plot(int figure, double value) {
+ Plot(figure, value, "-");
+}
+
+void Logging::Plot(int figure, double value, const std::string& alg_name) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ std::string label = state.tag + '@' + alg_name;
+ std::string prefix("Available");
+ if (alg_name.compare(0, prefix.length(), prefix) == 0) {
+ std::string receiver("Receiver");
+ size_t start_pos = label.find(receiver);
+ if (start_pos != std::string::npos) {
+ label.replace(start_pos, receiver.length(), "Sender");
+ }
+ }
+ if (state.enabled) {
+ printf("PLOT\t%d\t%s\t%f\t%f\n", figure, label.c_str(),
+ state.timestamp_ms * 0.001, value);
+ }
+}
+
+void Logging::PlotBar(int figure,
+ const std::string& name,
+ double value,
+ int flow_id) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("BAR\t%d\t%s_%d\t%f\n", figure, name.c_str(), flow_id, value);
+ }
+}
+
+void Logging::PlotBaselineBar(int figure,
+ const std::string& name,
+ double value,
+ int flow_id) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("BASELINE\t%d\t%s_%d\t%f\n", figure, name.c_str(), flow_id, value);
+ }
+}
+
+void Logging::PlotErrorBar(int figure,
+ const std::string& name,
+ double value,
+ double ylow,
+ double yhigh,
+ const std::string& error_title,
+ int flow_id) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("ERRORBAR\t%d\t%s_%d\t%f\t%f\t%f\t%s\n", figure, name.c_str(),
+ flow_id, value, ylow, yhigh, error_title.c_str());
+ }
+}
+
+void Logging::PlotLimitErrorBar(int figure,
+ const std::string& name,
+ double value,
+ double ylow,
+ double yhigh,
+ const std::string& error_title,
+ double ymax,
+ const std::string& limit_title,
+ int flow_id) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("LIMITERRORBAR\t%d\t%s_%d\t%f\t%f\t%f\t%s\t%f\t%s\n", figure,
+ name.c_str(), flow_id, value, ylow, yhigh, error_title.c_str(), ymax,
+ limit_title.c_str());
+ }
+}
+
+void Logging::PlotLabel(int figure,
+ const std::string& title,
+ const std::string& y_label,
+ int num_flows) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ const State& state = it->second.stack.top();
+ if (state.enabled) {
+ printf("LABEL\t%d\t%s\t%s\t%d\n", figure, title.c_str(), y_label.c_str(),
+ num_flows);
+ }
+}
+
+Logging::Logging()
+ : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
+ thread_map_() {
+}
+
+Logging::State::State() : tag(""), timestamp_ms(0), enabled(true) {}
+
+Logging::State::State(const std::string& tag, int64_t timestamp_ms,
+ bool enabled)
+ : tag(tag),
+ timestamp_ms(timestamp_ms),
+ enabled(enabled) {
+}
+
+void Logging::State::MergePrevious(const State& previous) {
+ if (tag.empty()) {
+ tag = previous.tag;
+ } else if (!previous.tag.empty()) {
+ tag = previous.tag + "_" + tag;
+ }
+ timestamp_ms = std::max(previous.timestamp_ms, timestamp_ms);
+ enabled = previous.enabled && enabled;
+}
+
+void Logging::PushState(const std::string& append_to_tag, int64_t timestamp_ms,
+ bool enabled) {
+ CriticalSectionScoped cs(crit_sect_.get());
+ State new_state(append_to_tag, timestamp_ms, enabled);
+ ThreadState* thread_state = &thread_map_[ThreadWrapper::GetThreadId()];
+ std::stack<State>* stack = &thread_state->stack;
+ if (stack->empty()) {
+ new_state.MergePrevious(thread_state->global_state);
+ } else {
+ new_state.MergePrevious(stack->top());
+ }
+ stack->push(new_state);
+}
+
+void Logging::PopState() {
+ CriticalSectionScoped cs(crit_sect_.get());
+ ThreadMap::iterator it = thread_map_.find(ThreadWrapper::GetThreadId());
+ assert(it != thread_map_.end());
+ std::stack<State>* stack = &it->second.stack;
+ int64_t newest_timestamp_ms = stack->top().timestamp_ms;
+ stack->pop();
+ if (!stack->empty()) {
+ State* state = &stack->top();
+ // Update time so that next log/plot will use the latest time seen so far
+ // in this call tree.
+ state->timestamp_ms = std::max(state->timestamp_ms, newest_timestamp_ms);
+ }
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h
new file mode 100644
index 0000000000..4115d30c2a
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h
@@ -0,0 +1,323 @@
+/*
+ * 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
+
+// To enable BWE logging, run this command from trunk/ :
+// build/gyp_chromium --depth=. webrtc/modules/modules.gyp
+// -Denable_bwe_test_logging=1
+#ifndef BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+#define BWE_TEST_LOGGING_COMPILE_TIME_ENABLE 0
+#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+
+// BWE logging allows you to insert dynamically named log/plot points in the
+// call tree. E.g. the function:
+// void f1() {
+// BWE_TEST_LOGGING_TIME(clock_->TimeInMilliseconds());
+// BWE_TEST_LOGGING_CONTEXT("stream");
+// for (uint32_t i=0; i<4; ++i) {
+// BWE_TEST_LOGGING_ENABLE(i & 1);
+// BWE_TEST_LOGGING_CONTEXT(i);
+// BWE_TEST_LOGGING_LOG1("weight", "%f tonnes", weights_[i]);
+// for (float j=0.0f; j<1.0; j+=0.4f) {
+// BWE_TEST_LOGGING_PLOT(0, "bps", -1, j);
+// }
+// }
+// }
+//
+// Might produce the output:
+// stream_00000001_weight 13.000000 tonnes
+// PLOT stream_00000001_bps 1.000000 0.000000
+// PLOT stream_00000001_bps 1.000000 0.400000
+// PLOT stream_00000001_bps 1.000000 0.800000
+// stream_00000003_weight 39.000000 tonnes
+// PLOT stream_00000003_bps 1.000000 0.000000
+// PLOT stream_00000003_bps 1.000000 0.400000
+// PLOT stream_00000003_bps 1.000000 0.800000
+//
+// Log *contexts* are names concatenated with '_' between them, with the name
+// of the logged/plotted string/value last. Plot *time* is inherited down the
+// tree. A branch is enabled by default but can be *disabled* to reduce output.
+// The difference between the LOG and PLOT macros is that PLOT prefixes the line
+// so it can be easily filtered, plus it outputs the current time.
+
+#if !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
+
+// Set a thread-global base logging context. This name will be prepended to all
+// hierarchical contexts.
+// |name| is a char*, std::string or uint32_t to name the context.
+#define BWE_TEST_LOGGING_GLOBAL_CONTEXT(name)
+
+// Thread-globally allow/disallow logging.
+// |enable| is expected to be a bool.
+#define BWE_TEST_LOGGING_GLOBAL_ENABLE(enabled)
+
+// Insert a (hierarchical) logging context.
+// |name| is a char*, std::string or uint32_t to name the context.
+#define BWE_TEST_LOGGING_CONTEXT(name)
+
+// Allow/disallow logging down the call tree from this point. Logging must be
+// enabled all the way to the root of the call tree to take place.
+// |enable| is expected to be a bool.
+#define BWE_TEST_LOGGING_ENABLE(enabled)
+
+// Set current time (only affects PLOT output). Down the call tree, the latest
+// time set always takes precedence.
+// |time| is an int64_t time in ms, or -1 to inherit time from previous context.
+#define BWE_TEST_LOGGING_TIME(time)
+
+// Print to stdout, e.g.:
+// Context1_Context2_Name printf-formated-string
+// |name| is a char*, std::string or uint32_t to name the log line.
+// |format| is a printf format string.
+// |_1...| are arguments for printf.
+#define BWE_TEST_LOGGING_LOG1(name, format, _1)
+#define BWE_TEST_LOGGING_LOG2(name, format, _1, _2)
+#define BWE_TEST_LOGGING_LOG3(name, format, _1, _2, _3)
+#define BWE_TEST_LOGGING_LOG4(name, format, _1, _2, _3, _4)
+#define BWE_TEST_LOGGING_LOG5(name, format, _1, _2, _3, _4, _5)
+
+// Print to stdout in tab-separated format suitable for plotting, e.g.:
+// PLOT figure Context1_Context2_Name time value
+// |figure| is a figure id. Different figures are plotted in different windows.
+// |name| is a char*, std::string or uint32_t to name the plotted value.
+// |time| is an int64_t time in ms, or -1 to inherit time from previous context.
+// |value| is a double precision float to be plotted.
+// |alg_name| is an optional argument, a string
+#define BWE_TEST_LOGGING_PLOT(figure, name, time, value)
+#define BWE_TEST_LOGGING_PLOT_WITH_NAME(figure, name, time, value, alg_name)
+
+// Print to stdout in tab-separated format suitable for plotting, e.g.:
+// BAR figure Context1_Context2_Name x_left width value
+// |figure| is a figure id. Different figures are plotted in different windows.
+// |name| is a char*, std::string or uint32_t to name the plotted value.
+// |value| is a double precision float to be plotted.
+// |ylow| and |yhigh| are double precision float for the error line.
+// |title| is a string and refers to the error label.
+// |ymax| is a double precision float for the limit horizontal line.
+// |limit_title| is a string and refers to the limit label.
+#define BWE_TEST_LOGGING_BAR(figure, name, value, flow_id)
+#define BWE_TEST_LOGGING_ERRORBAR(figure, name, value, ylow, yhigh, \
+ error_title, flow_id)
+#define BWE_TEST_LOGGING_LIMITERRORBAR( \
+ figure, name, value, ylow, yhigh, error_title, ymax, limit_title, flow_id)
+
+#define BWE_TEST_LOGGING_BASELINEBAR(figure, name, value, flow_id)
+
+// |num_flows| is an integer refering to the number of RMCAT flows in the
+// scenario.
+// Define |x_label| and |y_label| for plots.
+#define BWE_TEST_LOGGING_LABEL(figure, x_label, y_label, num_flows)
+
+#else // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+
+#include <map>
+#include <stack>
+#include <string>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/common_types.h"
+
+#define BWE_TEST_LOGGING_GLOBAL_CONTEXT(name) \
+ do { \
+ webrtc::testing::bwe::Logging::GetInstance()->SetGlobalContext(name); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_GLOBAL_ENABLE(enabled) \
+ do { \
+ webrtc::testing::bwe::Logging::GetInstance()->SetGlobalEnable(enabled); \
+ } while (0);
+
+#define __BWE_TEST_LOGGING_CONTEXT_NAME(ctx, line) ctx ## line
+#define __BWE_TEST_LOGGING_CONTEXT_DECLARE(ctx, line, name, time, enabled) \
+ webrtc::testing::bwe::Logging::Context \
+ __BWE_TEST_LOGGING_CONTEXT_NAME(ctx, line)(name, time, enabled)
+
+#define BWE_TEST_LOGGING_CONTEXT(name) \
+ __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, name, -1, true)
+#define BWE_TEST_LOGGING_ENABLE(enabled) \
+ __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, "", -1, \
+ static_cast<bool>(enabled))
+#define BWE_TEST_LOGGING_TIME(time) \
+ __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, "", \
+ static_cast<int64_t>(time), true)
+
+#define BWE_TEST_LOGGING_LOG1(name, format, _1) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1); \
+ } while (0);
+#define BWE_TEST_LOGGING_LOG2(name, format, _1, _2) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2); \
+ } while (0);
+#define BWE_TEST_LOGGING_LOG3(name, format, _1, _2, _3) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3); \
+ } while (0);
+#define BWE_TEST_LOGGING_LOG4(name, format, _1, _2, _3, _4) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3, \
+ _4); \
+ } while (0);
+#define BWE_TEST_LOGGING_LOG5(name, format, _1, _2, _3, _4, _5) \
+ do {\
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3, \
+ _4, _5); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_PLOT(figure, name, time, value) \
+ do { \
+ __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
+ static_cast<int64_t>(time), true); \
+ webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, value); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_PLOT_WITH_NAME(figure, name, time, value, alg_name) \
+ do { \
+ __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
+ static_cast<int64_t>(time), true); \
+ webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, value, \
+ alg_name); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_BAR(figure, name, value, flow_id) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->PlotBar(figure, name, value, \
+ flow_id); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_BASELINEBAR(figure, name, value, flow_id) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->PlotBaselineBar( \
+ figure, name, value, flow_id); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_ERRORBAR(figure, name, value, ylow, yhigh, title, \
+ flow_id) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->PlotErrorBar( \
+ figure, name, value, ylow, yhigh, title, flow_id); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_LIMITERRORBAR( \
+ figure, name, value, ylow, yhigh, error_title, ymax, limit_title, flow_id) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(name); \
+ webrtc::testing::bwe::Logging::GetInstance()->PlotLimitErrorBar( \
+ figure, name, value, ylow, yhigh, error_title, ymax, limit_title, \
+ flow_id); \
+ } while (0);
+
+#define BWE_TEST_LOGGING_LABEL(figure, title, y_label, num_flows) \
+ do { \
+ BWE_TEST_LOGGING_CONTEXT(title); \
+ webrtc::testing::bwe::Logging::GetInstance()->PlotLabel( \
+ figure, title, y_label, num_flows); \
+ } while (0);
+
+namespace webrtc {
+
+class CriticalSectionWrapper;
+
+namespace testing {
+namespace bwe {
+
+class Logging {
+ public:
+ class Context {
+ public:
+ Context(uint32_t name, int64_t timestamp_ms, bool enabled);
+ Context(const std::string& name, int64_t timestamp_ms, bool enabled);
+ Context(const char* name, int64_t timestamp_ms, bool enabled);
+ ~Context();
+ private:
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(Context);
+ };
+
+ static Logging* GetInstance();
+
+ void SetGlobalContext(uint32_t name);
+ void SetGlobalContext(const std::string& name);
+ void SetGlobalContext(const char* name);
+ void SetGlobalEnable(bool enabled);
+
+ void Log(const char format[], ...);
+ void Plot(int figure, double value);
+ void Plot(int figure, double value, const std::string& alg_name);
+ void PlotBar(int figure, const std::string& name, double value, int flow_id);
+ void PlotBaselineBar(int figure,
+ const std::string& name,
+ double value,
+ int flow_id);
+ void PlotErrorBar(int figure,
+ const std::string& name,
+ double value,
+ double ylow,
+ double yhigh,
+ const std::string& error_title,
+ int flow_id);
+
+ void PlotLimitErrorBar(int figure,
+ const std::string& name,
+ double value,
+ double ylow,
+ double yhigh,
+ const std::string& error_title,
+ double ymax,
+ const std::string& limit_title,
+ int flow_id);
+ void PlotLabel(int figure,
+ const std::string& title,
+ const std::string& y_label,
+ int num_flows);
+
+ private:
+ struct State {
+ State();
+ State(const std::string& new_tag, int64_t timestamp_ms, bool enabled);
+ void MergePrevious(const State& previous);
+
+ std::string tag;
+ int64_t timestamp_ms;
+ bool enabled;
+ };
+ struct ThreadState {
+ State global_state;
+ std::stack<State> stack;
+ };
+ typedef std::map<uint32_t, ThreadState> ThreadMap;
+
+ Logging();
+ void PushState(const std::string& append_to_tag, int64_t timestamp_ms,
+ bool enabled);
+ void PopState();
+
+ static Logging g_Logging;
+ rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_;
+ ThreadMap thread_map_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(Logging);
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/bwe_unittest.cc b/webrtc/modules/remote_bitrate_estimator/test/bwe_unittest.cc
new file mode 100644
index 0000000000..6b3ce4847c
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/bwe_unittest.cc
@@ -0,0 +1,393 @@
+/*
+ * Copyright (c) 2015 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/modules/remote_bitrate_estimator/test/bwe.h"
+
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+const int kSetCapacity = 1000;
+
+class LinkedSetTest : public ::testing::Test {
+ public:
+ LinkedSetTest() : linked_set_(kSetCapacity) {}
+
+ ~LinkedSetTest() {}
+
+ protected:
+ LinkedSet linked_set_;
+};
+
+TEST_F(LinkedSetTest, EmptySet) {
+ EXPECT_EQ(linked_set_.OldestSeqNumber(), 0);
+ EXPECT_EQ(linked_set_.NewestSeqNumber(), 0);
+}
+
+TEST_F(LinkedSetTest, SinglePacket) {
+ const uint16_t kSeqNumber = 1; // Arbitrary.
+ // Other parameters don't matter here.
+ linked_set_.Insert(kSeqNumber, 0, 0, 0);
+
+ EXPECT_EQ(linked_set_.OldestSeqNumber(), kSeqNumber);
+ EXPECT_EQ(linked_set_.NewestSeqNumber(), kSeqNumber);
+}
+
+TEST_F(LinkedSetTest, MultiplePackets) {
+ const uint16_t kNumberPackets = 100;
+
+ std::vector<uint16_t> sequence_numbers;
+ for (size_t i = 0; i < kNumberPackets; ++i) {
+ sequence_numbers.push_back(static_cast<uint16_t>(i + 1));
+ }
+ random_shuffle(sequence_numbers.begin(), sequence_numbers.end());
+
+ for (size_t i = 0; i < kNumberPackets; ++i) {
+ // Other parameters don't matter here.
+ linked_set_.Insert(static_cast<uint16_t>(i), 0, 0, 0);
+ }
+
+ // Packets arriving out of order should not affect the following values:
+ EXPECT_EQ(linked_set_.OldestSeqNumber(), 0);
+ EXPECT_EQ(linked_set_.NewestSeqNumber(), kNumberPackets - 1);
+}
+
+TEST_F(LinkedSetTest, Overflow) {
+ const int kFirstSeqNumber = -100;
+ const int kLastSeqNumber = 100;
+
+ for (int i = kFirstSeqNumber; i <= kLastSeqNumber; ++i) {
+ // Other parameters don't matter here.
+ linked_set_.Insert(static_cast<uint16_t>(i), 0, 0, 0);
+ }
+
+ // Packets arriving out of order should not affect the following values:
+ EXPECT_EQ(linked_set_.OldestSeqNumber(),
+ static_cast<uint16_t>(kFirstSeqNumber));
+ EXPECT_EQ(linked_set_.NewestSeqNumber(),
+ static_cast<uint16_t>(kLastSeqNumber));
+}
+
+class SequenceNumberOlderThanTest : public ::testing::Test {
+ public:
+ SequenceNumberOlderThanTest() {}
+ ~SequenceNumberOlderThanTest() {}
+
+ protected:
+ SequenceNumberOlderThan comparator_;
+};
+
+TEST_F(SequenceNumberOlderThanTest, Operator) {
+ // Operator()(x, y) returns true <==> y is newer than x.
+ EXPECT_TRUE(comparator_.operator()(0x0000, 0x0001));
+ EXPECT_TRUE(comparator_.operator()(0x0001, 0x1000));
+ EXPECT_FALSE(comparator_.operator()(0x0001, 0x0000));
+ EXPECT_FALSE(comparator_.operator()(0x0002, 0x0002));
+ EXPECT_TRUE(comparator_.operator()(0xFFF6, 0x000A));
+ EXPECT_FALSE(comparator_.operator()(0x000A, 0xFFF6));
+ EXPECT_TRUE(comparator_.operator()(0x0000, 0x8000));
+ EXPECT_FALSE(comparator_.operator()(0x8000, 0x0000));
+}
+
+class LossAccountTest : public ::testing::Test {
+ public:
+ LossAccountTest() {}
+ ~LossAccountTest() {}
+
+ protected:
+ LossAccount loss_account_;
+};
+
+TEST_F(LossAccountTest, Operations) {
+ const size_t kTotal = 100; // Arbitrary values.
+ const size_t kLost = 10;
+
+ LossAccount rhs(kTotal, kLost);
+
+ loss_account_.Add(rhs);
+ EXPECT_EQ(loss_account_.num_total, kTotal);
+ EXPECT_EQ(loss_account_.num_lost, kLost);
+ EXPECT_NEAR(loss_account_.LossRatio(), static_cast<float>(kLost) / kTotal,
+ 0.001f);
+
+ loss_account_.Subtract(rhs);
+ EXPECT_EQ(loss_account_.num_total, 0UL);
+ EXPECT_EQ(loss_account_.num_lost, 0UL);
+ EXPECT_NEAR(loss_account_.LossRatio(), 0.0f, 0.001f);
+}
+
+class BweReceiverTest : public ::testing::Test {
+ public:
+ BweReceiverTest() : bwe_receiver_(kFlowId) {}
+ ~BweReceiverTest() {}
+
+ protected:
+ const int kFlowId = 1; // Arbitrary.
+ BweReceiver bwe_receiver_;
+};
+
+TEST_F(BweReceiverTest, ReceivingRateNoPackets) {
+ EXPECT_EQ(bwe_receiver_.RecentKbps(), static_cast<size_t>(0));
+}
+
+TEST_F(BweReceiverTest, ReceivingRateSinglePacket) {
+ const size_t kPayloadSizeBytes = 500 * 1000;
+ const int64_t kSendTimeUs = 300 * 1000;
+ const int64_t kArrivalTimeMs = kSendTimeUs / 1000 + 100;
+ const uint16_t kSequenceNumber = 1;
+ const int64_t kTimeWindowMs = BweReceiver::kReceivingRateTimeWindowMs;
+
+ const MediaPacket media_packet(kFlowId, kSendTimeUs, kPayloadSizeBytes,
+ kSequenceNumber);
+ bwe_receiver_.ReceivePacket(kArrivalTimeMs, media_packet);
+
+ const size_t kReceivingRateKbps = 8 * kPayloadSizeBytes / kTimeWindowMs;
+
+ EXPECT_NEAR(bwe_receiver_.RecentKbps(), kReceivingRateKbps,
+ static_cast<float>(kReceivingRateKbps) / 100.0f);
+}
+
+TEST_F(BweReceiverTest, ReceivingRateSmallPackets) {
+ const size_t kPayloadSizeBytes = 100 * 1000;
+ const int64_t kTimeGapMs = 50; // Between each packet.
+ const int64_t kOneWayDelayMs = 50;
+
+ for (int i = 1; i < 50; ++i) {
+ int64_t send_time_us = i * kTimeGapMs * 1000;
+ int64_t arrival_time_ms = send_time_us / 1000 + kOneWayDelayMs;
+ uint16_t sequence_number = i;
+ const MediaPacket media_packet(kFlowId, send_time_us, kPayloadSizeBytes,
+ sequence_number);
+ bwe_receiver_.ReceivePacket(arrival_time_ms, media_packet);
+ }
+
+ const size_t kReceivingRateKbps = 8 * kPayloadSizeBytes / kTimeGapMs;
+ EXPECT_NEAR(bwe_receiver_.RecentKbps(), kReceivingRateKbps,
+ static_cast<float>(kReceivingRateKbps) / 100.0f);
+}
+
+TEST_F(BweReceiverTest, PacketLossNoPackets) {
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+}
+
+TEST_F(BweReceiverTest, PacketLossSinglePacket) {
+ const MediaPacket media_packet(kFlowId, 0, 0, 0);
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+}
+
+TEST_F(BweReceiverTest, PacketLossContiguousPackets) {
+ const int64_t kTimeWindowMs = BweReceiver::kPacketLossTimeWindowMs;
+ size_t set_capacity = bwe_receiver_.GetSetCapacity();
+
+ for (int i = 0; i < 10; ++i) {
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ // Sequence_number and flow_id are the only members that matter here.
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_number);
+ // Arrival time = 0, all packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ }
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+
+ for (int i = 30; i > 20; i--) {
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ // Sequence_number and flow_id are the only members that matter here.
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_number);
+ // Only the packets sent in this for loop will be considered.
+ bwe_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet);
+ }
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+
+ // Should handle uint16_t overflow.
+ for (int i = 0xFFFF - 10; i < 0xFFFF + 10; ++i) {
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_number);
+ // Only the packets sent in this for loop will be considered.
+ bwe_receiver_.ReceivePacket(4 * kTimeWindowMs, media_packet);
+ }
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+
+ // Should handle set overflow.
+ for (int i = 0; i < set_capacity * 1.5; ++i) {
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_number);
+ // Only the packets sent in this for loop will be considered.
+ bwe_receiver_.ReceivePacket(6 * kTimeWindowMs, media_packet);
+ }
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+}
+
+// Should handle duplicates.
+TEST_F(BweReceiverTest, PacketLossDuplicatedPackets) {
+ const int64_t kTimeWindowMs = BweReceiver::kPacketLossTimeWindowMs;
+
+ for (int i = 0; i < 10; ++i) {
+ const MediaPacket media_packet(kFlowId, 0, 0, 0);
+ // Arrival time = 0, all packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ }
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+
+ // Missing the element 5.
+ const uint16_t kSequenceNumbers[] = {1, 2, 3, 4, 6, 7, 8};
+ const int kNumPackets = ARRAY_SIZE(kSequenceNumbers);
+
+ // Insert each sequence number twice.
+ for (int i = 0; i < 2; ++i) {
+ for (int j = 0; j < kNumPackets; j++) {
+ const MediaPacket media_packet(kFlowId, 0, 0, kSequenceNumbers[j]);
+ // Only the packets sent in this for loop will be considered.
+ bwe_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet);
+ }
+ }
+
+ EXPECT_NEAR(bwe_receiver_.RecentPacketLossRatio(), 1.0f / (kNumPackets + 1),
+ 0.1f / (kNumPackets + 1));
+}
+
+TEST_F(BweReceiverTest, PacketLossLakingPackets) {
+ size_t set_capacity = bwe_receiver_.GetSetCapacity();
+ EXPECT_LT(set_capacity, static_cast<size_t>(0xFFFF));
+
+ // Missing every other packet.
+ for (size_t i = 0; i < set_capacity; ++i) {
+ if ((i & 1) == 0) { // Only even sequence numbers.
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_number);
+ // Arrival time = 0, all packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ }
+ }
+ EXPECT_NEAR(bwe_receiver_.RecentPacketLossRatio(), 0.5f, 0.01f);
+}
+
+TEST_F(BweReceiverTest, PacketLossLakingFewPackets) {
+ size_t set_capacity = bwe_receiver_.GetSetCapacity();
+ EXPECT_LT(set_capacity, static_cast<size_t>(0xFFFF));
+
+ const int kPeriod = 100;
+ // Missing one for each kPeriod packets.
+ for (size_t i = 0; i < set_capacity; ++i) {
+ if ((i % kPeriod) != 0) {
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_number);
+ // Arrival time = 0, all packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ }
+ }
+ EXPECT_NEAR(bwe_receiver_.RecentPacketLossRatio(), 1.0f / kPeriod,
+ 0.1f / kPeriod);
+}
+
+// Packet's sequence numbers greatly apart, expect high loss.
+TEST_F(BweReceiverTest, PacketLossWideGap) {
+ const int64_t kTimeWindowMs = BweReceiver::kPacketLossTimeWindowMs;
+
+ const MediaPacket media_packet1(0, 0, 0, 1);
+ const MediaPacket media_packet2(0, 0, 0, 1000);
+ // Only these two packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet1);
+ bwe_receiver_.ReceivePacket(0, media_packet2);
+ EXPECT_NEAR(bwe_receiver_.RecentPacketLossRatio(), 0.998f, 0.0001f);
+
+ const MediaPacket media_packet3(0, 0, 0, 0);
+ const MediaPacket media_packet4(0, 0, 0, 0x8000);
+ // Only these two packets will be considered.
+ bwe_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet3);
+ bwe_receiver_.ReceivePacket(2 * kTimeWindowMs, media_packet4);
+ EXPECT_NEAR(bwe_receiver_.RecentPacketLossRatio(), 0.99994f, 0.00001f);
+}
+
+// Packets arriving unordered should not be counted as losted.
+TEST_F(BweReceiverTest, PacketLossUnorderedPackets) {
+ size_t num_packets = bwe_receiver_.GetSetCapacity() / 2;
+ std::vector<uint16_t> sequence_numbers;
+
+ for (size_t i = 0; i < num_packets; ++i) {
+ sequence_numbers.push_back(static_cast<uint16_t>(i + 1));
+ }
+
+ random_shuffle(sequence_numbers.begin(), sequence_numbers.end());
+
+ for (size_t i = 0; i < num_packets; ++i) {
+ const MediaPacket media_packet(kFlowId, 0, 0, sequence_numbers[i]);
+ // Arrival time = 0, all packets will be considered.
+ bwe_receiver_.ReceivePacket(0, media_packet);
+ }
+
+ EXPECT_EQ(bwe_receiver_.RecentPacketLossRatio(), 0.0f);
+}
+
+TEST_F(BweReceiverTest, RecentKbps) {
+ EXPECT_EQ(bwe_receiver_.RecentKbps(), 0U);
+
+ const size_t kPacketSizeBytes = 1200;
+ const int kNumPackets = 100;
+
+ double window_size_s = bwe_receiver_.BitrateWindowS();
+
+ // Receive packets at the same time.
+ for (int i = 0; i < kNumPackets; ++i) {
+ MediaPacket packet(kFlowId, 0L, kPacketSizeBytes, static_cast<uint16_t>(i));
+ bwe_receiver_.ReceivePacket(0, packet);
+ }
+
+ EXPECT_NEAR(bwe_receiver_.RecentKbps(),
+ (8 * kNumPackets * kPacketSizeBytes) / (1000 * window_size_s),
+ 10);
+
+ int64_t time_gap_ms =
+ 2 * 1000 * window_size_s; // Larger than rate_counter time window.
+
+ MediaPacket packet(kFlowId, time_gap_ms * 1000, kPacketSizeBytes,
+ static_cast<uint16_t>(kNumPackets));
+ bwe_receiver_.ReceivePacket(time_gap_ms, packet);
+
+ EXPECT_NEAR(bwe_receiver_.RecentKbps(),
+ (8 * kPacketSizeBytes) / (1000 * window_size_s), 10);
+}
+
+TEST_F(BweReceiverTest, Loss) {
+ EXPECT_NEAR(bwe_receiver_.GlobalReceiverPacketLossRatio(), 0.0f, 0.001f);
+
+ LossAccount loss_account = bwe_receiver_.LinkedSetPacketLossRatio();
+ EXPECT_NEAR(loss_account.LossRatio(), 0.0f, 0.001f);
+
+ // Insert packets 1-50 and 151-200;
+ for (int i = 1; i <= 200; ++i) {
+ // Packet size and timestamp do not matter here.
+ MediaPacket packet(kFlowId, 0L, 0UL, static_cast<uint16_t>(i));
+ bwe_receiver_.ReceivePacket(0, packet);
+ if (i == 50) {
+ i += 100;
+ }
+ }
+
+ loss_account = bwe_receiver_.LinkedSetPacketLossRatio();
+ EXPECT_NEAR(loss_account.LossRatio(), 0.5f, 0.001f);
+
+ bwe_receiver_.RelieveSetAndUpdateLoss();
+ EXPECT_EQ(bwe_receiver_.received_packets_.size(), 100U / 10);
+
+ // No packet loss within the preserved packets.
+ loss_account = bwe_receiver_.LinkedSetPacketLossRatio();
+ EXPECT_NEAR(loss_account.LossRatio(), 0.0f, 0.001f);
+
+ // RelieveSetAndUpdateLoss automatically updates loss account.
+ EXPECT_NEAR(bwe_receiver_.GlobalReceiverPacketLossRatio(), 0.5f, 0.001f);
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc
new file mode 100644
index 0000000000..d77447f1ea
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.cc
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2015 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.
+ *
+ */
+
+// Implementation of Network-Assisted Dynamic Adaptation's (NADA's) proposal.
+// Version according to Draft Document (mentioned in references)
+// http://tools.ietf.org/html/draft-zhu-rmcat-nada-06
+// From March 26, 2015.
+
+#include <math.h>
+#include <algorithm>
+#include <vector>
+
+#include "webrtc/base/common.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+const int64_t NadaBweReceiver::kReceivingRateTimeWindowMs = 500;
+
+NadaBweReceiver::NadaBweReceiver(int flow_id)
+ : BweReceiver(flow_id, kReceivingRateTimeWindowMs),
+ clock_(0),
+ last_feedback_ms_(0),
+ recv_stats_(ReceiveStatistics::Create(&clock_)),
+ baseline_delay_ms_(10000), // Initialized as an upper bound.
+ delay_signal_ms_(0),
+ last_congestion_signal_ms_(0),
+ last_delays_index_(0),
+ exp_smoothed_delay_ms_(-1),
+ est_queuing_delay_signal_ms_(0) {
+}
+
+NadaBweReceiver::~NadaBweReceiver() {
+}
+
+void NadaBweReceiver::ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) {
+ const float kAlpha = 0.1f; // Used for exponential smoothing.
+ const int64_t kDelayLowThresholdMs = 50; // Referred as d_th.
+ const int64_t kDelayMaxThresholdMs = 400; // Referred as d_max.
+
+ clock_.AdvanceTimeMilliseconds(arrival_time_ms - clock_.TimeInMilliseconds());
+ recv_stats_->IncomingPacket(media_packet.header(),
+ media_packet.payload_size(), false);
+ // Refered as x_n.
+ int64_t delay_ms = arrival_time_ms - media_packet.sender_timestamp_ms();
+
+ // The min should be updated within the first 10 minutes.
+ if (clock_.TimeInMilliseconds() < 10 * 60 * 1000) {
+ baseline_delay_ms_ = std::min(baseline_delay_ms_, delay_ms);
+ }
+
+ delay_signal_ms_ = delay_ms - baseline_delay_ms_; // Refered as d_n.
+ const int kMedian = ARRAY_SIZE(last_delays_ms_);
+ last_delays_ms_[(last_delays_index_++) % kMedian] = delay_signal_ms_;
+ int size = std::min(last_delays_index_, kMedian);
+
+ int64_t median_filtered_delay_ms_ = MedianFilter(last_delays_ms_, size);
+ exp_smoothed_delay_ms_ = ExponentialSmoothingFilter(
+ median_filtered_delay_ms_, exp_smoothed_delay_ms_, kAlpha);
+
+ if (exp_smoothed_delay_ms_ < kDelayLowThresholdMs) {
+ est_queuing_delay_signal_ms_ = exp_smoothed_delay_ms_;
+ } else if (exp_smoothed_delay_ms_ < kDelayMaxThresholdMs) {
+ est_queuing_delay_signal_ms_ = static_cast<int64_t>(
+ pow((static_cast<double>(kDelayMaxThresholdMs -
+ exp_smoothed_delay_ms_)) /
+ (kDelayMaxThresholdMs - kDelayLowThresholdMs),
+ 4.0) *
+ kDelayLowThresholdMs);
+ } else {
+ est_queuing_delay_signal_ms_ = 0;
+ }
+
+ // Log received packet information.
+ BweReceiver::ReceivePacket(arrival_time_ms, media_packet);
+}
+
+FeedbackPacket* NadaBweReceiver::GetFeedback(int64_t now_ms) {
+ const int64_t kPacketLossPenaltyMs = 1000; // Referred as d_L.
+
+ if (now_ms - last_feedback_ms_ < 100) {
+ return NULL;
+ }
+
+ float loss_fraction = RecentPacketLossRatio();
+
+ int64_t loss_signal_ms =
+ static_cast<int64_t>(loss_fraction * kPacketLossPenaltyMs + 0.5f);
+ int64_t congestion_signal_ms = est_queuing_delay_signal_ms_ + loss_signal_ms;
+
+ float derivative = 0.0f;
+ if (last_feedback_ms_ > 0) {
+ derivative = (congestion_signal_ms - last_congestion_signal_ms_) /
+ static_cast<float>(now_ms - last_feedback_ms_);
+ }
+ last_feedback_ms_ = now_ms;
+ last_congestion_signal_ms_ = congestion_signal_ms;
+
+ int64_t corrected_send_time_ms = 0L;
+
+ if (!received_packets_.empty()) {
+ PacketIdentifierNode* latest = *(received_packets_.begin());
+ corrected_send_time_ms =
+ latest->send_time_ms + now_ms - latest->arrival_time_ms;
+ }
+
+ // Sends a tuple containing latest values of <d_hat_n, d_tilde_n, x_n, x'_n,
+ // R_r> and additional information.
+ return new NadaFeedback(flow_id_, now_ms * 1000, exp_smoothed_delay_ms_,
+ est_queuing_delay_signal_ms_, congestion_signal_ms,
+ derivative, RecentKbps(), corrected_send_time_ms);
+}
+
+// If size is even, the median is the average of the two middlemost numbers.
+int64_t NadaBweReceiver::MedianFilter(int64_t* last_delays_ms, int size) {
+ std::vector<int64_t> array_copy(last_delays_ms, last_delays_ms + size);
+ std::nth_element(array_copy.begin(), array_copy.begin() + size / 2,
+ array_copy.end());
+ if (size % 2 == 1) {
+ // Typically, size = 5. For odd size values, right and left are equal.
+ return array_copy.at(size / 2);
+ }
+ int64_t right = array_copy.at(size / 2);
+ std::nth_element(array_copy.begin(), array_copy.begin() + (size - 1) / 2,
+ array_copy.end());
+ int64_t left = array_copy.at((size - 1) / 2);
+ return (left + right + 1) / 2;
+}
+
+int64_t NadaBweReceiver::ExponentialSmoothingFilter(int64_t new_value,
+ int64_t last_smoothed_value,
+ float alpha) {
+ if (last_smoothed_value < 0) {
+ return new_value; // Handling initial case.
+ }
+ return static_cast<int64_t>(alpha * new_value +
+ (1.0f - alpha) * last_smoothed_value + 0.5f);
+}
+
+// Implementation according to Cisco's proposal by default.
+NadaBweSender::NadaBweSender(int kbps, BitrateObserver* observer, Clock* clock)
+ : BweSender(kbps), // Referred as "Reference Rate" = R_n.,
+ clock_(clock),
+ observer_(observer),
+ original_operating_mode_(true) {
+}
+
+NadaBweSender::NadaBweSender(BitrateObserver* observer, Clock* clock)
+ : BweSender(kMinBitrateKbps), // Referred as "Reference Rate" = R_n.
+ clock_(clock),
+ observer_(observer),
+ original_operating_mode_(true) {
+}
+
+NadaBweSender::~NadaBweSender() {
+}
+
+int NadaBweSender::GetFeedbackIntervalMs() const {
+ return 100;
+}
+
+void NadaBweSender::GiveFeedback(const FeedbackPacket& feedback) {
+ const NadaFeedback& fb = static_cast<const NadaFeedback&>(feedback);
+
+ // Following parameters might be optimized.
+ const int64_t kQueuingDelayUpperBoundMs = 10;
+ const float kDerivativeUpperBound = 10.0f / min_feedback_delay_ms_;
+ // In the modified version, a higher kMinUpperBound allows a higher d_hat
+ // upper bound for calling AcceleratedRampUp.
+ const float kProportionalityDelayBits = 20.0f;
+
+ int64_t now_ms = clock_->TimeInMilliseconds();
+ float delta_s = now_ms - last_feedback_ms_;
+ last_feedback_ms_ = now_ms;
+ // Update delta_0.
+ min_feedback_delay_ms_ =
+ std::min(min_feedback_delay_ms_, static_cast<int64_t>(delta_s));
+
+ // Update RTT_0.
+ int64_t rtt_ms = now_ms - fb.latest_send_time_ms();
+ min_round_trip_time_ms_ = std::min(min_round_trip_time_ms_, rtt_ms);
+
+ // Independent limits for AcceleratedRampUp conditions variables:
+ // x_n, d_tilde and x'_n in the original implementation, plus
+ // d_hat and receiving_rate in the modified one.
+ // There should be no packet losses/marking, hence x_n == d_tilde.
+ if (original_operating_mode_) {
+ // Original if conditions and rate update.
+ if (fb.congestion_signal() == fb.est_queuing_delay_signal_ms() &&
+ fb.est_queuing_delay_signal_ms() < kQueuingDelayUpperBoundMs &&
+ fb.derivative() < kDerivativeUpperBound) {
+ AcceleratedRampUp(fb);
+ } else {
+ GradualRateUpdate(fb, delta_s, 1.0);
+ }
+ } else {
+ // Modified if conditions and rate update; new ramp down mode.
+ if (fb.congestion_signal() == fb.est_queuing_delay_signal_ms() &&
+ fb.est_queuing_delay_signal_ms() < kQueuingDelayUpperBoundMs &&
+ fb.exp_smoothed_delay_ms() <
+ kMinBitrateKbps / kProportionalityDelayBits &&
+ fb.derivative() < kDerivativeUpperBound &&
+ fb.receiving_rate() > kMinBitrateKbps) {
+ AcceleratedRampUp(fb);
+ } else if (fb.congestion_signal() > kMaxCongestionSignalMs ||
+ fb.exp_smoothed_delay_ms() > kMaxCongestionSignalMs) {
+ AcceleratedRampDown(fb);
+ } else {
+ double bitrate_reference =
+ (2.0 * bitrate_kbps_) / (kMaxBitrateKbps + kMinBitrateKbps);
+ double smoothing_factor = pow(bitrate_reference, 0.75);
+ GradualRateUpdate(fb, delta_s, smoothing_factor);
+ }
+ }
+
+ bitrate_kbps_ = std::min(bitrate_kbps_, kMaxBitrateKbps);
+ bitrate_kbps_ = std::max(bitrate_kbps_, kMinBitrateKbps);
+
+ observer_->OnNetworkChanged(1000 * bitrate_kbps_, 0, rtt_ms);
+}
+
+int64_t NadaBweSender::TimeUntilNextProcess() {
+ return 100;
+}
+
+int NadaBweSender::Process() {
+ return 0;
+}
+
+void NadaBweSender::AcceleratedRampUp(const NadaFeedback& fb) {
+ const int kMaxRampUpQueuingDelayMs = 50; // Referred as T_th.
+ const float kGamma0 = 0.5f; // Referred as gamma_0.
+
+ float gamma =
+ std::min(kGamma0, static_cast<float>(kMaxRampUpQueuingDelayMs) /
+ (min_round_trip_time_ms_ + min_feedback_delay_ms_));
+
+ bitrate_kbps_ = static_cast<int>((1.0f + gamma) * fb.receiving_rate() + 0.5f);
+}
+
+void NadaBweSender::AcceleratedRampDown(const NadaFeedback& fb) {
+ const float kGamma0 = 0.9f;
+ float gamma = 3.0f * kMaxCongestionSignalMs /
+ (fb.congestion_signal() + fb.exp_smoothed_delay_ms());
+ gamma = std::min(gamma, kGamma0);
+ bitrate_kbps_ = gamma * fb.receiving_rate() + 0.5f;
+}
+
+void NadaBweSender::GradualRateUpdate(const NadaFeedback& fb,
+ float delta_s,
+ double smoothing_factor) {
+ const float kTauOMs = 500.0f; // Referred as tau_o.
+ const float kEta = 2.0f; // Referred as eta.
+ const float kKappa = 1.0f; // Referred as kappa.
+ const float kReferenceDelayMs = 10.0f; // Referred as x_ref.
+ const float kPriorityWeight = 1.0f; // Referred as w.
+
+ float x_hat = fb.congestion_signal() + kEta * kTauOMs * fb.derivative();
+
+ float kTheta =
+ kPriorityWeight * (kMaxBitrateKbps - kMinBitrateKbps) * kReferenceDelayMs;
+
+ int original_increase =
+ static_cast<int>((kKappa * delta_s *
+ (kTheta - (bitrate_kbps_ - kMinBitrateKbps) * x_hat)) /
+ (kTauOMs * kTauOMs) +
+ 0.5f);
+
+ bitrate_kbps_ = bitrate_kbps_ + smoothing_factor * original_increase;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h
new file mode 100644
index 0000000000..eee90cf463
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2015 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.
+ *
+*/
+
+// Implementation of Network-Assisted Dynamic Adaptation's (NADA's) proposal
+// Version according to Draft Document (mentioned in references)
+// http://tools.ietf.org/html/draft-zhu-rmcat-nada-06
+// From March 26, 2015.
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_NADA_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_NADA_H_
+
+#include <list>
+#include <map>
+
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/voice_engine/channel.h"
+
+namespace webrtc {
+
+class ReceiveStatistics;
+
+namespace testing {
+namespace bwe {
+
+class NadaBweReceiver : public BweReceiver {
+ public:
+ explicit NadaBweReceiver(int flow_id);
+ virtual ~NadaBweReceiver();
+
+ void ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) override;
+ FeedbackPacket* GetFeedback(int64_t now_ms) override;
+
+ static int64_t MedianFilter(int64_t* v, int size);
+ static int64_t ExponentialSmoothingFilter(int64_t new_value,
+ int64_t last_smoothed_value,
+ float alpha);
+
+ static const int64_t kReceivingRateTimeWindowMs;
+
+ private:
+ SimulatedClock clock_;
+ int64_t last_feedback_ms_;
+ rtc::scoped_ptr<ReceiveStatistics> recv_stats_;
+ int64_t baseline_delay_ms_; // Referred as d_f.
+ int64_t delay_signal_ms_; // Referred as d_n.
+ int64_t last_congestion_signal_ms_;
+ int last_delays_index_;
+ int64_t exp_smoothed_delay_ms_; // Referred as d_hat_n.
+ int64_t est_queuing_delay_signal_ms_; // Referred as d_tilde_n.
+ int64_t last_delays_ms_[5]; // Used for Median Filter.
+};
+
+class NadaBweSender : public BweSender {
+ public:
+ NadaBweSender(int kbps, BitrateObserver* observer, Clock* clock);
+ NadaBweSender(BitrateObserver* observer, Clock* clock);
+ virtual ~NadaBweSender();
+
+ int GetFeedbackIntervalMs() const override;
+ // Updates the min_feedback_delay_ms_ and the min_round_trip_time_ms_.
+ void GiveFeedback(const FeedbackPacket& feedback) override;
+ void OnPacketsSent(const Packets& packets) override {}
+ int64_t TimeUntilNextProcess() override;
+ int Process() override;
+ void AcceleratedRampUp(const NadaFeedback& fb);
+ void AcceleratedRampDown(const NadaFeedback& fb);
+ void GradualRateUpdate(const NadaFeedback& fb,
+ float delta_s,
+ double smoothing_factor);
+
+ int bitrate_kbps() const { return bitrate_kbps_; }
+ void set_bitrate_kbps(int bitrate_kbps) { bitrate_kbps_ = bitrate_kbps; }
+ bool original_operating_mode() const { return original_operating_mode_; }
+ void set_original_operating_mode(bool original_operating_mode) {
+ original_operating_mode_ = original_operating_mode;
+ }
+ int64_t NowMs() const { return clock_->TimeInMilliseconds(); }
+
+ private:
+ Clock* const clock_;
+ BitrateObserver* const observer_;
+ // Used as an upper bound for calling AcceleratedRampDown.
+ const float kMaxCongestionSignalMs = 40.0f + kMinBitrateKbps / 15;
+ // Referred as R_min, default initialization for bitrate R_n.
+ int64_t last_feedback_ms_ = 0;
+ // Referred as delta_0, initialized as an upper bound.
+ int64_t min_feedback_delay_ms_ = 200;
+ // Referred as RTT_0, initialized as an upper bound.
+ int64_t min_round_trip_time_ms_ = 100;
+ bool original_operating_mode_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(NadaBweSender);
+};
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_NADA_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc
new file mode 100644
index 0000000000..a0f56b73b7
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/nada_unittest.cc
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) 2015 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/modules/remote_bitrate_estimator/test/estimators/nada.h"
+
+#include <algorithm>
+#include <numeric>
+
+#include "webrtc/base/common.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
+#include "webrtc/test/testsupport/fileutils.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class FilterTest : public ::testing::Test {
+ public:
+ void MedianFilterConstantArray() {
+ std::fill_n(raw_signal_, kNumElements, kSignalValue);
+ for (int i = 0; i < kNumElements; ++i) {
+ int size = std::min(5, i + 1);
+ median_filtered_[i] =
+ NadaBweReceiver::MedianFilter(&raw_signal_[i + 1 - size], size);
+ }
+ }
+
+ void MedianFilterIntermittentNoise() {
+ const int kValue = 500;
+ const int kNoise = 100;
+
+ for (int i = 0; i < kNumElements; ++i) {
+ raw_signal_[i] = kValue + kNoise * (i % 10 == 9 ? 1 : 0);
+ }
+ for (int i = 0; i < kNumElements; ++i) {
+ int size = std::min(5, i + 1);
+ median_filtered_[i] =
+ NadaBweReceiver::MedianFilter(&raw_signal_[i + 1 - size], size);
+ EXPECT_EQ(median_filtered_[i], kValue);
+ }
+ }
+
+ void ExponentialSmoothingFilter(const int64_t raw_signal_[],
+ int num_elements,
+ int64_t exp_smoothed[]) {
+ exp_smoothed[0] =
+ NadaBweReceiver::ExponentialSmoothingFilter(raw_signal_[0], -1, kAlpha);
+ for (int i = 1; i < num_elements; ++i) {
+ exp_smoothed[i] = NadaBweReceiver::ExponentialSmoothingFilter(
+ raw_signal_[i], exp_smoothed[i - 1], kAlpha);
+ }
+ }
+
+ void ExponentialSmoothingConstantArray(int64_t exp_smoothed[]) {
+ std::fill_n(raw_signal_, kNumElements, kSignalValue);
+ ExponentialSmoothingFilter(raw_signal_, kNumElements, exp_smoothed);
+ }
+
+ protected:
+ static const int kNumElements = 1000;
+ static const int64_t kSignalValue;
+ static const float kAlpha;
+ int64_t raw_signal_[kNumElements];
+ int64_t median_filtered_[kNumElements];
+};
+
+const int64_t FilterTest::kSignalValue = 200;
+const float FilterTest::kAlpha = 0.1f;
+
+class TestBitrateObserver : public BitrateObserver {
+ public:
+ TestBitrateObserver()
+ : last_bitrate_(0), last_fraction_loss_(0), last_rtt_(0) {}
+
+ virtual void OnNetworkChanged(uint32_t bitrate,
+ uint8_t fraction_loss,
+ int64_t rtt) {
+ last_bitrate_ = bitrate;
+ last_fraction_loss_ = fraction_loss;
+ last_rtt_ = rtt;
+ }
+ uint32_t last_bitrate_;
+ uint8_t last_fraction_loss_;
+ int64_t last_rtt_;
+};
+
+class NadaSenderSideTest : public ::testing::Test {
+ public:
+ NadaSenderSideTest()
+ : observer_(),
+ simulated_clock_(0),
+ nada_sender_(&observer_, &simulated_clock_) {}
+ ~NadaSenderSideTest() {}
+
+ private:
+ TestBitrateObserver observer_;
+ SimulatedClock simulated_clock_;
+
+ protected:
+ NadaBweSender nada_sender_;
+};
+
+class NadaReceiverSideTest : public ::testing::Test {
+ public:
+ NadaReceiverSideTest() : nada_receiver_(kFlowId) {}
+ ~NadaReceiverSideTest() {}
+
+ protected:
+ const int kFlowId = 1; // Arbitrary.
+ NadaBweReceiver nada_receiver_;
+};
+
+class NadaFbGenerator {
+ public:
+ NadaFbGenerator();
+
+ static NadaFeedback NotCongestedFb(size_t receiving_rate,
+ int64_t ref_signal_ms,
+ int64_t send_time_ms) {
+ int64_t exp_smoothed_delay_ms = ref_signal_ms;
+ int64_t est_queuing_delay_signal_ms = ref_signal_ms;
+ int64_t congestion_signal_ms = ref_signal_ms;
+ float derivative = 0.0f;
+ return NadaFeedback(kFlowId, kNowMs, exp_smoothed_delay_ms,
+ est_queuing_delay_signal_ms, congestion_signal_ms,
+ derivative, receiving_rate, send_time_ms);
+ }
+
+ static NadaFeedback CongestedFb(size_t receiving_rate, int64_t send_time_ms) {
+ int64_t exp_smoothed_delay_ms = 1000;
+ int64_t est_queuing_delay_signal_ms = 800;
+ int64_t congestion_signal_ms = 1000;
+ float derivative = 1.0f;
+ return NadaFeedback(kFlowId, kNowMs, exp_smoothed_delay_ms,
+ est_queuing_delay_signal_ms, congestion_signal_ms,
+ derivative, receiving_rate, send_time_ms);
+ }
+
+ static NadaFeedback ExtremelyCongestedFb(size_t receiving_rate,
+ int64_t send_time_ms) {
+ int64_t exp_smoothed_delay_ms = 100000;
+ int64_t est_queuing_delay_signal_ms = 0;
+ int64_t congestion_signal_ms = 100000;
+ float derivative = 10000.0f;
+ return NadaFeedback(kFlowId, kNowMs, exp_smoothed_delay_ms,
+ est_queuing_delay_signal_ms, congestion_signal_ms,
+ derivative, receiving_rate, send_time_ms);
+ }
+
+ private:
+ // Arbitrary values, won't change these test results.
+ static const int kFlowId = 2;
+ static const int64_t kNowMs = 1000;
+};
+
+// Verify if AcceleratedRampUp is called and that bitrate increases.
+TEST_F(NadaSenderSideTest, AcceleratedRampUp) {
+ const int64_t kRefSignalMs = 1;
+ const int64_t kOneWayDelayMs = 50;
+ int original_bitrate = 2 * kMinBitrateKbps;
+ size_t receiving_rate = static_cast<size_t>(original_bitrate);
+ int64_t send_time_ms = nada_sender_.NowMs() - kOneWayDelayMs;
+
+ NadaFeedback not_congested_fb = NadaFbGenerator::NotCongestedFb(
+ receiving_rate, kRefSignalMs, send_time_ms);
+
+ nada_sender_.set_original_operating_mode(true);
+ nada_sender_.set_bitrate_kbps(original_bitrate);
+
+ // Trigger AcceleratedRampUp mode.
+ nada_sender_.GiveFeedback(not_congested_fb);
+ int bitrate_1_kbps = nada_sender_.bitrate_kbps();
+ EXPECT_GT(bitrate_1_kbps, original_bitrate);
+ // Updates the bitrate according to the receiving rate and other constant
+ // parameters.
+ nada_sender_.AcceleratedRampUp(not_congested_fb);
+ EXPECT_EQ(nada_sender_.bitrate_kbps(), bitrate_1_kbps);
+
+ nada_sender_.set_original_operating_mode(false);
+ nada_sender_.set_bitrate_kbps(original_bitrate);
+ // Trigger AcceleratedRampUp mode.
+ nada_sender_.GiveFeedback(not_congested_fb);
+ bitrate_1_kbps = nada_sender_.bitrate_kbps();
+ EXPECT_GT(bitrate_1_kbps, original_bitrate);
+ nada_sender_.AcceleratedRampUp(not_congested_fb);
+ EXPECT_EQ(nada_sender_.bitrate_kbps(), bitrate_1_kbps);
+}
+
+// Verify if AcceleratedRampDown is called and if bitrate decreases.
+TEST_F(NadaSenderSideTest, AcceleratedRampDown) {
+ const int64_t kOneWayDelayMs = 50;
+ int original_bitrate = 3 * kMinBitrateKbps;
+ size_t receiving_rate = static_cast<size_t>(original_bitrate);
+ int64_t send_time_ms = nada_sender_.NowMs() - kOneWayDelayMs;
+
+ NadaFeedback congested_fb =
+ NadaFbGenerator::CongestedFb(receiving_rate, send_time_ms);
+
+ nada_sender_.set_original_operating_mode(false);
+ nada_sender_.set_bitrate_kbps(original_bitrate);
+ nada_sender_.GiveFeedback(congested_fb); // Trigger AcceleratedRampDown mode.
+ int bitrate_1_kbps = nada_sender_.bitrate_kbps();
+ EXPECT_LE(bitrate_1_kbps, original_bitrate * 0.9f + 0.5f);
+ EXPECT_LT(bitrate_1_kbps, original_bitrate);
+
+ // Updates the bitrate according to the receiving rate and other constant
+ // parameters.
+ nada_sender_.AcceleratedRampDown(congested_fb);
+ int bitrate_2_kbps = std::max(nada_sender_.bitrate_kbps(), kMinBitrateKbps);
+ EXPECT_EQ(bitrate_2_kbps, bitrate_1_kbps);
+}
+
+TEST_F(NadaSenderSideTest, GradualRateUpdate) {
+ const int64_t kDeltaSMs = 20;
+ const int64_t kRefSignalMs = 20;
+ const int64_t kOneWayDelayMs = 50;
+ int original_bitrate = 2 * kMinBitrateKbps;
+ size_t receiving_rate = static_cast<size_t>(original_bitrate);
+ int64_t send_time_ms = nada_sender_.NowMs() - kOneWayDelayMs;
+
+ NadaFeedback congested_fb =
+ NadaFbGenerator::CongestedFb(receiving_rate, send_time_ms);
+ NadaFeedback not_congested_fb = NadaFbGenerator::NotCongestedFb(
+ original_bitrate, kRefSignalMs, send_time_ms);
+
+ nada_sender_.set_bitrate_kbps(original_bitrate);
+ double smoothing_factor = 0.0;
+ nada_sender_.GradualRateUpdate(congested_fb, kDeltaSMs, smoothing_factor);
+ EXPECT_EQ(nada_sender_.bitrate_kbps(), original_bitrate);
+
+ smoothing_factor = 1.0;
+ nada_sender_.GradualRateUpdate(congested_fb, kDeltaSMs, smoothing_factor);
+ EXPECT_LT(nada_sender_.bitrate_kbps(), original_bitrate);
+
+ nada_sender_.set_bitrate_kbps(original_bitrate);
+ nada_sender_.GradualRateUpdate(not_congested_fb, kDeltaSMs, smoothing_factor);
+ EXPECT_GT(nada_sender_.bitrate_kbps(), original_bitrate);
+}
+
+// Sending bitrate should decrease and reach its Min bound.
+TEST_F(NadaSenderSideTest, VeryLowBandwith) {
+ const int64_t kOneWayDelayMs = 50;
+
+ size_t receiving_rate = static_cast<size_t>(kMinBitrateKbps);
+ int64_t send_time_ms = nada_sender_.NowMs() - kOneWayDelayMs;
+
+ NadaFeedback extremely_congested_fb =
+ NadaFbGenerator::ExtremelyCongestedFb(receiving_rate, send_time_ms);
+ NadaFeedback congested_fb =
+ NadaFbGenerator::CongestedFb(receiving_rate, send_time_ms);
+
+ nada_sender_.set_bitrate_kbps(5 * kMinBitrateKbps);
+ nada_sender_.set_original_operating_mode(true);
+ for (int i = 0; i < 100; ++i) {
+ // Trigger GradualRateUpdate mode.
+ nada_sender_.GiveFeedback(extremely_congested_fb);
+ }
+ // The original implementation doesn't allow the bitrate to stay at kMin,
+ // even if the congestion signal is very high.
+ EXPECT_GE(nada_sender_.bitrate_kbps(), kMinBitrateKbps);
+
+ nada_sender_.set_original_operating_mode(false);
+ nada_sender_.set_bitrate_kbps(5 * kMinBitrateKbps);
+
+ for (int i = 0; i < 1000; ++i) {
+ int previous_bitrate = nada_sender_.bitrate_kbps();
+ // Trigger AcceleratedRampDown mode.
+ nada_sender_.GiveFeedback(congested_fb);
+ EXPECT_LE(nada_sender_.bitrate_kbps(), previous_bitrate);
+ }
+ EXPECT_EQ(nada_sender_.bitrate_kbps(), kMinBitrateKbps);
+}
+
+// Sending bitrate should increase and reach its Max bound.
+TEST_F(NadaSenderSideTest, VeryHighBandwith) {
+ const int64_t kOneWayDelayMs = 50;
+ const size_t kRecentReceivingRate = static_cast<size_t>(kMaxBitrateKbps);
+ const int64_t kRefSignalMs = 1;
+ int64_t send_time_ms = nada_sender_.NowMs() - kOneWayDelayMs;
+
+ NadaFeedback not_congested_fb = NadaFbGenerator::NotCongestedFb(
+ kRecentReceivingRate, kRefSignalMs, send_time_ms);
+
+ nada_sender_.set_original_operating_mode(true);
+ for (int i = 0; i < 100; ++i) {
+ int previous_bitrate = nada_sender_.bitrate_kbps();
+ nada_sender_.GiveFeedback(not_congested_fb);
+ EXPECT_GE(nada_sender_.bitrate_kbps(), previous_bitrate);
+ }
+ EXPECT_EQ(nada_sender_.bitrate_kbps(), kMaxBitrateKbps);
+
+ nada_sender_.set_original_operating_mode(false);
+ nada_sender_.set_bitrate_kbps(kMinBitrateKbps);
+
+ for (int i = 0; i < 100; ++i) {
+ int previous_bitrate = nada_sender_.bitrate_kbps();
+ nada_sender_.GiveFeedback(not_congested_fb);
+ EXPECT_GE(nada_sender_.bitrate_kbps(), previous_bitrate);
+ }
+ EXPECT_EQ(nada_sender_.bitrate_kbps(), kMaxBitrateKbps);
+}
+
+TEST_F(NadaReceiverSideTest, FeedbackInitialCases) {
+ rtc::scoped_ptr<NadaFeedback> nada_feedback(
+ static_cast<NadaFeedback*>(nada_receiver_.GetFeedback(0)));
+ EXPECT_EQ(nada_feedback, nullptr);
+
+ nada_feedback.reset(
+ static_cast<NadaFeedback*>(nada_receiver_.GetFeedback(100)));
+ EXPECT_EQ(nada_feedback->exp_smoothed_delay_ms(), -1);
+ EXPECT_EQ(nada_feedback->est_queuing_delay_signal_ms(), 0L);
+ EXPECT_EQ(nada_feedback->congestion_signal(), 0L);
+ EXPECT_EQ(nada_feedback->derivative(), 0.0f);
+ EXPECT_EQ(nada_feedback->receiving_rate(), 0.0f);
+}
+
+TEST_F(NadaReceiverSideTest, FeedbackEmptyQueues) {
+ const int64_t kTimeGapMs = 50; // Between each packet.
+ const int64_t kOneWayDelayMs = 50;
+
+ // No added latency, delay = kOneWayDelayMs.
+ for (int i = 1; i < 10; ++i) {
+ int64_t send_time_us = i * kTimeGapMs * 1000;
+ int64_t arrival_time_ms = send_time_us / 1000 + kOneWayDelayMs;
+ uint16_t sequence_number = static_cast<uint16_t>(i);
+ // Payload sizes are not important here.
+ const MediaPacket media_packet(kFlowId, send_time_us, 0, sequence_number);
+ nada_receiver_.ReceivePacket(arrival_time_ms, media_packet);
+ }
+
+ // Baseline delay will be equal kOneWayDelayMs.
+ rtc::scoped_ptr<NadaFeedback> nada_feedback(
+ static_cast<NadaFeedback*>(nada_receiver_.GetFeedback(500)));
+ EXPECT_EQ(nada_feedback->exp_smoothed_delay_ms(), 0L);
+ EXPECT_EQ(nada_feedback->est_queuing_delay_signal_ms(), 0L);
+ EXPECT_EQ(nada_feedback->congestion_signal(), 0L);
+ EXPECT_EQ(nada_feedback->derivative(), 0.0f);
+}
+
+TEST_F(NadaReceiverSideTest, FeedbackIncreasingDelay) {
+ // Since packets are 100ms apart, each one corresponds to a feedback.
+ const int64_t kTimeGapMs = 100; // Between each packet.
+
+ // Raw delays are = [10 20 30 40 50 60 70 80] ms.
+ // Baseline delay will be 50 ms.
+ // Delay signals should be: [0 10 20 30 40 50 60 70] ms.
+ const int64_t kMedianFilteredDelaysMs[] = {0, 5, 10, 15, 20, 30, 40, 50};
+ const int kNumPackets = ARRAY_SIZE(kMedianFilteredDelaysMs);
+ const float kAlpha = 0.1f; // Used for exponential smoothing.
+
+ int64_t exp_smoothed_delays_ms[kNumPackets];
+ exp_smoothed_delays_ms[0] = kMedianFilteredDelaysMs[0];
+
+ for (int i = 1; i < kNumPackets; ++i) {
+ exp_smoothed_delays_ms[i] = static_cast<int64_t>(
+ kAlpha * kMedianFilteredDelaysMs[i] +
+ (1.0f - kAlpha) * exp_smoothed_delays_ms[i - 1] + 0.5f);
+ }
+
+ for (int i = 0; i < kNumPackets; ++i) {
+ int64_t send_time_us = (i + 1) * kTimeGapMs * 1000;
+ int64_t arrival_time_ms = send_time_us / 1000 + 10 * (i + 1);
+ uint16_t sequence_number = static_cast<uint16_t>(i + 1);
+ // Payload sizes are not important here.
+ const MediaPacket media_packet(kFlowId, send_time_us, 0, sequence_number);
+ nada_receiver_.ReceivePacket(arrival_time_ms, media_packet);
+
+ rtc::scoped_ptr<NadaFeedback> nada_feedback(static_cast<NadaFeedback*>(
+ nada_receiver_.GetFeedback(arrival_time_ms)));
+ EXPECT_EQ(nada_feedback->exp_smoothed_delay_ms(),
+ exp_smoothed_delays_ms[i]);
+ // Since delay signals are lower than 50ms, they will not be non-linearly
+ // warped.
+ EXPECT_EQ(nada_feedback->est_queuing_delay_signal_ms(),
+ exp_smoothed_delays_ms[i]);
+ // Zero loss, congestion signal = queuing_delay
+ EXPECT_EQ(nada_feedback->congestion_signal(), exp_smoothed_delays_ms[i]);
+ if (i == 0) {
+ EXPECT_NEAR(nada_feedback->derivative(),
+ static_cast<float>(exp_smoothed_delays_ms[i]) / kTimeGapMs,
+ 0.005f);
+ } else {
+ EXPECT_NEAR(nada_feedback->derivative(),
+ static_cast<float>(exp_smoothed_delays_ms[i] -
+ exp_smoothed_delays_ms[i - 1]) /
+ kTimeGapMs,
+ 0.005f);
+ }
+ }
+}
+
+int64_t Warp(int64_t input) {
+ const int64_t kMinThreshold = 50; // Referred as d_th.
+ const int64_t kMaxThreshold = 400; // Referred as d_max.
+ if (input < kMinThreshold) {
+ return input;
+ } else if (input < kMaxThreshold) {
+ return static_cast<int64_t>(
+ pow((static_cast<double>(kMaxThreshold - input)) /
+ (kMaxThreshold - kMinThreshold),
+ 4.0) *
+ kMinThreshold);
+ } else {
+ return 0L;
+ }
+}
+
+TEST_F(NadaReceiverSideTest, FeedbackWarpedDelay) {
+ // Since packets are 100ms apart, each one corresponds to a feedback.
+ const int64_t kTimeGapMs = 100; // Between each packet.
+
+ // Raw delays are = [50 250 450 650 850 1050 1250 1450] ms.
+ // Baseline delay will be 50 ms.
+ // Delay signals should be: [0 200 400 600 800 1000 1200 1400] ms.
+ const int64_t kMedianFilteredDelaysMs[] = {
+ 0, 100, 200, 300, 400, 600, 800, 1000};
+ const int kNumPackets = ARRAY_SIZE(kMedianFilteredDelaysMs);
+ const float kAlpha = 0.1f; // Used for exponential smoothing.
+
+ int64_t exp_smoothed_delays_ms[kNumPackets];
+ exp_smoothed_delays_ms[0] = kMedianFilteredDelaysMs[0];
+
+ for (int i = 1; i < kNumPackets; ++i) {
+ exp_smoothed_delays_ms[i] = static_cast<int64_t>(
+ kAlpha * kMedianFilteredDelaysMs[i] +
+ (1.0f - kAlpha) * exp_smoothed_delays_ms[i - 1] + 0.5f);
+ }
+
+ for (int i = 0; i < kNumPackets; ++i) {
+ int64_t send_time_us = (i + 1) * kTimeGapMs * 1000;
+ int64_t arrival_time_ms = send_time_us / 1000 + 50 + 200 * i;
+ uint16_t sequence_number = static_cast<uint16_t>(i + 1);
+ // Payload sizes are not important here.
+ const MediaPacket media_packet(kFlowId, send_time_us, 0, sequence_number);
+ nada_receiver_.ReceivePacket(arrival_time_ms, media_packet);
+
+ rtc::scoped_ptr<NadaFeedback> nada_feedback(static_cast<NadaFeedback*>(
+ nada_receiver_.GetFeedback(arrival_time_ms)));
+ EXPECT_EQ(nada_feedback->exp_smoothed_delay_ms(),
+ exp_smoothed_delays_ms[i]);
+ // Delays can be non-linearly warped.
+ EXPECT_EQ(nada_feedback->est_queuing_delay_signal_ms(),
+ Warp(exp_smoothed_delays_ms[i]));
+ // Zero loss, congestion signal = queuing_delay
+ EXPECT_EQ(nada_feedback->congestion_signal(),
+ Warp(exp_smoothed_delays_ms[i]));
+ }
+}
+
+TEST_F(FilterTest, MedianConstantArray) {
+ MedianFilterConstantArray();
+ for (int i = 0; i < kNumElements; ++i) {
+ EXPECT_EQ(median_filtered_[i], raw_signal_[i]);
+ }
+}
+
+TEST_F(FilterTest, MedianIntermittentNoise) {
+ MedianFilterIntermittentNoise();
+}
+
+TEST_F(FilterTest, ExponentialSmoothingConstantArray) {
+ int64_t exp_smoothed[kNumElements];
+ ExponentialSmoothingConstantArray(exp_smoothed);
+ for (int i = 0; i < kNumElements; ++i) {
+ EXPECT_EQ(exp_smoothed[i], kSignalValue);
+ }
+}
+
+TEST_F(FilterTest, ExponentialSmoothingInitialPertubation) {
+ const int64_t kSignal[] = {90000, 0, 0, 0, 0, 0};
+ const int kNumElements = ARRAY_SIZE(kSignal);
+ int64_t exp_smoothed[kNumElements];
+ ExponentialSmoothingFilter(kSignal, kNumElements, exp_smoothed);
+ for (int i = 1; i < kNumElements; ++i) {
+ EXPECT_EQ(
+ exp_smoothed[i],
+ static_cast<int64_t>(exp_smoothed[i - 1] * (1.0f - kAlpha) + 0.5f));
+ }
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.cc
new file mode 100644
index 0000000000..b18b9f06b9
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.cc
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2015 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 <algorithm>
+
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/common.h"
+#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+RembBweSender::RembBweSender(int kbps, BitrateObserver* observer, Clock* clock)
+ : bitrate_controller_(
+ BitrateController::CreateBitrateController(clock, observer)),
+ feedback_observer_(bitrate_controller_->CreateRtcpBandwidthObserver()),
+ clock_(clock) {
+ assert(kbps >= kMinBitrateKbps);
+ assert(kbps <= kMaxBitrateKbps);
+ bitrate_controller_->SetStartBitrate(1000 * kbps);
+ bitrate_controller_->SetMinMaxBitrate(1000 * kMinBitrateKbps,
+ 1000 * kMaxBitrateKbps);
+}
+
+RembBweSender::~RembBweSender() {
+}
+
+void RembBweSender::GiveFeedback(const FeedbackPacket& feedback) {
+ const RembFeedback& remb_feedback =
+ static_cast<const RembFeedback&>(feedback);
+ feedback_observer_->OnReceivedEstimatedBitrate(remb_feedback.estimated_bps());
+ ReportBlockList report_blocks;
+ report_blocks.push_back(remb_feedback.report_block());
+ feedback_observer_->OnReceivedRtcpReceiverReport(
+ report_blocks, 0, clock_->TimeInMilliseconds());
+ bitrate_controller_->Process();
+}
+
+int64_t RembBweSender::TimeUntilNextProcess() {
+ return bitrate_controller_->TimeUntilNextProcess();
+}
+
+int RembBweSender::Process() {
+ return bitrate_controller_->Process();
+}
+
+int RembBweSender::GetFeedbackIntervalMs() const {
+ return 100;
+}
+
+RembReceiver::RembReceiver(int flow_id, bool plot)
+ : BweReceiver(flow_id),
+ estimate_log_prefix_(),
+ plot_estimate_(plot),
+ clock_(0),
+ recv_stats_(ReceiveStatistics::Create(&clock_)),
+ latest_estimate_bps_(-1),
+ last_feedback_ms_(-1),
+ estimator_(new RemoteBitrateEstimatorAbsSendTime(this, &clock_)) {
+ std::stringstream ss;
+ ss << "Estimate_" << flow_id_ << "#1";
+ estimate_log_prefix_ = ss.str();
+ // Default RTT in RemoteRateControl is 200 ms ; 50 ms is more realistic.
+ estimator_->OnRttUpdate(50, 50);
+ estimator_->SetMinBitrate(kRemoteBitrateEstimatorMinBitrateBps);
+}
+
+RembReceiver::~RembReceiver() {
+}
+
+void RembReceiver::ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) {
+ recv_stats_->IncomingPacket(media_packet.header(),
+ media_packet.payload_size(), false);
+
+ latest_estimate_bps_ = -1;
+
+ int64_t step_ms = std::max<int64_t>(estimator_->TimeUntilNextProcess(), 0);
+ while ((clock_.TimeInMilliseconds() + step_ms) < arrival_time_ms) {
+ clock_.AdvanceTimeMilliseconds(step_ms);
+ estimator_->Process();
+ step_ms = std::max<int64_t>(estimator_->TimeUntilNextProcess(), 0);
+ }
+ estimator_->IncomingPacket(arrival_time_ms, media_packet.payload_size(),
+ media_packet.header(), true);
+ clock_.AdvanceTimeMilliseconds(arrival_time_ms - clock_.TimeInMilliseconds());
+ ASSERT_TRUE(arrival_time_ms == clock_.TimeInMilliseconds());
+
+ // Log received packet information.
+ BweReceiver::ReceivePacket(arrival_time_ms, media_packet);
+}
+
+FeedbackPacket* RembReceiver::GetFeedback(int64_t now_ms) {
+ BWE_TEST_LOGGING_CONTEXT("Remb");
+ uint32_t estimated_bps = 0;
+ RembFeedback* feedback = NULL;
+ if (LatestEstimate(&estimated_bps)) {
+ StatisticianMap statisticians = recv_stats_->GetActiveStatisticians();
+ RTCPReportBlock report_block;
+ if (!statisticians.empty()) {
+ report_block = BuildReportBlock(statisticians.begin()->second);
+ }
+
+ feedback = new RembFeedback(flow_id_, now_ms * 1000, last_feedback_ms_,
+ estimated_bps, report_block);
+ last_feedback_ms_ = now_ms;
+
+ double estimated_kbps = static_cast<double>(estimated_bps) / 1000.0;
+ RTC_UNUSED(estimated_kbps);
+ if (plot_estimate_) {
+ BWE_TEST_LOGGING_PLOT(0, estimate_log_prefix_,
+ clock_.TimeInMilliseconds(), estimated_kbps);
+ }
+ }
+ return feedback;
+}
+
+void RembReceiver::OnReceiveBitrateChanged(
+ const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) {
+}
+
+RTCPReportBlock RembReceiver::BuildReportBlock(
+ StreamStatistician* statistician) {
+ RTCPReportBlock report_block;
+ RtcpStatistics stats;
+ if (!statistician->GetStatistics(&stats, true))
+ return report_block;
+ report_block.fractionLost = stats.fraction_lost;
+ report_block.cumulativeLost = stats.cumulative_lost;
+ report_block.extendedHighSeqNum = stats.extended_max_sequence_number;
+ report_block.jitter = stats.jitter;
+ return report_block;
+}
+
+bool RembReceiver::LatestEstimate(uint32_t* estimate_bps) {
+ if (latest_estimate_bps_ < 0) {
+ std::vector<unsigned int> ssrcs;
+ unsigned int bps = 0;
+ if (!estimator_->LatestEstimate(&ssrcs, &bps)) {
+ return false;
+ }
+ latest_estimate_bps_ = bps;
+ }
+ *estimate_bps = latest_estimate_bps_;
+ return true;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h
new file mode 100644
index 0000000000..7dfd7a8459
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/remb.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_REMB_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_REMB_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+
+namespace webrtc {
+
+class BitrateObserver;
+class BitrateController;
+class ReceiveStatistics;
+class StreamStatistician;
+
+namespace testing {
+namespace bwe {
+
+class RembBweSender : public BweSender {
+ public:
+ RembBweSender(int kbps, BitrateObserver* observer, Clock* clock);
+ virtual ~RembBweSender();
+
+ int GetFeedbackIntervalMs() const override;
+ void GiveFeedback(const FeedbackPacket& feedback) override;
+ void OnPacketsSent(const Packets& packets) override {}
+ int64_t TimeUntilNextProcess() override;
+ int Process() override;
+
+ protected:
+ rtc::scoped_ptr<BitrateController> bitrate_controller_;
+ rtc::scoped_ptr<RtcpBandwidthObserver> feedback_observer_;
+
+ private:
+ Clock* clock_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RembBweSender);
+};
+
+class RembReceiver : public BweReceiver, public RemoteBitrateObserver {
+ public:
+ static const uint32_t kRemoteBitrateEstimatorMinBitrateBps = 30000;
+
+ RembReceiver(int flow_id, bool plot);
+ virtual ~RembReceiver();
+
+ void ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) override;
+ FeedbackPacket* GetFeedback(int64_t now_ms) override;
+ // Implements RemoteBitrateObserver.
+ void OnReceiveBitrateChanged(const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) override;
+
+ private:
+ static RTCPReportBlock BuildReportBlock(StreamStatistician* statistician);
+ bool LatestEstimate(uint32_t* estimate_bps);
+
+ std::string estimate_log_prefix_;
+ bool plot_estimate_;
+ SimulatedClock clock_;
+ rtc::scoped_ptr<ReceiveStatistics> recv_stats_;
+ int64_t latest_estimate_bps_;
+ int64_t last_feedback_ms_;
+ rtc::scoped_ptr<RemoteBitrateEstimator> estimator_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RembReceiver);
+};
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_REMB_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc
new file mode 100644
index 0000000000..8a7352874b
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.cc
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2015 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/modules/remote_bitrate_estimator/test/estimators/send_side.h"
+
+#include "webrtc/base/logging.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+const int kFeedbackIntervalMs = 50;
+
+FullBweSender::FullBweSender(int kbps, BitrateObserver* observer, Clock* clock)
+ : bitrate_controller_(
+ BitrateController::CreateBitrateController(clock, observer)),
+ rbe_(new RemoteBitrateEstimatorAbsSendTime(this, clock)),
+ feedback_observer_(bitrate_controller_->CreateRtcpBandwidthObserver()),
+ clock_(clock),
+ send_time_history_(clock_, 10000),
+ has_received_ack_(false),
+ last_acked_seq_num_(0) {
+ assert(kbps >= kMinBitrateKbps);
+ assert(kbps <= kMaxBitrateKbps);
+ bitrate_controller_->SetStartBitrate(1000 * kbps);
+ bitrate_controller_->SetMinMaxBitrate(1000 * kMinBitrateKbps,
+ 1000 * kMaxBitrateKbps);
+ rbe_->SetMinBitrate(1000 * kMinBitrateKbps);
+}
+
+FullBweSender::~FullBweSender() {
+}
+
+int FullBweSender::GetFeedbackIntervalMs() const {
+ return kFeedbackIntervalMs;
+}
+
+void FullBweSender::GiveFeedback(const FeedbackPacket& feedback) {
+ const SendSideBweFeedback& fb =
+ static_cast<const SendSideBweFeedback&>(feedback);
+ if (fb.packet_feedback_vector().empty())
+ return;
+ std::vector<PacketInfo> packet_feedback_vector(fb.packet_feedback_vector());
+ for (PacketInfo& packet_info : packet_feedback_vector) {
+ if (!send_time_history_.GetInfo(&packet_info, true)) {
+ LOG(LS_WARNING) << "Ack arrived too late.";
+ }
+ }
+
+ int64_t rtt_ms =
+ clock_->TimeInMilliseconds() - feedback.latest_send_time_ms();
+ rbe_->OnRttUpdate(rtt_ms, rtt_ms);
+ BWE_TEST_LOGGING_PLOT(1, "RTT", clock_->TimeInMilliseconds(), rtt_ms);
+
+ rbe_->IncomingPacketFeedbackVector(packet_feedback_vector);
+ if (has_received_ack_) {
+ int expected_packets = fb.packet_feedback_vector().back().sequence_number -
+ last_acked_seq_num_;
+ // Assuming no reordering for now.
+ if (expected_packets > 0) {
+ int lost_packets = expected_packets -
+ static_cast<int>(fb.packet_feedback_vector().size());
+ report_block_.fractionLost = (lost_packets << 8) / expected_packets;
+ report_block_.cumulativeLost += lost_packets;
+ report_block_.extendedHighSeqNum =
+ packet_feedback_vector.back().sequence_number;
+ ReportBlockList report_blocks;
+ report_blocks.push_back(report_block_);
+ feedback_observer_->OnReceivedRtcpReceiverReport(
+ report_blocks, rtt_ms, clock_->TimeInMilliseconds());
+ }
+ bitrate_controller_->Process();
+
+ last_acked_seq_num_ = LatestSequenceNumber(
+ packet_feedback_vector.back().sequence_number, last_acked_seq_num_);
+ } else {
+ last_acked_seq_num_ = packet_feedback_vector.back().sequence_number;
+ has_received_ack_ = true;
+ }
+}
+
+void FullBweSender::OnPacketsSent(const Packets& packets) {
+ for (Packet* packet : packets) {
+ if (packet->GetPacketType() == Packet::kMedia) {
+ MediaPacket* media_packet = static_cast<MediaPacket*>(packet);
+ send_time_history_.AddAndRemoveOld(media_packet->header().sequenceNumber,
+ media_packet->payload_size(),
+ packet->paced());
+ send_time_history_.OnSentPacket(media_packet->header().sequenceNumber,
+ media_packet->sender_timestamp_ms());
+ }
+ }
+}
+
+void FullBweSender::OnReceiveBitrateChanged(
+ const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) {
+ feedback_observer_->OnReceivedEstimatedBitrate(bitrate);
+}
+
+int64_t FullBweSender::TimeUntilNextProcess() {
+ return bitrate_controller_->TimeUntilNextProcess();
+}
+
+int FullBweSender::Process() {
+ rbe_->Process();
+ return bitrate_controller_->Process();
+}
+
+SendSideBweReceiver::SendSideBweReceiver(int flow_id)
+ : BweReceiver(flow_id), last_feedback_ms_(0) {
+}
+
+SendSideBweReceiver::~SendSideBweReceiver() {
+}
+
+void SendSideBweReceiver::ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) {
+ packet_feedback_vector_.push_back(PacketInfo(
+ -1, arrival_time_ms, media_packet.sender_timestamp_ms(),
+ media_packet.header().sequenceNumber, media_packet.payload_size(), true));
+
+ // Log received packet information.
+ BweReceiver::ReceivePacket(arrival_time_ms, media_packet);
+}
+
+FeedbackPacket* SendSideBweReceiver::GetFeedback(int64_t now_ms) {
+ if (now_ms - last_feedback_ms_ < kFeedbackIntervalMs)
+ return NULL;
+ last_feedback_ms_ = now_ms;
+ int64_t corrected_send_time_ms =
+ packet_feedback_vector_.back().send_time_ms + now_ms -
+ packet_feedback_vector_.back().arrival_time_ms;
+ FeedbackPacket* fb = new SendSideBweFeedback(
+ flow_id_, now_ms * 1000, corrected_send_time_ms, packet_feedback_vector_);
+ packet_feedback_vector_.clear();
+ return fb;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h b/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h
new file mode 100644
index 0000000000..ab9abc5cbc
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/send_side.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_SEND_SIDE_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_SEND_SIDE_H_
+
+#include <vector>
+
+#include "webrtc/modules/remote_bitrate_estimator/include/send_time_history.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class FullBweSender : public BweSender, public RemoteBitrateObserver {
+ public:
+ FullBweSender(int kbps, BitrateObserver* observer, Clock* clock);
+ virtual ~FullBweSender();
+
+ int GetFeedbackIntervalMs() const override;
+ void GiveFeedback(const FeedbackPacket& feedback) override;
+ void OnPacketsSent(const Packets& packets) override;
+ void OnReceiveBitrateChanged(const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) override;
+ int64_t TimeUntilNextProcess() override;
+ int Process() override;
+
+ protected:
+ rtc::scoped_ptr<BitrateController> bitrate_controller_;
+ rtc::scoped_ptr<RemoteBitrateEstimator> rbe_;
+ rtc::scoped_ptr<RtcpBandwidthObserver> feedback_observer_;
+
+ private:
+ Clock* const clock_;
+ RTCPReportBlock report_block_;
+ SendTimeHistory send_time_history_;
+ bool has_received_ack_;
+ uint16_t last_acked_seq_num_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(FullBweSender);
+};
+
+class SendSideBweReceiver : public BweReceiver {
+ public:
+ explicit SendSideBweReceiver(int flow_id);
+ virtual ~SendSideBweReceiver();
+
+ void ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) override;
+ FeedbackPacket* GetFeedback(int64_t now_ms) override;
+
+ private:
+ int64_t last_feedback_ms_;
+ std::vector<PacketInfo> packet_feedback_vector_;
+};
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_SEND_SIDE_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc b/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc
new file mode 100644
index 0000000000..a02abc6ab8
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.cc
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2015 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 <algorithm>
+
+#include "webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/common.h"
+#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
+#include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+TcpBweReceiver::TcpBweReceiver(int flow_id)
+ : BweReceiver(flow_id),
+ last_feedback_ms_(0),
+ latest_owd_ms_(0) {
+}
+
+TcpBweReceiver::~TcpBweReceiver() {
+}
+
+void TcpBweReceiver::ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) {
+ latest_owd_ms_ = arrival_time_ms - media_packet.sender_timestamp_ms() / 1000;
+ acks_.push_back(media_packet.header().sequenceNumber);
+
+ // Log received packet information.
+ BweReceiver::ReceivePacket(arrival_time_ms, media_packet);
+}
+
+FeedbackPacket* TcpBweReceiver::GetFeedback(int64_t now_ms) {
+ int64_t corrected_send_time_ms = now_ms - latest_owd_ms_;
+ FeedbackPacket* fb =
+ new TcpFeedback(flow_id_, now_ms * 1000, corrected_send_time_ms, acks_);
+ last_feedback_ms_ = now_ms;
+ acks_.clear();
+ return fb;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h b/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h
new file mode 100644
index 0000000000..b33c93eef7
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/estimators/tcp.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_TCP_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_TCP_H_
+
+#include <vector>
+
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+class TcpBweReceiver : public BweReceiver {
+ public:
+ explicit TcpBweReceiver(int flow_id);
+ virtual ~TcpBweReceiver();
+
+ void ReceivePacket(int64_t arrival_time_ms,
+ const MediaPacket& media_packet) override;
+ FeedbackPacket* GetFeedback(int64_t now_ms) override;
+
+ private:
+ int64_t last_feedback_ms_;
+ int64_t latest_owd_ms_;
+ std::vector<uint16_t> acks_;
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_TCP_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc
new file mode 100644
index 0000000000..6202b4a6a3
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc
@@ -0,0 +1,445 @@
+/*
+ * Copyright (c) 2015 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/modules/remote_bitrate_estimator/test/metric_recorder.h"
+
+#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
+
+#include <algorithm>
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+namespace {
+// Holder mean, Manhattan distance for p=1, EuclidianNorm/sqrt(n) for p=2.
+template <typename T>
+double NormLp(T sum, size_t size, double p) {
+ return pow(sum / size, 1.0 / p);
+}
+}
+
+const double kP = 1.0; // Used for Norm Lp.
+
+LinkShare::LinkShare(ChokeFilter* choke_filter)
+ : choke_filter_(choke_filter), running_flows_(choke_filter->flow_ids()) {
+}
+
+void LinkShare::PauseFlow(int flow_id) {
+ running_flows_.erase(flow_id);
+}
+
+void LinkShare::ResumeFlow(int flow_id) {
+ running_flows_.insert(flow_id);
+}
+
+uint32_t LinkShare::TotalAvailableKbps() {
+ return choke_filter_->capacity_kbps();
+}
+
+uint32_t LinkShare::AvailablePerFlowKbps(int flow_id) {
+ uint32_t available_capacity_per_flow_kbps = 0;
+ if (running_flows_.find(flow_id) != running_flows_.end()) {
+ available_capacity_per_flow_kbps =
+ TotalAvailableKbps() / static_cast<uint32_t>(running_flows_.size());
+ }
+ return available_capacity_per_flow_kbps;
+}
+
+MetricRecorder::MetricRecorder(const std::string algorithm_name,
+ int flow_id,
+ PacketSender* packet_sender,
+ LinkShare* link_share)
+ : algorithm_name_(algorithm_name),
+ flow_id_(flow_id),
+ link_share_(link_share),
+ now_ms_(0),
+ sum_delays_ms_(0),
+ delay_histogram_ms_(),
+ sum_delays_square_ms2_(0),
+ sum_throughput_bytes_(0),
+ last_unweighted_estimate_error_(0),
+ optimal_throughput_bits_(0),
+ last_available_bitrate_per_flow_kbps_(0),
+ start_computing_metrics_ms_(0),
+ started_computing_metrics_(false),
+ num_packets_received_(0) {
+ std::fill_n(sum_lp_weighted_estimate_error_, 2, 0);
+ if (packet_sender != nullptr)
+ packet_sender->set_metric_recorder(this);
+}
+
+void MetricRecorder::SetPlotInformation(
+ const std::vector<std::string>& prefixes,
+ bool plot_delay,
+ bool plot_loss) {
+ assert(prefixes.size() == kNumMetrics);
+ for (size_t i = 0; i < kNumMetrics; ++i) {
+ plot_information_[i].prefix = prefixes[i];
+ }
+ plot_information_[kThroughput].plot_interval_ms = 100;
+ plot_information_[kSendingEstimate].plot_interval_ms = 100;
+ plot_information_[kDelay].plot_interval_ms = 100;
+ plot_information_[kLoss].plot_interval_ms = 500;
+ plot_information_[kObjective].plot_interval_ms = 1000;
+ plot_information_[kTotalAvailable].plot_interval_ms = 1000;
+ plot_information_[kAvailablePerFlow].plot_interval_ms = 1000;
+
+ for (int i = kThroughput; i < kNumMetrics; ++i) {
+ plot_information_[i].last_plot_ms = 0;
+ switch (i) {
+ case kSendingEstimate:
+ case kObjective:
+ case kAvailablePerFlow:
+ plot_information_[i].plot = false;
+ break;
+ case kLoss:
+ plot_information_[i].plot = plot_loss;
+ break;
+ case kDelay:
+ plot_information_[i].plot = plot_delay;
+ break;
+ default:
+ plot_information_[i].plot = true;
+ }
+ }
+}
+
+void MetricRecorder::PlotAllDynamics() {
+ for (int i = kThroughput; i < kNumMetrics; ++i) {
+ if (plot_information_[i].plot &&
+ now_ms_ - plot_information_[i].last_plot_ms >=
+ plot_information_[i].plot_interval_ms) {
+ PlotDynamics(i);
+ }
+ }
+}
+
+void MetricRecorder::PlotDynamics(int metric) {
+ if (metric == kTotalAvailable) {
+ BWE_TEST_LOGGING_PLOT_WITH_NAME(
+ 0, plot_information_[kTotalAvailable].prefix, now_ms_,
+ GetTotalAvailableKbps(), "Available");
+ } else if (metric == kAvailablePerFlow) {
+ BWE_TEST_LOGGING_PLOT_WITH_NAME(
+ 0, plot_information_[kAvailablePerFlow].prefix, now_ms_,
+ GetAvailablePerFlowKbps(), "Available_per_flow");
+ } else {
+ PlotLine(metric, plot_information_[metric].prefix,
+ plot_information_[metric].time_ms,
+ plot_information_[metric].value);
+ }
+ plot_information_[metric].last_plot_ms = now_ms_;
+}
+
+template <typename T>
+void MetricRecorder::PlotLine(int windows_id,
+ const std::string& prefix,
+ int64_t time_ms,
+ T y) {
+ BWE_TEST_LOGGING_PLOT_WITH_NAME(windows_id, prefix, time_ms,
+ static_cast<double>(y), algorithm_name_);
+}
+
+void MetricRecorder::UpdateTimeMs(int64_t time_ms) {
+ now_ms_ = std::max(now_ms_, time_ms);
+}
+
+void MetricRecorder::UpdateThroughput(int64_t bitrate_kbps,
+ size_t payload_size) {
+ // Total throughput should be computed before updating the time.
+ PushThroughputBytes(payload_size, now_ms_);
+ plot_information_[kThroughput].Update(now_ms_, bitrate_kbps);
+}
+
+void MetricRecorder::UpdateSendingEstimateKbps(int64_t bitrate_kbps) {
+ plot_information_[kSendingEstimate].Update(now_ms_, bitrate_kbps);
+}
+
+void MetricRecorder::UpdateDelayMs(int64_t delay_ms) {
+ PushDelayMs(delay_ms, now_ms_);
+ plot_information_[kDelay].Update(now_ms_, delay_ms);
+}
+
+void MetricRecorder::UpdateLoss(float loss_ratio) {
+ plot_information_[kLoss].Update(now_ms_, loss_ratio);
+}
+
+void MetricRecorder::UpdateObjective() {
+ plot_information_[kObjective].Update(now_ms_, ObjectiveFunction());
+}
+
+uint32_t MetricRecorder::GetTotalAvailableKbps() {
+ if (link_share_ == nullptr)
+ return 0;
+ return link_share_->TotalAvailableKbps();
+}
+
+uint32_t MetricRecorder::GetAvailablePerFlowKbps() {
+ if (link_share_ == nullptr)
+ return 0;
+ return link_share_->AvailablePerFlowKbps(flow_id_);
+}
+
+uint32_t MetricRecorder::GetSendingEstimateKbps() {
+ return static_cast<uint32_t>(plot_information_[kSendingEstimate].value);
+}
+
+void MetricRecorder::PushDelayMs(int64_t delay_ms, int64_t arrival_time_ms) {
+ if (ShouldRecord(arrival_time_ms)) {
+ sum_delays_ms_ += delay_ms;
+ sum_delays_square_ms2_ += delay_ms * delay_ms;
+ if (delay_histogram_ms_.find(delay_ms) == delay_histogram_ms_.end()) {
+ delay_histogram_ms_[delay_ms] = 0;
+ }
+ ++delay_histogram_ms_[delay_ms];
+ }
+}
+
+void MetricRecorder::UpdateEstimateError(int64_t new_value) {
+ int64_t lp_value = pow(static_cast<double>(std::abs(new_value)), kP);
+ if (new_value < 0) {
+ sum_lp_weighted_estimate_error_[0] += lp_value;
+ } else {
+ sum_lp_weighted_estimate_error_[1] += lp_value;
+ }
+}
+
+void MetricRecorder::PushThroughputBytes(size_t payload_size,
+ int64_t arrival_time_ms) {
+ if (ShouldRecord(arrival_time_ms)) {
+ ++num_packets_received_;
+ sum_throughput_bytes_ += payload_size;
+
+ int64_t current_available_per_flow_kbps =
+ static_cast<int64_t>(GetAvailablePerFlowKbps());
+
+ int64_t current_bitrate_diff_kbps =
+ static_cast<int64_t>(GetSendingEstimateKbps()) -
+ current_available_per_flow_kbps;
+
+ int64_t weighted_estimate_error =
+ (((current_bitrate_diff_kbps + last_unweighted_estimate_error_) *
+ (arrival_time_ms - plot_information_[kThroughput].time_ms)) /
+ 2);
+
+ UpdateEstimateError(weighted_estimate_error);
+
+ optimal_throughput_bits_ +=
+ ((current_available_per_flow_kbps +
+ last_available_bitrate_per_flow_kbps_) *
+ (arrival_time_ms - plot_information_[kThroughput].time_ms)) /
+ 2;
+
+ last_available_bitrate_per_flow_kbps_ = current_available_per_flow_kbps;
+ }
+}
+
+bool MetricRecorder::ShouldRecord(int64_t arrival_time_ms) {
+ if (arrival_time_ms >= start_computing_metrics_ms_) {
+ if (!started_computing_metrics_) {
+ start_computing_metrics_ms_ = arrival_time_ms;
+ now_ms_ = arrival_time_ms;
+ started_computing_metrics_ = true;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void MetricRecorder::PlotThroughputHistogram(
+ const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t extra_offset_ms,
+ const std::string optimum_id) const {
+ double optimal_bitrate_per_flow_kbps = static_cast<double>(
+ optimal_throughput_bits_ / RunDurationMs(extra_offset_ms));
+
+ double neg_error = Renormalize(
+ NormLp(sum_lp_weighted_estimate_error_[0], num_packets_received_, kP));
+ double pos_error = Renormalize(
+ NormLp(sum_lp_weighted_estimate_error_[1], num_packets_received_, kP));
+
+ double average_bitrate_kbps = AverageBitrateKbps(extra_offset_ms);
+
+ // Prevent the error to be too close to zero (plotting issue).
+ double extra_error = average_bitrate_kbps / 500;
+
+ std::string optimum_title =
+ optimum_id.empty() ? "optimal_bitrate" : "optimal_bitrates#" + optimum_id;
+
+ BWE_TEST_LOGGING_LABEL(4, title, "average_bitrate_(kbps)", num_flows);
+ BWE_TEST_LOGGING_LIMITERRORBAR(
+ 4, bwe_name, average_bitrate_kbps,
+ average_bitrate_kbps - neg_error - extra_error,
+ average_bitrate_kbps + pos_error + extra_error, "estimate_error",
+ optimal_bitrate_per_flow_kbps, optimum_title, flow_id_);
+
+ BWE_TEST_LOGGING_LOG1("RESULTS >>> " + bwe_name + " Channel utilization : ",
+ "%lf %%",
+ 100.0 * static_cast<double>(average_bitrate_kbps) /
+ optimal_bitrate_per_flow_kbps);
+
+ RTC_UNUSED(pos_error);
+ RTC_UNUSED(neg_error);
+ RTC_UNUSED(extra_error);
+ RTC_UNUSED(optimal_bitrate_per_flow_kbps);
+}
+
+void MetricRecorder::PlotThroughputHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t extra_offset_ms) const {
+ PlotThroughputHistogram(title, bwe_name, num_flows, extra_offset_ms, "");
+}
+
+void MetricRecorder::PlotDelayHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t one_way_path_delay_ms) const {
+ double average_delay_ms =
+ static_cast<double>(sum_delays_ms_) / num_packets_received_;
+
+ // Prevent the error to be too close to zero (plotting issue).
+ double extra_error = average_delay_ms / 500;
+ double tenth_sigma_ms = DelayStdDev() / 10.0 + extra_error;
+ int64_t percentile_5_ms = NthDelayPercentile(5);
+ int64_t percentile_95_ms = NthDelayPercentile(95);
+
+ BWE_TEST_LOGGING_LABEL(5, title, "average_delay_(ms)", num_flows)
+ BWE_TEST_LOGGING_ERRORBAR(5, bwe_name, average_delay_ms, percentile_5_ms,
+ percentile_95_ms, "5th and 95th percentiles",
+ flow_id_);
+
+ // Log added latency, disregard baseline path delay.
+ BWE_TEST_LOGGING_LOG1("RESULTS >>> " + bwe_name + " Delay average : ",
+ "%lf ms", average_delay_ms - one_way_path_delay_ms);
+ BWE_TEST_LOGGING_LOG1("RESULTS >>> " + bwe_name + " Delay 5th percentile : ",
+ "%ld ms", percentile_5_ms - one_way_path_delay_ms);
+ BWE_TEST_LOGGING_LOG1("RESULTS >>> " + bwe_name + " Delay 95th percentile : ",
+ "%ld ms", percentile_95_ms - one_way_path_delay_ms);
+
+ RTC_UNUSED(tenth_sigma_ms);
+ RTC_UNUSED(percentile_5_ms);
+ RTC_UNUSED(percentile_95_ms);
+}
+
+void MetricRecorder::PlotLossHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ float global_loss_ratio) const {
+ BWE_TEST_LOGGING_LABEL(6, title, "packet_loss_ratio_(%)", num_flows)
+ BWE_TEST_LOGGING_BAR(6, bwe_name, 100.0f * global_loss_ratio, flow_id_);
+
+ BWE_TEST_LOGGING_LOG1("RESULTS >>> " + bwe_name + " Loss Ratio : ", "%f %%",
+ 100.0f * global_loss_ratio);
+}
+
+void MetricRecorder::PlotObjectiveHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows) const {
+ BWE_TEST_LOGGING_LABEL(7, title, "objective_function", num_flows)
+ BWE_TEST_LOGGING_BAR(7, bwe_name, ObjectiveFunction(), flow_id_);
+}
+
+void MetricRecorder::PlotZero() {
+ for (int i = kThroughput; i <= kLoss; ++i) {
+ if (plot_information_[i].plot) {
+ std::stringstream prefix;
+ prefix << "Receiver_" << flow_id_ << "_" + plot_information_[i].prefix;
+ PlotLine(i, prefix.str(), now_ms_, 0);
+ plot_information_[i].last_plot_ms = now_ms_;
+ }
+ }
+}
+
+void MetricRecorder::PauseFlow() {
+ PlotZero();
+ link_share_->PauseFlow(flow_id_);
+}
+
+void MetricRecorder::ResumeFlow(int64_t paused_time_ms) {
+ UpdateTimeMs(now_ms_ + paused_time_ms);
+ PlotZero();
+ link_share_->ResumeFlow(flow_id_);
+}
+
+double MetricRecorder::AverageBitrateKbps(int64_t extra_offset_ms) const {
+ int64_t duration_ms = RunDurationMs(extra_offset_ms);
+ if (duration_ms == 0)
+ return 0.0;
+ return static_cast<double>(8 * sum_throughput_bytes_ / duration_ms);
+}
+
+int64_t MetricRecorder::RunDurationMs(int64_t extra_offset_ms) const {
+ return now_ms_ - start_computing_metrics_ms_ - extra_offset_ms;
+}
+
+double MetricRecorder::DelayStdDev() const {
+ if (num_packets_received_ == 0) {
+ return 0.0;
+ }
+ double mean = static_cast<double>(sum_delays_ms_) / num_packets_received_;
+ double mean2 =
+ static_cast<double>(sum_delays_square_ms2_) / num_packets_received_;
+ return sqrt(mean2 - pow(mean, 2.0));
+}
+
+// Since delay values are bounded in a subset of [0, 5000] ms,
+// this function's execution time is O(1), independend of num_packets_received_.
+int64_t MetricRecorder::NthDelayPercentile(int n) const {
+ if (num_packets_received_ == 0) {
+ return 0;
+ }
+ size_t num_packets_remaining = (n * num_packets_received_) / 100;
+ for (auto hist : delay_histogram_ms_) {
+ if (num_packets_remaining <= hist.second)
+ return static_cast<int64_t>(hist.first);
+ num_packets_remaining -= hist.second;
+ }
+
+ assert(false);
+ return -1;
+}
+
+// The weighted_estimate_error_ was weighted based on time windows.
+// This function scales back the result before plotting.
+double MetricRecorder::Renormalize(double x) const {
+ return (x * num_packets_received_) / now_ms_;
+}
+
+inline double U(int64_t x, double alpha) {
+ if (alpha == 1.0) {
+ return log(static_cast<double>(x));
+ }
+ return pow(static_cast<double>(x), 1.0 - alpha) / (1.0 - alpha);
+}
+
+inline double U(size_t x, double alpha) {
+ return U(static_cast<int64_t>(x), alpha);
+}
+
+// TODO(magalhaesc): Update ObjectiveFunction.
+double MetricRecorder::ObjectiveFunction() const {
+ const double kDelta = 0.15; // Delay penalty factor.
+ const double kAlpha = 1.0;
+ const double kBeta = 1.0;
+
+ double throughput_metric = U(sum_throughput_bytes_, kAlpha);
+ double delay_penalty = kDelta * U(sum_delays_ms_, kBeta);
+
+ return throughput_metric - delay_penalty;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h
new file mode 100644
index 0000000000..2be13e0b0b
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_METRIC_RECORDER_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_METRIC_RECORDER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "webrtc/base/common.h"
+#include "webrtc/test/testsupport/gtest_prod_util.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class ChokeFilter;
+class PacketSender;
+
+class LinkShare {
+ public:
+ explicit LinkShare(ChokeFilter* choke_filter);
+
+ void PauseFlow(int flow_id); // Increases available capacity per flow.
+ void ResumeFlow(int flow_id); // Decreases available capacity per flow.
+
+ uint32_t TotalAvailableKbps();
+ // If the given flow is paused, its output is zero.
+ uint32_t AvailablePerFlowKbps(int flow_id);
+
+ private:
+ ChokeFilter* choke_filter_;
+ std::set<int> running_flows_;
+};
+
+struct PlotInformation {
+ PlotInformation()
+ : prefix(),
+ last_plot_ms(0),
+ time_ms(0),
+ value(0.0),
+ plot_interval_ms(0) {}
+ template <typename T>
+ void Update(int64_t now_ms, T new_value) {
+ time_ms = now_ms;
+ value = static_cast<double>(new_value);
+ }
+ std::string prefix;
+ bool plot;
+ int64_t last_plot_ms;
+ int64_t time_ms;
+ double value;
+ int64_t plot_interval_ms;
+};
+
+class MetricRecorder {
+ public:
+ MetricRecorder(const std::string algorithm_name,
+ int flow_id,
+ PacketSender* packet_sender,
+ LinkShare* link_share);
+
+ void SetPlotInformation(const std::vector<std::string>& prefixes,
+ bool plot_delay,
+ bool plot_loss);
+
+ template <typename T>
+ void PlotLine(int windows_id,
+ const std::string& prefix,
+ int64_t time_ms,
+ T y);
+
+ void PlotDynamics(int metric);
+ void PlotAllDynamics();
+
+ void UpdateTimeMs(int64_t time_ms);
+ void UpdateThroughput(int64_t bitrate_kbps, size_t payload_size);
+ void UpdateSendingEstimateKbps(int64_t bitrate_kbps);
+ void UpdateDelayMs(int64_t delay_ms);
+ void UpdateLoss(float loss_ratio);
+ void UpdateObjective();
+
+ void PlotThroughputHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t extra_offset_ms,
+ const std::string optimum_id) const;
+
+ void PlotThroughputHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t extra_offset_ms) const;
+
+ void PlotDelayHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ int64_t one_way_path_delay_ms) const;
+
+ void PlotLossHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows,
+ float global_loss_ratio) const;
+
+ void PlotObjectiveHistogram(const std::string& title,
+ const std::string& bwe_name,
+ size_t num_flows) const;
+
+ void set_start_computing_metrics_ms(int64_t start_computing_metrics_ms) {
+ start_computing_metrics_ms_ = start_computing_metrics_ms;
+ }
+
+ void set_plot_available_capacity(bool plot) {
+ plot_information_[kTotalAvailable].plot = plot;
+ }
+
+ void PauseFlow(); // Plot zero.
+ void ResumeFlow(int64_t paused_time_ms); // Plot zero.
+ void PlotZero();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(MetricRecorderTest, NoPackets);
+ FRIEND_TEST_ALL_PREFIXES(MetricRecorderTest, RegularPackets);
+ FRIEND_TEST_ALL_PREFIXES(MetricRecorderTest, VariableDelayPackets);
+
+ uint32_t GetTotalAvailableKbps();
+ uint32_t GetAvailablePerFlowKbps();
+ uint32_t GetSendingEstimateKbps();
+ double ObjectiveFunction() const;
+
+ double Renormalize(double x) const;
+ bool ShouldRecord(int64_t arrival_time_ms);
+
+ void PushDelayMs(int64_t delay_ms, int64_t arrival_time_ms);
+ void PushThroughputBytes(size_t throughput_bytes, int64_t arrival_time_ms);
+
+ void UpdateEstimateError(int64_t new_value);
+ double DelayStdDev() const;
+ int64_t NthDelayPercentile(int n) const;
+ double AverageBitrateKbps(int64_t extra_offset_ms) const;
+ int64_t RunDurationMs(int64_t extra_offset_ms) const;
+
+ enum Metrics {
+ kThroughput = 0,
+ kSendingEstimate,
+ kDelay,
+ kLoss,
+ kObjective,
+ kTotalAvailable,
+ kAvailablePerFlow,
+ kNumMetrics
+ };
+
+ std::string algorithm_name_;
+ int flow_id_;
+ LinkShare* link_share_;
+
+ int64_t now_ms_;
+
+ PlotInformation plot_information_[kNumMetrics];
+
+ int64_t sum_delays_ms_;
+ // delay_histogram_ms_[i] counts how many packets have delay = i ms.
+ std::map<int64_t, size_t> delay_histogram_ms_;
+ int64_t sum_delays_square_ms2_; // Used to compute standard deviation.
+ size_t sum_throughput_bytes_;
+ // ((Receiving rate - available bitrate per flow) * time window)^p.
+ // 0 for negative values, 1 for positive values.
+ int64_t sum_lp_weighted_estimate_error_[2];
+ int64_t last_unweighted_estimate_error_;
+ int64_t optimal_throughput_bits_;
+ int64_t last_available_bitrate_per_flow_kbps_;
+ int64_t start_computing_metrics_ms_;
+ bool started_computing_metrics_;
+ size_t num_packets_received_;
+};
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_METRIC_RECORDER_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/metric_recorder_unittest.cc b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder_unittest.cc
new file mode 100644
index 0000000000..7d4ed5fd5f
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder_unittest.cc
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2015 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/modules/remote_bitrate_estimator/test/metric_recorder.h"
+
+#include <math.h>
+#include <algorithm>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class MetricRecorderTest : public ::testing::Test {
+ public:
+ MetricRecorderTest() : metric_recorder_("Test", 0, nullptr, nullptr) {}
+
+ ~MetricRecorderTest() {}
+
+ protected:
+ MetricRecorder metric_recorder_;
+};
+
+TEST_F(MetricRecorderTest, NoPackets) {
+ EXPECT_EQ(metric_recorder_.AverageBitrateKbps(0), 0);
+ EXPECT_EQ(metric_recorder_.DelayStdDev(), 0.0);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(0), 0);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(5), 0);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(95), 0);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(100), 0);
+}
+
+TEST_F(MetricRecorderTest, RegularPackets) {
+ const size_t kPayloadSizeBytes = 1200;
+ const int64_t kDelayMs = 20;
+ const int64_t kInterpacketGapMs = 5;
+ const int kNumPackets = 1000;
+
+ for (int i = 0; i < kNumPackets; ++i) {
+ int64_t arrival_time_ms = kInterpacketGapMs * i + kDelayMs;
+ metric_recorder_.UpdateTimeMs(arrival_time_ms);
+ metric_recorder_.PushDelayMs(kDelayMs, arrival_time_ms);
+ metric_recorder_.PushThroughputBytes(kPayloadSizeBytes, arrival_time_ms);
+ }
+
+ EXPECT_NEAR(
+ metric_recorder_.AverageBitrateKbps(0),
+ static_cast<uint32_t>(kPayloadSizeBytes * 8) / (kInterpacketGapMs), 10);
+
+ EXPECT_EQ(metric_recorder_.DelayStdDev(), 0.0);
+
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(0), kDelayMs);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(5), kDelayMs);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(95), kDelayMs);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(100), kDelayMs);
+}
+
+TEST_F(MetricRecorderTest, VariableDelayPackets) {
+ const size_t kPayloadSizeBytes = 1200;
+ const int64_t kInterpacketGapMs = 2000;
+ const int kNumPackets = 1000;
+
+ std::vector<int64_t> delays_ms;
+ for (int i = 0; i < kNumPackets; ++i) {
+ delays_ms.push_back(static_cast<int64_t>(i + 1));
+ }
+ // Order of packets should not matter here.
+ std::random_shuffle(delays_ms.begin(), delays_ms.end());
+
+ int first_received_ms = delays_ms[0];
+ int64_t last_received_ms = 0;
+ for (int i = 0; i < kNumPackets; ++i) {
+ int64_t arrival_time_ms = kInterpacketGapMs * i + delays_ms[i];
+ last_received_ms = std::max(last_received_ms, arrival_time_ms);
+ metric_recorder_.UpdateTimeMs(arrival_time_ms);
+ metric_recorder_.PushDelayMs(delays_ms[i], arrival_time_ms);
+ metric_recorder_.PushThroughputBytes(kPayloadSizeBytes, arrival_time_ms);
+ }
+
+ size_t received_bits = kPayloadSizeBytes * 8 * kNumPackets;
+ EXPECT_NEAR(metric_recorder_.AverageBitrateKbps(0),
+ static_cast<uint32_t>(received_bits) /
+ ((last_received_ms - first_received_ms)),
+ 10);
+
+ double expected_x = (kNumPackets + 1) / 2.0;
+ double expected_x2 = ((kNumPackets + 1) * (2 * kNumPackets + 1)) / 6.0;
+ double var = expected_x2 - pow(expected_x, 2.0);
+ EXPECT_NEAR(metric_recorder_.DelayStdDev(), sqrt(var), kNumPackets / 1000.0);
+
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(0), 1);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(5), (5 * kNumPackets) / 100);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(95), (95 * kNumPackets) / 100);
+ EXPECT_EQ(metric_recorder_.NthDelayPercentile(100), kNumPackets);
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet.h b/webrtc/modules/remote_bitrate_estimator/test/packet.h
new file mode 100644
index 0000000000..11885a4544
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/packet.h
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_H_
+
+#include <list>
+#include <map>
+#include <vector>
+
+#include "webrtc/common_types.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class Packet {
+ public:
+ enum Type { kMedia, kFeedback };
+
+ Packet();
+ Packet(int flow_id, int64_t send_time_us, size_t payload_size);
+ virtual ~Packet();
+
+ virtual bool operator<(const Packet& rhs) const;
+
+ virtual int flow_id() const { return flow_id_; }
+ virtual void set_send_time_us(int64_t send_time_us);
+ virtual int64_t send_time_us() const { return send_time_us_; }
+ virtual int64_t sender_timestamp_us() const { return sender_timestamp_us_; }
+ virtual size_t payload_size() const { return payload_size_; }
+ virtual Packet::Type GetPacketType() const = 0;
+ virtual void set_sender_timestamp_us(int64_t sender_timestamp_us) {
+ sender_timestamp_us_ = sender_timestamp_us;
+ }
+ virtual void set_paced(bool paced) { paced_ = paced; }
+ virtual bool paced() const { return paced_; }
+ virtual int64_t creation_time_ms() const {
+ return (creation_time_us_ + 500) / 1000;
+ }
+ virtual int64_t sender_timestamp_ms() const {
+ return (sender_timestamp_us_ + 500) / 1000;
+ }
+ virtual int64_t send_time_ms() const { return (send_time_us_ + 500) / 1000; }
+
+ protected:
+ int flow_id_;
+ int64_t creation_time_us_; // Time when the packet was created.
+ int64_t send_time_us_; // Time the packet left last processor touching it.
+ int64_t sender_timestamp_us_; // Time the packet left the Sender.
+ size_t payload_size_; // Size of the (non-existent, simulated) payload.
+ bool paced_; // True if sent through paced sender.
+};
+
+class MediaPacket : public Packet {
+ public:
+ MediaPacket();
+ MediaPacket(int flow_id,
+ int64_t send_time_us,
+ size_t payload_size,
+ uint16_t sequence_number);
+ MediaPacket(int flow_id,
+ int64_t send_time_us,
+ size_t payload_size,
+ const RTPHeader& header);
+ MediaPacket(int64_t send_time_us, uint16_t sequence_number);
+
+ virtual ~MediaPacket() {}
+
+ int64_t GetAbsSendTimeInMs() const {
+ int64_t timestamp = header_.extension.absoluteSendTime
+ << kAbsSendTimeInterArrivalUpshift;
+ return 1000.0 * timestamp / static_cast<double>(1 << kInterArrivalShift);
+ }
+ void SetAbsSendTimeMs(int64_t abs_send_time_ms);
+ const RTPHeader& header() const { return header_; }
+ virtual Packet::Type GetPacketType() const { return kMedia; }
+ uint16_t sequence_number() const { return header_.sequenceNumber; }
+
+ private:
+ static const int kAbsSendTimeFraction = 18;
+ static const int kAbsSendTimeInterArrivalUpshift = 8;
+ static const int kInterArrivalShift =
+ kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift;
+
+ RTPHeader header_;
+};
+
+class FeedbackPacket : public Packet {
+ public:
+ FeedbackPacket(int flow_id,
+ int64_t this_send_time_us,
+ int64_t latest_send_time_ms)
+ : Packet(flow_id, this_send_time_us, 0),
+ latest_send_time_ms_(latest_send_time_ms) {}
+ virtual ~FeedbackPacket() {}
+
+ virtual Packet::Type GetPacketType() const { return kFeedback; }
+ int64_t latest_send_time_ms() const { return latest_send_time_ms_; }
+
+ private:
+ int64_t latest_send_time_ms_; // Time stamp for the latest sent FbPacket.
+};
+
+class RembFeedback : public FeedbackPacket {
+ public:
+ RembFeedback(int flow_id,
+ int64_t send_time_us,
+ int64_t latest_send_time_ms,
+ uint32_t estimated_bps,
+ RTCPReportBlock report_block);
+ virtual ~RembFeedback() {}
+
+ uint32_t estimated_bps() const { return estimated_bps_; }
+ RTCPReportBlock report_block() const { return report_block_; }
+
+ private:
+ const uint32_t estimated_bps_;
+ const RTCPReportBlock report_block_;
+};
+
+class SendSideBweFeedback : public FeedbackPacket {
+ public:
+ typedef std::map<uint16_t, int64_t> ArrivalTimesMap;
+ SendSideBweFeedback(int flow_id,
+ int64_t send_time_us,
+ int64_t latest_send_time_ms,
+ const std::vector<PacketInfo>& packet_feedback_vector);
+ virtual ~SendSideBweFeedback() {}
+
+ const std::vector<PacketInfo>& packet_feedback_vector() const {
+ return packet_feedback_vector_;
+ }
+
+ private:
+ const std::vector<PacketInfo> packet_feedback_vector_;
+};
+
+class NadaFeedback : public FeedbackPacket {
+ public:
+ NadaFeedback(int flow_id,
+ int64_t this_send_time_us,
+ int64_t exp_smoothed_delay_ms,
+ int64_t est_queuing_delay_signal_ms,
+ int64_t congestion_signal,
+ float derivative,
+ float receiving_rate,
+ int64_t latest_send_time_ms)
+ : FeedbackPacket(flow_id, this_send_time_us, latest_send_time_ms),
+ exp_smoothed_delay_ms_(exp_smoothed_delay_ms),
+ est_queuing_delay_signal_ms_(est_queuing_delay_signal_ms),
+ congestion_signal_(congestion_signal),
+ derivative_(derivative),
+ receiving_rate_(receiving_rate) {}
+ virtual ~NadaFeedback() {}
+
+ int64_t exp_smoothed_delay_ms() const { return exp_smoothed_delay_ms_; }
+ int64_t est_queuing_delay_signal_ms() const {
+ return est_queuing_delay_signal_ms_;
+ }
+ int64_t congestion_signal() const { return congestion_signal_; }
+ float derivative() const { return derivative_; }
+ float receiving_rate() const { return receiving_rate_; }
+
+ private:
+ int64_t exp_smoothed_delay_ms_; // Referred as d_hat_n.
+ int64_t est_queuing_delay_signal_ms_; // Referred as d_tilde_n.
+ int64_t congestion_signal_; // Referred as x_n.
+ float derivative_; // Referred as x'_n.
+ float receiving_rate_; // Referred as R_r.
+};
+
+class TcpFeedback : public FeedbackPacket {
+ public:
+ TcpFeedback(int flow_id,
+ int64_t send_time_us,
+ int64_t latest_send_time_ms,
+ const std::vector<uint16_t>& acked_packets)
+ : FeedbackPacket(flow_id, send_time_us, latest_send_time_ms),
+ acked_packets_(acked_packets) {}
+ virtual ~TcpFeedback() {}
+
+ const std::vector<uint16_t>& acked_packets() const { return acked_packets_; }
+
+ private:
+ const std::vector<uint16_t> acked_packets_;
+};
+
+typedef std::list<Packet*> Packets;
+typedef std::list<Packet*>::iterator PacketsIt;
+typedef std::list<Packet*>::const_iterator PacketsConstIt;
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.cc b/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.cc
new file mode 100644
index 0000000000..f70c212af7
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.cc
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2015 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/modules/remote_bitrate_estimator/test/packet_receiver.h"
+
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/common.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+#include "webrtc/modules/rtp_rtcp/interface/receive_statistics.h"
+#include "webrtc/system_wrappers/include/clock.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+PacketReceiver::PacketReceiver(PacketProcessorListener* listener,
+ int flow_id,
+ BandwidthEstimatorType bwe_type,
+ bool plot_delay,
+ bool plot_bwe,
+ MetricRecorder* metric_recorder)
+ : PacketProcessor(listener, flow_id, kReceiver),
+ bwe_receiver_(CreateBweReceiver(bwe_type, flow_id, plot_bwe)),
+ metric_recorder_(metric_recorder),
+ plot_delay_(plot_delay),
+ last_delay_plot_ms_(0),
+ // #2 aligns the plot with the right axis.
+ delay_prefix_("Delay_ms#2"),
+ bwe_type_(bwe_type) {
+ if (metric_recorder_ != nullptr) {
+ // Setup the prefix std::strings used when logging.
+ std::vector<std::string> prefixes;
+
+ // Metric recorder plots them in separated figures,
+ // alignment will take place with the #1 left axis.
+ prefixes.push_back("Throughput_kbps#1");
+ prefixes.push_back("Sending_Estimate_kbps#1");
+ prefixes.push_back("Delay_ms_#1");
+ prefixes.push_back("Packet_Loss_#1");
+ prefixes.push_back("Objective_function_#1");
+
+ // Plot Total/PerFlow Available capacity together with throughputs.
+ prefixes.push_back("Throughput_kbps#1"); // Total Available.
+ prefixes.push_back("Throughput_kbps#1"); // Available per flow.
+
+ bool plot_loss = plot_delay; // Plot loss if delay is plotted.
+ metric_recorder_->SetPlotInformation(prefixes, plot_delay, plot_loss);
+ }
+}
+
+PacketReceiver::PacketReceiver(PacketProcessorListener* listener,
+ int flow_id,
+ BandwidthEstimatorType bwe_type,
+ bool plot_delay,
+ bool plot_bwe)
+ : PacketReceiver(listener,
+ flow_id,
+ bwe_type,
+ plot_delay,
+ plot_bwe,
+ nullptr) {
+}
+
+PacketReceiver::~PacketReceiver() {
+}
+
+void PacketReceiver::RunFor(int64_t time_ms, Packets* in_out) {
+ Packets feedback;
+ for (auto it = in_out->begin(); it != in_out->end();) {
+ // PacketReceivers are only associated with a single stream, and therefore
+ // should only process a single flow id.
+ // TODO(holmer): Break this out into a Demuxer which implements both
+ // PacketProcessorListener and PacketProcessor.
+ BWE_TEST_LOGGING_CONTEXT("Receiver");
+ if ((*it)->GetPacketType() == Packet::kMedia &&
+ (*it)->flow_id() == *flow_ids().begin()) {
+ BWE_TEST_LOGGING_CONTEXT(*flow_ids().begin());
+ const MediaPacket* media_packet = static_cast<const MediaPacket*>(*it);
+ // We're treating the send time (from previous filter) as the arrival
+ // time once packet reaches the estimator.
+ int64_t arrival_time_ms = media_packet->send_time_ms();
+ int64_t send_time_ms = media_packet->creation_time_ms();
+ delay_stats_.Push(arrival_time_ms - send_time_ms);
+
+ if (metric_recorder_ != nullptr) {
+ metric_recorder_->UpdateTimeMs(arrival_time_ms);
+ UpdateMetrics(arrival_time_ms, send_time_ms,
+ media_packet->payload_size());
+ metric_recorder_->PlotAllDynamics();
+ } else if (plot_delay_) {
+ PlotDelay(arrival_time_ms, send_time_ms);
+ }
+
+ bwe_receiver_->ReceivePacket(arrival_time_ms, *media_packet);
+ FeedbackPacket* fb = bwe_receiver_->GetFeedback(arrival_time_ms);
+ if (fb)
+ feedback.push_back(fb);
+ delete media_packet;
+ it = in_out->erase(it);
+ } else {
+ ++it;
+ }
+ }
+ // Insert feedback packets to be sent back to the sender.
+ in_out->merge(feedback, DereferencingComparator<Packet>);
+}
+
+void PacketReceiver::UpdateMetrics(int64_t arrival_time_ms,
+ int64_t send_time_ms,
+ size_t payload_size) {
+ metric_recorder_->UpdateThroughput(bwe_receiver_->RecentKbps(), payload_size);
+ metric_recorder_->UpdateDelayMs(arrival_time_ms - send_time_ms);
+ metric_recorder_->UpdateLoss(bwe_receiver_->RecentPacketLossRatio());
+ metric_recorder_->UpdateObjective();
+}
+
+void PacketReceiver::PlotDelay(int64_t arrival_time_ms, int64_t send_time_ms) {
+ const int64_t kDelayPlotIntervalMs = 100;
+ if (arrival_time_ms >= last_delay_plot_ms_ + kDelayPlotIntervalMs) {
+ BWE_TEST_LOGGING_PLOT_WITH_NAME(0, delay_prefix_, arrival_time_ms,
+ arrival_time_ms - send_time_ms,
+ bwe_names[bwe_type_]);
+ last_delay_plot_ms_ = arrival_time_ms;
+ }
+}
+
+float PacketReceiver::GlobalPacketLoss() {
+ return bwe_receiver_->GlobalReceiverPacketLossRatio();
+}
+
+Stats<double> PacketReceiver::GetDelayStats() const {
+ return delay_stats_;
+}
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h b/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h
new file mode 100644
index 0000000000..fb9e9fd7ab
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/packet_receiver.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_RECEIVER_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_RECEIVER_H_
+
+#include <string>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class PacketReceiver : public PacketProcessor {
+ public:
+ PacketReceiver(PacketProcessorListener* listener,
+ int flow_id,
+ BandwidthEstimatorType bwe_type,
+ bool plot_delay,
+ bool plot_bwe);
+ PacketReceiver(PacketProcessorListener* listener,
+ int flow_id,
+ BandwidthEstimatorType bwe_type,
+ bool plot_delay,
+ bool plot_bwe,
+ MetricRecorder* metric_recorder);
+ ~PacketReceiver();
+
+ // Implements PacketProcessor.
+ void RunFor(int64_t time_ms, Packets* in_out) override;
+
+ void LogStats();
+
+ Stats<double> GetDelayStats() const;
+
+ float GlobalPacketLoss();
+
+ protected:
+ void UpdateMetrics(int64_t arrival_time_ms,
+ int64_t send_time_ms,
+ size_t payload_size);
+
+ Stats<double> delay_stats_;
+ rtc::scoped_ptr<BweReceiver> bwe_receiver_;
+
+ private:
+ void PlotDelay(int64_t arrival_time_ms, int64_t send_time_ms);
+ MetricRecorder* metric_recorder_;
+ bool plot_delay_; // Used in case there isn't a metric recorder.
+ int64_t last_delay_plot_ms_;
+ std::string delay_prefix_;
+ BandwidthEstimatorType bwe_type_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(PacketReceiver);
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_RECEIVER_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc
new file mode 100644
index 0000000000..f1faa49d7e
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.cc
@@ -0,0 +1,494 @@
+/*
+ * Copyright (c) 2015 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/modules/remote_bitrate_estimator/test/packet_sender.h"
+
+#include <algorithm>
+#include <list>
+#include <sstream>
+
+#include "webrtc/base/checks.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+void PacketSender::Pause() {
+ running_ = false;
+ if (metric_recorder_ != nullptr) {
+ metric_recorder_->PauseFlow();
+ }
+}
+
+void PacketSender::Resume(int64_t paused_time_ms) {
+ running_ = true;
+ if (metric_recorder_ != nullptr) {
+ metric_recorder_->ResumeFlow(paused_time_ms);
+ }
+}
+
+void PacketSender::set_metric_recorder(MetricRecorder* metric_recorder) {
+ metric_recorder_ = metric_recorder;
+}
+
+void PacketSender::RecordBitrate() {
+ if (metric_recorder_ != nullptr) {
+ BWE_TEST_LOGGING_CONTEXT("Sender");
+ BWE_TEST_LOGGING_CONTEXT(*flow_ids().begin());
+ metric_recorder_->UpdateTimeMs(clock_.TimeInMilliseconds());
+ metric_recorder_->UpdateSendingEstimateKbps(TargetBitrateKbps());
+ }
+}
+
+std::list<FeedbackPacket*> GetFeedbackPackets(Packets* in_out,
+ int64_t end_time_ms,
+ int flow_id) {
+ std::list<FeedbackPacket*> fb_packets;
+ for (auto it = in_out->begin(); it != in_out->end();) {
+ if ((*it)->send_time_us() > 1000 * end_time_ms)
+ break;
+ if ((*it)->GetPacketType() == Packet::kFeedback &&
+ flow_id == (*it)->flow_id()) {
+ fb_packets.push_back(static_cast<FeedbackPacket*>(*it));
+ it = in_out->erase(it);
+ } else {
+ ++it;
+ }
+ }
+ return fb_packets;
+}
+
+VideoSender::VideoSender(PacketProcessorListener* listener,
+ VideoSource* source,
+ BandwidthEstimatorType estimator_type)
+ : PacketSender(listener, source->flow_id()),
+ source_(source),
+ bwe_(CreateBweSender(estimator_type,
+ source_->bits_per_second() / 1000,
+ this,
+ &clock_)),
+ previous_sending_bitrate_(0) {
+ modules_.push_back(bwe_.get());
+}
+
+VideoSender::~VideoSender() {
+}
+
+void VideoSender::Pause() {
+ previous_sending_bitrate_ = TargetBitrateKbps();
+ PacketSender::Pause();
+}
+
+void VideoSender::Resume(int64_t paused_time_ms) {
+ source_->SetBitrateBps(previous_sending_bitrate_);
+ PacketSender::Resume(paused_time_ms);
+}
+
+void VideoSender::RunFor(int64_t time_ms, Packets* in_out) {
+ std::list<FeedbackPacket*> feedbacks = GetFeedbackPackets(
+ in_out, clock_.TimeInMilliseconds() + time_ms, source_->flow_id());
+ ProcessFeedbackAndGeneratePackets(time_ms, &feedbacks, in_out);
+}
+
+void VideoSender::ProcessFeedbackAndGeneratePackets(
+ int64_t time_ms,
+ std::list<FeedbackPacket*>* feedbacks,
+ Packets* packets) {
+ do {
+ // Make sure to at least run Process() below every 100 ms.
+ int64_t time_to_run_ms = std::min<int64_t>(time_ms, 100);
+ if (!feedbacks->empty()) {
+ int64_t time_until_feedback_ms =
+ feedbacks->front()->send_time_ms() - clock_.TimeInMilliseconds();
+ time_to_run_ms =
+ std::max<int64_t>(std::min(time_ms, time_until_feedback_ms), 0);
+ }
+
+ if (!running_) {
+ source_->SetBitrateBps(0);
+ }
+
+ Packets generated;
+ source_->RunFor(time_to_run_ms, &generated);
+ bwe_->OnPacketsSent(generated);
+ packets->merge(generated, DereferencingComparator<Packet>);
+
+ clock_.AdvanceTimeMilliseconds(time_to_run_ms);
+
+ if (!feedbacks->empty()) {
+ bwe_->GiveFeedback(*feedbacks->front());
+ delete feedbacks->front();
+ feedbacks->pop_front();
+ }
+
+ bwe_->Process();
+
+ time_ms -= time_to_run_ms;
+ } while (time_ms > 0);
+ assert(feedbacks->empty());
+}
+
+int VideoSender::GetFeedbackIntervalMs() const {
+ return bwe_->GetFeedbackIntervalMs();
+}
+
+void VideoSender::OnNetworkChanged(uint32_t target_bitrate_bps,
+ uint8_t fraction_lost,
+ int64_t rtt) {
+ source_->SetBitrateBps(target_bitrate_bps);
+ RecordBitrate();
+}
+
+uint32_t VideoSender::TargetBitrateKbps() {
+ return (source_->bits_per_second() + 500) / 1000;
+}
+
+PacedVideoSender::PacedVideoSender(PacketProcessorListener* listener,
+ VideoSource* source,
+ BandwidthEstimatorType estimator)
+ : VideoSender(listener, source, estimator),
+ pacer_(&clock_,
+ this,
+ source->bits_per_second() / 1000,
+ PacedSender::kDefaultPaceMultiplier * source->bits_per_second() /
+ 1000,
+ 0) {
+ modules_.push_back(&pacer_);
+}
+
+PacedVideoSender::~PacedVideoSender() {
+ for (Packet* packet : pacer_queue_)
+ delete packet;
+ for (Packet* packet : queue_)
+ delete packet;
+}
+
+void PacedVideoSender::RunFor(int64_t time_ms, Packets* in_out) {
+ int64_t end_time_ms = clock_.TimeInMilliseconds() + time_ms;
+ // Run process periodically to allow the packets to be paced out.
+ std::list<FeedbackPacket*> feedbacks =
+ GetFeedbackPackets(in_out, end_time_ms, source_->flow_id());
+ int64_t last_run_time_ms = -1;
+ BWE_TEST_LOGGING_CONTEXT("Sender");
+ BWE_TEST_LOGGING_CONTEXT(source_->flow_id());
+ do {
+ int64_t time_until_process_ms = TimeUntilNextProcess(modules_);
+ int64_t time_until_feedback_ms = time_ms;
+ if (!feedbacks.empty())
+ time_until_feedback_ms = std::max<int64_t>(
+ feedbacks.front()->send_time_ms() - clock_.TimeInMilliseconds(), 0);
+
+ int64_t time_until_next_event_ms =
+ std::min(time_until_feedback_ms, time_until_process_ms);
+
+ time_until_next_event_ms =
+ std::min(source_->GetTimeUntilNextFrameMs(), time_until_next_event_ms);
+
+ // Never run for longer than we have been asked for.
+ if (clock_.TimeInMilliseconds() + time_until_next_event_ms > end_time_ms)
+ time_until_next_event_ms = end_time_ms - clock_.TimeInMilliseconds();
+
+ // Make sure we don't get stuck if an event doesn't trigger. This typically
+ // happens if the prober wants to probe, but there's no packet to send.
+ if (time_until_next_event_ms == 0 && last_run_time_ms == 0)
+ time_until_next_event_ms = 1;
+ last_run_time_ms = time_until_next_event_ms;
+
+ Packets generated_packets;
+ source_->RunFor(time_until_next_event_ms, &generated_packets);
+ if (!generated_packets.empty()) {
+ for (Packet* packet : generated_packets) {
+ MediaPacket* media_packet = static_cast<MediaPacket*>(packet);
+ pacer_.InsertPacket(
+ PacedSender::kNormalPriority, media_packet->header().ssrc,
+ media_packet->header().sequenceNumber, media_packet->send_time_ms(),
+ media_packet->payload_size(), false);
+ pacer_queue_.push_back(packet);
+ assert(pacer_queue_.size() < 10000);
+ }
+ }
+
+ clock_.AdvanceTimeMilliseconds(time_until_next_event_ms);
+
+ if (time_until_next_event_ms == time_until_feedback_ms) {
+ if (!feedbacks.empty()) {
+ bwe_->GiveFeedback(*feedbacks.front());
+ delete feedbacks.front();
+ feedbacks.pop_front();
+ }
+ bwe_->Process();
+ }
+
+ if (time_until_next_event_ms == time_until_process_ms) {
+ CallProcess(modules_);
+ }
+ } while (clock_.TimeInMilliseconds() < end_time_ms);
+ QueuePackets(in_out, end_time_ms * 1000);
+}
+
+int64_t PacedVideoSender::TimeUntilNextProcess(
+ const std::list<Module*>& modules) {
+ int64_t time_until_next_process_ms = 10;
+ for (Module* module : modules) {
+ int64_t next_process_ms = module->TimeUntilNextProcess();
+ if (next_process_ms < time_until_next_process_ms)
+ time_until_next_process_ms = next_process_ms;
+ }
+ if (time_until_next_process_ms < 0)
+ time_until_next_process_ms = 0;
+ return time_until_next_process_ms;
+}
+
+void PacedVideoSender::CallProcess(const std::list<Module*>& modules) {
+ for (Module* module : modules) {
+ if (module->TimeUntilNextProcess() <= 0) {
+ module->Process();
+ }
+ }
+}
+
+void PacedVideoSender::QueuePackets(Packets* batch,
+ int64_t end_of_batch_time_us) {
+ queue_.merge(*batch, DereferencingComparator<Packet>);
+ if (queue_.empty()) {
+ return;
+ }
+ Packets::iterator it = queue_.begin();
+ for (; it != queue_.end(); ++it) {
+ if ((*it)->send_time_us() > end_of_batch_time_us) {
+ break;
+ }
+ }
+ Packets to_transfer;
+ to_transfer.splice(to_transfer.begin(), queue_, queue_.begin(), it);
+ for (Packet* packet : to_transfer)
+ packet->set_paced(true);
+ bwe_->OnPacketsSent(to_transfer);
+ batch->merge(to_transfer, DereferencingComparator<Packet>);
+}
+
+bool PacedVideoSender::TimeToSendPacket(uint32_t ssrc,
+ uint16_t sequence_number,
+ int64_t capture_time_ms,
+ bool retransmission) {
+ for (Packets::iterator it = pacer_queue_.begin(); it != pacer_queue_.end();
+ ++it) {
+ MediaPacket* media_packet = static_cast<MediaPacket*>(*it);
+ if (media_packet->header().sequenceNumber == sequence_number) {
+ int64_t pace_out_time_ms = clock_.TimeInMilliseconds();
+
+ // Make sure a packet is never paced out earlier than when it was put into
+ // the pacer.
+ assert(pace_out_time_ms >= media_packet->send_time_ms());
+
+ media_packet->SetAbsSendTimeMs(pace_out_time_ms);
+ media_packet->set_send_time_us(1000 * pace_out_time_ms);
+ media_packet->set_sender_timestamp_us(1000 * pace_out_time_ms);
+ queue_.push_back(media_packet);
+ pacer_queue_.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+size_t PacedVideoSender::TimeToSendPadding(size_t bytes) {
+ return 0;
+}
+
+void PacedVideoSender::OnNetworkChanged(uint32_t target_bitrate_bps,
+ uint8_t fraction_lost,
+ int64_t rtt) {
+ VideoSender::OnNetworkChanged(target_bitrate_bps, fraction_lost, rtt);
+ pacer_.UpdateBitrate(
+ target_bitrate_bps / 1000,
+ PacedSender::kDefaultPaceMultiplier * target_bitrate_bps / 1000, 0);
+}
+
+const int kNoLimit = std::numeric_limits<int>::max();
+const int kPacketSizeBytes = 1200;
+
+TcpSender::TcpSender(PacketProcessorListener* listener,
+ int flow_id,
+ int64_t offset_ms)
+ : TcpSender(listener, flow_id, offset_ms, kNoLimit) {
+}
+
+TcpSender::TcpSender(PacketProcessorListener* listener,
+ int flow_id,
+ int64_t offset_ms,
+ int send_limit_bytes)
+ : PacketSender(listener, flow_id),
+ cwnd_(10),
+ ssthresh_(kNoLimit),
+ ack_received_(false),
+ last_acked_seq_num_(0),
+ next_sequence_number_(0),
+ offset_ms_(offset_ms),
+ last_reduction_time_ms_(-1),
+ last_rtt_ms_(0),
+ total_sent_bytes_(0),
+ send_limit_bytes_(send_limit_bytes),
+ last_generated_packets_ms_(0),
+ num_recent_sent_packets_(0),
+ bitrate_kbps_(0) {
+}
+
+void TcpSender::RunFor(int64_t time_ms, Packets* in_out) {
+ if (clock_.TimeInMilliseconds() + time_ms < offset_ms_) {
+ clock_.AdvanceTimeMilliseconds(time_ms);
+ if (running_) {
+ Pause();
+ }
+ return;
+ }
+
+ if (!running_ && total_sent_bytes_ == 0) {
+ Resume(offset_ms_);
+ }
+
+ int64_t start_time_ms = clock_.TimeInMilliseconds();
+
+ std::list<FeedbackPacket*> feedbacks = GetFeedbackPackets(
+ in_out, clock_.TimeInMilliseconds() + time_ms, *flow_ids().begin());
+ // The number of packets which are sent in during time_ms depends on the
+ // number of packets in_flight_ and the max number of packets in flight
+ // (cwnd_). Therefore SendPackets() isn't directly dependent on time_ms.
+ for (FeedbackPacket* fb : feedbacks) {
+ clock_.AdvanceTimeMilliseconds(fb->send_time_ms() -
+ clock_.TimeInMilliseconds());
+ last_rtt_ms_ = fb->send_time_ms() - fb->latest_send_time_ms();
+ UpdateCongestionControl(fb);
+ SendPackets(in_out);
+ }
+
+ for (auto it = in_flight_.begin(); it != in_flight_.end();) {
+ if (it->time_ms < clock_.TimeInMilliseconds() - 1000)
+ in_flight_.erase(it++);
+ else
+ ++it;
+ }
+
+ clock_.AdvanceTimeMilliseconds(time_ms -
+ (clock_.TimeInMilliseconds() - start_time_ms));
+ SendPackets(in_out);
+}
+
+void TcpSender::SendPackets(Packets* in_out) {
+ int cwnd = ceil(cwnd_);
+ int packets_to_send = std::max(cwnd - static_cast<int>(in_flight_.size()), 0);
+ int timed_out = TriggerTimeouts();
+ if (timed_out > 0) {
+ HandleLoss();
+ }
+ if (packets_to_send > 0) {
+ Packets generated = GeneratePackets(packets_to_send);
+ for (Packet* packet : generated)
+ in_flight_.insert(InFlight(*static_cast<MediaPacket*>(packet)));
+
+ in_out->merge(generated, DereferencingComparator<Packet>);
+ }
+}
+
+void TcpSender::UpdateCongestionControl(const FeedbackPacket* fb) {
+ const TcpFeedback* tcp_fb = static_cast<const TcpFeedback*>(fb);
+ RTC_DCHECK(!tcp_fb->acked_packets().empty());
+ ack_received_ = true;
+
+ uint16_t expected = tcp_fb->acked_packets().back() - last_acked_seq_num_;
+ uint16_t missing =
+ expected - static_cast<uint16_t>(tcp_fb->acked_packets().size());
+
+ for (uint16_t ack_seq_num : tcp_fb->acked_packets())
+ in_flight_.erase(InFlight(ack_seq_num, clock_.TimeInMilliseconds()));
+
+ if (missing > 0) {
+ HandleLoss();
+ } else if (cwnd_ <= ssthresh_) {
+ cwnd_ += tcp_fb->acked_packets().size();
+ } else {
+ cwnd_ += 1.0f / cwnd_;
+ }
+
+ last_acked_seq_num_ =
+ LatestSequenceNumber(tcp_fb->acked_packets().back(), last_acked_seq_num_);
+}
+
+int TcpSender::TriggerTimeouts() {
+ int timed_out = 0;
+ for (auto it = in_flight_.begin(); it != in_flight_.end();) {
+ if (it->time_ms < clock_.TimeInMilliseconds() - 1000) {
+ in_flight_.erase(it++);
+ ++timed_out;
+ } else {
+ ++it;
+ }
+ }
+ return timed_out;
+}
+
+void TcpSender::HandleLoss() {
+ if (clock_.TimeInMilliseconds() - last_reduction_time_ms_ < last_rtt_ms_)
+ return;
+ last_reduction_time_ms_ = clock_.TimeInMilliseconds();
+ ssthresh_ = std::max(static_cast<int>(in_flight_.size() / 2), 2);
+ cwnd_ = ssthresh_;
+}
+
+Packets TcpSender::GeneratePackets(size_t num_packets) {
+ Packets generated;
+
+ UpdateSendBitrateEstimate(num_packets);
+
+ for (size_t i = 0; i < num_packets; ++i) {
+ if ((total_sent_bytes_ + kPacketSizeBytes) > send_limit_bytes_) {
+ if (running_) {
+ Pause();
+ }
+ break;
+ }
+ generated.push_back(
+ new MediaPacket(*flow_ids().begin(), 1000 * clock_.TimeInMilliseconds(),
+ kPacketSizeBytes, next_sequence_number_++));
+ generated.back()->set_sender_timestamp_us(
+ 1000 * clock_.TimeInMilliseconds());
+
+ total_sent_bytes_ += kPacketSizeBytes;
+ }
+
+ return generated;
+}
+
+void TcpSender::UpdateSendBitrateEstimate(size_t num_packets) {
+ const int kTimeWindowMs = 500;
+ num_recent_sent_packets_ += num_packets;
+
+ int64_t delta_ms = clock_.TimeInMilliseconds() - last_generated_packets_ms_;
+ if (delta_ms >= kTimeWindowMs) {
+ bitrate_kbps_ =
+ static_cast<uint32_t>(8 * num_recent_sent_packets_ * kPacketSizeBytes) /
+ delta_ms;
+ last_generated_packets_ms_ = clock_.TimeInMilliseconds();
+ num_recent_sent_packets_ = 0;
+ }
+
+ RecordBitrate();
+}
+
+uint32_t TcpSender::TargetBitrateKbps() {
+ return bitrate_kbps_;
+}
+
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/test/packet_sender.h b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.h
new file mode 100644
index 0000000000..c42647e2d3
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/packet_sender.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_SENDER_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_SENDER_H_
+
+#include <list>
+#include <limits>
+#include <string>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/interface/module.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
+#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
+
+namespace webrtc {
+namespace testing {
+namespace bwe {
+
+class MetricRecorder;
+
+class PacketSender : public PacketProcessor {
+ public:
+ PacketSender(PacketProcessorListener* listener, int flow_id)
+ : PacketProcessor(listener, flow_id, kSender),
+ running_(true),
+ // For Packet::send_time_us() to be comparable with timestamps from
+ // clock_, the clock of the PacketSender and the Source must be aligned.
+ // We assume that both start at time 0.
+ clock_(0),
+ metric_recorder_(nullptr) {}
+ virtual ~PacketSender() {}
+ // Call GiveFeedback() with the returned interval in milliseconds, provided
+ // there is a new estimate available.
+ // Note that changing the feedback interval affects the timing of when the
+ // output of the estimators is sampled and therefore the baseline files may
+ // have to be regenerated.
+ virtual int GetFeedbackIntervalMs() const = 0;
+ void SetSenderTimestamps(Packets* in_out);
+
+ virtual uint32_t TargetBitrateKbps() { return 0; }
+
+ virtual void Pause();
+ virtual void Resume(int64_t paused_time_ms);
+
+ void set_metric_recorder(MetricRecorder* metric_recorder);
+ virtual void RecordBitrate();
+
+ protected:
+ bool running_; // Initialized by default as true.
+ SimulatedClock clock_;
+
+ private:
+ MetricRecorder* metric_recorder_;
+};
+
+class VideoSender : public PacketSender, public BitrateObserver {
+ public:
+ VideoSender(PacketProcessorListener* listener,
+ VideoSource* source,
+ BandwidthEstimatorType estimator);
+ virtual ~VideoSender();
+
+ int GetFeedbackIntervalMs() const override;
+ void RunFor(int64_t time_ms, Packets* in_out) override;
+
+ virtual VideoSource* source() const { return source_; }
+
+ uint32_t TargetBitrateKbps() override;
+
+ // Implements BitrateObserver.
+ void OnNetworkChanged(uint32_t target_bitrate_bps,
+ uint8_t fraction_lost,
+ int64_t rtt) override;
+
+ void Pause() override;
+ void Resume(int64_t paused_time_ms) override;
+
+ protected:
+ void ProcessFeedbackAndGeneratePackets(int64_t time_ms,
+ std::list<FeedbackPacket*>* feedbacks,
+ Packets* generated);
+
+ VideoSource* source_;
+ rtc::scoped_ptr<BweSender> bwe_;
+ int64_t start_of_run_ms_;
+ std::list<Module*> modules_;
+
+ private:
+ uint32_t previous_sending_bitrate_;
+ RTC_DISALLOW_COPY_AND_ASSIGN(VideoSender);
+};
+
+class PacedVideoSender : public VideoSender, public PacedSender::Callback {
+ public:
+ PacedVideoSender(PacketProcessorListener* listener,
+ VideoSource* source,
+ BandwidthEstimatorType estimator);
+ virtual ~PacedVideoSender();
+
+ void RunFor(int64_t time_ms, Packets* in_out) override;
+
+ // Implements PacedSender::Callback.
+ bool TimeToSendPacket(uint32_t ssrc,
+ uint16_t sequence_number,
+ int64_t capture_time_ms,
+ bool retransmission) override;
+ size_t TimeToSendPadding(size_t bytes) override;
+
+ // Implements BitrateObserver.
+ void OnNetworkChanged(uint32_t target_bitrate_bps,
+ uint8_t fraction_lost,
+ int64_t rtt) override;
+
+ private:
+ int64_t TimeUntilNextProcess(const std::list<Module*>& modules);
+ void CallProcess(const std::list<Module*>& modules);
+ void QueuePackets(Packets* batch, int64_t end_of_batch_time_us);
+
+ PacedSender pacer_;
+ Packets queue_;
+ Packets pacer_queue_;
+
+ RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(PacedVideoSender);
+};
+
+class TcpSender : public PacketSender {
+ public:
+ TcpSender(PacketProcessorListener* listener, int flow_id, int64_t offset_ms);
+ TcpSender(PacketProcessorListener* listener,
+ int flow_id,
+ int64_t offset_ms,
+ int send_limit_bytes);
+ virtual ~TcpSender() {}
+
+ void RunFor(int64_t time_ms, Packets* in_out) override;
+ int GetFeedbackIntervalMs() const override { return 10; }
+
+ uint32_t TargetBitrateKbps() override;
+
+ private:
+ struct InFlight {
+ public:
+ InFlight(const MediaPacket& packet)
+ : sequence_number(packet.header().sequenceNumber),
+ time_ms(packet.send_time_ms()) {}
+
+ InFlight(uint16_t seq_num, int64_t now_ms)
+ : sequence_number(seq_num), time_ms(now_ms) {}
+
+ bool operator<(const InFlight& rhs) const {
+ return sequence_number < rhs.sequence_number;
+ }
+
+ uint16_t sequence_number; // Sequence number of a packet in flight, or a
+ // packet which has just been acked.
+ int64_t time_ms; // Time of when the packet left the sender, or when the
+ // ack was received.
+ };
+
+ void SendPackets(Packets* in_out);
+ void UpdateCongestionControl(const FeedbackPacket* fb);
+ int TriggerTimeouts();
+ void HandleLoss();
+ Packets GeneratePackets(size_t num_packets);
+ void UpdateSendBitrateEstimate(size_t num_packets);
+
+ float cwnd_;
+ int ssthresh_;
+ std::set<InFlight> in_flight_;
+ bool ack_received_;
+ uint16_t last_acked_seq_num_;
+ uint16_t next_sequence_number_;
+ int64_t offset_ms_;
+ int64_t last_reduction_time_ms_;
+ int64_t last_rtt_ms_;
+ int total_sent_bytes_;
+ int send_limit_bytes_; // Initialized by default as kNoLimit.
+ int64_t last_generated_packets_ms_;
+ size_t num_recent_sent_packets_;
+ uint32_t bitrate_kbps_;
+};
+} // namespace bwe
+} // namespace testing
+} // namespace webrtc
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_PACKET_SENDER_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/test/plot_bars.sh b/webrtc/modules/remote_bitrate_estimator/test/plot_bars.sh
new file mode 100755
index 0000000000..9f7fb16203
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/plot_bars.sh
@@ -0,0 +1,286 @@
+#!/bin/bash
+
+# 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.
+
+# To set up in e.g. Eclipse, run a separate shell and pipe the output from the
+# test into this script.
+#
+# In Eclipse, that amounts to creating a Run Configuration which starts
+# "/bin/bash" with the arguments "-c [trunk_path]/out/Debug/modules_unittests
+# --gtest_filter=*BweTest* | [trunk_path]/webrtc/modules/
+# remote_bitrate_estimator/test/plot_bars.sh
+
+# This script supports multiple figures (windows), the figure is specified as an
+# identifier at the first argument after the PLOT command. Each figure has a
+# single y axis and a dual y axis mode. If any line specifies an axis by ending
+# with "#<axis number (1 or 2)>" two y axis will be used, the first will be
+# assumed to represent bitrate (in kbps) and the second will be assumed to
+# represent time deltas (in ms).
+
+log=$(</dev/stdin)
+
+# Plot histograms.
+function gen_gnuplot_bar_input {
+ x_start=1
+ x_end=3.75
+ bars=$(echo "$log" | grep "BAR")
+
+ labels=$(echo "$log" | grep "^LABEL")
+ figures=($(echo "$bars" | cut -f 2 | sort | uniq))
+
+ echo "reset" # Clears previous settings.
+
+ echo "set title font 'Verdana,22'"
+ echo "set xtics font 'Verdana,24'"
+ echo "set ytics font 'Verdana,14'"
+ echo "set ylabel font 'Verdana,16'"
+
+ echo "set xrange[$x_start:$x_end]"
+ echo "set style fill solid 0.5"
+ echo "set style fill solid border -1"
+
+ declare -a ydist=(11.5 10.5 10.5) # Used to correctly offset the y label.
+ i=0
+ for figure in "${figures[@]}" ; do
+
+ echo "set terminal wxt $figure size 440,440 dashed"
+ echo "set ylabel offset ${ydist[$i]}, -3"
+ (( i++ ))
+
+ title=$(echo "$labels" | grep "^LABEL.$figure" | cut -f 3 | \
+ head -n 1 | sed 's/_/ /g')
+ y_label=$(echo "$labels" | grep "^LABEL.$figure" | cut -f 4 | \
+ head -n 1 | sed 's/_/ /g')
+
+ # RMCAT flows.
+ num_flows=$(echo "$labels" | grep "^LABEL.$figure" | cut -f 5 | \
+ head -n 1)
+
+ # RMCAT algorithm 1.
+ x_label_1=$(echo "$log" | grep "BAR.$figure" | cut -f 3 | sed 's/_/\t/g' \
+ | cut -f 1 | sort | uniq | head -n 1 )
+
+ # RMCAT algorithm 2.
+ x_label_2=$(echo "$log" | grep "BAR.$figure" | cut -f 3 | sed 's/_/\t/g' \
+ | cut -f 1 | sort | uniq | sed -n 2p)
+
+ x_labels="('$x_label_1' 2, '$x_label_2' 3)"
+ tcp_flow=false
+
+ tcp_space=0.2 # Extra horizontal space between bars.
+
+ # Parse labels if there are other flows in addition to RMCAT ones.
+ IFS='x' read -ra split_label_1 <<< "$x_label_1"
+
+ if (( ${#split_label_1[@]} > "1" )); then
+ tcp_flow=true
+ box_width=$(echo "(1.0-$tcp_space/2)/$num_flows" | bc -l)
+ echo "set xtics font 'Verdana,16'"
+ x_labels="("
+ delimiter=""
+ abscissa=$(echo $x_start + 0.5 + 0.5*$box_width | bc)
+ for label in "${split_label_1[@]}" ; do
+ x_labels+="$delimiter'$label' $abscissa"
+ abscissa=$(echo $abscissa + $box_width | bc)
+ delimiter=", "
+ done
+ abscissa=$(echo $abscissa + $tcp_space | bc)
+ IFS='x' read -ra split_label_2 <<< "$x_label_2"
+ for label in "${split_label_2[@]}" ; do
+ x_labels+="$delimiter'$label' $abscissa"
+ abscissa=$(echo $abscissa + $box_width | bc)
+ done
+ x_labels="$x_labels)"
+ else
+ box_width=$(echo 1.0/$num_flows | bc -l)
+ fi
+
+ echo "set boxwidth $box_width"
+
+ # Plots can be directly exported to image files.
+ file_name=$(echo "$labels" | grep "^LABEL.$figure" | cut -f 5 | head -n 1)
+
+ y_max=0 # Used to scale the plot properly.
+
+ # Scale all latency plots with the same vertical scale.
+ delay_figure=5
+ if (( $figure==$delay_figure )) ; then
+ y_max=400
+ else # Take y_max = 1.1 * highest plot value.
+
+ # Since only the optimal bitrate for the first flow is being ploted,
+ # consider only this one for scalling purposes.
+ data_sets=$(echo "$bars" | grep "LIMITERRORBAR.$figure" | cut -f 3 | \
+ sed 's/_/\t/g' | cut -f 1 | sort | uniq)
+
+ if (( ${#data_sets[@]} > "0" )); then
+ for set in $data_sets ; do
+ y=$(echo "$bars" | grep "LIMITERRORBAR.$figure.$set" | cut -f 8 | \
+ head -n 1)
+ if (( $(bc <<< "$y > $y_max") == 1 )); then
+ y_max=$y
+ fi
+ done
+ fi
+
+ data_sets=$(echo "$bars" | grep "ERRORBAR.$figure" | cut -f 3 | \
+ sort | uniq)
+ if (( ${#data_sets[@]} > "0" )); then
+ for set in $data_sets ; do
+ y=$(echo "$bars" | grep "ERRORBAR.$figure.$set" | cut -f 6 | \
+ head -n 1)
+ if (( $(bc <<< "$y > $y_max") == 1 )) ; then
+ y_max=$y
+ fi
+ done
+ fi
+
+ data_sets=$(echo "$bars" | grep "BAR.$figure" | cut -f 3 | sort | uniq)
+
+ for set in $data_sets ; do
+ y=$(echo "$bars" | grep "BAR.$figure.$set" | cut -f 4 | head -n 1)
+ if (( $(bc <<< "$y > $y_max") == 1 )) ; then
+ y_max=$y
+ fi
+ done
+
+ y_max=$(echo $y_max*1.1 | bc)
+ fi
+
+
+ echo "set ylabel \"$y_label\""
+ echo "set yrange[0:$y_max]"
+
+ echo "set multiplot"
+
+ # Plot bars.
+ data_sets=$(echo "$bars" | grep "BAR.$figure" | cut -f 3 | sort | uniq)
+
+ echo "set xtics $x_labels"
+ echo "plot '-' using 1:4:2 with boxes lc variable notitle"
+
+ echo
+
+ color=11 # Green.
+ x_bar=$(echo $x_start + 0.5 + 0.5*$box_width | bc)
+ for set in $data_sets ; do
+ echo -n "$x_bar $color "
+ echo "$bars" | grep "BAR.$figure.$set" | cut -f 3,4
+
+ # Add extra space if TCP flows are being plotted.
+ if $tcp_flow && \
+ (( $(bc <<< "$x_bar < $x_start + 1.5 - 0.5*$tcp_space") == 1 )) && \
+ (( $(bc <<< "$x_bar + $box_width > $x_start + 1.5 + 0.5*$tcp_space") \
+ == 1 )); then
+ x_bar=$(echo $x_bar + $tcp_space | bc)
+ fi
+
+ x_bar=$(echo $x_bar + $box_width | bc)
+
+ if (( $(bc <<< "$x_bar > 2.5") == 1 )) ; then
+ color=12 # Blue.
+ fi
+ # Different bar color for TCP flows:
+ if $tcp_flow && \
+ (( $(bc <<< "(100*$x_bar)%100 < 50") == 1 ))
+ then
+ color=18 # Gray.
+ fi
+ done
+ echo "e"
+
+ # Plot Baseline bars, e.g. one-way path delay on latency plots.
+ data_sets=$(echo "$log" | grep "BASELINE.$figure" | cut -f 3 | sort | uniq)
+
+ if (( ${#data_sets} > "0" )); then
+ echo "set xtics $x_labels"
+ echo "plot '-' using 1:4:2 with boxes lc variable notitle"
+
+ echo
+
+ color=18 # Gray.
+ x_bar=$(echo $x_start + 0.5 + 0.5*$box_width | bc)
+ for set in $data_sets ; do
+ echo -n "$x_bar $color "
+ echo "$log" | grep "BASELINE.$figure.$set" | cut -f 3,4
+
+ # Add extra space if TCP flows are being plotted.
+ if $tcp_flow && \
+ (( $(bc <<< "$x_bar < $x_start + 1.5 - 0.5*$tcp_space") == 1 )) && \
+ (( $(bc <<< "$x_bar + $box_width > $x_start + 1.5 \
+ + 0.5*$tcp_space") == 1 )); then
+ x_bar=$(echo $x_bar + $tcp_space | bc)
+ fi
+
+ x_bar=$(echo $x_bar + $box_width | bc)
+
+ done
+ echo "e"
+ fi
+
+ # Plot vertical error lines, e.g. y +- sigma.
+ data_sets=$(echo "$bars" | grep "ERRORBAR.$figure" | cut -f 3 | sort | uniq)
+
+ if (( ${#data_sets} > "0" )); then
+
+ echo "set key left"
+ error_title=$(echo "$bars" | grep "ERRORBAR.$figure" | cut -f 7 | \
+ head -n 1 | sed 's/_/ /g')
+
+ echo "set xtics $x_labels"
+ echo "plot '-' using 1:3:4:5 title '$error_title' with yerr"
+
+ x_error_line=$(echo $x_start + 0.5 + 0.5*$box_width | bc)
+ for set in $data_sets ; do
+ echo -n "$x_error_line "
+ echo "$bars" | grep "ERRORBAR.$figure.$set" | cut -f 3,4,5,6
+
+ # Add extra space if TCP flows are being plotted.
+ if $tcp_flow && \
+ (( $(bc <<< "$x_error_line < $x_start + 1.5 - 0.5*$tcp_space") == 1 \
+ )) && (( $(bc <<< "$x_error_line + $box_width > $x_start + 1.5 \
+ + 0.5*$tcp_space") == 1 )); then
+ x_error_line=$(echo $x_error_line + $tcp_space | bc)
+ fi
+
+ x_error_line=$(echo $x_error_line + $box_width | bc)
+ done
+ echo "e"
+ fi
+
+ # Plot horizontal dashed lines, e.g. y = optimal bitrate.
+ data_sets=$(echo "$bars" | grep "LIMITERRORBAR.$figure" | cut -f 3 \
+ | sort | uniq)
+ if (( ${#data_sets} > "0" )); then
+
+ echo "set style line 1 lt 1 lw 3 pt 3 ps 0 linecolor rgb 'black'"
+
+ limit_titles=$(echo "$bars" | grep "LIMITERRORBAR.$figure" | cut -f 9 \
+ | sort | uniq)
+
+ for title in $limit_titles ; do
+ y_max=$(echo "$bars" | grep "LIMITERRORBAR.$figure" | grep "$title" \
+ | cut -f 8 | head -n 1)
+
+ retouched_title=$(echo "$title" | sed 's/#/\t/g' | cut -f 1 \
+ | sed 's/_/ /g')
+
+ echo "set key right top"
+ echo "set xtics $x_labels"
+ echo "plot $y_max lt 7 lw 1 linecolor rgb 'black' \
+ title '$retouched_title'"
+ done
+
+ fi
+
+ echo "unset multiplot"
+ done
+}
+
+gen_gnuplot_bar_input | gnuplot -persist
diff --git a/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py
new file mode 100644
index 0000000000..1bae1e81f0
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python
+# Copyright (c) 2015 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.
+
+# This script is used to plot simulation dynamics.
+# Able to plot each flow separately. Other plot boxes can be added,
+# currently one for Throughput, one for Latency and one for Packet Loss.
+
+import matplotlib
+import matplotlib.pyplot as plt
+import numpy
+import re
+import sys
+
+# Change this to True to save the figure to a file. Look below for details.
+save_figure = False
+
+class Variable(object):
+ def __init__(self, variable):
+ self._ID = variable[0]
+ self._xlabel = variable[1]
+ self._ylabel = variable[2]
+ self._subplot = variable[3]
+ self._y_max = variable[4]
+ self.samples = dict()
+
+ def getID(self):
+ return self._ID
+
+ def getXLabel(self):
+ return self._xlabel
+
+ def getYLabel(self):
+ return self._ylabel
+
+ def getSubplot(self):
+ return self._subplot
+
+ def getYMax(self):
+ return self._y_max
+
+ def getNumberOfFlows(self):
+ return len(self.samples)
+
+
+ def addSample(self, line):
+ groups = re.search(r'_(((\d)+((,(\d)+)*))_(\D+))#\d@(\S+)', line)
+
+ # Each variable will be plotted in a separated box.
+ var_name = groups.group(1)
+ alg_name = groups.group(8)
+
+ alg_name = alg_name.replace('_', ' ')
+
+ if alg_name not in self.samples.keys():
+ self.samples[alg_name] = {}
+
+ if var_name not in self.samples[alg_name].keys():
+ self.samples[alg_name][var_name] = []
+
+ sample = re.search(r'(\d+\.\d+)\t([-]?\d+\.\d+)', line)
+
+ s = (sample.group(1), sample.group(2))
+ self.samples[alg_name][var_name].append(s)
+
+def plotVar(v, ax, show_legend, show_x_label):
+ if show_x_label:
+ ax.set_xlabel(v.getXLabel(), fontsize='large')
+ ax.set_ylabel(v.getYLabel(), fontsize='large')
+
+ for alg in v.samples.keys():
+
+ for series in v.samples[alg].keys():
+
+ x = [sample[0] for sample in v.samples[alg][series]]
+ y = [sample[1] for sample in v.samples[alg][series]]
+ x = numpy.array(x)
+ y = numpy.array(y)
+
+ line = plt.plot(x, y, label=alg, linewidth=4.0)
+
+ colormap = {'Available0':'#AAAAAA',
+ 'Available1':'#AAAAAA',
+ 'GCC0':'#80D000',
+ 'GCC1':'#008000',
+ 'GCC2':'#00F000',
+ 'GCC3':'#00B000',
+ 'GCC4':'#70B020',
+ 'NADA0':'#0000AA',
+ 'NADA1':'#A0A0FF',
+ 'NADA2':'#0000FF',
+ 'NADA3':'#C0A0FF',
+ 'NADA4':'#9060B0',}
+
+ flow_id = re.search(r'(\d+(,\d+)*)', series) # One or multiple ids.
+ key = alg + flow_id.group(1)
+
+ if key in colormap:
+ plt.setp(line, color=colormap[key])
+ elif alg == 'TCP':
+ plt.setp(line, color='#AAAAAA')
+ else:
+ plt.setp(line, color='#654321')
+
+ if alg.startswith('Available'):
+ plt.setp(line, linestyle='--')
+ plt.grid(True)
+
+ # x1, x2, y1, y2
+ _, x2, _, y2 = plt.axis()
+ if v.getYMax() >= 0:
+ y2 = v.getYMax()
+ plt.axis((0, x2, 0, y2))
+
+ if show_legend:
+ plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.40),
+ shadow=True, fontsize='large', ncol=len(v.samples))
+
+def main():
+ variables = [
+ ('Throughput_kbps', "Time (s)", "Throughput (kbps)", 1, 4000),
+ ('Delay_ms', "Time (s)", "One-way Delay (ms)", 2, 500),
+ ('Packet_Loss', "Time (s)", "Packet Loss Ratio", 3, 1.0),
+ # ('Sending_Estimate_kbps', "Time (s)", "Sending Estimate (kbps)",
+ # 4, 4000),
+ ]
+
+ var = []
+
+ # Create objects.
+ for variable in variables:
+ var.append(Variable(variable))
+
+ # Add samples to the objects.
+ for line in sys.stdin:
+ if line.startswith("[ RUN ]"):
+ test_name = re.search(r'\.(\w+)', line).group(1)
+ if line.startswith("PLOT"):
+ for v in var:
+ if v.getID() in line:
+ v.addSample(line)
+
+ matplotlib.rcParams.update({'font.size': 48/len(variables)})
+
+ # Plot variables.
+ fig = plt.figure()
+
+ # Offest and threshold on the same plot.
+ n = var[-1].getSubplot()
+ i = 0
+ for v in var:
+ ax = fig.add_subplot(n, 1, v.getSubplot())
+ plotVar(v, ax, i == 0, i == n - 1)
+ i += 1
+
+ if save_figure:
+ fig.savefig(test_name + ".png")
+ plt.show()
+
+if __name__ == '__main__':
+ main()
diff --git a/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.sh b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.sh
new file mode 100755
index 0000000000..fd104a1704
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+# 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.
+
+# To set up in e.g. Eclipse, run a separate shell and pipe the output from the
+# test into this script.
+#
+# In Eclipse, that amounts to creating a Run Configuration which starts
+# "/bin/bash" with the arguments "-c [trunk_path]/out/Debug/modules_unittests
+# --gtest_filter=*BweTest* | [trunk_path]/webrtc/modules/
+# remote_bitrate_estimator/test/plot_dynamics.sh
+
+# This script supports multiple figures (windows), the figure is specified as an
+# identifier at the first argument after the PLOT command. Each figure has a
+# single y axis and a dual y axis mode. If any line specifies an axis by ending
+# with "#<axis number (1 or 2)>" two y axis will be used, the first will be
+# assumed to represent bitrate (in kbps) and the second will be assumed to
+# represent time deltas (in ms).
+
+log=$(</dev/stdin)
+
+# Plot dynamics.
+function gen_gnuplot_input {
+ colors=(a7001f 0a60c2 b2582b 21a66c d6604d 4393c3 f4a582 92c5de edcbb7 b1c5d0)
+ plots=$(echo "$log" | grep "^PLOT")
+ # Each figure corresponds to a separate plot window.
+ figures=($(echo "$plots" | cut -f 2 | sort | uniq))
+
+ for figure in "${figures[@]}" ; do
+ # Each data set corresponds to a plot line.
+ data_sets=$(echo "$plots" | grep "^PLOT.$figure" | cut -f 3 | sort | uniq)
+ # Lines can be scaled on the left (1) or right (2) axis.
+ linetypes=($(echo "$data_sets" | grep "#" | cut -d '#' -f 2 | \
+ cut -d '@' -f 1 | uniq))
+
+ # Set plot configurations.
+ echo "reset; "
+ echo "set terminal wxt $figure size 1440,900 font \"Arial,9\"; "
+ echo "set xlabel \"Seconds\"; "
+ if (( "${#linetypes[@]}" > "1" )); then
+ echo "set ylabel 'Bitrate (kbps)';" # Left side.
+ echo "set ytics nomirror;"
+ echo "set y2label 'Time delta (ms)';" # Right side.
+ echo "set y2tics nomirror;"
+ else
+ # Single axis (left side), set its label according to data.
+ y_label=$(echo "$data_sets" | grep "#" | cut -d '#' -f 1 | \
+ cut -d ' ' -f 1 | cut -d '/' -f 3 | sed 's/[0-9]/#/g' | \
+ cut -d '#' -f 3 | head -n 1 | sed 's/_/ /g')
+ echo "set ylabel \"$y_label\";"
+ fi
+
+ i=0
+ echo -n "plot "
+ for set in $data_sets ; do
+ (( i++ )) && echo -n ","
+ echo -n "'-' with "
+ echo -n "linespoints "
+ echo -n "ps 0.5 "
+ echo -n "lc rgbcolor \"#${colors[$(($i % 10))]}\" "
+ if (( "${#linetypes[@]}" > "1" )); then
+ # Multiple sets can have a same line plot.
+ linetype=$(echo "$set" | grep "#" | cut -d '#' -f 2 | cut -d '@' -f 1)
+ if (( "${#linetype}" > "0")); then
+ echo -n "axes x1y$linetype "
+ else
+ # If no line type is specified, but line types are used, we will
+ # default to scale on the left axis.
+ echo -n "axes x1y1 "
+ fi
+ fi
+ echo -n "title \"$set\" "
+ done
+ echo
+ for set in $data_sets ; do
+ echo "$log" | grep "^PLOT.$figure.$set" | cut -f 4,5
+ echo "e"
+ done
+ done
+}
+gen_gnuplot_input | gnuplot -persist
diff --git a/webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.cc b/webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.cc
new file mode 100644
index 0000000000..9493805a1c
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.cc
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2014 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/modules/remote_bitrate_estimator/tools/bwe_rtp.h"
+
+#include <sstream>
+#include <stdio.h>
+#include <string>
+
+#include "gflags/gflags.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h"
+#include "webrtc/test/rtp_file_reader.h"
+
+namespace flags {
+
+DEFINE_string(extension_type,
+ "abs",
+ "Extension type, either abs for absolute send time or tsoffset "
+ "for timestamp offset.");
+std::string ExtensionType() {
+ return static_cast<std::string>(FLAGS_extension_type);
+}
+
+DEFINE_int32(extension_id, 3, "Extension id.");
+int ExtensionId() {
+ return static_cast<int>(FLAGS_extension_id);
+}
+
+DEFINE_string(input_file, "", "Input file.");
+std::string InputFile() {
+ return static_cast<std::string>(FLAGS_input_file);
+}
+
+DEFINE_string(ssrc_filter,
+ "",
+ "Comma-separated list of SSRCs in hexadecimal which are to be "
+ "used as input to the BWE (only applicable to pcap files).");
+std::set<uint32_t> SsrcFilter() {
+ std::string ssrc_filter_string = static_cast<std::string>(FLAGS_ssrc_filter);
+ if (ssrc_filter_string.empty())
+ return std::set<uint32_t>();
+ std::stringstream ss;
+ std::string ssrc_filter = ssrc_filter_string;
+ std::set<uint32_t> ssrcs;
+
+ // Parse the ssrcs in hexadecimal format.
+ ss << std::hex << ssrc_filter;
+ uint32_t ssrc;
+ while (ss >> ssrc) {
+ ssrcs.insert(ssrc);
+ ss.ignore(1, ',');
+ }
+ return ssrcs;
+}
+} // namespace flags
+
+bool ParseArgsAndSetupEstimator(int argc,
+ char** argv,
+ webrtc::Clock* clock,
+ webrtc::RemoteBitrateObserver* observer,
+ webrtc::test::RtpFileReader** rtp_reader,
+ webrtc::RtpHeaderParser** parser,
+ webrtc::RemoteBitrateEstimator** estimator,
+ std::string* estimator_used) {
+ google::ParseCommandLineFlags(&argc, &argv, true);
+ std::string filename = flags::InputFile();
+
+ std::set<uint32_t> ssrc_filter = flags::SsrcFilter();
+ fprintf(stderr, "Filter on SSRC: ");
+ for (auto& s : ssrc_filter) {
+ fprintf(stderr, "0x%08x, ", s);
+ }
+ fprintf(stderr, "\n");
+ if (filename.substr(filename.find_last_of(".")) == ".pcap") {
+ fprintf(stderr, "Opening as pcap\n");
+ *rtp_reader = webrtc::test::RtpFileReader::Create(
+ webrtc::test::RtpFileReader::kPcap, filename.c_str(),
+ flags::SsrcFilter());
+ } else {
+ fprintf(stderr, "Opening as rtp\n");
+ *rtp_reader = webrtc::test::RtpFileReader::Create(
+ webrtc::test::RtpFileReader::kRtpDump, filename.c_str());
+ }
+ if (!*rtp_reader) {
+ fprintf(stderr, "Cannot open input file %s\n", filename.c_str());
+ return false;
+ }
+ fprintf(stderr, "Input file: %s\n\n", filename.c_str());
+
+ webrtc::RTPExtensionType extension = webrtc::kRtpExtensionAbsoluteSendTime;
+ if (flags::ExtensionType() == "tsoffset") {
+ extension = webrtc::kRtpExtensionTransmissionTimeOffset;
+ fprintf(stderr, "Extension: toffset\n");
+ } else if (flags::ExtensionType() == "abs") {
+ fprintf(stderr, "Extension: abs\n");
+ } else {
+ fprintf(stderr, "Unknown extension type\n");
+ return false;
+ }
+
+ // Setup the RTP header parser and the bitrate estimator.
+ *parser = webrtc::RtpHeaderParser::Create();
+ (*parser)->RegisterRtpHeaderExtension(extension, flags::ExtensionId());
+ if (estimator) {
+ switch (extension) {
+ case webrtc::kRtpExtensionAbsoluteSendTime: {
+ *estimator =
+ new webrtc::RemoteBitrateEstimatorAbsSendTime(observer, clock);
+ *estimator_used = "AbsoluteSendTimeRemoteBitrateEstimator";
+ break;
+ }
+ case webrtc::kRtpExtensionTransmissionTimeOffset: {
+ *estimator =
+ new webrtc::RemoteBitrateEstimatorSingleStream(observer, clock);
+ *estimator_used = "RemoteBitrateEstimator";
+ break;
+ }
+ default:
+ assert(false);
+ }
+ }
+ return true;
+}
diff --git a/webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.h b/webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.h
new file mode 100644
index 0000000000..2d12a8083f
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2014 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TOOLS_BWE_RTP_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TOOLS_BWE_RTP_H_
+
+#include <string>
+
+namespace webrtc {
+class Clock;
+class RemoteBitrateEstimator;
+class RemoteBitrateObserver;
+class RtpHeaderParser;
+namespace test {
+class RtpFileReader;
+}
+}
+
+bool ParseArgsAndSetupEstimator(
+ int argc,
+ char** argv,
+ webrtc::Clock* clock,
+ webrtc::RemoteBitrateObserver* observer,
+ webrtc::test::RtpFileReader** rtp_reader,
+ webrtc::RtpHeaderParser** parser,
+ webrtc::RemoteBitrateEstimator** estimator,
+ std::string* estimator_used);
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TOOLS_BWE_RTP_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp_play.cc b/webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp_play.cc
new file mode 100644
index 0000000000..19e4a07b4d
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp_play.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2014 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 <stdio.h>
+
+#include "webrtc/base/format_macros.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h"
+#include "webrtc/test/rtp_file_reader.h"
+
+class Observer : public webrtc::RemoteBitrateObserver {
+ public:
+ explicit Observer(webrtc::Clock* clock) : clock_(clock) {}
+
+ // Called when a receive channel group has a new bitrate estimate for the
+ // incoming streams.
+ virtual void OnReceiveBitrateChanged(const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) {
+ printf("[%u] Num SSRCs: %d, bitrate: %u\n",
+ static_cast<uint32_t>(clock_->TimeInMilliseconds()),
+ static_cast<int>(ssrcs.size()), bitrate);
+ }
+
+ virtual ~Observer() {}
+
+ private:
+ webrtc::Clock* clock_;
+};
+
+int main(int argc, char** argv) {
+ webrtc::test::RtpFileReader* reader;
+ webrtc::RemoteBitrateEstimator* estimator;
+ webrtc::RtpHeaderParser* parser;
+ std::string estimator_used;
+ webrtc::SimulatedClock clock(0);
+ Observer observer(&clock);
+ if (!ParseArgsAndSetupEstimator(argc, argv, &clock, &observer, &reader,
+ &parser, &estimator, &estimator_used)) {
+ return -1;
+ }
+ rtc::scoped_ptr<webrtc::test::RtpFileReader> rtp_reader(reader);
+ rtc::scoped_ptr<webrtc::RtpHeaderParser> rtp_parser(parser);
+ rtc::scoped_ptr<webrtc::RemoteBitrateEstimator> rbe(estimator);
+
+ // Process the file.
+ int packet_counter = 0;
+ int64_t next_rtp_time_ms = 0;
+ int64_t first_rtp_time_ms = -1;
+ int abs_send_time_count = 0;
+ int ts_offset_count = 0;
+ webrtc::test::RtpPacket packet;
+ if (!rtp_reader->NextPacket(&packet)) {
+ printf("No RTP packet found\n");
+ return 0;
+ }
+ first_rtp_time_ms = packet.time_ms;
+ packet.time_ms = packet.time_ms - first_rtp_time_ms;
+ while (true) {
+ if (next_rtp_time_ms <= clock.TimeInMilliseconds()) {
+ if (!parser->IsRtcp(packet.data, packet.length)) {
+ webrtc::RTPHeader header;
+ parser->Parse(packet.data, packet.length, &header);
+ if (header.extension.hasAbsoluteSendTime)
+ ++abs_send_time_count;
+ if (header.extension.hasTransmissionTimeOffset)
+ ++ts_offset_count;
+ size_t packet_length = packet.length;
+ // Some RTP dumps only include the header, in which case packet.length
+ // is equal to the header length. In those cases packet.original_length
+ // usually contains the original packet length.
+ if (packet.original_length > 0) {
+ packet_length = packet.original_length;
+ }
+ rbe->IncomingPacket(clock.TimeInMilliseconds(),
+ packet_length - header.headerLength, header, true);
+ ++packet_counter;
+ }
+ if (!rtp_reader->NextPacket(&packet)) {
+ break;
+ }
+ packet.time_ms = packet.time_ms - first_rtp_time_ms;
+ next_rtp_time_ms = packet.time_ms;
+ }
+ int64_t time_until_process_ms = rbe->TimeUntilNextProcess();
+ if (time_until_process_ms <= 0) {
+ rbe->Process();
+ }
+ int64_t time_until_next_event =
+ std::min(rbe->TimeUntilNextProcess(),
+ next_rtp_time_ms - clock.TimeInMilliseconds());
+ clock.AdvanceTimeMilliseconds(std::max<int64_t>(time_until_next_event, 0));
+ }
+ printf("Parsed %d packets\nTime passed: %" PRId64 " ms\n", packet_counter,
+ clock.TimeInMilliseconds());
+ printf("Estimator used: %s\n", estimator_used.c_str());
+ printf("Packets with absolute send time: %d\n",
+ abs_send_time_count);
+ printf("Packets with timestamp offset: %d\n",
+ ts_offset_count);
+ printf("Packets with no extension: %d\n",
+ packet_counter - ts_offset_count - abs_send_time_count);
+ return 0;
+}
diff --git a/webrtc/modules/remote_bitrate_estimator/tools/rtp_to_text.cc b/webrtc/modules/remote_bitrate_estimator/tools/rtp_to_text.cc
new file mode 100644
index 0000000000..e277481886
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/tools/rtp_to_text.cc
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2014 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 <stdio.h>
+#include <sstream>
+
+#include "webrtc/base/format_macros.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_payload_registry.h"
+#include "webrtc/test/rtp_file_reader.h"
+
+int main(int argc, char** argv) {
+ webrtc::test::RtpFileReader* reader;
+ webrtc::RtpHeaderParser* parser;
+ if (!ParseArgsAndSetupEstimator(argc, argv, NULL, NULL, &reader, &parser,
+ NULL, NULL)) {
+ return -1;
+ }
+ bool arrival_time_only = (argc >= 5 && strncmp(argv[4], "-t", 2) == 0);
+ rtc::scoped_ptr<webrtc::test::RtpFileReader> rtp_reader(reader);
+ rtc::scoped_ptr<webrtc::RtpHeaderParser> rtp_parser(parser);
+ fprintf(stdout, "seqnum timestamp ts_offset abs_sendtime recvtime "
+ "markerbit ssrc size original_size\n");
+ int packet_counter = 0;
+ int non_zero_abs_send_time = 0;
+ int non_zero_ts_offsets = 0;
+ webrtc::test::RtpPacket packet;
+ while (rtp_reader->NextPacket(&packet)) {
+ webrtc::RTPHeader header;
+ parser->Parse(packet.data, packet.length, &header);
+ if (header.extension.absoluteSendTime != 0)
+ ++non_zero_abs_send_time;
+ if (header.extension.transmissionTimeOffset != 0)
+ ++non_zero_ts_offsets;
+ if (arrival_time_only) {
+ std::stringstream ss;
+ ss << static_cast<int64_t>(packet.time_ms) * 1000000;
+ fprintf(stdout, "%s\n", ss.str().c_str());
+ } else {
+ fprintf(stdout,
+ "%u %u %d %u %u %d %u %" PRIuS " %" PRIuS "\n",
+ header.sequenceNumber,
+ header.timestamp,
+ header.extension.transmissionTimeOffset,
+ header.extension.absoluteSendTime,
+ packet.time_ms,
+ header.markerBit,
+ header.ssrc,
+ packet.length,
+ packet.original_length);
+ }
+ ++packet_counter;
+ }
+ fprintf(stderr, "Parsed %d packets\n", packet_counter);
+ fprintf(stderr, "Packets with non-zero absolute send time: %d\n",
+ non_zero_abs_send_time);
+ fprintf(stderr, "Packets with non-zero timestamp offset: %d\n",
+ non_zero_ts_offsets);
+ return 0;
+}
diff --git a/webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter.cc b/webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter.cc
new file mode 100644
index 0000000000..f2e073aa53
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter.cc
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2015 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 <limits>
+
+#include "webrtc/base/checks.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h"
+#include "webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter.h"
+#include "webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
+#include "webrtc/modules/utility/interface/process_thread.h"
+
+namespace webrtc {
+
+const int64_t kNoTimestamp = -1;
+const int64_t kSendTimeHistoryWindowMs = 10000;
+const int64_t kBaseTimestampScaleFactor =
+ rtcp::TransportFeedback::kDeltaScaleFactor * (1 << 8);
+const int64_t kBaseTimestampRangeSizeUs = kBaseTimestampScaleFactor * (1 << 24);
+
+TransportFeedbackAdapter::TransportFeedbackAdapter(
+ RtcpBandwidthObserver* bandwidth_observer,
+ Clock* clock,
+ ProcessThread* process_thread)
+ : send_time_history_(clock, kSendTimeHistoryWindowMs),
+ rtcp_bandwidth_observer_(bandwidth_observer),
+ process_thread_(process_thread),
+ clock_(clock),
+ current_offset_ms_(kNoTimestamp),
+ last_timestamp_us_(kNoTimestamp) {}
+
+TransportFeedbackAdapter::~TransportFeedbackAdapter() {
+ if (bitrate_estimator_.get())
+ process_thread_->DeRegisterModule(bitrate_estimator_.get());
+}
+
+void TransportFeedbackAdapter::SetBitrateEstimator(
+ RemoteBitrateEstimator* rbe) {
+ if (bitrate_estimator_.get() != rbe) {
+ bitrate_estimator_.reset(rbe);
+ process_thread_->RegisterModule(rbe);
+ }
+}
+
+void TransportFeedbackAdapter::AddPacket(uint16_t sequence_number,
+ size_t length,
+ bool was_paced) {
+ rtc::CritScope cs(&lock_);
+ send_time_history_.AddAndRemoveOld(sequence_number, length, was_paced);
+}
+
+void TransportFeedbackAdapter::OnSentPacket(uint16_t sequence_number,
+ int64_t send_time_ms) {
+ rtc::CritScope cs(&lock_);
+ send_time_history_.OnSentPacket(sequence_number, send_time_ms);
+}
+
+void TransportFeedbackAdapter::OnTransportFeedback(
+ const rtcp::TransportFeedback& feedback) {
+ int64_t timestamp_us = feedback.GetBaseTimeUs();
+ // Add timestamp deltas to a local time base selected on first packet arrival.
+ // This won't be the true time base, but makes it easier to manually inspect
+ // time stamps.
+ if (last_timestamp_us_ == kNoTimestamp) {
+ current_offset_ms_ = clock_->TimeInMilliseconds();
+ } else {
+ int64_t delta = timestamp_us - last_timestamp_us_;
+
+ // Detect and compensate for wrap-arounds in base time.
+ if (std::abs(delta - kBaseTimestampRangeSizeUs) < std::abs(delta)) {
+ delta -= kBaseTimestampRangeSizeUs; // Wrap backwards.
+ } else if (std::abs(delta + kBaseTimestampRangeSizeUs) < std::abs(delta)) {
+ delta += kBaseTimestampRangeSizeUs; // Wrap forwards.
+ }
+
+ current_offset_ms_ += delta / 1000;
+ }
+ last_timestamp_us_ = timestamp_us;
+
+ uint16_t sequence_number = feedback.GetBaseSequence();
+ std::vector<int64_t> delta_vec = feedback.GetReceiveDeltasUs();
+ auto delta_it = delta_vec.begin();
+ std::vector<PacketInfo> packet_feedback_vector;
+ packet_feedback_vector.reserve(delta_vec.size());
+
+ {
+ rtc::CritScope cs(&lock_);
+ size_t failed_lookups = 0;
+ int64_t offset_us = 0;
+ for (auto symbol : feedback.GetStatusVector()) {
+ if (symbol != rtcp::TransportFeedback::StatusSymbol::kNotReceived) {
+ RTC_DCHECK(delta_it != delta_vec.end());
+ offset_us += *(delta_it++);
+ int64_t timestamp_ms = current_offset_ms_ + (offset_us / 1000);
+ PacketInfo info(timestamp_ms, sequence_number);
+ if (send_time_history_.GetInfo(&info, true) && info.send_time_ms >= 0) {
+ packet_feedback_vector.push_back(info);
+ } else {
+ ++failed_lookups;
+ }
+ }
+ ++sequence_number;
+ }
+ RTC_DCHECK(delta_it == delta_vec.end());
+ if (failed_lookups > 0) {
+ LOG(LS_WARNING) << "Failed to lookup send time for " << failed_lookups
+ << " packet" << (failed_lookups > 1 ? "s" : "")
+ << ". Send time history too small?";
+ }
+ }
+
+ RTC_DCHECK(bitrate_estimator_.get() != nullptr);
+ bitrate_estimator_->IncomingPacketFeedbackVector(packet_feedback_vector);
+}
+
+void TransportFeedbackAdapter::OnReceiveBitrateChanged(
+ const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) {
+ rtcp_bandwidth_observer_->OnReceivedEstimatedBitrate(bitrate);
+}
+
+void TransportFeedbackAdapter::OnRttUpdate(int64_t avg_rtt_ms,
+ int64_t max_rtt_ms) {
+ RTC_DCHECK(bitrate_estimator_.get() != nullptr);
+ bitrate_estimator_->OnRttUpdate(avg_rtt_ms, max_rtt_ms);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter.h b/webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter.h
new file mode 100644
index 0000000000..58829b072b
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TRANSPORT_FEEDBACK_ADAPTER_H_
+#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TRANSPORT_FEEDBACK_ADAPTER_H_
+
+#include <vector>
+
+#include "webrtc/base/criticalsection.h"
+#include "webrtc/base/thread_annotations.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
+#include "webrtc/modules/interface/module_common_types.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/send_time_history.h"
+
+namespace webrtc {
+
+class ProcessThread;
+
+class TransportFeedbackAdapter : public TransportFeedbackObserver,
+ public CallStatsObserver,
+ public RemoteBitrateObserver {
+ public:
+ TransportFeedbackAdapter(RtcpBandwidthObserver* bandwidth_observer,
+ Clock* clock,
+ ProcessThread* process_thread);
+ virtual ~TransportFeedbackAdapter();
+
+ void AddPacket(uint16_t sequence_number,
+ size_t length,
+ bool was_paced) override;
+
+ void OnSentPacket(uint16_t sequence_number, int64_t send_time_ms);
+
+ void OnTransportFeedback(const rtcp::TransportFeedback& feedback) override;
+
+ void SetBitrateEstimator(RemoteBitrateEstimator* rbe);
+
+ RemoteBitrateEstimator* GetBitrateEstimator() const {
+ return bitrate_estimator_.get();
+ }
+
+ private:
+ void OnReceiveBitrateChanged(const std::vector<unsigned int>& ssrcs,
+ unsigned int bitrate) override;
+ void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override;
+
+ rtc::CriticalSection lock_;
+ SendTimeHistory send_time_history_ GUARDED_BY(&lock_);
+ rtc::scoped_ptr<RtcpBandwidthObserver> rtcp_bandwidth_observer_;
+ rtc::scoped_ptr<RemoteBitrateEstimator> bitrate_estimator_;
+ ProcessThread* const process_thread_;
+ Clock* const clock_;
+ int64_t current_offset_ms_;
+ int64_t last_timestamp_us_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TRANSPORT_FEEDBACK_ADAPTER_H_
diff --git a/webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter_unittest.cc b/webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter_unittest.cc
new file mode 100644
index 0000000000..b2bc646e2d
--- /dev/null
+++ b/webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter_unittest.cc
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2015 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 <limits>
+#include <vector>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "webrtc/base/checks.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/modules/remote_bitrate_estimator/include/mock/mock_remote_bitrate_estimator.h"
+#include "webrtc/modules/remote_bitrate_estimator/transport_feedback_adapter.h"
+#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
+#include "webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
+#include "webrtc/modules/utility/interface/mock/mock_process_thread.h"
+#include "webrtc/system_wrappers/include/clock.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+
+namespace webrtc {
+namespace test {
+
+class TransportFeedbackAdapterTest : public ::testing::Test {
+ public:
+ TransportFeedbackAdapterTest()
+ : clock_(0),
+ bitrate_estimator_(nullptr),
+ receiver_estimated_bitrate_(0) {}
+
+ virtual ~TransportFeedbackAdapterTest() {}
+
+ virtual void SetUp() {
+ adapter_.reset(new TransportFeedbackAdapter(
+ new RtcpBandwidthObserverAdapter(this), &clock_, &process_thread_));
+
+ bitrate_estimator_ = new MockRemoteBitrateEstimator();
+ EXPECT_CALL(process_thread_, RegisterModule(bitrate_estimator_)).Times(1);
+ adapter_->SetBitrateEstimator(bitrate_estimator_);
+ }
+
+ virtual void TearDown() {
+ EXPECT_CALL(process_thread_, DeRegisterModule(bitrate_estimator_)).Times(1);
+ adapter_.reset();
+ }
+
+ protected:
+ // Proxy class used since TransportFeedbackAdapter will own the instance
+ // passed at construction.
+ class RtcpBandwidthObserverAdapter : public RtcpBandwidthObserver {
+ public:
+ explicit RtcpBandwidthObserverAdapter(TransportFeedbackAdapterTest* owner)
+ : owner_(owner) {}
+
+ void OnReceivedEstimatedBitrate(uint32_t bitrate) override {
+ owner_->receiver_estimated_bitrate_ = bitrate;
+ }
+
+ void OnReceivedRtcpReceiverReport(const ReportBlockList& report_blocks,
+ int64_t rtt,
+ int64_t now_ms) override {
+ RTC_NOTREACHED();
+ }
+
+ TransportFeedbackAdapterTest* const owner_;
+ };
+
+ void OnReceivedEstimatedBitrate(uint32_t bitrate) {}
+
+ void OnReceivedRtcpReceiverReport(const ReportBlockList& report_blocks,
+ int64_t rtt,
+ int64_t now_ms) {}
+
+ void ComparePacketVectors(const std::vector<PacketInfo>& truth,
+ const std::vector<PacketInfo>& input) {
+ ASSERT_EQ(truth.size(), input.size());
+ size_t len = truth.size();
+ // truth contains the input data for the test, and input is what will be
+ // sent to the bandwidth estimator. truth.arrival_tims_ms is used to
+ // populate the transport feedback messages. As these times may be changed
+ // (because of resolution limits in the packets, and because of the time
+ // base adjustment performed by the TransportFeedbackAdapter at the first
+ // packet, the truth[x].arrival_time and input[x].arrival_time may not be
+ // equal. However, the difference must be the same for all x.
+ int64_t arrival_time_delta =
+ truth[0].arrival_time_ms - input[0].arrival_time_ms;
+ for (size_t i = 0; i < len; ++i) {
+ EXPECT_EQ(truth[i].arrival_time_ms,
+ input[i].arrival_time_ms + arrival_time_delta);
+ EXPECT_EQ(truth[i].send_time_ms, input[i].send_time_ms);
+ EXPECT_EQ(truth[i].sequence_number, input[i].sequence_number);
+ EXPECT_EQ(truth[i].payload_size, input[i].payload_size);
+ EXPECT_EQ(truth[i].was_paced, input[i].was_paced);
+ }
+ }
+
+ // Utility method, to reset arrival_time_ms before adding send time.
+ void OnSentPacket(PacketInfo info) {
+ info.arrival_time_ms = 0;
+ adapter_->AddPacket(info.sequence_number, info.payload_size,
+ info.was_paced);
+ adapter_->OnSentPacket(info.sequence_number, info.send_time_ms);
+ }
+
+ SimulatedClock clock_;
+ MockProcessThread process_thread_;
+ MockRemoteBitrateEstimator* bitrate_estimator_;
+ rtc::scoped_ptr<TransportFeedbackAdapter> adapter_;
+
+ uint32_t receiver_estimated_bitrate_;
+};
+
+TEST_F(TransportFeedbackAdapterTest, AdaptsFeedbackAndPopulatesSendTimes) {
+ std::vector<PacketInfo> packets;
+ packets.push_back(PacketInfo(100, 200, 0, 1500, true));
+ packets.push_back(PacketInfo(110, 210, 1, 1500, true));
+ packets.push_back(PacketInfo(120, 220, 2, 1500, true));
+ packets.push_back(PacketInfo(130, 230, 3, 1500, true));
+ packets.push_back(PacketInfo(140, 240, 4, 1500, true));
+
+ for (const PacketInfo& packet : packets)
+ OnSentPacket(packet);
+
+ rtcp::TransportFeedback feedback;
+ feedback.WithBase(packets[0].sequence_number,
+ packets[0].arrival_time_ms * 1000);
+
+ for (const PacketInfo& packet : packets) {
+ EXPECT_TRUE(feedback.WithReceivedPacket(packet.sequence_number,
+ packet.arrival_time_ms * 1000));
+ }
+
+ feedback.Build();
+
+ EXPECT_CALL(*bitrate_estimator_, IncomingPacketFeedbackVector(_))
+ .Times(1)
+ .WillOnce(Invoke(
+ [packets, this](const std::vector<PacketInfo>& feedback_vector) {
+ ComparePacketVectors(packets, feedback_vector);
+ }));
+ adapter_->OnTransportFeedback(feedback);
+}
+
+TEST_F(TransportFeedbackAdapterTest, HandlesDroppedPackets) {
+ std::vector<PacketInfo> packets;
+ packets.push_back(PacketInfo(100, 200, 0, 1500, true));
+ packets.push_back(PacketInfo(110, 210, 1, 1500, true));
+ packets.push_back(PacketInfo(120, 220, 2, 1500, true));
+ packets.push_back(PacketInfo(130, 230, 3, 1500, true));
+ packets.push_back(PacketInfo(140, 240, 4, 1500, true));
+
+ const uint16_t kSendSideDropBefore = 1;
+ const uint16_t kReceiveSideDropAfter = 3;
+
+ for (const PacketInfo& packet : packets) {
+ if (packet.sequence_number >= kSendSideDropBefore)
+ OnSentPacket(packet);
+ }
+
+ rtcp::TransportFeedback feedback;
+ feedback.WithBase(packets[0].sequence_number,
+ packets[0].arrival_time_ms * 1000);
+
+ for (const PacketInfo& packet : packets) {
+ if (packet.sequence_number <= kReceiveSideDropAfter) {
+ EXPECT_TRUE(feedback.WithReceivedPacket(packet.sequence_number,
+ packet.arrival_time_ms * 1000));
+ }
+ }
+
+ feedback.Build();
+
+ std::vector<PacketInfo> expected_packets(
+ packets.begin() + kSendSideDropBefore,
+ packets.begin() + kReceiveSideDropAfter + 1);
+
+ EXPECT_CALL(*bitrate_estimator_, IncomingPacketFeedbackVector(_))
+ .Times(1)
+ .WillOnce(Invoke([expected_packets,
+ this](const std::vector<PacketInfo>& feedback_vector) {
+ ComparePacketVectors(expected_packets, feedback_vector);
+ }));
+ adapter_->OnTransportFeedback(feedback);
+}
+
+TEST_F(TransportFeedbackAdapterTest, SendTimeWrapsBothWays) {
+ int64_t kHighArrivalTimeMs = rtcp::TransportFeedback::kDeltaScaleFactor *
+ static_cast<int64_t>(1 << 8) *
+ static_cast<int64_t>((1 << 23) - 1) / 1000;
+ std::vector<PacketInfo> packets;
+ packets.push_back(PacketInfo(kHighArrivalTimeMs - 64, 200, 0, 1500, true));
+ packets.push_back(PacketInfo(kHighArrivalTimeMs + 64, 210, 1, 1500, true));
+ packets.push_back(PacketInfo(kHighArrivalTimeMs, 220, 2, 1500, true));
+
+ for (const PacketInfo& packet : packets)
+ OnSentPacket(packet);
+
+ for (size_t i = 0; i < packets.size(); ++i) {
+ rtc::scoped_ptr<rtcp::TransportFeedback> feedback(
+ new rtcp::TransportFeedback());
+ feedback->WithBase(packets[i].sequence_number,
+ packets[i].arrival_time_ms * 1000);
+
+ EXPECT_TRUE(feedback->WithReceivedPacket(
+ packets[i].sequence_number, packets[i].arrival_time_ms * 1000));
+
+ rtc::scoped_ptr<rtcp::RawPacket> raw_packet = feedback->Build();
+ feedback = rtcp::TransportFeedback::ParseFrom(raw_packet->Buffer(),
+ raw_packet->Length());
+
+ std::vector<PacketInfo> expected_packets;
+ expected_packets.push_back(packets[i]);
+
+ EXPECT_CALL(*bitrate_estimator_, IncomingPacketFeedbackVector(_))
+ .Times(1)
+ .WillOnce(Invoke([expected_packets, this](
+ const std::vector<PacketInfo>& feedback_vector) {
+ ComparePacketVectors(expected_packets, feedback_vector);
+ }));
+ adapter_->OnTransportFeedback(*feedback.get());
+ }
+}
+
+TEST_F(TransportFeedbackAdapterTest, TimestampDeltas) {
+ std::vector<PacketInfo> sent_packets;
+ const int64_t kSmallDeltaUs =
+ rtcp::TransportFeedback::kDeltaScaleFactor * ((1 << 8) - 1);
+ const int64_t kLargePositiveDeltaUs =
+ rtcp::TransportFeedback::kDeltaScaleFactor *
+ std::numeric_limits<int16_t>::max();
+ const int64_t kLargeNegativeDeltaUs =
+ rtcp::TransportFeedback::kDeltaScaleFactor *
+ std::numeric_limits<int16_t>::min();
+
+ PacketInfo info(100, 200, 0, 1500, true);
+ sent_packets.push_back(info);
+
+ info.send_time_ms += kSmallDeltaUs / 1000;
+ info.arrival_time_ms += kSmallDeltaUs / 1000;
+ ++info.sequence_number;
+ sent_packets.push_back(info);
+
+ info.send_time_ms += kLargePositiveDeltaUs / 1000;
+ info.arrival_time_ms += kLargePositiveDeltaUs / 1000;
+ ++info.sequence_number;
+ sent_packets.push_back(info);
+
+ info.send_time_ms += kLargeNegativeDeltaUs / 1000;
+ info.arrival_time_ms += kLargeNegativeDeltaUs / 1000;
+ ++info.sequence_number;
+ sent_packets.push_back(info);
+
+ // Too large, delta - will need two feedback messages.
+ info.send_time_ms += (kLargePositiveDeltaUs + 1000) / 1000;
+ info.arrival_time_ms += (kLargePositiveDeltaUs + 1000) / 1000;
+ ++info.sequence_number;
+
+ // Packets will be added to send history.
+ for (const PacketInfo& packet : sent_packets)
+ OnSentPacket(packet);
+ OnSentPacket(info);
+
+ // Create expected feedback and send into adapter.
+ rtc::scoped_ptr<rtcp::TransportFeedback> feedback(
+ new rtcp::TransportFeedback());
+ feedback->WithBase(sent_packets[0].sequence_number,
+ sent_packets[0].arrival_time_ms * 1000);
+
+ for (const PacketInfo& packet : sent_packets) {
+ EXPECT_TRUE(feedback->WithReceivedPacket(packet.sequence_number,
+ packet.arrival_time_ms * 1000));
+ }
+ EXPECT_FALSE(feedback->WithReceivedPacket(info.sequence_number,
+ info.arrival_time_ms * 1000));
+
+ rtc::scoped_ptr<rtcp::RawPacket> raw_packet = feedback->Build();
+ feedback = rtcp::TransportFeedback::ParseFrom(raw_packet->Buffer(),
+ raw_packet->Length());
+
+ std::vector<PacketInfo> received_feedback;
+
+ EXPECT_TRUE(feedback.get() != nullptr);
+ EXPECT_CALL(*bitrate_estimator_, IncomingPacketFeedbackVector(_))
+ .Times(1)
+ .WillOnce(Invoke([sent_packets, &received_feedback](
+ const std::vector<PacketInfo>& feedback_vector) {
+ EXPECT_EQ(sent_packets.size(), feedback_vector.size());
+ received_feedback = feedback_vector;
+ }));
+ adapter_->OnTransportFeedback(*feedback.get());
+
+ // Create a new feedback message and add the trailing item.
+ feedback.reset(new rtcp::TransportFeedback());
+ feedback->WithBase(info.sequence_number, info.arrival_time_ms * 1000);
+ EXPECT_TRUE(feedback->WithReceivedPacket(info.sequence_number,
+ info.arrival_time_ms * 1000));
+ raw_packet = feedback->Build();
+ feedback = rtcp::TransportFeedback::ParseFrom(raw_packet->Buffer(),
+ raw_packet->Length());
+
+ EXPECT_TRUE(feedback.get() != nullptr);
+ EXPECT_CALL(*bitrate_estimator_, IncomingPacketFeedbackVector(_))
+ .Times(1)
+ .WillOnce(Invoke(
+ [&received_feedback](const std::vector<PacketInfo>& feedback_vector) {
+ EXPECT_EQ(1u, feedback_vector.size());
+ received_feedback.push_back(feedback_vector[0]);
+ }));
+ adapter_->OnTransportFeedback(*feedback.get());
+
+ sent_packets.push_back(info);
+
+ ComparePacketVectors(sent_packets, received_feedback);
+}
+
+} // namespace test
+} // namespace webrtc