Moving src/webrtc into src/.

In order to eliminate the WebRTC Subtree mirror in Chromium, 
WebRTC is moving the content of the src/webrtc directory up
to the src/ directory.

NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
TBR=tommi@webrtc.org

Bug: chromium:611808
Change-Id: Iac59c5b51b950f174119565bac87955a7994bc38
Reviewed-on: https://webrtc-review.googlesource.com/1560
Commit-Queue: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Henrik Kjellander <kjellander@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#19845}
This commit is contained in:
Mirko Bonadei
2017-09-15 06:15:48 +02:00
committed by Commit Bot
parent 6674846b4a
commit bb547203bf
4576 changed files with 1092 additions and 1196 deletions

View File

@ -0,0 +1,263 @@
# 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.
import("../../webrtc.gni")
rtc_static_library("remote_bitrate_estimator") {
# TODO(mbonadei): Remove (bugs.webrtc.org/6828)
# Errors on cyclic dependency with:
# rtp_rtcp:rtp_rtcp if enabled.
check_includes = false
sources = [
"aimd_rate_control.cc",
"aimd_rate_control.h",
"bwe_defines.cc",
"include/bwe_defines.h",
"include/remote_bitrate_estimator.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_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",
"test/bwe_test_logging.h",
]
if (rtc_enable_bwe_test_logging) {
defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=1" ]
sources += [ "test/bwe_test_logging.cc" ]
} else {
defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0" ]
}
if (!build_with_chromium && is_clang) {
# Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
}
deps = [
"../..:webrtc_common",
"../../rtc_base:rtc_base",
"../../rtc_base:rtc_base_approved",
"../../system_wrappers",
]
}
if (!build_with_chromium) {
rtc_source_set("bwe_rtp") {
testonly = true
sources = [
"tools/bwe_rtp.cc",
"tools/bwe_rtp.h",
]
deps = [
":remote_bitrate_estimator",
"../../rtc_base:rtc_base_approved",
"../../test:rtp_test_utils",
"../rtp_rtcp:rtp_rtcp",
]
if (is_clang) {
# Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
}
}
}
if (rtc_include_tests) {
rtc_static_library("bwe_simulator_lib") {
testonly = true
sources = [
"test/bbr_paced_sender.cc",
"test/bbr_paced_sender.h",
"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.h",
"test/estimators/bbr.cc",
"test/estimators/bbr.h",
"test/estimators/congestion_window.cc",
"test/estimators/congestion_window.h",
"test/estimators/max_bandwidth_filter.cc",
"test/estimators/max_bandwidth_filter.h",
"test/estimators/min_rtt_filter.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",
"test/metric_recorder.cc",
"test/metric_recorder.h",
"test/packet.h",
"test/packet_receiver.cc",
"test/packet_receiver.h",
"test/packet_sender.cc",
"test/packet_sender.h",
]
if (rtc_enable_bwe_test_logging) {
defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=1" ]
} else {
defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0" ]
}
if (!build_with_chromium && is_clang) {
# Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
}
if (is_win) {
cflags = [
# TODO(kjellander): Bug 261: fix this warning.
"/wd4373", # virtual function override.
]
}
deps = [
":remote_bitrate_estimator",
"..:module_api",
"../..:webrtc_common",
"../../api:optional",
"../../rtc_base:gtest_prod",
"../../rtc_base:rtc_base",
"../../rtc_base:rtc_base_approved",
"../../system_wrappers",
"../../test:test_support",
"../../voice_engine",
"../bitrate_controller",
"../congestion_controller",
"../pacing",
"../rtp_rtcp",
"//testing/gmock",
"//testing/gtest",
]
}
rtc_source_set("remote_bitrate_estimator_perf_tests") {
testonly = true
# Skip restricting visibility on mobile platforms since the tests on those
# gets additional generated targets which would require many lines here to
# cover (which would be confusing to read and hard to maintain).
if (!is_android && !is_ios) {
visibility = [ "../..:webrtc_perf_tests" ]
}
sources = [
"remote_bitrate_estimators_test.cc",
]
deps = [
":bwe_simulator_lib",
":remote_bitrate_estimator",
"../../rtc_base:rtc_base_approved",
"../../test:test_support",
]
if (!build_with_chromium && is_clang) {
# Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
}
}
rtc_source_set("remote_bitrate_estimator_unittests") {
testonly = true
# Skip restricting visibility on mobile platforms since the tests on those
# gets additional generated targets which would require many lines here to
# cover (which would be confusing to read and hard to maintain).
if (!is_android && !is_ios) {
visibility = [ "..:modules_unittests" ]
}
sources = [
"aimd_rate_control_unittest.cc",
"inter_arrival_unittest.cc",
"overuse_detector_unittest.cc",
"remote_bitrate_estimator_abs_send_time_unittest.cc",
"remote_bitrate_estimator_single_stream_unittest.cc",
"remote_bitrate_estimator_unittest_helper.cc",
"remote_bitrate_estimator_unittest_helper.h",
"remote_estimator_proxy_unittest.cc",
"send_time_history_unittest.cc",
"test/bwe_test_framework_unittest.cc",
"test/bwe_unittest.cc",
"test/estimators/congestion_window_unittest.cc",
"test/estimators/max_bandwidth_filter_unittest.cc",
"test/estimators/min_rtt_filter_unittest.cc",
"test/estimators/nada_unittest.cc",
"test/metric_recorder_unittest.cc",
]
deps = [
":bwe_simulator_lib",
":remote_bitrate_estimator",
"../..:webrtc_common",
"../../rtc_base:rtc_base",
"../../rtc_base:rtc_base_approved",
"../../system_wrappers:system_wrappers",
"../../test:field_trial",
"../../test:test_support",
"../pacing:pacing",
"../rtp_rtcp:rtp_rtcp",
"//testing/gmock",
]
if (is_win) {
cflags = [
# TODO(kjellander): bugs.webrtc.org/261: Fix this warning.
"/wd4373", # virtual function override.
]
}
if (!build_with_chromium && is_clang) {
# Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
}
}
rtc_test("bwe_simulations_tests") {
testonly = true
sources = [
"bwe_simulations.cc",
]
deps = [
":bwe_simulator_lib",
":remote_bitrate_estimator",
"../..:webrtc_common",
"../../rtc_base:rtc_base_approved",
"../../test:test_main",
"//testing/gmock",
"//testing/gtest",
]
if (!build_with_chromium && is_clang) {
# Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
}
if (is_win) {
cflags = [
# TODO(kjellander): bugs.webrtc.org/261: Fix this warning.
"/wd4373", # virtual function override.
]
}
}
}

View File

@ -0,0 +1,10 @@
include_rules = [
"+webrtc/logging/rtc_event_log",
"+webrtc/system_wrappers",
]
specific_include_rules = {
"nada\.h": [
"+webrtc/voice_engine",
],
}

View File

@ -0,0 +1,10 @@
stefan@webrtc.org
terelius@webrtc.org
asapersson@webrtc.org
mflodman@webrtc.org
philipel@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 *.gn=*
per-file *.gni=*

View File

@ -0,0 +1,327 @@
/*
* 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/rtc_base/checks.h"
#include "webrtc/rtc_base/safe_minmax.h"
#include "webrtc/modules/remote_bitrate_estimator/overuse_detector.h"
#include "webrtc/modules/remote_bitrate_estimator/include/bwe_defines.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 kMaxFeedbackIntervalMs = 1000;
AimdRateControl::AimdRateControl()
: min_configured_bitrate_bps_(congestion_controller::GetMinBitrateBps()),
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),
time_first_incoming_estimate_(-1),
bitrate_is_initialized_(false),
beta_(0.85f),
rtt_(kDefaultRttMs),
in_experiment_(!AdaptiveThresholdExperimentIsDisabled()) {}
AimdRateControl::~AimdRateControl() {}
void AimdRateControl::SetStartBitrate(int start_bitrate_bps) {
current_bitrate_bps_ = start_bitrate_bps;
bitrate_is_initialized_ = true;
}
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;
const 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 rtc::SafeClamp(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()) {
// TODO(terelius/holmer): Investigate consequences of increasing
// the threshold to 0.95 * LatestEstimate().
const uint32_t threshold = static_cast<uint32_t> (0.5 * LatestEstimate());
return incoming_bitrate_bps < threshold;
}
return false;
}
uint32_t AimdRateControl::LatestEstimate() const {
return current_bitrate_bps_;
}
void AimdRateControl::SetRtt(int64_t rtt) {
rtt_ = rtt;
}
uint32_t AimdRateControl::Update(const RateControlInput* input,
int64_t now_ms) {
RTC_CHECK(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->incoming_bitrate)
time_first_incoming_estimate_ = now_ms;
} else if (now_ms - time_first_incoming_estimate_ > kInitializationTimeMs &&
input->incoming_bitrate) {
current_bitrate_bps_ = *input->incoming_bitrate;
bitrate_is_initialized_ = true;
}
}
current_bitrate_bps_ = ChangeBitrate(current_bitrate_bps_, *input, now_ms);
return current_bitrate_bps_;
}
void AimdRateControl::SetEstimate(int bitrate_bps, int64_t now_ms) {
bitrate_is_initialized_ = true;
current_bitrate_bps_ = ClampBitrate(bitrate_bps, bitrate_bps);
time_last_bitrate_change_ = now_ms;
}
int AimdRateControl::GetNearMaxIncreaseRateBps() const {
RTC_DCHECK_GT(current_bitrate_bps_, 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;
// Approximate the over-use estimator delay to 100 ms.
const int64_t response_time = in_experiment_ ? (rtt_ + 100) * 2 : rtt_ + 100;
constexpr double kMinIncreaseRateBps = 4000;
return static_cast<int>(std::max(
kMinIncreaseRateBps, (avg_packet_size_bits * 1000) / response_time));
}
int AimdRateControl::GetExpectedBandwidthPeriodMs() const {
constexpr int kMinPeriodMs = 500;
constexpr int kMaxPeriodMs = 50000;
int increase_rate = GetNearMaxIncreaseRateBps();
if (!last_decrease_)
return kMinPeriodMs;
return std::min(kMaxPeriodMs,
std::max<int>(1000 * static_cast<int64_t>(*last_decrease_) /
increase_rate,
kMinPeriodMs));
}
uint32_t AimdRateControl::ChangeBitrate(uint32_t new_bitrate_bps,
const RateControlInput& input,
int64_t now_ms) {
uint32_t incoming_bitrate_bps =
input.incoming_bitrate.value_or(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_ &&
input.bw_state != BandwidthUsage::kBwOverusing)
return current_bitrate_bps_;
ChangeState(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) {
uint32_t additive_increase_bps =
AdditiveRateIncrease(now_ms, time_last_bitrate_change_);
new_bitrate_bps += additive_increase_bps;
} else {
uint32_t multiplicative_increase_bps = MultiplicativeRateIncrease(
now_ms, time_last_bitrate_change_, new_bitrate_bps);
new_bitrate_bps += multiplicative_increase_bps;
}
time_last_bitrate_change_ = now_ms;
break;
case kRcDecrease:
// Set bit rate to something slightly lower than max
// to get rid of any self-induced delay.
new_bitrate_bps =
static_cast<uint32_t>(beta_ * incoming_bitrate_bps + 0.5);
if (new_bitrate_bps > current_bitrate_bps_) {
// Avoid increasing the rate when over-using.
if (rate_control_region_ != kRcMaxUnknown) {
new_bitrate_bps = static_cast<uint32_t>(
beta_ * avg_max_bitrate_kbps_ * 1000 + 0.5f);
}
new_bitrate_bps = std::min(new_bitrate_bps, current_bitrate_bps_);
}
ChangeRegion(kRcNearMax);
if (bitrate_is_initialized_ &&
incoming_bitrate_bps < current_bitrate_bps_) {
constexpr float kDegradationFactor = 0.9f;
if (new_bitrate_bps <
kDegradationFactor * beta_ * current_bitrate_bps_) {
// If bitrate decreases more than a normal back off after overuse, it
// indicates a real network degradation. We do not let such a decrease
// to determine the bandwidth estimation period.
last_decrease_ = rtc::Optional<int>();
} else {
last_decrease_ = rtc::Optional<int>(
current_bitrate_bps_ - new_bitrate_bps);
}
}
if (incoming_bitrate_kbps <
avg_max_bitrate_kbps_ - 3 * std_max_bit_rate) {
avg_max_bitrate_kbps_ = -1.0f;
}
bitrate_is_initialized_ = true;
UpdateMaxBitRateEstimate(incoming_bitrate_kbps);
// Stay on hold until the pipes are cleared.
rate_control_state_ = kRcHold;
time_last_bitrate_change_ = now_ms;
break;
default:
assert(false);
}
return ClampBitrate(new_bitrate_bps, incoming_bitrate_bps);
}
uint32_t AimdRateControl::ClampBitrate(uint32_t new_bitrate_bps,
uint32_t incoming_bitrate_bps) const {
// Don't change the bit rate if the send side is too far off.
// We allow a bit more lag at very low rates to not too easily get stuck if
// the encoder produces uneven outputs.
const uint32_t max_bitrate_bps =
static_cast<uint32_t>(1.5f * incoming_bitrate_bps) + 10000;
if (new_bitrate_bps > current_bitrate_bps_ &&
new_bitrate_bps > max_bitrate_bps) {
new_bitrate_bps = std::max(current_bitrate_bps_, max_bitrate_bps);
}
new_bitrate_bps = std::max(new_bitrate_bps, min_configured_bitrate_bps_);
return new_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) {
auto time_since_last_update_ms =
rtc::SafeMin<int64_t>(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) const {
return static_cast<uint32_t>((now_ms - last_ms) *
GetNearMaxIncreaseRateBps() / 1000);
}
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 (input.bw_state) {
case BandwidthUsage::kBwNormal:
if (rate_control_state_ == kRcHold) {
time_last_bitrate_change_ = now_ms;
rate_control_state_ = kRcIncrease;
}
break;
case BandwidthUsage::kBwOverusing:
if (rate_control_state_ != kRcDecrease) {
rate_control_state_ = kRcDecrease;
}
break;
case BandwidthUsage::kBwUnderusing:
rate_control_state_ = kRcHold;
break;
default:
assert(false);
}
}
void AimdRateControl::ChangeRegion(RateControlRegion region) {
rate_control_region_ = region;
}
} // namespace webrtc

View File

@ -0,0 +1,92 @@
/*
* 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/modules/remote_bitrate_estimator/include/bwe_defines.h"
#include "webrtc/rtc_base/constructormagic.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();
~AimdRateControl();
// Returns true if there is a valid estimate of the incoming bitrate, false
// otherwise.
bool ValidEstimate() const;
void SetStartBitrate(int start_bitrate_bps);
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 less than half of 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;
void SetRtt(int64_t rtt);
uint32_t Update(const RateControlInput* input, int64_t now_ms);
void SetEstimate(int bitrate_bps, int64_t now_ms);
// Returns the increase rate when used bandwidth is near the link capacity.
int GetNearMaxIncreaseRateBps() const;
// Returns the expected time between overuse signals (assuming steady state).
int GetExpectedBandwidthPeriodMs() const;
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_bitrate,
const RateControlInput& input,
int64_t now_ms);
// Clamps new_bitrate_bps to within the configured min bitrate and a linear
// function of the incoming bitrate, so that the new bitrate can't grow too
// large compared to the bitrate actually being received by the other end.
uint32_t ClampBitrate(uint32_t new_bitrate_bps,
uint32_t incoming_bitrate_bps) const;
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) const;
void UpdateChangePeriod(int64_t now_ms);
void UpdateMaxBitRateEstimate(float incoming_bit_rate_kbps);
void ChangeState(const RateControlInput& input, int64_t now_ms);
void ChangeRegion(RateControlRegion region);
uint32_t min_configured_bitrate_bps_;
uint32_t max_configured_bitrate_bps_;
uint32_t current_bitrate_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_;
int64_t time_first_incoming_estimate_;
bool bitrate_is_initialized_;
float beta_;
int64_t rtt_;
bool in_experiment_;
rtc::Optional<int> last_decrease_;
};
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_

View File

@ -0,0 +1,197 @@
/*
* Copyright (c) 2016 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 <memory>
#include "webrtc/modules/remote_bitrate_estimator/aimd_rate_control.h"
#include "webrtc/system_wrappers/include/clock.h"
#include "webrtc/test/gtest.h"
namespace webrtc {
namespace {
constexpr int64_t kClockInitialTime = 123456;
constexpr int kMinBwePeriodMs = 500;
constexpr int kMaxBwePeriodMs = 50000;
// After an overuse, we back off to 85% to the received bitrate.
constexpr double kFractionAfterOveruse = 0.85;
struct AimdRateControlStates {
std::unique_ptr<AimdRateControl> aimd_rate_control;
std::unique_ptr<SimulatedClock> simulated_clock;
};
AimdRateControlStates CreateAimdRateControlStates() {
AimdRateControlStates states;
states.aimd_rate_control.reset(new AimdRateControl());
states.simulated_clock.reset(new SimulatedClock(kClockInitialTime));
return states;
}
void UpdateRateControl(const AimdRateControlStates& states,
const BandwidthUsage& bandwidth_usage,
int bitrate,
int64_t now_ms) {
RateControlInput input(bandwidth_usage, rtc::Optional<uint32_t>(bitrate),
now_ms);
states.aimd_rate_control->Update(&input, now_ms);
}
} // namespace
TEST(AimdRateControlTest, MinNearMaxIncreaseRateOnLowBandwith) {
auto states = CreateAimdRateControlStates();
constexpr int kBitrate = 30000;
states.aimd_rate_control->SetEstimate(
kBitrate, states.simulated_clock->TimeInMilliseconds());
EXPECT_EQ(4000, states.aimd_rate_control->GetNearMaxIncreaseRateBps());
}
TEST(AimdRateControlTest, NearMaxIncreaseRateIs5kbpsOn90kbpsAnd200msRtt) {
auto states = CreateAimdRateControlStates();
constexpr int kBitrate = 90000;
states.aimd_rate_control->SetEstimate(
kBitrate, states.simulated_clock->TimeInMilliseconds());
EXPECT_EQ(5000, states.aimd_rate_control->GetNearMaxIncreaseRateBps());
}
TEST(AimdRateControlTest, NearMaxIncreaseRateIs5kbpsOn60kbpsAnd100msRtt) {
auto states = CreateAimdRateControlStates();
constexpr int kBitrate = 60000;
states.aimd_rate_control->SetEstimate(
kBitrate, states.simulated_clock->TimeInMilliseconds());
states.aimd_rate_control->SetRtt(100);
EXPECT_EQ(5000, states.aimd_rate_control->GetNearMaxIncreaseRateBps());
}
TEST(AimdRateControlTest, GetIncreaseRateAndBandwidthPeriod) {
auto states = CreateAimdRateControlStates();
constexpr int kBitrate = 300000;
states.aimd_rate_control->SetEstimate(
kBitrate, states.simulated_clock->TimeInMilliseconds());
UpdateRateControl(states, BandwidthUsage::kBwOverusing, kBitrate,
states.simulated_clock->TimeInMilliseconds());
EXPECT_NEAR(14000, states.aimd_rate_control->GetNearMaxIncreaseRateBps(),
1000);
EXPECT_EQ(kMinBwePeriodMs,
states.aimd_rate_control->GetExpectedBandwidthPeriodMs());
}
TEST(AimdRateControlTest, BweLimitedByAckedBitrate) {
auto states = CreateAimdRateControlStates();
constexpr int kAckedBitrate = 10000;
states.aimd_rate_control->SetEstimate(
kAckedBitrate, states.simulated_clock->TimeInMilliseconds());
while (states.simulated_clock->TimeInMilliseconds() - kClockInitialTime <
20000) {
UpdateRateControl(states, BandwidthUsage::kBwNormal, kAckedBitrate,
states.simulated_clock->TimeInMilliseconds());
states.simulated_clock->AdvanceTimeMilliseconds(100);
}
ASSERT_TRUE(states.aimd_rate_control->ValidEstimate());
EXPECT_EQ(static_cast<uint32_t>(1.5 * kAckedBitrate + 10000),
states.aimd_rate_control->LatestEstimate());
}
TEST(AimdRateControlTest, BweNotLimitedByDecreasingAckedBitrate) {
auto states = CreateAimdRateControlStates();
constexpr int kAckedBitrate = 100000;
states.aimd_rate_control->SetEstimate(
kAckedBitrate, states.simulated_clock->TimeInMilliseconds());
while (states.simulated_clock->TimeInMilliseconds() - kClockInitialTime <
20000) {
UpdateRateControl(states, BandwidthUsage::kBwNormal, kAckedBitrate,
states.simulated_clock->TimeInMilliseconds());
states.simulated_clock->AdvanceTimeMilliseconds(100);
}
ASSERT_TRUE(states.aimd_rate_control->ValidEstimate());
// If the acked bitrate decreases the BWE shouldn't be reduced to 1.5x
// what's being acked, but also shouldn't get to increase more.
uint32_t prev_estimate = states.aimd_rate_control->LatestEstimate();
UpdateRateControl(states, BandwidthUsage::kBwNormal, kAckedBitrate / 2,
states.simulated_clock->TimeInMilliseconds());
uint32_t new_estimate = states.aimd_rate_control->LatestEstimate();
EXPECT_NEAR(new_estimate, static_cast<uint32_t>(1.5 * kAckedBitrate + 10000),
2000);
EXPECT_EQ(new_estimate, prev_estimate);
}
TEST(AimdRateControlTest, MinPeriodUntilFirstOveruse) {
auto states = CreateAimdRateControlStates();
states.aimd_rate_control->SetStartBitrate(300000);
EXPECT_EQ(kMinBwePeriodMs,
states.aimd_rate_control->GetExpectedBandwidthPeriodMs());
states.simulated_clock->AdvanceTimeMilliseconds(100);
UpdateRateControl(states, BandwidthUsage::kBwOverusing, 280000,
states.simulated_clock->TimeInMilliseconds());
EXPECT_NE(kMinBwePeriodMs,
states.aimd_rate_control->GetExpectedBandwidthPeriodMs());
}
TEST(AimdRateControlTest, ExpectedPeriodAfter20kbpsDropAnd5kbpsIncrease) {
auto states = CreateAimdRateControlStates();
constexpr int kInitialBitrate = 110000;
states.aimd_rate_control->SetEstimate(
kInitialBitrate, states.simulated_clock->TimeInMilliseconds());
states.simulated_clock->AdvanceTimeMilliseconds(100);
// Make the bitrate drop by 20 kbps to get to 90 kbps.
// The rate increase at 90 kbps should be 5 kbps, so the period should be 4 s.
constexpr int kAckedBitrate =
(kInitialBitrate - 20000) / kFractionAfterOveruse;
UpdateRateControl(states, BandwidthUsage::kBwOverusing, kAckedBitrate,
states.simulated_clock->TimeInMilliseconds());
EXPECT_EQ(5000, states.aimd_rate_control->GetNearMaxIncreaseRateBps());
EXPECT_EQ(4000, states.aimd_rate_control->GetExpectedBandwidthPeriodMs());
}
TEST(AimdRateControlTest, MinPeriodAfterLargeBitrateDecrease) {
auto states = CreateAimdRateControlStates();
constexpr int kInitialBitrate = 110000;
states.aimd_rate_control->SetEstimate(
kInitialBitrate, states.simulated_clock->TimeInMilliseconds());
states.simulated_clock->AdvanceTimeMilliseconds(100);
// Make such a large drop in bitrate that should be treated as network
// degradation.
constexpr int kAckedBitrate = kInitialBitrate * 3 / 4 / kFractionAfterOveruse;
UpdateRateControl(states, BandwidthUsage::kBwOverusing, kAckedBitrate,
states.simulated_clock->TimeInMilliseconds());
EXPECT_EQ(kMinBwePeriodMs,
states.aimd_rate_control->GetExpectedBandwidthPeriodMs());
}
TEST(AimdRateControlTest, BandwidthPeriodIsNotBelowMin) {
auto states = CreateAimdRateControlStates();
constexpr int kInitialBitrate = 10000;
states.aimd_rate_control->SetEstimate(
kInitialBitrate, states.simulated_clock->TimeInMilliseconds());
states.simulated_clock->AdvanceTimeMilliseconds(100);
// Make a small (1.5 kbps) bitrate drop to 8.5 kbps.
UpdateRateControl(states, BandwidthUsage::kBwOverusing, kInitialBitrate - 1,
states.simulated_clock->TimeInMilliseconds());
EXPECT_EQ(kMinBwePeriodMs,
states.aimd_rate_control->GetExpectedBandwidthPeriodMs());
}
TEST(AimdRateControlTest, BandwidthPeriodIsNotAboveMax) {
auto states = CreateAimdRateControlStates();
constexpr int kInitialBitrate = 50000000;
states.aimd_rate_control->SetEstimate(
kInitialBitrate, states.simulated_clock->TimeInMilliseconds());
states.simulated_clock->AdvanceTimeMilliseconds(100);
// Make a large (10 Mbps) bitrate drop to 10 kbps.
constexpr int kAckedBitrate = 40000000 / kFractionAfterOveruse;
UpdateRateControl(states, BandwidthUsage::kBwOverusing, kAckedBitrate,
states.simulated_clock->TimeInMilliseconds());
EXPECT_EQ(kMaxBwePeriodMs,
states.aimd_rate_control->GetExpectedBandwidthPeriodMs());
}
} // namespace webrtc

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2016 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/include/bwe_defines.h"
#include "webrtc/system_wrappers/include/field_trial.h"
namespace webrtc {
const char kBweTypeHistogram[] = "WebRTC.BWE.Types";
namespace congestion_controller {
int GetMinBitrateBps() {
constexpr int kAudioMinBitrateBps = 5000;
constexpr int kMinBitrateBps = 10000;
if (webrtc::field_trial::IsEnabled("WebRTC-Audio-SendSideBwe")) {
return kAudioMinBitrateBps;
}
return kMinBitrateBps;
}
} // namespace congestion_controller
RateControlInput::RateControlInput(
BandwidthUsage bw_state,
const rtc::Optional<uint32_t>& incoming_bitrate,
double noise_var)
: bw_state(bw_state),
incoming_bitrate(incoming_bitrate),
noise_var(noise_var) {}
RateControlInput::~RateControlInput() = default;
} // namespace webrtc

View File

@ -0,0 +1,554 @@
/*
* 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 <memory>
#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/rtc_base/constructormagic.h"
#include "webrtc/test/gtest.h"
#include "webrtc/test/testsupport/fileutils.h"
namespace webrtc {
namespace testing {
namespace bwe {
// 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);
}
Random random_;
private:
RTC_DISALLOW_COPY_AND_ASSIGN(BweSimulation);
};
INSTANTIATE_TEST_CASE_P(VideoSendersTest,
BweSimulation,
::testing::Values(kRembEstimator,
kSendSideEstimator,
kNadaEstimator,
kBbrEstimator));
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",
std::string() + bwe_names[GetParam()] + "_up");
TraceBasedDeliveryFilter filter(&downlink_, 0, "link_capacity");
RateCounterFilter counter2(&downlink_, 0, "Receiver",
std::string() + 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, SimulationsCompiled) {
AdaptiveVideoSource source(0, 30, 300, 0, 0);
PacedVideoSender sender(&uplink_, &source, GetParam());
int zero = 0;
// CreateFlowIds() doesn't support passing int as a flow id, so we pass
// pointer instead.
DelayFilter delay(&uplink_, CreateFlowIds(&zero, 1));
delay.SetOneWayDelayMs(100);
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);
filter.set_capacity_kbps(1000);
RunFor(60 * 1000);
filter.set_capacity_kbps(500);
RunFor(50 * 1000);
filter.set_capacity_kbps(1000);
RunFor(60 * 1000);
filter.set_capacity_kbps(200);
RunFor(60 * 1000);
filter.set_capacity_kbps(50);
RunFor(60 * 1000);
filter.set_capacity_kbps(200);
RunFor(60 * 1000);
filter.set_capacity_kbps(500);
RunFor(60 * 1000);
filter.set_capacity_kbps(300);
RunFor(60 * 1000);
filter.set_capacity_kbps(1000);
RunFor(60 * 1000);
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);
}
for (int i = kEndingCapacityKbps; i >= kStartingCapacityKbps;
i -= kStepKbps) {
filter.set_capacity_kbps(i);
RunFor(kStepTimeMs);
}
filter.set_capacity_kbps(150);
RunFor(120 * 1000);
filter.set_capacity_kbps(500);
RunFor(60 * 1000);
}
TEST_P(BweSimulation, PacerChoke1000kbps500kbps1000kbps) {
AdaptiveVideoSource source(0, 30, 300, 0, 0);
PacedVideoSender sender(&uplink_, &source, GetParam());
const int kFlowId = 0;
// CreateFlowIds() doesn't support passing int as a flow id, so we pass
// pointer instead.
DelayFilter delay(&uplink_, CreateFlowIds(&kFlowId, 1));
delay.SetOneWayDelayMs(100);
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, PacerChoke50kbps15kbps50kbps) {
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(50);
filter.set_max_delay_ms(500);
RunFor(60 * 1000);
filter.set_capacity_kbps(15);
RunFor(60 * 1000);
filter.set_capacity_kbps(50);
RunFor(60 * 1000);
}
TEST_P(BweSimulation, Choke50kbps15kbps50kbps) {
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(50);
filter.set_max_delay_ms(500);
RunFor(60 * 1000);
filter.set_capacity_kbps(15);
RunFor(60 * 1000);
filter.set_capacity_kbps(50);
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());
int kFlowId = 0;
// CreateFlowIds() doesn't support passing int as a flow id, so we pass
// pointer instead.
DelayFilter delay(&uplink_, CreateFlowIds(&kFlowId, 1));
delay.SetOneWayDelayMs(100);
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, PacerGoogleWifiTrace3MbpsLowFramerate) {
PeriodicKeyFrameSource source(0, 5, 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) {
Random prng(Clock::GetRealTimeClock()->TimeInMicroseconds());
const int kAllFlowIds[] = {0, 1, 2, 3};
const size_t kNumFlows = sizeof(kAllFlowIds) / sizeof(kAllFlowIds[0]);
std::unique_ptr<VideoSource> sources[kNumFlows];
std::unique_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 * prng.Rand(39999)));
senders[i].reset(new VideoSender(&uplink_, sources[i].get(), GetParam()));
}
ChokeFilter choke(&uplink_, CreateFlowIds(kAllFlowIds, kNumFlows));
choke.set_capacity_kbps(1000);
std::unique_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");
std::unique_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(2 * kAverageOffsetMs);
for (int i = 1; i < kNumRmcatFlows; ++i) {
offsets_ms[i] = offsets_ms[i - 1] + random_.Rand(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(2 * kAverageOffsetMs);
for (int i = 1; i < kNumRmcatFlows; ++i) {
offsets_ms[i] = offsets_ms[i - 1] + random_.Rand(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(2 * kAverageOffsetMs);
for (int i = 1; i < kNumRmcatFlows; ++i) {
offsets_ms[i] = offsets_ms[i - 1] + random_.Rand(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(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(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(2 * kAverageOffsetMs), 0};
RunFairnessTest(GetParam(), 1, 1, 1000, 2000, 1000, 50, 0, offset_ms);
}
// The following test cases begin with "Evaluation" as a reference 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(kSendSideEstimator);
}
TEST_P(BweSimulation, GccComparison2) {
const size_t kNumFlows = 2;
RunVariableCapacity2MultipleFlows(GetParam(), kNumFlows);
BweTest gcc_test(false);
gcc_test.RunVariableCapacity2MultipleFlows(kSendSideEstimator, kNumFlows);
}
TEST_P(BweSimulation, GccComparison3) {
RunBidirectionalFlow(GetParam());
BweTest gcc_test(false);
gcc_test.RunBidirectionalFlow(kSendSideEstimator);
}
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(kSendSideEstimator);
}
TEST_P(BweSimulation, GccComparison6) {
RunLongTcpFairness(GetParam());
BweTest gcc_test(false);
gcc_test.RunLongTcpFairness(kSendSideEstimator);
}
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(kSendSideEstimator, tcp_file_sizes_bytes,
tcp_starting_times_ms);
}
TEST_P(BweSimulation, GccComparison8) {
RunPauseResumeFlows(GetParam());
BweTest gcc_test(false);
gcc_test.RunPauseResumeFlows(kSendSideEstimator);
}
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(kSendSideEstimator, capacities_kbps);
}
} // namespace bwe
} // namespace testing
} // namespace webrtc

View File

@ -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_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_
#include "webrtc/api/optional.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 {
namespace congestion_controller {
int GetMinBitrateBps();
} // namespace congestion_controller
static const int64_t kBitrateWindowMs = 1000;
extern const char kBweTypeHistogram[];
enum BweNames {
kReceiverNoExtension = 0,
kReceiverTOffset = 1,
kReceiverAbsSendTime = 2,
kSendSideTransportSeqNum = 3,
kBweNamesMax = 4
};
enum class BandwidthUsage {
kBwNormal = 0,
kBwUnderusing = 1,
kBwOverusing = 2,
};
enum RateControlState { kRcHold, kRcIncrease, kRcDecrease };
enum RateControlRegion { kRcNearMax, kRcAboveMax, kRcMaxUnknown };
struct RateControlInput {
RateControlInput(BandwidthUsage bw_state,
const rtc::Optional<uint32_t>& incoming_bitrate,
double noise_var);
~RateControlInput();
BandwidthUsage bw_state;
rtc::Optional<uint32_t> incoming_bitrate;
double noise_var;
};
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_

View File

@ -0,0 +1,83 @@
/*
* 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/include/module.h"
#include "webrtc/modules/include/module_common_types.h"
#include "webrtc/modules/rtp_rtcp/include/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<uint32_t>& ssrcs,
uint32_t bitrate) = 0;
virtual ~RemoteBitrateObserver() {}
};
// TODO(holmer): Remove when all implementations have been updated.
struct ReceiveBandwidthEstimatorStats {};
class RemoteBitrateEstimator : public CallStatsObserver, public Module {
public:
~RemoteBitrateEstimator() override {}
// 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) = 0;
// Removes all data for |ssrc|.
virtual void RemoveStream(uint32_t 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<uint32_t>* ssrcs,
uint32_t* bitrate_bps) const = 0;
// TODO(holmer): Remove when all implementations have been updated.
virtual bool GetStats(ReceiveBandwidthEstimatorStats* output) const;
virtual void SetMinBitrate(int min_bitrate_bps) = 0;
protected:
static const int64_t kProcessIntervalMs = 500;
static const int64_t kStreamTimeOutMs = 2000;
};
inline bool RemoteBitrateEstimator::GetStats(
ReceiveBandwidthEstimatorStats* output) const {
return false;
}
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_

View File

@ -0,0 +1,55 @@
/*
* 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_SEND_TIME_HISTORY_H_
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_SEND_TIME_HISTORY_H_
#include <map>
#include "webrtc/modules/include/module_common_types.h"
#include "webrtc/rtc_base/basictypes.h"
#include "webrtc/rtc_base/constructormagic.h"
namespace webrtc {
class Clock;
struct PacketFeedback;
class SendTimeHistory {
public:
SendTimeHistory(const Clock* clock, int64_t packet_age_limit_ms);
~SendTimeHistory();
// Cleanup old entries, then add new packet info with provided parameters.
void AddAndRemoveOld(const PacketFeedback& packet);
// Updates packet info identified by |sequence_number| with |send_time_ms|.
// Return false if not found.
bool OnSentPacket(uint16_t sequence_number, int64_t send_time_ms);
// Look up PacketFeedback for a sent packet, based on the sequence number, and
// populate all fields except for arrival_time. The packet parameter must
// thus be non-null and have the sequence_number field set.
bool GetFeedback(PacketFeedback* packet_feedback, bool remove);
size_t GetOutstandingBytes(uint16_t local_net_id,
uint16_t remote_net_id) const;
private:
const Clock* const clock_;
const int64_t packet_age_limit_ms_;
SequenceNumberUnwrapper seq_num_unwrapper_;
std::map<int64_t, PacketFeedback> history_;
rtc::Optional<int64_t> latest_acked_seq_num_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(SendTimeHistory);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_SEND_TIME_HISTORY_H_

View File

@ -0,0 +1,157 @@
/*
* 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/modules/include/module_common_types.h"
#include "webrtc/rtc_base/logging.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),
num_consecutive_reordered_packets_(0) {}
bool InterArrival::ComputeDeltas(uint32_t timestamp,
int64_t arrival_time_ms,
int64_t system_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;
// Check system time differences to see if we have an unproportional jump
// in arrival time. In that case reset the inter-arrival computations.
int64_t system_time_delta_ms =
current_timestamp_group_.last_system_time_ms -
prev_timestamp_group_.last_system_time_ms;
if (*arrival_time_delta_ms - system_time_delta_ms >=
kArrivalTimeOffsetThresholdMs) {
LOG(LS_WARNING) << "The arrival time clock offset has changed (diff = "
<< *arrival_time_delta_ms - system_time_delta_ms
<< " ms), resetting.";
Reset();
return false;
}
if (*arrival_time_delta_ms < 0) {
// The group of packets has been reordered since receiving its local
// arrival timestamp.
++num_consecutive_reordered_packets_;
if (num_consecutive_reordered_packets_ >= kReorderedResetThreshold) {
LOG(LS_WARNING) << "Packets are being reordered on the path from the "
"socket to the bandwidth estimator. Ignoring this "
"packet for bandwidth estimation, resetting.";
Reset();
}
return false;
} else {
num_consecutive_reordered_packets_ = 0;
}
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;
current_timestamp_group_.last_system_time_ms = system_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;
}
void InterArrival::Reset() {
num_consecutive_reordered_packets_ = 0;
current_timestamp_group_ = TimestampGroup();
prev_timestamp_group_ = TimestampGroup();
}
} // namespace webrtc

View File

@ -0,0 +1,95 @@
/*
* 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/rtc_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:
// After this many packet groups received out of order InterArrival will
// reset, assuming that clocks have made a jump.
static constexpr int kReorderedResetThreshold = 3;
static constexpr int64_t kArrivalTimeOffsetThresholdMs = 3000;
// 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,
int64_t system_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;
int64_t last_system_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;
void Reset();
const uint32_t kTimestampGroupLengthTicks;
TimestampGroup current_timestamp_group_;
TimestampGroup prev_timestamp_group_;
double timestamp_to_ms_coeff_;
bool burst_grouping_;
int num_consecutive_reordered_packets_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(InterArrival);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_

View File

@ -0,0 +1,536 @@
/*
* 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 <memory>
#include "webrtc/common_types.h"
#include "webrtc/modules/remote_bitrate_estimator/inter_arrival.h"
#include "webrtc/test/gtest.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_.reset(
new InterArrival(kTimestampGroupLengthUs / 1000, 1.0, true));
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);
}
std::unique_ptr<InterArrival> inter_arrival_;
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, 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, 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);
}
std::unique_ptr<InterArrival> inter_arrival_rtp_;
std::unique_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);
}
TEST_F(InterArrivalTest, PositiveArrivalTimeJump) {
const size_t kPacketSize = 1000;
uint32_t send_time_ms = 10000;
int64_t arrival_time_ms = 20000;
int64_t system_time_ms = 30000;
uint32_t send_delta;
int64_t arrival_delta;
int size_delta;
EXPECT_FALSE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
const int kTimeDeltaMs = 30;
send_time_ms += kTimeDeltaMs;
arrival_time_ms += kTimeDeltaMs;
system_time_ms += kTimeDeltaMs;
EXPECT_FALSE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
send_time_ms += kTimeDeltaMs;
arrival_time_ms += kTimeDeltaMs + InterArrival::kArrivalTimeOffsetThresholdMs;
system_time_ms += kTimeDeltaMs;
EXPECT_TRUE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
EXPECT_EQ(kTimeDeltaMs, static_cast<int>(send_delta));
EXPECT_EQ(kTimeDeltaMs, arrival_delta);
EXPECT_EQ(size_delta, 0);
send_time_ms += kTimeDeltaMs;
arrival_time_ms += kTimeDeltaMs;
system_time_ms += kTimeDeltaMs;
// The previous arrival time jump should now be detected and cause a reset.
EXPECT_FALSE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
// The two next packets will not give a valid delta since we're in the initial
// state.
for (int i = 0; i < 2; ++i) {
send_time_ms += kTimeDeltaMs;
arrival_time_ms += kTimeDeltaMs;
system_time_ms += kTimeDeltaMs;
EXPECT_FALSE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
}
send_time_ms += kTimeDeltaMs;
arrival_time_ms += kTimeDeltaMs;
system_time_ms += kTimeDeltaMs;
EXPECT_TRUE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
EXPECT_EQ(kTimeDeltaMs, static_cast<int>(send_delta));
EXPECT_EQ(kTimeDeltaMs, arrival_delta);
EXPECT_EQ(size_delta, 0);
}
TEST_F(InterArrivalTest, NegativeArrivalTimeJump) {
const size_t kPacketSize = 1000;
uint32_t send_time_ms = 10000;
int64_t arrival_time_ms = 20000;
int64_t system_time_ms = 30000;
uint32_t send_delta;
int64_t arrival_delta;
int size_delta;
EXPECT_FALSE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
const int kTimeDeltaMs = 30;
send_time_ms += kTimeDeltaMs;
arrival_time_ms += kTimeDeltaMs;
system_time_ms += kTimeDeltaMs;
EXPECT_FALSE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
send_time_ms += kTimeDeltaMs;
arrival_time_ms += kTimeDeltaMs;
system_time_ms += kTimeDeltaMs;
EXPECT_TRUE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
EXPECT_EQ(kTimeDeltaMs, static_cast<int>(send_delta));
EXPECT_EQ(kTimeDeltaMs, arrival_delta);
EXPECT_EQ(size_delta, 0);
// Three out of order will fail, after that we will be reset and two more will
// fail before we get our first valid delta after the reset.
arrival_time_ms -= 1000;
for (int i = 0; i < InterArrival::kReorderedResetThreshold + 3; ++i) {
send_time_ms += kTimeDeltaMs;
arrival_time_ms += kTimeDeltaMs;
system_time_ms += kTimeDeltaMs;
// The previous arrival time jump should now be detected and cause a reset.
EXPECT_FALSE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
}
send_time_ms += kTimeDeltaMs;
arrival_time_ms += kTimeDeltaMs;
system_time_ms += kTimeDeltaMs;
EXPECT_TRUE(inter_arrival_->ComputeDeltas(
send_time_ms, arrival_time_ms, system_time_ms, kPacketSize, &send_delta,
&arrival_delta, &size_delta));
EXPECT_EQ(kTimeDeltaMs, static_cast<int>(send_delta));
EXPECT_EQ(kTimeDeltaMs, arrival_delta);
EXPECT_EQ(size_delta, 0);
}
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,162 @@
/*
* 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 <math.h>
#include <stdlib.h>
#include <algorithm>
#include <sstream>
#include <string>
#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/rtc_base/checks.h"
#include "webrtc/rtc_base/logging.h"
#include "webrtc/rtc_base/safe_minmax.h"
#include "webrtc/system_wrappers/include/field_trial.h"
namespace webrtc {
const char kAdaptiveThresholdExperiment[] = "WebRTC-AdaptiveBweThreshold";
const char kEnabledPrefix[] = "Enabled";
const size_t kEnabledPrefixLength = sizeof(kEnabledPrefix) - 1;
const char kDisabledPrefix[] = "Disabled";
const size_t kDisabledPrefixLength = sizeof(kDisabledPrefix) - 1;
const double kMaxAdaptOffsetMs = 15.0;
const double kOverUsingTimeThreshold = 10;
const int kMinNumDeltas = 60;
bool AdaptiveThresholdExperimentIsDisabled() {
std::string experiment_string =
webrtc::field_trial::FindFullName(kAdaptiveThresholdExperiment);
const size_t kMinExperimentLength = kDisabledPrefixLength;
if (experiment_string.length() < kMinExperimentLength)
return false;
return experiment_string.substr(0, kDisabledPrefixLength) == kDisabledPrefix;
}
// 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);
const size_t kMinExperimentLength = kEnabledPrefixLength + 3;
if (experiment_string.length() < kMinExperimentLength ||
experiment_string.substr(0, kEnabledPrefixLength) != kEnabledPrefix)
return false;
return sscanf(experiment_string.substr(kEnabledPrefixLength + 1).c_str(),
"%lf,%lf", k_up, k_down) == 2;
}
OveruseDetector::OveruseDetector()
// Experiment is on by default, but can be disabled with finch by setting
// the field trial string to "WebRTC-AdaptiveBweThreshold/Disabled/".
: in_experiment_(!AdaptiveThresholdExperimentIsDisabled()),
k_up_(0.0087),
k_down_(0.039),
overusing_time_threshold_(100),
threshold_(12.5),
last_update_ms_(-1),
prev_offset_(0.0),
time_over_using_(-1),
overuse_counter_(0),
hypothesis_(BandwidthUsage::kBwNormal) {
if (!AdaptiveThresholdExperimentIsDisabled())
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 BandwidthUsage::kBwNormal;
}
const double T = std::min(num_of_deltas, kMinNumDeltas) * offset;
BWE_TEST_LOGGING_PLOT(1, "T", 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_ = BandwidthUsage::kBwOverusing;
}
}
} else if (T < -threshold_) {
time_over_using_ = -1;
overuse_counter_ = 0;
hypothesis_ = BandwidthUsage::kBwUnderusing;
} else {
time_over_using_ = -1;
overuse_counter_ = 0;
hypothesis_ = BandwidthUsage::kBwNormal;
}
prev_offset_ = offset;
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_;
const int64_t kMaxTimeDeltaMs = 100;
int64_t time_delta_ms = std::min(now_ms - last_update_ms_, kMaxTimeDeltaMs);
threshold_ +=
k * (fabs(modified_offset) - threshold_) * time_delta_ms;
threshold_ = rtc::SafeClamp(threshold_, 6.f, 600.f);
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

View File

@ -0,0 +1,62 @@
/*
* 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/modules/include/module_common_types.h"
#include "webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/typedefs.h"
namespace webrtc {
bool AdaptiveThresholdExperimentIsDisabled();
class OveruseDetector {
public:
OveruseDetector();
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();
bool in_experiment_;
double k_up_;
double k_down_;
double overusing_time_threshold_;
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_

View File

@ -0,0 +1,779 @@
/*
* 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 <algorithm>
#include <cmath>
#include <cstdlib>
#include <memory>
#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/rtc_base/random.h"
#include "webrtc/rtc_base/rate_statistics.h"
#include "webrtc/test/field_trial.h"
#include "webrtc/test/gtest.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_(123456789) {}
protected:
void SetUp() override {
overuse_detector_.reset(new OveruseDetector());
}
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<int64_t>(
receive_time_ms_,
now_ms_ + static_cast<int64_t>(
random_.Gaussian(0, standard_deviation_ms) + 0.5));
if (BandwidthUsage::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<int64_t>(
receive_time_ms_,
now_ms_ + static_cast<int64_t>(
random_.Gaussian(0, standard_deviation_ms) + 0.5));
if (BandwidthUsage::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, 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(), receive_time_ms);
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_;
std::unique_ptr<OveruseDetector> overuse_detector_;
std::unique_ptr<OveruseEstimator> overuse_estimator_;
std::unique_ptr<InterArrival> inter_arrival_;
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(BandwidthUsage::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(BandwidthUsage::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(BandwidthUsage::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(7, 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(7, 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 = random_.Rand(0, 49);
now_ms_ += frame_duration_ms - offset;
} else {
now_ms_ += frame_duration_ms + offset;
}
EXPECT_EQ(BandwidthUsage::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(BandwidthUsage::kBwNormal, overuse_detector_->State());
}
UpdateDetector(rtp_timestamp, now_ms_, packet_size);
EXPECT_EQ(BandwidthUsage::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 = random_.Rand(0, 1);
now_ms_ += frame_duration_ms - offset;
} else {
now_ms_ += frame_duration_ms + offset;
}
EXPECT_EQ(BandwidthUsage::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(BandwidthUsage::kBwNormal, overuse_detector_->State());
}
UpdateDetector(rtp_timestamp, now_ms_, packet_size);
EXPECT_EQ(BandwidthUsage::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 = random_.Rand(0, 1);
now_ms_ += frame_duration_ms - offset;
} else {
now_ms_ += frame_duration_ms + offset;
}
EXPECT_EQ(BandwidthUsage::kBwNormal, overuse_detector_->State());
}
// Simulate a higher send pace, that is too high.
// Total build up of 30 ms.
for (int j = 0; j < 3; ++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(BandwidthUsage::kBwNormal, overuse_detector_->State());
}
UpdateDetector(rtp_timestamp, now_ms_, packet_size);
EXPECT_EQ(BandwidthUsage::kBwOverusing, overuse_detector_->State());
}
#if defined(WEBRTC_ANDROID)
#define MAYBE_LowGaussianVariance30Kbit3fps \
DISABLED_LowGaussianVariance30Kbit3fps
#else
#define MAYBE_LowGaussianVariance30Kbit3fps LowGaussianVariance30Kbit3fps
#endif
TEST_F(OveruseDetectorTest, MAYBE_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(0, unique_overuse);
int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
frame_duration_ms, sigma_ms, drift_per_frame_ms);
EXPECT_EQ(20, 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(0, 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(0, unique_overuse);
int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
frame_duration_ms, sigma_ms, drift_per_frame_ms);
EXPECT_EQ(44, 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(0, 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);
}
#if defined(WEBRTC_ANDROID)
#define MAYBE_LowGaussianVariance100Kbit5fps \
DISABLED_LowGaussianVariance100Kbit5fps
#else
#define MAYBE_LowGaussianVariance100Kbit5fps LowGaussianVariance100Kbit5fps
#endif
TEST_F(OveruseDetectorTest, MAYBE_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(0, unique_overuse);
int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
frame_duration_ms, sigma_ms, drift_per_frame_ms);
EXPECT_EQ(20, frames_until_overuse);
}
#if defined(WEBRTC_ANDROID)
#define MAYBE_HighGaussianVariance100Kbit5fps \
DISABLED_HighGaussianVariance100Kbit5fps
#else
#define MAYBE_HighGaussianVariance100Kbit5fps HighGaussianVariance100Kbit5fps
#endif
TEST_F(OveruseDetectorTest, MAYBE_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(0, unique_overuse);
int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
frame_duration_ms, sigma_ms, drift_per_frame_ms);
EXPECT_EQ(44, frames_until_overuse);
}
#if defined(WEBRTC_ANDROID)
#define MAYBE_LowGaussianVariance100Kbit10fps \
DISABLED_LowGaussianVariance100Kbit10fps
#else
#define MAYBE_LowGaussianVariance100Kbit10fps LowGaussianVariance100Kbit10fps
#endif
TEST_F(OveruseDetectorTest, MAYBE_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(0, unique_overuse);
int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
frame_duration_ms, sigma_ms, drift_per_frame_ms);
EXPECT_EQ(20, frames_until_overuse);
}
#if defined(WEBRTC_ANDROID)
#define MAYBE_HighGaussianVariance100Kbit10fps \
DISABLED_HighGaussianVariance100Kbit10fps
#else
#define MAYBE_HighGaussianVariance100Kbit10fps HighGaussianVariance100Kbit10fps
#endif
TEST_F(OveruseDetectorTest, MAYBE_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(0, unique_overuse);
int frames_until_overuse = RunUntilOveruse(packets_per_frame, packet_size,
frame_duration_ms, sigma_ms, drift_per_frame_ms);
EXPECT_EQ(44, frames_until_overuse);
}
#if defined(WEBRTC_ANDROID)
#define MAYBE_LowGaussianVariance300Kbit30fps \
DISABLED_LowGaussianVariance300Kbit30fps
#else
#define MAYBE_LowGaussianVariance300Kbit30fps LowGaussianVariance300Kbit30fps
#endif
TEST_F(OveruseDetectorTest, MAYBE_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(19, 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(5, 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(44, 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(10, frames_until_overuse);
}
#if defined(WEBRTC_ANDROID)
#define MAYBE_LowGaussianVariance1000Kbit30fps \
DISABLED_LowGaussianVariance1000Kbit30fps
#else
#define MAYBE_LowGaussianVariance1000Kbit30fps LowGaussianVariance1000Kbit30fps
#endif
TEST_F(OveruseDetectorTest, MAYBE_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(19, 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(5, 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(44, 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(10, frames_until_overuse);
}
#if defined(WEBRTC_ANDROID)
#define MAYBE_LowGaussianVariance2000Kbit30fps \
DISABLED_LowGaussianVariance2000Kbit30fps
#else
#define MAYBE_LowGaussianVariance2000Kbit30fps LowGaussianVariance2000Kbit30fps
#endif
TEST_F(OveruseDetectorTest, MAYBE_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(19, 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(5, 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(44, 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(10, 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());
}
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 == BandwidthUsage::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 == BandwidthUsage::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 == BandwidthUsage::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 == BandwidthUsage::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 == BandwidthUsage::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 == BandwidthUsage::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 == BandwidthUsage::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 == BandwidthUsage::kBwOverusing) {
overuse_detected = true;
}
++num_deltas;
now_ms += 5;
}
EXPECT_TRUE(overuse_detected);
}
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,166 @@
/*
* 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 <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include "webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
#include "webrtc/rtc_base/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,
int64_t now_ms) {
const double min_frame_period = UpdateMinFramePeriod(ts_delta);
const double t_ts_delta = t_delta - ts_delta;
BWE_TEST_LOGGING_PLOT(1, "dm_ms", now_ms, t_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 == BandwidthUsage::kBwOverusing &&
offset_ < prev_offset_) ||
(current_hypothesis == BandwidthUsage::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]};
BWE_TEST_LOGGING_PLOT(1, "d_ms", now_ms, slope_ * h[0] - offset_);
const double residual = t_ts_delta - slope_*h[0] - offset_;
const bool in_stable_state =
(current_hypothesis == BandwidthUsage::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;
BWE_TEST_LOGGING_PLOT(1, "kc", now_ms, K[0]);
BWE_TEST_LOGGING_PLOT(1, "km", now_ms, K[1]);
BWE_TEST_LOGGING_PLOT(1, "slope_1/bps", now_ms, slope_);
BWE_TEST_LOGGING_PLOT(1, "var_noise", now_ms, var_noise_);
}
double OveruseEstimator::UpdateMinFramePeriod(double ts_delta) {
double min_frame_period = ts_delta;
if (ts_delta_hist_.size() >= kMinFramePeriodHistoryLength) {
ts_delta_hist_.pop_front();
}
for (const double old_ts_delta : ts_delta_hist_) {
min_frame_period = std::min(old_ts_delta, 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

View File

@ -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.
*/
#ifndef WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_
#include <deque>
#include "webrtc/common_types.h"
#include "webrtc/modules/remote_bitrate_estimator/include/bwe_defines.h"
#include "webrtc/rtc_base/constructormagic.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,
int64_t now_ms);
// 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::deque<double> ts_delta_hist_;
RTC_DISALLOW_COPY_AND_ASSIGN(OveruseEstimator);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_

View File

@ -0,0 +1,416 @@
/*
* 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 <algorithm>
#include "webrtc/modules/pacing/paced_sender.h"
#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
#include "webrtc/rtc_base/checks.h"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/rtc_base/logging.h"
#include "webrtc/rtc_base/thread_annotations.h"
#include "webrtc/system_wrappers/include/metrics.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 double kTimestampToMs = 1000.0 /
static_cast<double>(1 << kInterArrivalShift);
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);
}
RemoteBitrateEstimatorAbsSendTime::RemoteBitrateEstimatorAbsSendTime(
RemoteBitrateObserver* observer,
const Clock* clock)
: clock_(clock),
observer_(observer),
inter_arrival_(),
estimator_(),
detector_(),
incoming_bitrate_(kBitrateWindowMs, 8000),
incoming_bitrate_initialized_(false),
total_probes_received_(0),
first_packet_time_ms_(-1),
last_update_ms_(-1),
uma_recorded_(false) {
RTC_DCHECK(observer_);
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 &&
current.send_mean_ms > 0.0f &&
current.recv_mean_ms > 0.0f) {
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 &&
current.send_mean_ms > 0.0f &&
current.recv_mean_ms > 0.0f) {
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;
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 {
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;
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;
}
RemoteBitrateEstimatorAbsSendTime::ProbeResult
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 ProbeResult::kNoUpdate;
}
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)) {
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);
return ProbeResult::kBitrateUpdated;
}
}
// Not probing and received non-probe packet, or finished with current set
// of probes.
if (clusters.size() >= kExpectedNumberOfProbes)
probes_.clear();
return ProbeResult::kNoUpdate;
}
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::IncomingPacket(
int64_t arrival_time_ms,
size_t payload_size,
const RTPHeader& header) {
RTC_DCHECK_RUNS_SERIALIZED(&network_race_);
if (!header.extension.hasAbsoluteSendTime) {
LOG(LS_WARNING) << "RemoteBitrateEstimatorAbsSendTimeImpl: Incoming packet "
"is missing absolute send time extension!";
return;
}
IncomingPacketInfo(arrival_time_ms, header.extension.absoluteSendTime,
payload_size, header.ssrc);
}
void RemoteBitrateEstimatorAbsSendTime::IncomingPacketInfo(
int64_t arrival_time_ms,
uint32_t send_time_24bits,
size_t payload_size,
uint32_t ssrc) {
RTC_CHECK(send_time_24bits < (1ul << 24));
if (!uma_recorded_) {
RTC_HISTOGRAM_ENUMERATION(kBweTypeHistogram, BweNames::kReceiverAbsSendTime,
BweNames::kBweNamesMax);
uma_recorded_ = true;
}
// 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;
int64_t now_ms = clock_->TimeInMilliseconds();
// TODO(holmer): SSRCs are only needed for REMB, should be broken out from
// here.
// Check if incoming bitrate estimate is valid, and if it needs to be reset.
rtc::Optional<uint32_t> incoming_bitrate =
incoming_bitrate_.Rate(arrival_time_ms);
if (incoming_bitrate) {
incoming_bitrate_initialized_ = true;
} else if (incoming_bitrate_initialized_) {
// Incoming bitrate had a previous valid value, but now not enough data
// point are left within the current window. Reset incoming bitrate
// estimator so that the window size will only contain new data points.
incoming_bitrate_.Reset();
incoming_bitrate_initialized_ = false;
}
incoming_bitrate_.Update(payload_size, arrival_time_ms);
if (first_packet_time_ms_ == -1)
first_packet_time_ms_ = now_ms;
uint32_t ts_delta = 0;
int64_t t_delta = 0;
int size_delta = 0;
bool update_estimate = false;
uint32_t target_bitrate_bps = 0;
std::vector<uint32_t> ssrcs;
{
rtc::CritScope lock(&crit_);
TimeoutStreams(now_ms);
RTC_DCHECK(inter_arrival_.get());
RTC_DCHECK(estimator_.get());
ssrcs_[ssrc] = now_ms;
// For now only try to detect probes while we don't have a valid estimate.
// We currently assume that only packets larger than 200 bytes are paced by
// the sender.
const size_t kMinProbePacketSize = 200;
if (payload_size > kMinProbePacketSize &&
(!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_;
// Make sure that a probe which updated the bitrate immediately has an
// effect by calling the OnReceiveBitrateChanged callback.
if (ProcessClusters(now_ms) == ProbeResult::kBitrateUpdated)
update_estimate = true;
}
if (inter_arrival_->ComputeDeltas(timestamp, arrival_time_ms, now_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(),
arrival_time_ms);
detector_.Detect(estimator_->offset(), ts_delta_ms,
estimator_->num_of_deltas(), arrival_time_ms);
}
if (!update_estimate) {
// Check if it's time for a periodic update or if we should update because
// of an over-use.
if (last_update_ms_ == -1 ||
now_ms - last_update_ms_ > remote_rate_.GetFeedbackInterval()) {
update_estimate = true;
} else if (detector_.State() == BandwidthUsage::kBwOverusing) {
rtc::Optional<uint32_t> incoming_rate =
incoming_bitrate_.Rate(arrival_time_ms);
if (incoming_rate &&
remote_rate_.TimeToReduceFurther(now_ms, *incoming_rate)) {
update_estimate = true;
}
}
}
if (update_estimate) {
// 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.
const RateControlInput input(detector_.State(),
incoming_bitrate_.Rate(arrival_time_ms),
estimator_->var_noise());
target_bitrate_bps = remote_rate_.Update(&input, now_ms);
update_estimate = remote_rate_.ValidEstimate();
ssrcs = Keys(ssrcs_);
}
}
if (update_estimate) {
last_update_ms_ = now_ms;
observer_->OnReceiveBitrateChanged(ssrcs, target_bitrate_bps);
}
}
void RemoteBitrateEstimatorAbsSendTime::Process() {}
int64_t RemoteBitrateEstimatorAbsSendTime::TimeUntilNextProcess() {
const int64_t kDisabledModuleTime = 1000;
return kDisabledModuleTime;
}
void RemoteBitrateEstimatorAbsSendTime::TimeoutStreams(int64_t now_ms) {
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(
new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000,
kTimestampToMs, true));
estimator_.reset(new OveruseEstimator(OverUseDetectorOptions()));
// 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.
}
}
void RemoteBitrateEstimatorAbsSendTime::OnRttUpdate(int64_t avg_rtt_ms,
int64_t max_rtt_ms) {
rtc::CritScope lock(&crit_);
remote_rate_.SetRtt(avg_rtt_ms);
}
void RemoteBitrateEstimatorAbsSendTime::RemoveStream(uint32_t ssrc) {
rtc::CritScope lock(&crit_);
ssrcs_.erase(ssrc);
}
bool RemoteBitrateEstimatorAbsSendTime::LatestEstimate(
std::vector<uint32_t>* ssrcs,
uint32_t* bitrate_bps) const {
// Currently accessed from both the process thread (see
// ModuleRtpRtcpImpl::Process()) and the configuration thread (see
// Call::GetStats()). Should in the future only be accessed from a single
// thread.
RTC_DCHECK(ssrcs);
RTC_DCHECK(bitrate_bps);
rtc::CritScope lock(&crit_);
if (!remote_rate_.ValidEstimate()) {
return false;
}
*ssrcs = Keys(ssrcs_);
if (ssrcs_.empty()) {
*bitrate_bps = 0;
} else {
*bitrate_bps = remote_rate_.LatestEstimate();
}
return true;
}
void RemoteBitrateEstimatorAbsSendTime::SetMinBitrate(int min_bitrate_bps) {
// Called from both the configuration thread and the network thread. Shouldn't
// be called from the network thread in the future.
rtc::CritScope lock(&crit_);
remote_rate_.SetMinBitrate(min_bitrate_bps);
}
} // namespace webrtc

View File

@ -0,0 +1,142 @@
/*
* 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 <memory>
#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/inter_arrival.h"
#include "webrtc/modules/remote_bitrate_estimator/overuse_detector.h"
#include "webrtc/modules/remote_bitrate_estimator/overuse_estimator.h"
#include "webrtc/rtc_base/checks.h"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/rtc_base/criticalsection.h"
#include "webrtc/rtc_base/race_checker.h"
#include "webrtc/rtc_base/rate_statistics.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,
const Clock* clock);
virtual ~RemoteBitrateEstimatorAbsSendTime() {}
void IncomingPacket(int64_t arrival_time_ms,
size_t payload_size,
const RTPHeader& header) 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.
void Process() override;
int64_t TimeUntilNextProcess() override;
void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override;
void RemoveStream(uint32_t ssrc) override;
bool LatestEstimate(std::vector<uint32_t>* ssrcs,
uint32_t* bitrate_bps) const override;
void SetMinBitrate(int min_bitrate_bps) override;
private:
typedef std::map<uint32_t, int64_t> Ssrcs;
enum class ProbeResult { kBitrateUpdated, kNoUpdate };
static bool IsWithinClusterBounds(int send_delta_ms,
const Cluster& cluster_aggregate);
static void AddCluster(std::list<Cluster>* clusters, Cluster* cluster);
void IncomingPacketInfo(int64_t arrival_time_ms,
uint32_t send_time_24bits,
size_t payload_size,
uint32_t ssrc);
void ComputeClusters(std::list<Cluster>* clusters) const;
std::list<Cluster>::const_iterator FindBestProbe(
const std::list<Cluster>& clusters) const;
// Returns true if a probe which changed the estimate was detected.
ProbeResult ProcessClusters(int64_t now_ms)
RTC_EXCLUSIVE_LOCKS_REQUIRED(&crit_);
bool IsBitrateImproving(int probe_bitrate_bps) const
RTC_EXCLUSIVE_LOCKS_REQUIRED(&crit_);
void TimeoutStreams(int64_t now_ms) RTC_EXCLUSIVE_LOCKS_REQUIRED(&crit_);
rtc::RaceChecker network_race_;
const Clock* const clock_;
RemoteBitrateObserver* const observer_;
std::unique_ptr<InterArrival> inter_arrival_;
std::unique_ptr<OveruseEstimator> estimator_;
OveruseDetector detector_;
RateStatistics incoming_bitrate_;
bool incoming_bitrate_initialized_;
std::vector<int> recent_propagation_delta_ms_;
std::vector<int64_t> recent_update_time_ms_;
std::list<Probe> probes_;
size_t total_probes_received_;
int64_t first_packet_time_ms_;
int64_t last_update_ms_;
bool uma_recorded_;
rtc::CriticalSection crit_;
Ssrcs ssrcs_ RTC_GUARDED_BY(&crit_);
AimdRateControl remote_rate_ RTC_GUARDED_BY(&crit_);
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RemoteBitrateEstimatorAbsSendTime);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_

View File

@ -0,0 +1,289 @@
/*
* 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 "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h"
#include "webrtc/rtc_base/constructormagic.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(674840);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, RateIncreaseReordering) {
RateIncreaseReorderingTestHelper(674840);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, RateIncreaseRtpTimestamps) {
RateIncreaseRtpTimestampsTestHelper(1237);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropOneStream) {
CapacityDropTestHelper(1, false, 633, 0);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropPosOffsetChange) {
CapacityDropTestHelper(1, false, 267, 30000);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropNegOffsetChange) {
CapacityDropTestHelper(1, false, 267, -30000);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropOneStreamWrap) {
CapacityDropTestHelper(1, true, 633, 0);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropTwoStreamsWrap) {
CapacityDropTestHelper(2, true, 700, 0);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropThreeStreamsWrap) {
CapacityDropTestHelper(3, true, 633, 0);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropThirteenStreamsWrap) {
CapacityDropTestHelper(13, true, 667, 0);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropNineteenStreamsWrap) {
CapacityDropTestHelper(19, true, 667, 0);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, CapacityDropThirtyStreamsWrap) {
CapacityDropTestHelper(30, true, 667, 0);
}
TEST_F(RemoteBitrateEstimatorAbsSendTimeTest, TestTimestampGrouping) {
TestTimestampGroupingTestHelper();
}
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);
clock_.AdvanceTimeMilliseconds(kStreamTimeOutMs + 1);
// Trigger timeout.
bitrate_estimator_->Process();
clock_.AdvanceTimeMilliseconds(kProcessIntervalMs);
// This shouldn't crash.
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));
}
// 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));
}
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));
// Non-paced packet, arriving 5 ms after.
clock_.AdvanceTimeMilliseconds(5);
IncomingPacket(0, 100, now_ms, 90 * now_ms, AbsSendTime(now_ms, 1000));
}
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));
}
// 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));
}
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));
}
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));
}
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));
}
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));
}
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));
}
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));
}
// Wait long enough so that we can call Process again.
clock_.AdvanceTimeMilliseconds(1000);
bitrate_estimator_->Process();
EXPECT_TRUE(bitrate_observer_->updated());
EXPECT_NEAR(bitrate_observer_->latest_bitrate(), 800000u, 10000);
}
} // namespace webrtc

View File

@ -0,0 +1,256 @@
/*
* 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 <utility>
#include "webrtc/modules/remote_bitrate_estimator/aimd_rate_control.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/rtc_base/logging.h"
#include "webrtc/system_wrappers/include/clock.h"
#include "webrtc/system_wrappers/include/metrics.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() {}
int64_t last_packet_time_ms;
InterArrival inter_arrival;
OveruseEstimator estimator;
OveruseDetector detector;
};
RemoteBitrateEstimatorSingleStream::RemoteBitrateEstimatorSingleStream(
RemoteBitrateObserver* observer,
const Clock* clock)
: clock_(clock),
incoming_bitrate_(kBitrateWindowMs, 8000),
last_valid_incoming_bitrate_(0),
remote_rate_(new AimdRateControl()),
observer_(observer),
last_process_time_(-1),
process_interval_ms_(kProcessIntervalMs),
uma_recorded_(false) {
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) {
if (!uma_recorded_) {
BweNames type = BweNames::kReceiverTOffset;
if (!header.extension.hasTransmissionTimeOffset)
type = BweNames::kReceiverNoExtension;
RTC_HISTOGRAM_ENUMERATION(kBweTypeHistogram, type, BweNames::kBweNamesMax);
uma_recorded_ = true;
}
uint32_t ssrc = header.ssrc;
uint32_t rtp_timestamp = header.timestamp +
header.extension.transmissionTimeOffset;
int64_t now_ms = clock_->TimeInMilliseconds();
rtc::CritScope cs(&crit_sect_);
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;
// Check if incoming bitrate estimate is valid, and if it needs to be reset.
rtc::Optional<uint32_t> incoming_bitrate = incoming_bitrate_.Rate(now_ms);
if (incoming_bitrate) {
last_valid_incoming_bitrate_ = *incoming_bitrate;
} else if (last_valid_incoming_bitrate_ > 0) {
// Incoming bitrate had a previous valid value, but now not enough data
// point are left within the current window. Reset incoming bitrate
// estimator so that the window size will only contain new data points.
incoming_bitrate_.Reset();
last_valid_incoming_bitrate_ = 0;
}
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, now_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(), now_ms);
estimator->detector.Detect(estimator->estimator.offset(),
timestamp_delta_ms,
estimator->estimator.num_of_deltas(), now_ms);
}
if (estimator->detector.State() == BandwidthUsage::kBwOverusing) {
rtc::Optional<uint32_t> incoming_bitrate_bps =
incoming_bitrate_.Rate(now_ms);
if (incoming_bitrate_bps &&
(prior_state != BandwidthUsage::kBwOverusing ||
GetRemoteRate()->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);
}
}
}
void RemoteBitrateEstimatorSingleStream::Process() {
{
rtc::CritScope cs(&crit_sect_);
UpdateEstimate(clock_->TimeInMilliseconds());
}
last_process_time_ = clock_->TimeInMilliseconds();
}
int64_t RemoteBitrateEstimatorSingleStream::TimeUntilNextProcess() {
if (last_process_time_ < 0) {
return 0;
}
rtc::CritScope cs_(&crit_sect_);
RTC_DCHECK_GT(process_interval_ms_, 0);
return last_process_time_ + process_interval_ms_ -
clock_->TimeInMilliseconds();
}
void RemoteBitrateEstimatorSingleStream::UpdateEstimate(int64_t now_ms) {
BandwidthUsage bw_state = BandwidthUsage::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()) {
return;
}
AimdRateControl* remote_rate = GetRemoteRate();
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);
uint32_t target_bitrate = remote_rate->Update(&input, now_ms);
if (remote_rate->ValidEstimate()) {
process_interval_ms_ = remote_rate->GetFeedbackInterval();
RTC_DCHECK_GT(process_interval_ms_, 0);
std::vector<uint32_t> ssrcs;
GetSsrcs(&ssrcs);
if (observer_)
observer_->OnReceiveBitrateChanged(ssrcs, target_bitrate);
}
}
void RemoteBitrateEstimatorSingleStream::OnRttUpdate(int64_t avg_rtt_ms,
int64_t max_rtt_ms) {
rtc::CritScope cs(&crit_sect_);
GetRemoteRate()->SetRtt(avg_rtt_ms);
}
void RemoteBitrateEstimatorSingleStream::RemoveStream(unsigned int ssrc) {
rtc::CritScope cs(&crit_sect_);
SsrcOveruseEstimatorMap::iterator it = overuse_detectors_.find(ssrc);
if (it != overuse_detectors_.end()) {
delete it->second;
overuse_detectors_.erase(it);
}
}
bool RemoteBitrateEstimatorSingleStream::LatestEstimate(
std::vector<uint32_t>* ssrcs,
uint32_t* bitrate_bps) const {
rtc::CritScope cs(&crit_sect_);
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;
}
void RemoteBitrateEstimatorSingleStream::GetSsrcs(
std::vector<uint32_t>* 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;
}
}
AimdRateControl* RemoteBitrateEstimatorSingleStream::GetRemoteRate() {
if (!remote_rate_)
remote_rate_.reset(new AimdRateControl());
return remote_rate_.get();
}
void RemoteBitrateEstimatorSingleStream::SetMinBitrate(int min_bitrate_bps) {
rtc::CritScope cs(&crit_sect_);
remote_rate_->SetMinBitrate(min_bitrate_bps);
}
} // namespace webrtc

View File

@ -0,0 +1,75 @@
/*
* 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 <memory>
#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/rtc_base/constructormagic.h"
#include "webrtc/rtc_base/criticalsection.h"
#include "webrtc/rtc_base/rate_statistics.h"
namespace webrtc {
class RemoteBitrateEstimatorSingleStream : public RemoteBitrateEstimator {
public:
RemoteBitrateEstimatorSingleStream(RemoteBitrateObserver* observer,
const Clock* clock);
virtual ~RemoteBitrateEstimatorSingleStream();
void IncomingPacket(int64_t arrival_time_ms,
size_t payload_size,
const RTPHeader& header) override;
void Process() override;
int64_t TimeUntilNextProcess() override;
void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override;
void RemoveStream(uint32_t ssrc) override;
bool LatestEstimate(std::vector<uint32_t>* ssrcs,
uint32_t* bitrate_bps) const override;
void SetMinBitrate(int min_bitrate_bps) override;
private:
struct Detector;
typedef std::map<uint32_t, Detector*> SsrcOveruseEstimatorMap;
// Triggers a new estimate calculation.
void UpdateEstimate(int64_t time_now)
RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_sect_);
void GetSsrcs(std::vector<uint32_t>* ssrcs) const
RTC_SHARED_LOCKS_REQUIRED(crit_sect_);
// Returns |remote_rate_| if the pointed to object exists,
// otherwise creates it.
AimdRateControl* GetRemoteRate() RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_sect_);
const Clock* const clock_;
SsrcOveruseEstimatorMap overuse_detectors_ RTC_GUARDED_BY(crit_sect_);
RateStatistics incoming_bitrate_ RTC_GUARDED_BY(crit_sect_);
uint32_t last_valid_incoming_bitrate_ RTC_GUARDED_BY(crit_sect_);
std::unique_ptr<AimdRateControl> remote_rate_ RTC_GUARDED_BY(crit_sect_);
RemoteBitrateObserver* const observer_ RTC_GUARDED_BY(crit_sect_);
rtc::CriticalSection crit_sect_;
int64_t last_process_time_;
int64_t process_interval_ms_ RTC_GUARDED_BY(crit_sect_);
bool uma_recorded_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RemoteBitrateEstimatorSingleStream);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_SINGLE_STREAM_H_

View File

@ -0,0 +1,72 @@
/*
* 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_single_stream.h"
#include "webrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_unittest_helper.h"
#include "webrtc/rtc_base/constructormagic.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(1267);
}
TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropOneStream) {
CapacityDropTestHelper(1, false, 633, 0);
}
TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropOneStreamWrap) {
CapacityDropTestHelper(1, true, 633, 0);
}
TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropTwoStreamsWrap) {
CapacityDropTestHelper(2, true, 767, 0);
}
TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropThreeStreamsWrap) {
CapacityDropTestHelper(3, true, 567, 0);
}
TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropThirteenStreamsWrap) {
CapacityDropTestHelper(13, true, 733, 0);
}
TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropNineteenStreamsWrap) {
CapacityDropTestHelper(19, true, 700, 0);
}
TEST_F(RemoteBitrateEstimatorSingleTest, CapacityDropThirtyStreamsWrap) {
CapacityDropTestHelper(30, true, 733, 0);
}
TEST_F(RemoteBitrateEstimatorSingleTest, TestTimestampGrouping) {
TestTimestampGroupingTestHelper();
}
} // namespace webrtc

View File

@ -0,0 +1,616 @@
/*
* 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 <limits>
#include <utility>
#include "webrtc/rtc_base/checks.h"
namespace webrtc {
const size_t kMtu = 1200;
const uint32_t kAcceptedBitrateErrorBps = 50000;
// Number of packets needed before we have a valid estimate.
const int kNumInitialPackets = 2;
namespace testing {
void TestBitrateObserver::OnReceiveBitrateChanged(
const std::vector<uint32_t>& ssrcs,
uint32_t bitrate) {
latest_bitrate_ = bitrate;
updated_ = true;
}
RtpStream::RtpStream(int fps,
int bitrate_bps,
uint32_t ssrc,
uint32_t 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.
int64_t 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_;
}
uint32_t RtpStream::ssrc() const {
return ssrc_;
}
bool RtpStream::Compare(const std::pair<uint32_t, RtpStream*>& left,
const std::pair<uint32_t, RtpStream*>& right) {
return left.second->next_rtp_time_ < right.second->next_rtp_time_;
}
StreamGenerator::StreamGenerator(int capacity, int64_t 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(uint32_t 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 std::max((*it).second->next_rtp_time(), time_now_us);
}
} // namespace testing
RemoteBitrateEstimatorTest::RemoteBitrateEstimatorTest()
: clock_(100000000),
bitrate_observer_(new testing::TestBitrateObserver),
stream_generator_(new testing::StreamGenerator(
1e6, // Capacity.
clock_.TimeInMicroseconds())),
arrival_time_offset_ms_(0) {}
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 uint32_t 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) {
RTPHeader header;
memset(&header, 0, sizeof(header));
header.ssrc = ssrc;
header.timestamp = rtp_timestamp;
header.extension.hasAbsoluteSendTime = true;
header.extension.absoluteSendTime = absolute_send_time;
RTC_CHECK_GE(arrival_time + arrival_time_offset_ms_, 0);
bitrate_estimator_->IncomingPacket(arrival_time + arrival_time_offset_ms_,
payload_size, header);
}
// 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(uint32_t ssrc,
uint32_t bitrate_bps) {
RTC_DCHECK_GT(bitrate_bps, 0);
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));
if (bitrate_observer_->updated()) {
if (bitrate_observer_->latest_bitrate() < bitrate_bps)
overuse = true;
}
delete packet;
packets.pop_front();
}
if (bitrate_estimator_->TimeUntilNextProcess() <= 0)
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.
uint32_t RemoteBitrateEstimatorTest::SteadyStateRun(uint32_t ssrc,
int max_number_of_frames,
uint32_t start_bitrate,
uint32_t min_bitrate,
uint32_t max_bitrate,
uint32_t target_bitrate) {
uint32_t 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(
uint32_t 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);
uint32_t bitrate_bps = 0;
uint32_t timestamp = 0;
uint32_t absolute_send_time = 0;
std::vector<uint32_t> 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 packets for 5 seconds to get a valid estimate.
for (int i = 0; i < 5 * kFramerate + 1 + kNumInitialPackets; ++i) {
if (i == kNumInitialPackets) {
bitrate_estimator_->Process();
EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps));
EXPECT_EQ(0u, ssrcs.size());
EXPECT_FALSE(bitrate_observer_->updated());
bitrate_observer_->Reset();
}
IncomingPacket(kDefaultSsrc, kMtu, clock_.TimeInMilliseconds(), timestamp,
absolute_send_time);
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;
// Inserting packets for five seconds to get a valid estimate.
for (int i = 0; i < 5 * kFramerate + 1 + kNumInitialPackets; ++i) {
// TODO(sprang): Remove this hack once the single stream estimator is gone,
// as it doesn't do anything in Process().
if (i == kNumInitialPackets) {
// Process after we have enough frames to get a valid input rate estimate.
bitrate_estimator_->Process();
EXPECT_FALSE(bitrate_observer_->updated()); // No valid estimate.
}
IncomingPacket(kDefaultSsrc, kMtu, clock_.TimeInMilliseconds(), timestamp,
absolute_send_time);
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);
IncomingPacket(
kDefaultSsrc, 1000, clock_.TimeInMilliseconds(),
timestamp - 90 * kFrameIntervalMs,
AddAbsSendTime(absolute_send_time,
-static_cast<int>(kFrameIntervalAbsSendTime)));
}
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.
uint32_t 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,
uint32_t expected_bitrate_drop_delta,
int64_t receiver_clock_offset_change_ms) {
const int kFramerate = 30;
const int kStartBitrate = 900e3;
const int kMinExpectedBitrate = 800e3;
const int kMaxExpectedBitrate = 1100e3;
const uint32_t kInitialCapacityBps = 1000e3;
const uint32_t 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;
}
uint32_t mask = ~0ull << (32 - i);
stream_generator_->AddStream(
new testing::RtpStream(kFramerate, // Frames per second.
bitrate, // Bitrate.
kDefaultSsrc + i, // SSRC.
90000, // RTP frequency.
0xFFFFF000u ^ mask, // 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);
uint32_t bitrate_bps = SteadyStateRun(
kDefaultSsrc, steady_state_time * kFramerate, kStartBitrate,
kMinExpectedBitrate, kMaxExpectedBitrate, kInitialCapacityBps);
EXPECT_NEAR(kInitialCapacityBps, bitrate_bps, 130000u);
bitrate_observer_->Reset();
// Add an offset to make sure the BWE can handle it.
arrival_time_offset_ms_ += receiver_clock_offset_change_ms;
// 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);
if (bitrate_drop_time == -1 &&
bitrate_observer_->latest_bitrate() <= kReducedCapacityBps) {
bitrate_drop_time = clock_.TimeInMilliseconds();
}
if (bitrate_observer_->updated())
bitrate_bps = bitrate_observer_->latest_bitrate();
}
EXPECT_NEAR(expected_bitrate_drop_delta,
bitrate_drop_time - overuse_start_time, 33);
// Remove stream one by one.
uint32_t latest_bps = 0;
std::vector<uint32_t> 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), -static_cast<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);
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);
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,
-static_cast<int>(kTimestampGroupLengthAbsSendTime)));
bitrate_estimator_->Process();
}
EXPECT_TRUE(bitrate_observer_->updated());
// Should have reduced the estimate.
EXPECT_LT(bitrate_observer_->latest_bitrate(), 400000u);
}
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);
timestamp += kFrameIntervalMs;
clock_.AdvanceTimeMilliseconds(kFrameIntervalMs);
absolute_send_time = AddAbsSendTime(absolute_send_time,
kFrameIntervalAbsSendTime);
bitrate_estimator_->Process();
}
uint32_t bitrate_before = 0;
std::vector<uint32_t> 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 < 21; ++i) {
IncomingPacket(kDefaultSsrc, 1000, clock_.TimeInMilliseconds(), timestamp,
absolute_send_time);
timestamp += kFrameIntervalMs;
clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs);
absolute_send_time = AddAbsSendTime(absolute_send_time,
kFrameIntervalAbsSendTime);
bitrate_estimator_->Process();
}
uint32_t bitrate_after = 0;
bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_after);
EXPECT_LT(bitrate_after, bitrate_before);
}
} // namespace webrtc

View File

@ -0,0 +1,221 @@
/*
* 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 <memory>
#include <utility>
#include <vector>
#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/system_wrappers/include/clock.h"
#include "webrtc/test/gtest.h"
namespace webrtc {
namespace testing {
class TestBitrateObserver : public RemoteBitrateObserver {
public:
TestBitrateObserver() : updated_(false), latest_bitrate_(0) {}
virtual ~TestBitrateObserver() {}
void OnReceiveBitrateChanged(const std::vector<uint32_t>& ssrcs,
uint32_t bitrate) override;
void Reset() { updated_ = false; }
bool updated() const { return updated_; }
uint32_t latest_bitrate() const { return latest_bitrate_; }
private:
bool updated_;
uint32_t latest_bitrate_;
};
class RtpStream {
public:
struct RtpPacket {
int64_t send_time;
int64_t arrival_time;
uint32_t rtp_timestamp;
size_t size;
uint32_t ssrc;
};
struct RtcpPacket {
uint32_t ntp_secs;
uint32_t ntp_frac;
uint32_t timestamp;
uint32_t ssrc;
};
typedef std::list<RtpPacket*> PacketList;
enum { kSendSideOffsetUs = 1000000 };
RtpStream(int fps,
int bitrate_bps,
uint32_t ssrc,
uint32_t 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.
int64_t 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;
uint32_t ssrc() const;
static bool Compare(const std::pair<uint32_t, RtpStream*>& left,
const std::pair<uint32_t, RtpStream*>& right);
private:
enum { kRtcpIntervalUs = 1000000 };
int fps_;
int bitrate_bps_;
uint32_t ssrc_;
uint32_t 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, int64_t 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(uint32_t 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<uint32_t, 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);
// 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(uint32_t ssrc, uint32_t 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.
uint32_t SteadyStateRun(uint32_t ssrc,
int number_of_frames,
uint32_t start_bitrate,
uint32_t min_bitrate,
uint32_t max_bitrate,
uint32_t target_bitrate);
void TestTimestampGroupingTestHelper();
void TestWrappingHelper(int silence_time_s);
void InitialBehaviorTestHelper(uint32_t expected_converge_bitrate);
void RateIncreaseReorderingTestHelper(uint32_t expected_bitrate);
void RateIncreaseRtpTimestampsTestHelper(int expected_iterations);
void CapacityDropTestHelper(int number_of_streams,
bool wrap_time_stamp,
uint32_t expected_bitrate_drop_delta,
int64_t receiver_clock_offset_change_ms);
static const uint32_t kDefaultSsrc;
SimulatedClock clock_; // Time at the receiver.
std::unique_ptr<testing::TestBitrateObserver> bitrate_observer_;
std::unique_ptr<RemoteBitrateEstimator> bitrate_estimator_;
std::unique_ptr<testing::StreamGenerator> stream_generator_;
int64_t arrival_time_offset_ms_;
RTC_DISALLOW_COPY_AND_ASSIGN(RemoteBitrateEstimatorTest);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_UNITTEST_HELPER_H_

View File

@ -0,0 +1,227 @@
/*
* 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 <algorithm>
#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/rtc_base/constructormagic.h"
#include "webrtc/rtc_base/random.h"
#include "webrtc/test/testsupport/fileutils.h"
namespace webrtc {
namespace testing {
namespace bwe {
// This test fixture is used to instantiate tests running with adaptive video
// senders.
class BweFeedbackTest
: public BweTest,
public ::testing::TestWithParam<BandwidthEstimatorType> {
public:
#ifdef WEBRTC_WIN
BweFeedbackTest()
: BweTest(), random_(Clock::GetRealTimeClock()->TimeInMicroseconds()) {}
#else
BweFeedbackTest()
: BweTest(),
// Multiply the time by a random-ish odd number derived from the PID.
random_((getpid() | 1) *
Clock::GetRealTimeClock()->TimeInMicroseconds()) {}
#endif
virtual ~BweFeedbackTest() {}
protected:
Random random_;
private:
RTC_DISALLOW_COPY_AND_ASSIGN(BweFeedbackTest);
};
INSTANTIATE_TEST_CASE_P(VideoSendersTest,
BweFeedbackTest,
::testing::Values(kRembEstimator, kSendSideEstimator));
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 + random_.Rand(-1000, 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 + random_.Rand(-1000, 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 + random_.Rand(-1000, 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 + random_.Rand(-1000, 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 + random_.Rand(-1000, 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 + random_.Rand(-1000, 1000));
}
RunFairnessTest(GetParam(), 1, 1, 300, 2000, 1000, kRttMs, kMaxJitterMs,
offset_ms);
}
} // namespace bwe
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,201 @@
/*
* 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 <limits>
#include <algorithm>
#include "webrtc/modules/pacing/packet_router.h"
#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp.h"
#include "webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
#include "webrtc/rtc_base/checks.h"
#include "webrtc/rtc_base/logging.h"
#include "webrtc/rtc_base/safe_minmax.h"
#include "webrtc/system_wrappers/include/clock.h"
namespace webrtc {
// TODO(sprang): Tune these!
const int RemoteEstimatorProxy::kBackWindowMs = 500;
const int RemoteEstimatorProxy::kMinSendIntervalMs = 50;
const int RemoteEstimatorProxy::kMaxSendIntervalMs = 250;
const int RemoteEstimatorProxy::kDefaultSendIntervalMs = 100;
// The maximum allowed value for a timestamp in milliseconds. This is lower
// than the numerical limit since we often convert to microseconds.
static constexpr int64_t kMaxTimeMs =
std::numeric_limits<int64_t>::max() / 1000;
RemoteEstimatorProxy::RemoteEstimatorProxy(const 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),
send_interval_ms_(kDefaultSendIntervalMs) {}
RemoteEstimatorProxy::~RemoteEstimatorProxy() {}
void RemoteEstimatorProxy::IncomingPacket(int64_t arrival_time_ms,
size_t payload_size,
const RTPHeader& header) {
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);
}
bool RemoteEstimatorProxy::LatestEstimate(std::vector<unsigned int>* ssrcs,
unsigned int* bitrate_bps) const {
return false;
}
int64_t RemoteEstimatorProxy::TimeUntilNextProcess() {
int64_t time_until_next = 0;
if (last_process_time_ms_ != -1) {
rtc::CritScope cs(&lock_);
int64_t now = clock_->TimeInMilliseconds();
if (now - last_process_time_ms_ < send_interval_ms_)
time_until_next = (last_process_time_ms_ + send_interval_ms_ - now);
}
return time_until_next;
}
void RemoteEstimatorProxy::Process() {
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_->SendTransportFeedback(&feedback_packet);
} else {
more_to_build = false;
}
}
}
void RemoteEstimatorProxy::OnBitrateChanged(int bitrate_bps) {
// TwccReportSize = Ipv4(20B) + UDP(8B) + SRTP(10B) +
// AverageTwccReport(30B)
// TwccReport size at 50ms interval is 24 byte.
// TwccReport size at 250ms interval is 36 byte.
// AverageTwccReport = (TwccReport(50ms) + TwccReport(250ms)) / 2
constexpr int kTwccReportSize = 20 + 8 + 10 + 30;
constexpr double kMinTwccRate =
kTwccReportSize * 8.0 * 1000.0 / kMaxSendIntervalMs;
constexpr double kMaxTwccRate =
kTwccReportSize * 8.0 * 1000.0 / kMinSendIntervalMs;
// Let TWCC reports occupy 5% of total bandwidth.
rtc::CritScope cs(&lock_);
send_interval_ms_ = static_cast<int>(
0.5 + kTwccReportSize * 8.0 * 1000.0 /
rtc::SafeClamp(0.05 * bitrate_bps, kMinTwccRate, kMaxTwccRate));
}
void RemoteEstimatorProxy::OnPacketArrival(uint16_t sequence_number,
int64_t arrival_time) {
if (arrival_time < 0 || arrival_time > kMaxTimeMs) {
LOG(LS_WARNING) << "Arrival time out of bounds: " << arrival_time;
return;
}
// TODO(holmer): We should handle a backwards wrap here if the first
// sequence number was small and the new sequence number is large. The
// SequenceNumberUnwrapper doesn't do this, so we should replace this with
// calls to IsNewerSequenceNumber instead.
int64_t seq = unwrapper_.Unwrap(sequence_number);
if (seq > window_start_seq_ + 0xFFFF / 2) {
LOG(LS_WARNING) << "Skipping this sequence number (" << sequence_number
<< ") since it likely is reordered, but the unwrapper"
"failed to handle it. Feedback window starts at "
<< window_start_seq_ << ".";
return;
}
if (packet_arrival_times_.lower_bound(window_start_seq_) ==
packet_arrival_times_.end()) {
// 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);
}
}
if (window_start_seq_ == -1) {
window_start_seq_ = sequence_number;
} else if (seq < window_start_seq_) {
window_start_seq_ = seq;
}
// We are only interested in the first time a packet is received.
if (packet_arrival_times_.find(seq) != packet_arrival_times_.end())
return;
packet_arrival_times_[seq] = arrival_time;
}
bool RemoteEstimatorProxy::BuildFeedbackPacket(
rtcp::TransportFeedback* feedback_packet) {
// 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.
rtc::CritScope cs(&lock_);
auto it = packet_arrival_times_.lower_bound(window_start_seq_);
if (it == packet_arrival_times_.end()) {
// Feedback for all packets already sent.
return false;
}
// TODO(sprang): Measure receive times in microseconds and remove the
// conversions below.
const int64_t first_sequence = it->first;
feedback_packet->SetMediaSsrc(media_ssrc_);
// Base sequence is the expected next (window_start_seq_). This is known, but
// we might not have actually received it, so the base time shall be the time
// of the first received packet in the feedback.
feedback_packet->SetBase(static_cast<uint16_t>(window_start_seq_ & 0xFFFF),
it->second * 1000);
feedback_packet->SetFeedbackSequenceNumber(feedback_sequence_++);
for (; it != packet_arrival_times_.end(); ++it) {
if (!feedback_packet->AddReceivedPacket(
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(first_sequence, it->first);
// Could not add timestamp, feedback packet might be full. Return and
// try again with a fresh packet.
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.
window_start_seq_ = it->first + 1;
}
return true;
}
} // namespace webrtc

View File

@ -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/modules/include/module_common_types.h"
#include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
#include "webrtc/rtc_base/criticalsection.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(const Clock* clock, PacketRouter* packet_router);
virtual ~RemoteEstimatorProxy();
void IncomingPacket(int64_t arrival_time_ms,
size_t payload_size,
const RTPHeader& header) override;
void RemoveStream(uint32_t ssrc) override {}
bool LatestEstimate(std::vector<unsigned int>* ssrcs,
unsigned int* bitrate_bps) 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;
void Process() override;
void OnBitrateChanged(int bitrate);
static const int kMinSendIntervalMs;
static const int kMaxSendIntervalMs;
static const int kDefaultSendIntervalMs;
static const int kBackWindowMs;
private:
void OnPacketArrival(uint16_t sequence_number, int64_t arrival_time)
RTC_EXCLUSIVE_LOCKS_REQUIRED(&lock_);
bool BuildFeedbackPacket(rtcp::TransportFeedback* feedback_packet);
const Clock* const clock_;
PacketRouter* const packet_router_;
int64_t last_process_time_ms_;
rtc::CriticalSection lock_;
uint32_t media_ssrc_ RTC_GUARDED_BY(&lock_);
uint8_t feedback_sequence_ RTC_GUARDED_BY(&lock_);
SequenceNumberUnwrapper unwrapper_ RTC_GUARDED_BY(&lock_);
int64_t window_start_seq_ RTC_GUARDED_BY(&lock_);
// Map unwrapped seq -> time.
std::map<int64_t, int64_t> packet_arrival_times_ RTC_GUARDED_BY(&lock_);
int64_t send_interval_ms_ RTC_GUARDED_BY(&lock_);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_ESTIMATOR_PROXY_H_

View File

@ -0,0 +1,338 @@
/*
* 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/pacing/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"
#include "webrtc/test/gmock.h"
#include "webrtc/test/gtest.h"
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Invoke;
using ::testing::Return;
namespace webrtc {
namespace {
constexpr size_t kDefaultPacketSize = 100;
constexpr uint32_t kMediaSsrc = 456;
constexpr uint16_t kBaseSeq = 10;
constexpr int64_t kBaseTimeMs = 123;
constexpr int64_t kMaxSmallDeltaMs =
(rtcp::TransportFeedback::kDeltaScaleFactor * 0xFF) / 1000;
std::vector<uint16_t> SequenceNumbers(
const rtcp::TransportFeedback& feedback_packet) {
std::vector<uint16_t> sequence_numbers;
for (const auto& rtp_packet_received : feedback_packet.GetReceivedPackets()) {
sequence_numbers.push_back(rtp_packet_received.sequence_number());
}
return sequence_numbers;
}
std::vector<int64_t> TimestampsMs(
const rtcp::TransportFeedback& feedback_packet) {
std::vector<int64_t> timestamps;
int64_t timestamp_us = feedback_packet.GetBaseTimeUs();
for (const auto& rtp_packet_received : feedback_packet.GetReceivedPackets()) {
timestamp_us += rtp_packet_received.delta_us();
timestamps.push_back(timestamp_us / 1000);
}
return timestamps;
}
class MockPacketRouter : public PacketRouter {
public:
MOCK_METHOD1(SendTransportFeedback,
bool(rtcp::TransportFeedback* feedback_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);
}
void Process() {
clock_.AdvanceTimeMilliseconds(
RemoteEstimatorProxy::kDefaultSendIntervalMs);
proxy_.Process();
}
SimulatedClock clock_;
testing::StrictMock<MockPacketRouter> router_;
RemoteEstimatorProxy proxy_;
};
TEST_F(RemoteEstimatorProxyTest, SendsSinglePacketFeedback) {
IncomingPacket(kBaseSeq, kBaseTimeMs);
EXPECT_CALL(router_, SendTransportFeedback(_))
.WillOnce(Invoke([](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq, feedback_packet->GetBaseSequence());
EXPECT_EQ(kMediaSsrc, feedback_packet->media_ssrc());
EXPECT_THAT(SequenceNumbers(*feedback_packet), ElementsAre(kBaseSeq));
EXPECT_THAT(TimestampsMs(*feedback_packet), ElementsAre(kBaseTimeMs));
return true;
}));
Process();
}
TEST_F(RemoteEstimatorProxyTest, DuplicatedPackets) {
IncomingPacket(kBaseSeq, kBaseTimeMs);
IncomingPacket(kBaseSeq, kBaseTimeMs + 1000);
EXPECT_CALL(router_, SendTransportFeedback(_))
.WillOnce(Invoke([](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq, feedback_packet->GetBaseSequence());
EXPECT_EQ(kMediaSsrc, feedback_packet->media_ssrc());
EXPECT_THAT(SequenceNumbers(*feedback_packet), ElementsAre(kBaseSeq));
EXPECT_THAT(TimestampsMs(*feedback_packet), ElementsAre(kBaseTimeMs));
return true;
}));
Process();
}
TEST_F(RemoteEstimatorProxyTest, FeedbackWithMissingStart) {
// First feedback.
IncomingPacket(kBaseSeq, kBaseTimeMs);
IncomingPacket(kBaseSeq + 1, kBaseTimeMs + 1000);
EXPECT_CALL(router_, SendTransportFeedback(_)).WillOnce(Return(true));
Process();
// Second feedback starts with a missing packet (DROP kBaseSeq + 2).
IncomingPacket(kBaseSeq + 3, kBaseTimeMs + 3000);
EXPECT_CALL(router_, SendTransportFeedback(_))
.WillOnce(Invoke([](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq + 2, feedback_packet->GetBaseSequence());
EXPECT_EQ(kMediaSsrc, feedback_packet->media_ssrc());
EXPECT_THAT(SequenceNumbers(*feedback_packet),
ElementsAre(kBaseSeq + 3));
EXPECT_THAT(TimestampsMs(*feedback_packet),
ElementsAre(kBaseTimeMs + 3000));
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_, SendTransportFeedback(_))
.WillOnce(Invoke([](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq, feedback_packet->GetBaseSequence());
EXPECT_EQ(kMediaSsrc, feedback_packet->media_ssrc());
EXPECT_THAT(SequenceNumbers(*feedback_packet),
ElementsAre(kBaseSeq, kBaseSeq + 1, kBaseSeq + 2));
EXPECT_THAT(TimestampsMs(*feedback_packet),
ElementsAre(kBaseTimeMs, kBaseTimeMs + kMaxSmallDeltaMs,
kBaseTimeMs + (2 * kMaxSmallDeltaMs) + 1));
return true;
}));
Process();
}
TEST_F(RemoteEstimatorProxyTest, SendsFragmentedFeedback) {
static constexpr int64_t kTooLargeDelta =
rtcp::TransportFeedback::kDeltaScaleFactor * (1 << 16);
IncomingPacket(kBaseSeq, kBaseTimeMs);
IncomingPacket(kBaseSeq + 1, kBaseTimeMs + kTooLargeDelta);
EXPECT_CALL(router_, SendTransportFeedback(_))
.WillOnce(Invoke([](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq, feedback_packet->GetBaseSequence());
EXPECT_EQ(kMediaSsrc, feedback_packet->media_ssrc());
EXPECT_THAT(SequenceNumbers(*feedback_packet), ElementsAre(kBaseSeq));
EXPECT_THAT(TimestampsMs(*feedback_packet), ElementsAre(kBaseTimeMs));
return true;
}))
.WillOnce(Invoke([](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq + 1, feedback_packet->GetBaseSequence());
EXPECT_EQ(kMediaSsrc, feedback_packet->media_ssrc());
EXPECT_THAT(SequenceNumbers(*feedback_packet),
ElementsAre(kBaseSeq + 1));
EXPECT_THAT(TimestampsMs(*feedback_packet),
ElementsAre(kBaseTimeMs + kTooLargeDelta));
return true;
}));
Process();
}
TEST_F(RemoteEstimatorProxyTest, GracefullyHandlesReorderingAndWrap) {
const int64_t kDeltaMs = 1000;
const uint16_t kLargeSeq = 62762;
IncomingPacket(kBaseSeq, kBaseTimeMs);
IncomingPacket(kLargeSeq, kBaseTimeMs + kDeltaMs);
EXPECT_CALL(router_, SendTransportFeedback(_))
.WillOnce(Invoke([](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq, feedback_packet->GetBaseSequence());
EXPECT_EQ(kMediaSsrc, feedback_packet->media_ssrc());
EXPECT_THAT(TimestampsMs(*feedback_packet), ElementsAre(kBaseTimeMs));
return true;
}));
Process();
}
TEST_F(RemoteEstimatorProxyTest, ResendsTimestampsOnReordering) {
IncomingPacket(kBaseSeq, kBaseTimeMs);
IncomingPacket(kBaseSeq + 2, kBaseTimeMs + 2);
EXPECT_CALL(router_, SendTransportFeedback(_))
.WillOnce(Invoke([](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq, feedback_packet->GetBaseSequence());
EXPECT_EQ(kMediaSsrc, feedback_packet->media_ssrc());
EXPECT_THAT(SequenceNumbers(*feedback_packet),
ElementsAre(kBaseSeq, kBaseSeq + 2));
EXPECT_THAT(TimestampsMs(*feedback_packet),
ElementsAre(kBaseTimeMs, kBaseTimeMs + 2));
return true;
}));
Process();
IncomingPacket(kBaseSeq + 1, kBaseTimeMs + 1);
EXPECT_CALL(router_, SendTransportFeedback(_))
.WillOnce(Invoke([](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq + 1, feedback_packet->GetBaseSequence());
EXPECT_EQ(kMediaSsrc, feedback_packet->media_ssrc());
EXPECT_THAT(SequenceNumbers(*feedback_packet),
ElementsAre(kBaseSeq + 1, kBaseSeq + 2));
EXPECT_THAT(TimestampsMs(*feedback_packet),
ElementsAre(kBaseTimeMs + 1, kBaseTimeMs + 2));
return true;
}));
Process();
}
TEST_F(RemoteEstimatorProxyTest, RemovesTimestampsOutOfScope) {
const int64_t kTimeoutTimeMs =
kBaseTimeMs + RemoteEstimatorProxy::kBackWindowMs;
IncomingPacket(kBaseSeq + 2, kBaseTimeMs);
EXPECT_CALL(router_, SendTransportFeedback(_))
.WillOnce(Invoke([](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq + 2, feedback_packet->GetBaseSequence());
EXPECT_THAT(TimestampsMs(*feedback_packet), ElementsAre(kBaseTimeMs));
return true;
}));
Process();
IncomingPacket(kBaseSeq + 3, kTimeoutTimeMs); // kBaseSeq + 2 times out here.
EXPECT_CALL(router_, SendTransportFeedback(_))
.WillOnce(
Invoke([kTimeoutTimeMs](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq + 3, feedback_packet->GetBaseSequence());
EXPECT_THAT(TimestampsMs(*feedback_packet),
ElementsAre(kTimeoutTimeMs));
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_, SendTransportFeedback(_))
.WillOnce(
Invoke([kTimeoutTimeMs](rtcp::TransportFeedback* feedback_packet) {
EXPECT_EQ(kBaseSeq, feedback_packet->GetBaseSequence());
EXPECT_THAT(SequenceNumbers(*feedback_packet),
ElementsAre(kBaseSeq, kBaseSeq + 1, kBaseSeq + 3));
EXPECT_THAT(TimestampsMs(*feedback_packet),
ElementsAre(kBaseTimeMs - 1, kTimeoutTimeMs - 1,
kTimeoutTimeMs));
return true;
}));
Process();
}
TEST_F(RemoteEstimatorProxyTest, TimeUntilNextProcessIsZeroBeforeFirstProcess) {
EXPECT_EQ(0, proxy_.TimeUntilNextProcess());
}
TEST_F(RemoteEstimatorProxyTest, TimeUntilNextProcessIsDefaultOnUnkownBitrate) {
Process();
EXPECT_EQ(RemoteEstimatorProxy::kDefaultSendIntervalMs,
proxy_.TimeUntilNextProcess());
}
TEST_F(RemoteEstimatorProxyTest, TimeUntilNextProcessIsMinIntervalOn300kbps) {
Process();
proxy_.OnBitrateChanged(300000);
EXPECT_EQ(RemoteEstimatorProxy::kMinSendIntervalMs,
proxy_.TimeUntilNextProcess());
}
TEST_F(RemoteEstimatorProxyTest, TimeUntilNextProcessIsMaxIntervalOn0kbps) {
Process();
// TimeUntilNextProcess should be limited by |kMaxSendIntervalMs| when
// bitrate is small. We choose 0 bps as a special case, which also tests
// erroneous behaviors like division-by-zero.
proxy_.OnBitrateChanged(0);
EXPECT_EQ(RemoteEstimatorProxy::kMaxSendIntervalMs,
proxy_.TimeUntilNextProcess());
}
TEST_F(RemoteEstimatorProxyTest, TimeUntilNextProcessIsMaxIntervalOn20kbps) {
Process();
proxy_.OnBitrateChanged(20000);
EXPECT_EQ(RemoteEstimatorProxy::kMaxSendIntervalMs,
proxy_.TimeUntilNextProcess());
}
TEST_F(RemoteEstimatorProxyTest, TwccReportsUse5PercentOfAvailableBandwidth) {
Process();
proxy_.OnBitrateChanged(80000);
// 80kbps * 0.05 = TwccReportSize(68B * 8b/B) * 1000ms / SendInterval(136ms)
EXPECT_EQ(136, proxy_.TimeUntilNextProcess());
}
} // namespace
} // namespace webrtc

View File

@ -0,0 +1,89 @@
/*
* 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/include/send_time_history.h"
#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "webrtc/rtc_base/checks.h"
#include "webrtc/system_wrappers/include/clock.h"
namespace webrtc {
SendTimeHistory::SendTimeHistory(const Clock* clock,
int64_t packet_age_limit_ms)
: clock_(clock), packet_age_limit_ms_(packet_age_limit_ms) {}
SendTimeHistory::~SendTimeHistory() {}
void SendTimeHistory::AddAndRemoveOld(const PacketFeedback& packet) {
int64_t now_ms = clock_->TimeInMilliseconds();
// Remove old.
while (!history_.empty() &&
now_ms - history_.begin()->second.creation_time_ms >
packet_age_limit_ms_) {
// TODO(sprang): Warn if erasing (too many) old items?
history_.erase(history_.begin());
}
// Add new.
int64_t unwrapped_seq_num = seq_num_unwrapper_.Unwrap(packet.sequence_number);
history_.insert(std::make_pair(unwrapped_seq_num, packet));
}
bool SendTimeHistory::OnSentPacket(uint16_t sequence_number,
int64_t send_time_ms) {
int64_t unwrapped_seq_num = seq_num_unwrapper_.Unwrap(sequence_number);
auto it = history_.find(unwrapped_seq_num);
if (it == history_.end())
return false;
it->second.send_time_ms = send_time_ms;
return true;
}
bool SendTimeHistory::GetFeedback(PacketFeedback* packet_feedback,
bool remove) {
RTC_DCHECK(packet_feedback);
int64_t unwrapped_seq_num =
seq_num_unwrapper_.Unwrap(packet_feedback->sequence_number);
latest_acked_seq_num_.emplace(
std::max(unwrapped_seq_num, latest_acked_seq_num_.value_or(0)));
RTC_DCHECK_GE(*latest_acked_seq_num_, 0);
auto it = history_.find(unwrapped_seq_num);
if (it == history_.end())
return false;
// Save arrival_time not to overwrite it.
int64_t arrival_time_ms = packet_feedback->arrival_time_ms;
*packet_feedback = it->second;
packet_feedback->arrival_time_ms = arrival_time_ms;
if (remove)
history_.erase(it);
return true;
}
size_t SendTimeHistory::GetOutstandingBytes(uint16_t local_net_id,
uint16_t remote_net_id) const {
size_t outstanding_bytes = 0;
auto unacked_it = history_.begin();
if (latest_acked_seq_num_) {
unacked_it = history_.lower_bound(*latest_acked_seq_num_);
}
for (; unacked_it != history_.end(); ++unacked_it) {
if (unacked_it->second.local_net_id == local_net_id &&
unacked_it->second.remote_net_id == remote_net_id &&
unacked_it->second.send_time_ms >= 0) {
outstanding_bytes += unacked_it->second.payload_size;
}
}
return outstanding_bytes;
}
} // namespace webrtc

View File

@ -0,0 +1,235 @@
/*
* 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 "webrtc/modules/remote_bitrate_estimator/include/send_time_history.h"
#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "webrtc/system_wrappers/include/clock.h"
#include "webrtc/test/gtest.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,
int64_t send_time_ms,
const PacedPacketInfo& pacing_info) {
PacketFeedback packet(clock_.TimeInMilliseconds(), sequence_number, length,
0, 0, pacing_info);
history_.AddAndRemoveOld(packet);
history_.OnSentPacket(sequence_number, send_time_ms);
}
webrtc::SimulatedClock clock_;
SendTimeHistory history_;
};
TEST_F(SendTimeHistoryTest, SaveAndRestoreNetworkId) {
const PacedPacketInfo kPacingInfo(0, 5, 1200);
uint16_t sequence_number = 0;
int64_t now_ms = clock_.TimeInMilliseconds();
for (int i = 1; i < 5; ++i) {
PacketFeedback packet(now_ms, sequence_number, 1000, i, i - 1,
kPacingInfo);
history_.AddAndRemoveOld(packet);
history_.OnSentPacket(sequence_number, now_ms);
PacketFeedback restored(now_ms, sequence_number);
EXPECT_TRUE(history_.GetFeedback(&restored, sequence_number++));
EXPECT_EQ(packet.local_net_id, restored.local_net_id);
EXPECT_EQ(packet.remote_net_id, restored.remote_net_id);
}
}
TEST_F(SendTimeHistoryTest, AddRemoveOne) {
const uint16_t kSeqNo = 10;
// TODO(philipel): Fix PacedPacketInfo constructor?
const PacedPacketInfo kPacingInfo(0, 5, 1200);
const PacketFeedback kSentPacket(0, 1, kSeqNo, 1, kPacingInfo);
AddPacketWithSendTime(kSeqNo, 1, 1, kPacingInfo);
PacketFeedback received_packet(0, 0, kSeqNo, 0, kPacingInfo);
EXPECT_TRUE(history_.GetFeedback(&received_packet, false));
EXPECT_EQ(kSentPacket, received_packet);
PacketFeedback received_packet2(0, 0, kSeqNo, 0, kPacingInfo);
EXPECT_TRUE(history_.GetFeedback(&received_packet2, true));
EXPECT_EQ(kSentPacket, received_packet2);
PacketFeedback received_packet3(0, 0, kSeqNo, 0, kPacingInfo);
EXPECT_FALSE(history_.GetFeedback(&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 PacedPacketInfo kPacingInfo(3, 10, 1212);
AddPacketWithSendTime(kSeqNo, kPayloadSize, kSendTime, kPacingInfo);
PacketFeedback packet_feedback(kReceiveTime, kSeqNo);
EXPECT_TRUE(history_.GetFeedback(&packet_feedback, true));
EXPECT_EQ(kReceiveTime, packet_feedback.arrival_time_ms);
EXPECT_EQ(kSendTime, packet_feedback.send_time_ms);
EXPECT_EQ(kSeqNo, packet_feedback.sequence_number);
EXPECT_EQ(kPayloadSize, packet_feedback.payload_size);
EXPECT_EQ(kPacingInfo, packet_feedback.pacing_info);
}
TEST_F(SendTimeHistoryTest, AddThenRemoveOutOfOrder) {
std::vector<PacketFeedback> sent_packets;
std::vector<PacketFeedback> received_packets;
const size_t num_items = 100;
const size_t kPacketSize = 400;
const size_t kTransmissionTime = 1234;
const PacedPacketInfo kPacingInfo(1, 2, 200);
for (size_t i = 0; i < num_items; ++i) {
sent_packets.push_back(PacketFeedback(0, static_cast<int64_t>(i),
static_cast<uint16_t>(i), kPacketSize,
kPacingInfo));
received_packets.push_back(PacketFeedback(
static_cast<int64_t>(i) + kTransmissionTime, 0,
static_cast<uint16_t>(i), kPacketSize, PacedPacketInfo()));
}
for (size_t i = 0; i < num_items; ++i) {
PacketFeedback packet = sent_packets[i];
packet.arrival_time_ms = -1;
packet.send_time_ms = -1;
history_.AddAndRemoveOld(packet);
}
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) {
PacketFeedback packet = received_packets[i];
EXPECT_TRUE(history_.GetFeedback(&packet, false));
PacketFeedback sent_packet = sent_packets[packet.sequence_number];
sent_packet.arrival_time_ms = packet.arrival_time_ms;
EXPECT_EQ(sent_packet, packet);
EXPECT_TRUE(history_.GetFeedback(&packet, true));
}
for (PacketFeedback packet : sent_packets)
EXPECT_FALSE(history_.GetFeedback(&packet, false));
}
TEST_F(SendTimeHistoryTest, HistorySize) {
const int kItems = kDefaultHistoryLengthMs / 100;
for (int i = 0; i < kItems; ++i) {
clock_.AdvanceTimeMilliseconds(100);
AddPacketWithSendTime(i, 0, i * 100, PacedPacketInfo());
}
for (int i = 0; i < kItems; ++i) {
PacketFeedback packet(0, 0, static_cast<uint16_t>(i), 0, PacedPacketInfo());
EXPECT_TRUE(history_.GetFeedback(&packet, false));
EXPECT_EQ(i * 100, packet.send_time_ms);
}
clock_.AdvanceTimeMilliseconds(101);
AddPacketWithSendTime(kItems, 0, kItems * 101, PacedPacketInfo());
PacketFeedback packet(0, 0, 0, 0, PacedPacketInfo());
EXPECT_FALSE(history_.GetFeedback(&packet, false));
for (int i = 1; i < (kItems + 1); ++i) {
PacketFeedback packet2(0, 0, static_cast<uint16_t>(i), 0,
PacedPacketInfo());
EXPECT_TRUE(history_.GetFeedback(&packet2, false));
int64_t expected_time_ms = (i == kItems) ? i * 101 : i * 100;
EXPECT_EQ(expected_time_ms, packet2.send_time_ms);
}
}
TEST_F(SendTimeHistoryTest, HistorySizeWithWraparound) {
const uint16_t kMaxSeqNo = std::numeric_limits<uint16_t>::max();
AddPacketWithSendTime(kMaxSeqNo - 2, 0, 0, PacedPacketInfo());
clock_.AdvanceTimeMilliseconds(100);
AddPacketWithSendTime(kMaxSeqNo - 1, 1, 100, PacedPacketInfo());
clock_.AdvanceTimeMilliseconds(100);
AddPacketWithSendTime(kMaxSeqNo, 0, 200, PacedPacketInfo());
clock_.AdvanceTimeMilliseconds(kDefaultHistoryLengthMs - 200 + 1);
AddPacketWithSendTime(0, 0, kDefaultHistoryLengthMs, PacedPacketInfo());
PacketFeedback packet(0, static_cast<uint16_t>(kMaxSeqNo - 2));
EXPECT_FALSE(history_.GetFeedback(&packet, false));
PacketFeedback packet2(0, static_cast<uint16_t>(kMaxSeqNo - 1));
EXPECT_TRUE(history_.GetFeedback(&packet2, false));
PacketFeedback packet3(0, static_cast<uint16_t>(kMaxSeqNo));
EXPECT_TRUE(history_.GetFeedback(&packet3, false));
PacketFeedback packet4(0, 0);
EXPECT_TRUE(history_.GetFeedback(&packet4, false));
// Create a gap (kMaxSeqNo - 1) -> 0.
PacketFeedback packet5(0, kMaxSeqNo);
EXPECT_TRUE(history_.GetFeedback(&packet5, true));
clock_.AdvanceTimeMilliseconds(100);
AddPacketWithSendTime(1, 0, 1100, PacedPacketInfo());
PacketFeedback packet6(0, static_cast<uint16_t>(kMaxSeqNo - 2));
EXPECT_FALSE(history_.GetFeedback(&packet6, false));
PacketFeedback packet7(0, static_cast<uint16_t>(kMaxSeqNo - 1));
EXPECT_FALSE(history_.GetFeedback(&packet7, false));
PacketFeedback packet8(0, kMaxSeqNo);
EXPECT_FALSE(history_.GetFeedback(&packet8, false));
PacketFeedback packet9(0, 0);
EXPECT_TRUE(history_.GetFeedback(&packet9, false));
PacketFeedback packet10(0, 1);
EXPECT_TRUE(history_.GetFeedback(&packet10, false));
}
TEST_F(SendTimeHistoryTest, InterlievedGetAndRemove) {
const uint16_t kSeqNo = 1;
const int64_t kTimestamp = 2;
const PacedPacketInfo kPacingInfo1(1, 1, 100);
const PacedPacketInfo kPacingInfo2(2, 2, 200);
const PacedPacketInfo kPacingInfo3(3, 3, 300);
PacketFeedback packets[3] = {
{0, kTimestamp, kSeqNo, 0, kPacingInfo1},
{0, kTimestamp + 1, kSeqNo + 1, 0, kPacingInfo2},
{0, kTimestamp + 2, kSeqNo + 2, 0, kPacingInfo3}};
AddPacketWithSendTime(packets[0].sequence_number, packets[0].payload_size,
packets[0].send_time_ms, packets[0].pacing_info);
AddPacketWithSendTime(packets[1].sequence_number, packets[1].payload_size,
packets[1].send_time_ms, packets[1].pacing_info);
PacketFeedback packet(0, 0, packets[0].sequence_number, 0, PacedPacketInfo());
EXPECT_TRUE(history_.GetFeedback(&packet, true));
EXPECT_EQ(packets[0], packet);
AddPacketWithSendTime(packets[2].sequence_number, packets[2].payload_size,
packets[2].send_time_ms, packets[2].pacing_info);
PacketFeedback packet2(0, 0, packets[1].sequence_number, 0, kPacingInfo1);
EXPECT_TRUE(history_.GetFeedback(&packet2, true));
EXPECT_EQ(packets[1], packet2);
PacketFeedback packet3(0, 0, packets[2].sequence_number, 0, kPacingInfo2);
EXPECT_TRUE(history_.GetFeedback(&packet3, true));
EXPECT_EQ(packets[2], packet3);
}
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) 2017 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/bbr_paced_sender.h"
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include "webrtc/modules/pacing/paced_sender.h"
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/congestion_window.h"
#include "webrtc/system_wrappers/include/clock.h"
namespace webrtc {
BbrPacedSender::BbrPacedSender(const Clock* clock,
PacedSender::PacketSender* packet_sender,
RtcEventLog* event_log)
: clock_(clock),
packet_sender_(packet_sender),
estimated_bitrate_bps_(100000),
min_send_bitrate_kbps_(0),
pacing_bitrate_kbps_(0),
time_last_update_us_(clock->TimeInMicroseconds()),
time_last_update_ms_(clock->TimeInMilliseconds()),
next_packet_send_time_(clock_->TimeInMilliseconds()),
rounding_error_time_ms_(0.0f),
packets_(),
max_data_inflight_bytes_(10000),
congestion_window_(new testing::bwe::CongestionWindow()) {}
BbrPacedSender::~BbrPacedSender() {}
void BbrPacedSender::SetEstimatedBitrateAndCongestionWindow(
uint32_t bitrate_bps,
bool in_probe_rtt,
uint64_t congestion_window) {
estimated_bitrate_bps_ = bitrate_bps;
max_data_inflight_bytes_ = congestion_window;
}
void BbrPacedSender::SetMinBitrate(int min_send_bitrate_bps) {
min_send_bitrate_kbps_ = min_send_bitrate_bps / 1000;
pacing_bitrate_kbps_ =
std::max(min_send_bitrate_kbps_, estimated_bitrate_bps_ / 1000);
}
void BbrPacedSender::InsertPacket(RtpPacketSender::Priority priority,
uint32_t ssrc,
uint16_t sequence_number,
int64_t capture_time_ms,
size_t bytes,
bool retransmission) {
int64_t now_ms = clock_->TimeInMilliseconds();
if (capture_time_ms < 0)
capture_time_ms = now_ms;
packets_.push_back(new Packet(priority, ssrc, sequence_number,
capture_time_ms, now_ms, bytes,
retransmission));
}
int64_t BbrPacedSender::TimeUntilNextProcess() {
// Once errors absolute value hits 1 millisecond, add compensating term to
// the |next_packet_send_time_|, so that we can send packet earlier or later,
// depending on the error.
rounding_error_time_ms_ = std::min(rounding_error_time_ms_, 1.0f);
if (rounding_error_time_ms_ < -0.9f)
rounding_error_time_ms_ = -1.0f;
int64_t result =
std::max<int64_t>(next_packet_send_time_ + time_last_update_ms_ -
clock_->TimeInMilliseconds(),
0);
if (rounding_error_time_ms_ == 1.0f || rounding_error_time_ms_ == -1.0f) {
next_packet_send_time_ -= rounding_error_time_ms_;
result = std::max<int64_t>(next_packet_send_time_ + time_last_update_ms_ -
clock_->TimeInMilliseconds(),
0);
rounding_error_time_ms_ = 0;
}
return result;
}
void BbrPacedSender::OnBytesAcked(size_t bytes) {
congestion_window_->AckReceived(bytes);
}
void BbrPacedSender::Process() {
pacing_bitrate_kbps_ =
std::max(min_send_bitrate_kbps_, estimated_bitrate_bps_ / 1000);
// If we have nothing to send, try sending again in 1 millisecond.
if (packets_.empty()) {
next_packet_send_time_ = 1;
return;
}
// If congestion window doesn't allow sending, try again in 1 millisecond.
if (packets_.front()->size_in_bytes + congestion_window_->data_inflight() >
max_data_inflight_bytes_) {
next_packet_send_time_ = 1;
return;
}
bool sent = TryToSendPacket(packets_.front());
if (sent) {
congestion_window_->PacketSent(packets_.front()->size_in_bytes);
delete packets_.front();
packets_.pop_front();
time_last_update_ms_ = clock_->TimeInMilliseconds();
if (!packets_.empty()) {
// Calculate in what time we should send current packet.
next_packet_send_time_ = (packets_.front()->size_in_bytes * 8000 +
estimated_bitrate_bps_ / 2) /
estimated_bitrate_bps_;
// As rounding errors may happen, |rounding_error_time_ms_| could be
// positive or negative depending on packet was sent earlier or later,
// after it hits certain threshold we will send a packet earlier or later
// depending on error we had so far.
rounding_error_time_ms_ +=
(next_packet_send_time_ - packets_.front()->size_in_bytes * 8000.0f /
estimated_bitrate_bps_ * 1.0f);
} else {
// If sending was unsuccessful try again in 1 millisecond.
next_packet_send_time_ = 1;
}
}
}
bool BbrPacedSender::TryToSendPacket(Packet* packet) {
PacedPacketInfo pacing_info;
return packet_sender_->TimeToSendPacket(packet->ssrc, packet->sequence_number,
packet->capture_time_ms,
packet->retransmission, pacing_info);
}
} // namespace webrtc

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) 2017 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_BBR_PACED_SENDER_H_
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BBR_PACED_SENDER_H_
#include <list>
#include <memory>
#include "webrtc/modules/pacing/paced_sender.h"
#include "webrtc/modules/pacing/pacer.h"
namespace webrtc {
namespace testing {
namespace bwe {
class CongestionWindow;
}
} // namespace testing
struct Packet {
Packet(RtpPacketSender::Priority priority,
uint32_t ssrc,
uint16_t seq_number,
int64_t capture_time_ms,
int64_t enqueue_time_ms,
size_t size_in_bytes,
bool retransmission)
: priority(priority),
ssrc(ssrc),
sequence_number(seq_number),
capture_time_ms(capture_time_ms),
enqueue_time_ms(enqueue_time_ms),
size_in_bytes(size_in_bytes),
retransmission(retransmission) {}
RtpPacketSender::Priority priority;
uint32_t ssrc;
uint16_t sequence_number;
int64_t capture_time_ms;
int64_t enqueue_time_ms;
size_t size_in_bytes;
bool retransmission;
};
class Clock;
class RtcEventLog;
struct Packet;
class BbrPacedSender : public Pacer {
public:
BbrPacedSender(const Clock* clock,
PacedSender::PacketSender* packet_sender,
RtcEventLog* event_log);
~BbrPacedSender() override;
void SetEstimatedBitrateAndCongestionWindow(
uint32_t bitrate_bps,
bool in_probe_rtt,
uint64_t congestion_window) override;
void SetMinBitrate(int min_send_bitrate_bps);
void InsertPacket(RtpPacketSender::Priority priority,
uint32_t ssrc,
uint16_t sequence_number,
int64_t capture_time_ms,
size_t bytes,
bool retransmission) override;
int64_t TimeUntilNextProcess() override;
void OnBytesAcked(size_t bytes) override;
void Process() override;
bool TryToSendPacket(Packet* packet);
private:
const Clock* const clock_;
PacedSender::PacketSender* const packet_sender_;
uint32_t estimated_bitrate_bps_;
uint32_t min_send_bitrate_kbps_;
uint32_t pacing_bitrate_kbps_;
int64_t time_last_update_us_;
int64_t time_last_update_ms_;
int64_t next_packet_send_time_;
float rounding_error_time_ms_;
std::list<Packet*> packets_;
// TODO(gnish): integrate |max_data_inflight| into congestion window class.
size_t max_data_inflight_bytes_;
std::unique_ptr<testing::bwe::CongestionWindow> congestion_window_;
};
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BBR_PACED_SENDER_H_

View File

@ -0,0 +1,285 @@
/*
* 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/modules/remote_bitrate_estimator/test/estimators/bbr.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"
#include "webrtc/rtc_base/constructormagic.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();
}
void Process() override {}
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 kSendSideEstimator:
return new SendSideBweSender(kbps, observer, clock);
case kNadaEstimator:
return new NadaBweSender(kbps, observer, clock);
case kBbrEstimator:
return new BbrBweSender(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 kSendSideEstimator:
return new SendSideBweReceiver(flow_id);
case kNadaEstimator:
return new NadaBweReceiver(flow_id);
case kBbrEstimator:
return new BbrBweReceiver(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

View File

@ -0,0 +1,199 @@
/*
* 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 <list>
#include <map>
#include <sstream>
#include <string>
#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
#include "webrtc/modules/remote_bitrate_estimator/test/packet.h"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/rtc_base/gtest_prod_util.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 = 10;
const int kMaxBitrateKbps = 25000;
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,
kSendSideEstimator,
kTcpEstimator,
kBbrEstimator
};
const char* const bwe_names[] = {"Null", "NADA", "REMB", "GCC", "TCP", "BBR"};
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_

View File

@ -0,0 +1,996 @@
/*
* 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 <memory>
#include <sstream>
#include "webrtc/modules/include/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/rtc_base/arraysize.h"
#include "webrtc/system_wrappers/include/clock.h"
#include "webrtc/system_wrappers/include/field_trial.h"
#include "webrtc/test/testsupport/perf_test.h"
using std::vector;
namespace {
const int kQuickTestTimeoutMs = 500;
}
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();
std::string test_name =
std::string(test_info->test_case_name()) + "_" +
std::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 (std::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.
// For quick perf tests ignore passed timeout
if (field_trial::IsEnabled("WebRTC-QuickPerfTest")) {
time_ms = kQuickTestTimeoutMs;
}
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_);
}
}
std::string BweTest::GetTestName() const {
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
return std::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);
if (!field_trial::IsEnabled("WebRTC-QuickPerfTest")) {
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;
std::unique_ptr<AdaptiveVideoSource> sources[kNumFlows];
std::unique_ptr<VideoSender> senders[kNumFlows];
std::unique_ptr<MetricRecorder> metric_recorders[kNumFlows];
std::unique_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 = arraysize(kAllFlowIds);
std::unique_ptr<AdaptiveVideoSource> sources[kNumFlows];
std::unique_ptr<VideoSender> senders[kNumFlows];
std::unique_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));
std::unique_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().
std::unique_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_);
}
std::unique_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 = std::string() +
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() == arraysize(kAllTcpFlowIds));
const size_t kNumRmcatFlows = arraysize(kAllRmcatFlowIds);
const size_t kNumTotalFlows = kNumRmcatFlows + arraysize(kAllTcpFlowIds);
std::unique_ptr<AdaptiveVideoSource> sources[kNumRmcatFlows];
std::unique_ptr<PacketSender> senders[kNumTotalFlows];
std::unique_ptr<MetricRecorder> metric_recorders[kNumTotalFlows];
std::unique_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 = arraysize(kAllFlowIds);
std::unique_ptr<AdaptiveVideoSource> sources[kNumFlows];
std::unique_ptr<VideoSender> senders[kNumFlows];
std::unique_ptr<MetricRecorder> metric_recorders[kNumFlows];
std::unique_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;
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;
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

View File

@ -0,0 +1,196 @@
/*
* 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 "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"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/test/gtest.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_

View File

@ -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 <memory>
#include <vector>
#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_fileutils.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
#include "webrtc/rtc_base/constructormagic.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) {
std::unique_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:
std::unique_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;
}
std::unique_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:
std::unique_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;
std::unique_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

View File

@ -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/include/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_

View File

@ -0,0 +1,96 @@
/*
* 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/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

View File

@ -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/modules/include/module_common_types.h"
#include "webrtc/rtc_base/constructormagic.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_

View File

@ -0,0 +1,829 @@
/*
* 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>
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/rtc_base/safe_minmax.h"
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) {}
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) {}
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;
}
BbrBweFeedback::BbrBweFeedback(
int flow_id,
int64_t send_time_us,
int64_t latest_send_time_ms,
const std::vector<uint16_t>& packet_feedback_vector)
: FeedbackPacket(flow_id, send_time_us, latest_send_time_ms),
packet_feedback_vector_(packet_feedback_vector) {}
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<PacketFeedback>& 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& algorithm_name)
: PacketProcessor(listener, flow_id, kRegular),
packets_per_second_stats_(),
kbps_stats_(),
start_plotting_time_ms_(0),
flow_id_(flow_id),
name_(name),
algorithm_name_(algorithm_name) {
// Only used when compiling with BWE test logging enabled.
RTC_UNUSED(flow_id_);
}
RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
const FlowIds& flow_ids,
const char* name,
const std::string& algorithm_name)
: PacketProcessor(listener, flow_ids, kRegular),
packets_per_second_stats_(),
kbps_stats_(),
start_plotting_time_ms_(0),
name_(name),
algorithm_name_(algorithm_name) {
// TODO(terelius): Appending the flow IDs to the algorithm name is a hack to
// keep the current plot functionality without having to print the full
// context for each PLOT line. It is unclear whether multiple flow IDs are
// needed at all in the long term.
std::stringstream ss;
ss << algorithm_name_;
for (int flow_id : flow_ids) {
ss << ',' << flow_id;
}
algorithm_name_ = ss.str();
}
RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
const FlowIds& flow_ids,
const char* name,
int64_t start_plotting_time_ms,
const std::string& algorithm_name)
: RateCounterFilter(listener, flow_ids, name, algorithm_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) {
// TODO(stefan): Reorganize logging configuration to reduce amount
// of preprocessor conditionals in the code.
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 (algorithm_name_.empty()) {
BWE_TEST_LOGGING_PLOT_WITH_SSRC(0, "Throughput_kbps#1", timestamp_ms,
plot_kbps, flow_id_);
} else {
BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(0, "Throughput_kbps#1",
timestamp_ms, plot_kbps, flow_id_,
algorithm_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<float>() < 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(Random* const random,
int64_t mean,
int64_t std_dev) {
const int64_t gaussian_random = random->Gaussian(mean, std_dev);
return rtc::SafeClamp(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<float>() < 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_;
BWE_TEST_LOGGING_PLOT(0, "MaxThroughput_", new_send_time_us / 1000,
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),
random_(0x12345678),
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 * (random_.Rand<float>() - 0.5);
// 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_ = bitrate_bps;
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

View File

@ -0,0 +1,473 @@
/*
* 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 <memory>
#include <numeric>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
#include "webrtc/modules/include/module_common_types.h"
#include "webrtc/modules/pacing/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/include/rtp_rtcp_defines.h"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/rtc_base/random.h"
#include "webrtc/system_wrappers/include/clock.h"
#include "webrtc/typedefs.h"
namespace webrtc {
class RtcpBandwidthObserver;
namespace testing {
namespace bwe {
class DelayCapHelper;
class RateCounter {
public:
explicit 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& algorithm_name);
RateCounterFilter(PacketProcessorListener* listener,
const FlowIds& flow_ids,
const char* name,
const std::string& algorithm_name);
RateCounterFilter(PacketProcessorListener* listener,
const FlowIds& flow_ids,
const char* name,
int64_t start_plotting_time_ms,
const std::string& algorithm_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_;
int64_t start_plotting_time_ms_;
int flow_id_ = 0;
std::string name_;
// Algorithm name if single flow, Total link utilization if all flows.
std::string algorithm_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:
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:
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:
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_;
std::unique_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_;
std::unique_ptr<RateCounter> rate_counter_;
std::string name_;
std::unique_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:
Random random_;
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_

View File

@ -0,0 +1,975 @@
/*
* 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 "webrtc/modules/remote_bitrate_estimator/test/packet.h"
#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/test/gtest.h"
#include "webrtc/test/testsupport/fileutils.h"
namespace webrtc {
namespace testing {
namespace bwe {
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) {
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;
// The expected position for sequence number s is in position s-1.
if (sequence_number < last_sequence_number) {
distance += last_sequence_number - sequence_number;
}
last_sequence_number = sequence_number;
}
// The probability that two elements are swapped is p = reorder_percent / 100.
double p = static_cast<double>(reorder_percent) / 100;
// The expected number of swaps we perform is p * (PacketCount - 1),
// and each swap increases the distance by one.
double mean = p * (kPacketCount - 1);
// If pair i is chosen to be swapped with probability p, the variance for that
// pair is p * (1 - p). Since there are (kPacketCount - 1) independent pairs,
// the variance for the number of swaps is (kPacketCount - 1) * p * (1 - p).
double std_deviation = sqrt((kPacketCount - 1) * p * (1 - p));
EXPECT_NEAR(mean, distance, 3 * std_deviation);
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);
}
TEST(BweTestFramework_ReorderFilterTest, Reorder10) {
TestReorderFilter(10);
}
TEST(BweTestFramework_ReorderFilterTest, Reorder20) {
TestReorderFilter(20);
}
TEST(BweTestFramework_ReorderFilterTest, Reorder50) {
TestReorderFilter(50);
}
TEST(BweTestFramework_ReorderFilterTest, Reorder70) {
TestReorderFilter(70);
}
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);
}
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);
}
} // namespace bwe
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,259 @@
/*
* 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/rtc_base/checks.h"
#include "webrtc/rtc_base/format_macros.h"
#include "webrtc/rtc_base/platform_thread.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) {
rtc::CritScope cs(&crit_sect_);
thread_map_[rtc::CurrentThreadId()].global_state.tag = ToString(name);
}
void Logging::SetGlobalContext(const std::string& name) {
rtc::CritScope cs(&crit_sect_);
thread_map_[rtc::CurrentThreadId()].global_state.tag = name;
}
void Logging::SetGlobalContext(const char* name) {
rtc::CritScope cs(&crit_sect_);
thread_map_[rtc::CurrentThreadId()].global_state.tag = name;
}
void Logging::SetGlobalEnable(bool enabled) {
rtc::CritScope cs(&crit_sect_);
thread_map_[rtc::CurrentThreadId()].global_state.enabled = enabled;
}
void Logging::Log(const char format[], ...) {
rtc::CritScope cs(&crit_sect_);
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
RTC_DCHECK(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, const std::string& name, double value) {
Plot(figure, name, value, 0, "-");
}
void Logging::Plot(int figure,
const std::string& name,
double value,
uint32_t ssrc) {
Plot(figure, name, value, ssrc, "-");
}
void Logging::Plot(int figure,
const std::string& name,
double value,
const std::string& alg_name) {
Plot(figure, name, value, 0, alg_name);
}
void Logging::Plot(int figure,
const std::string& name,
double value,
uint32_t ssrc,
const std::string& alg_name) {
rtc::CritScope cs(&crit_sect_);
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
RTC_DCHECK(it != thread_map_.end());
const State& state = it->second.stack.top();
if (state.enabled) {
printf("PLOT\t%d\t%s:%" PRIu32 "@%s\t%f\t%f\n", figure, name.c_str(), ssrc,
alg_name.c_str(), state.timestamp_ms * 0.001, value);
}
}
void Logging::PlotBar(int figure,
const std::string& name,
double value,
int flow_id) {
rtc::CritScope cs(&crit_sect_);
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
RTC_DCHECK(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) {
rtc::CritScope cs(&crit_sect_);
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
RTC_DCHECK(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) {
rtc::CritScope cs(&crit_sect_);
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
RTC_DCHECK(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) {
rtc::CritScope cs(&crit_sect_);
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
RTC_DCHECK(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) {
rtc::CritScope cs(&crit_sect_);
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
RTC_DCHECK(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()
: 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) {
rtc::CritScope cs(&crit_sect_);
State new_state(append_to_tag, timestamp_ms, enabled);
ThreadState* thread_state = &thread_map_[rtc::CurrentThreadId()];
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() {
rtc::CritScope cs(&crit_sect_);
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
RTC_DCHECK(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

View File

@ -0,0 +1,351 @@
/*
* 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.
// |ssrc| identifies the source of a stream
// |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)
#define BWE_TEST_LOGGING_PLOT_WITH_SSRC(figure, name, time, value, ssrc)
#define BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(figure, name, time, value, \
ssrc, 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 <memory>
#include <stack>
#include <string>
#include "webrtc/common_types.h"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/rtc_base/criticalsection.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, name, 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, name, value, \
alg_name); \
} while (0)
#define BWE_TEST_LOGGING_PLOT_WITH_SSRC(figure, name, time, value, ssrc) \
do { \
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
static_cast<int64_t>(time), true); \
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value, \
ssrc); \
} while (0)
#define BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(figure, name, time, value, \
ssrc, 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, name, value, \
ssrc, 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 {
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, const std::string& name, double value);
void Plot(int figure,
const std::string& name,
double value,
const std::string& alg_name);
void Plot(int figure, const std::string& name, double value, uint32_t ssrc);
void Plot(int figure,
const std::string& name,
double value,
uint32_t ssrc,
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::CriticalSection 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_

View File

@ -0,0 +1,394 @@
/*
* 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 "webrtc/rtc_base/arraysize.h"
#include "webrtc/test/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 = arraysize(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

View File

@ -0,0 +1,553 @@
/*
* Copyright (c) 2017 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/bbr.h"
#include <stdlib.h>
#include <algorithm>
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/congestion_window.h"
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/max_bandwidth_filter.h"
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/min_rtt_filter.h"
namespace webrtc {
namespace testing {
namespace bwe {
namespace {
const int kFeedbackIntervalsMs = 5;
// BBR uses this value to double sending rate each round trip. Design document
// suggests using this value.
const float kHighGain = 2.885f;
// BBR uses this value to drain queues created during STARTUP in one round trip
// time.
const float kDrainGain = 1 / kHighGain;
// kStartupGrowthTarget and kMaxRoundsWithoutGrowth are chosen from
// experiments, according to the design document.
const float kStartupGrowthTarget = 1.25f;
const int kMaxRoundsWithoutGrowth = 3;
// Pacing gain values for Probe Bandwidth mode.
const float kPacingGain[] = {1.1f, 0.9f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
const size_t kGainCycleLength = sizeof(kPacingGain) / sizeof(kPacingGain[0]);
// Least number of rounds PROBE_RTT should last.
const int kProbeRttDurationRounds = 1;
// The least amount of milliseconds PROBE_RTT mode should last.
const int kProbeRttDurationMs = 200;
// Gain value for congestion window for assuming that network has no queues.
const float kTargetCongestionWindowGain = 1;
// Gain value for congestion window in PROBE_BW mode. In theory it should be
// equal to 1, but in practice because of delayed acks and the way networks
// work, it is nice to have some extra room in congestion window for full link
// utilization. Value chosen by observations on different tests.
const float kCruisingCongestionWindowGain = 2;
// Pacing gain specific for Recovery mode. Chosen by experiments in simulation
// tool.
const float kRecoveryPacingGain = 0.5f;
// Congestion window gain specific for Recovery mode. Chosen by experiments in
// simulation tool.
const float kRecoveryCongestionWindowGain = 1.5f;
// Number of rounds over which average RTT is stored for Recovery mode.
const size_t kPastRttsFilterSize = 1;
// Threshold to assume average RTT has increased for a round. Chosen by
// experiments in simulation tool.
const float kRttIncreaseThreshold = 3;
// Threshold to assume average RTT has decreased for a round. Chosen by
// experiments in simulation tool.
const float kRttDecreaseThreshold = 1.5f;
// If |kCongestionWindowThreshold| of the congestion window is filled up, tell
// encoder to stop, to avoid building sender side queues.
const float kCongestionWindowThreshold = 0.69f;
// Duration we send at |kDefaultRatebps| in order to ensure BBR has data to work
// with.
const int64_t kDefaultDurationMs = 200;
const int64_t kDefaultRatebps = 300000;
// Congestion window gain for PROBE_RTT mode.
const float kProbeRttCongestionWindowGain = 0.65f;
// We need to be sure that data inflight has increased by at least
// |kTargetCongestionWindowGainForHighGain| compared to the congestion window in
// PROBE_BW's high gain phase, to make ramp-up quicker. As high gain value has
// been decreased from 1.25 to 1.1 we need to make
// |kTargetCongestionWindowGainForHighGain| slightly higher than the actual high
// gain value.
const float kTargetCongestionWindowGainForHighGain = 1.15f;
// Encoder rate gain value for PROBE_RTT mode.
const float kEncoderRateGainForProbeRtt = 0.1f;
} // namespace
BbrBweSender::BbrBweSender(BitrateObserver* observer, Clock* clock)
: BweSender(0),
observer_(observer),
clock_(clock),
mode_(STARTUP),
max_bandwidth_filter_(new MaxBandwidthFilter()),
min_rtt_filter_(new MinRttFilter()),
congestion_window_(new CongestionWindow()),
rand_(new Random(time(NULL))),
round_count_(0),
round_trip_end_(0),
full_bandwidth_reached_(false),
cycle_start_time_ms_(0),
cycle_index_(0),
bytes_acked_(0),
probe_rtt_start_time_ms_(0),
minimum_congestion_window_start_time_ms_(0),
minimum_congestion_window_start_round_(0),
bytes_sent_(0),
last_packet_sent_sequence_number_(0),
last_packet_acked_sequence_number_(0),
last_packet_ack_time_(0),
last_packet_send_time_(0),
pacing_rate_bps_(0),
last_packet_send_time_during_high_gain_ms_(-1),
data_sent_before_high_gain_started_bytes_(-1),
data_sent_before_high_gain_ended_bytes_(-1),
first_packet_ack_time_during_high_gain_ms_(-1),
last_packet_ack_time_during_high_gain_ms_(-1),
data_acked_before_high_gain_started_bytes_(-1),
data_acked_before_high_gain_ended_bytes_(-1),
first_packet_seq_num_during_high_gain_(0),
last_packet_seq_num_during_high_gain_(0),
high_gain_over_(false),
packet_stats_(),
past_rtts_() {
// Initially enter Startup mode.
EnterStartup();
}
BbrBweSender::~BbrBweSender() {}
int BbrBweSender::GetFeedbackIntervalMs() const {
return kFeedbackIntervalsMs;
}
void BbrBweSender::CalculatePacingRate() {
pacing_rate_bps_ =
max_bandwidth_filter_->max_bandwidth_estimate_bps() * pacing_gain_;
}
// Declare lost packets as acked.
void BbrBweSender::HandleLoss(uint64_t last_acked_packet,
uint64_t recently_acked_packet) {
for (uint16_t i = last_acked_packet + 1;
AheadOrAt<uint16_t>(recently_acked_packet - 1, i); i++) {
congestion_window_->AckReceived(packet_stats_[i].payload_size_bytes);
observer_->OnBytesAcked(packet_stats_[i].payload_size_bytes);
}
}
void BbrBweSender::AddToPastRtts(int64_t rtt_sample_ms) {
uint64_t last_round = 0;
if (!past_rtts_.empty())
last_round = past_rtts_.back().round;
// Try to add the sample to the last round.
if (last_round == round_count_ && !past_rtts_.empty()) {
past_rtts_.back().sum_of_rtts_ms += rtt_sample_ms;
past_rtts_.back().num_samples++;
} else {
// If the sample belongs to a new round, keep number of rounds in the window
// equal to |kPastRttsFilterSize|.
if (past_rtts_.size() == kPastRttsFilterSize)
past_rtts_.pop_front();
past_rtts_.push_back(
BbrBweSender::AverageRtt(rtt_sample_ms, 1, round_count_));
}
}
void BbrBweSender::GiveFeedback(const FeedbackPacket& feedback) {
int64_t now_ms = clock_->TimeInMilliseconds();
last_packet_ack_time_ = now_ms;
const BbrBweFeedback& fb = static_cast<const BbrBweFeedback&>(feedback);
// feedback_vector holds values of acknowledged packets' sequence numbers.
const std::vector<uint16_t>& feedback_vector = fb.packet_feedback_vector();
// Go through all the packets acked, update variables/containers accordingly.
for (uint16_t sequence_number : feedback_vector) {
// Completing packet information with a recently received ack.
PacketStats* packet = &packet_stats_[sequence_number];
bytes_acked_ += packet->payload_size_bytes;
packet->data_sent_bytes = bytes_sent_;
packet->last_sent_packet_send_time_ms = last_packet_send_time_;
packet->data_acked_bytes = bytes_acked_;
packet->ack_time_ms = now_ms;
// Logic specific to applying "bucket" to high gain, in order to have
// quicker ramp-up. We check if we started receiving acks for the packets
// sent during high gain phase.
if (packet->sequence_number == first_packet_seq_num_during_high_gain_) {
first_packet_ack_time_during_high_gain_ms_ = now_ms;
// Substracting half of the packet's size to avoid overestimation.
data_acked_before_high_gain_started_bytes_ =
bytes_acked_ - packet->payload_size_bytes / 2;
}
// If the last packet of high gain phase has been acked, high gain phase is
// over.
if (packet->sequence_number == last_packet_seq_num_during_high_gain_) {
last_packet_ack_time_during_high_gain_ms_ = now_ms;
data_acked_before_high_gain_ended_bytes_ =
bytes_acked_ - packet->payload_size_bytes / 2;
high_gain_over_ = true;
}
observer_->OnBytesAcked(packet->payload_size_bytes);
congestion_window_->AckReceived(packet->payload_size_bytes);
HandleLoss(last_packet_acked_sequence_number_, packet->sequence_number);
last_packet_acked_sequence_number_ = packet->sequence_number;
// Logic for wrapping sequence numbers. If round started with packet number
// x, it can never end on y, if x > y. That could happen when sequence
// numbers are wrapped after some point.
if (packet->sequence_number == 0)
round_trip_end_ = 0;
}
// Check if new round started for the connection.
bool new_round_started = false;
if (!feedback_vector.empty()) {
if (last_packet_acked_sequence_number_ > round_trip_end_) {
new_round_started = true;
round_count_++;
round_trip_end_ = last_packet_sent_sequence_number_;
}
}
TryEnteringProbeRtt(now_ms);
UpdateBandwidthAndMinRtt(now_ms, feedback_vector, bytes_acked_);
if (new_round_started && !full_bandwidth_reached_) {
full_bandwidth_reached_ = max_bandwidth_filter_->FullBandwidthReached(
kStartupGrowthTarget, kMaxRoundsWithoutGrowth);
}
switch (mode_) {
break;
case STARTUP:
TryExitingStartup();
break;
case DRAIN:
TryExitingDrain(now_ms);
break;
case PROBE_BW:
TryUpdatingCyclePhase(now_ms);
break;
case PROBE_RTT:
TryExitingProbeRtt(now_ms, round_count_);
break;
case RECOVERY:
TryExitingRecovery(new_round_started);
break;
}
TryEnteringRecovery(new_round_started); // Comment this line to disable
// entering Recovery mode.
for (uint16_t f : feedback_vector)
AddToPastRtts(packet_stats_[f].ack_time_ms - packet_stats_[f].send_time_ms);
CalculatePacingRate();
size_t cwnd = congestion_window_->GetCongestionWindow(
mode_, max_bandwidth_filter_->max_bandwidth_estimate_bps(),
min_rtt_filter_->min_rtt_ms(), congestion_window_gain_);
// Make sure we don't get stuck when pacing_rate is 0, because of simulation
// tool specifics.
if (!pacing_rate_bps_)
pacing_rate_bps_ = 100;
BWE_TEST_LOGGING_PLOT(1, "SendRate", now_ms, pacing_rate_bps_ / 1000);
int64_t rate_for_pacer_bps = pacing_rate_bps_;
int64_t rate_for_encoder_bps = pacing_rate_bps_;
if (congestion_window_->data_inflight() >= cwnd * kCongestionWindowThreshold)
rate_for_encoder_bps = 0;
// We dont completely stop sending during PROBE_RTT, so we need encoder to
// produce something, another way of doing this would be telling encoder to
// stop and send padding instead of actual data.
if (mode_ == PROBE_RTT)
rate_for_encoder_bps = rate_for_pacer_bps * kEncoderRateGainForProbeRtt;
// Send for 300 kbps for first 200 ms, so that BBR has data to work with.
if (now_ms <= kDefaultDurationMs)
observer_->OnNetworkChanged(
kDefaultRatebps, kDefaultRatebps, false,
clock_->TimeInMicroseconds() + kFeedbackIntervalsMs * 1000, cwnd);
else
observer_->OnNetworkChanged(
rate_for_encoder_bps, rate_for_pacer_bps, mode_ == PROBE_RTT,
clock_->TimeInMicroseconds() + kFeedbackIntervalsMs * 1000, cwnd);
}
size_t BbrBweSender::TargetCongestionWindow(float gain) {
size_t target_congestion_window =
congestion_window_->GetTargetCongestionWindow(
max_bandwidth_filter_->max_bandwidth_estimate_bps(),
min_rtt_filter_->min_rtt_ms(), gain);
return target_congestion_window;
}
rtc::Optional<int64_t> BbrBweSender::CalculateBandwidthSample(
size_t data_sent_bytes,
int64_t send_time_delta_ms,
size_t data_acked_bytes,
int64_t ack_time_delta_ms) {
rtc::Optional<int64_t> bandwidth_sample;
if (send_time_delta_ms > 0)
bandwidth_sample.emplace(data_sent_bytes * 8000 / send_time_delta_ms);
rtc::Optional<int64_t> ack_rate;
if (ack_time_delta_ms > 0)
ack_rate.emplace(data_acked_bytes * 8000 / ack_time_delta_ms);
// If send rate couldn't be calculated automaticaly set |bandwidth_sample| to
// ack_rate.
if (!bandwidth_sample)
bandwidth_sample = ack_rate;
if (bandwidth_sample && ack_rate)
bandwidth_sample.emplace(std::min(*bandwidth_sample, *ack_rate));
return bandwidth_sample;
}
void BbrBweSender::AddSampleForHighGain() {
if (!high_gain_over_)
return;
high_gain_over_ = false;
// Calculate data sent/acked and time elapsed only for packets sent during
// high gain phase.
size_t data_sent_bytes = data_sent_before_high_gain_ended_bytes_ -
data_sent_before_high_gain_started_bytes_;
int64_t send_time_delta_ms = last_packet_send_time_during_high_gain_ms_ -
*first_packet_send_time_during_high_gain_ms_;
size_t data_acked_bytes = data_acked_before_high_gain_ended_bytes_ -
data_acked_before_high_gain_started_bytes_;
int64_t ack_time_delta_ms = last_packet_ack_time_during_high_gain_ms_ -
first_packet_ack_time_during_high_gain_ms_;
rtc::Optional<int64_t> bandwidth_sample = CalculateBandwidthSample(
data_sent_bytes, send_time_delta_ms, data_acked_bytes, ack_time_delta_ms);
if (bandwidth_sample)
max_bandwidth_filter_->AddBandwidthSample(*bandwidth_sample, round_count_);
first_packet_send_time_during_high_gain_ms_.reset();
}
void BbrBweSender::UpdateBandwidthAndMinRtt(
int64_t now_ms,
const std::vector<uint16_t>& feedback_vector,
int64_t bytes_acked) {
rtc::Optional<int64_t> min_rtt_sample_ms;
for (uint16_t f : feedback_vector) {
PacketStats packet = packet_stats_[f];
size_t data_sent_bytes =
packet.data_sent_bytes - packet.data_sent_before_last_sent_packet_bytes;
int64_t send_time_delta_ms =
packet.last_sent_packet_send_time_ms - packet.send_time_ms;
size_t data_acked_bytes = packet.data_acked_bytes -
packet.data_acked_before_last_acked_packet_bytes;
int64_t ack_time_delta_ms =
packet.ack_time_ms - packet.last_acked_packet_ack_time_ms;
rtc::Optional<int64_t> bandwidth_sample =
CalculateBandwidthSample(data_sent_bytes, send_time_delta_ms,
data_acked_bytes, ack_time_delta_ms);
if (bandwidth_sample)
max_bandwidth_filter_->AddBandwidthSample(*bandwidth_sample,
round_count_);
// AddSampleForHighGain(); // Comment to disable bucket for high gain.
if (!min_rtt_sample_ms)
min_rtt_sample_ms.emplace(packet.ack_time_ms - packet.send_time_ms);
else
*min_rtt_sample_ms = std::min(*min_rtt_sample_ms,
packet.ack_time_ms - packet.send_time_ms);
BWE_TEST_LOGGING_PLOT(1, "MinRtt", now_ms,
packet.ack_time_ms - packet.send_time_ms);
}
// We only feed RTT samples into the min_rtt filter which were not produced
// during 1.1 gain phase, to ensure they contain no queueing delay. But if the
// rtt sample from 1.1 gain phase improves the current estimate then we should
// make it as a new best estimate.
if (pacing_gain_ <= 1.0f || !min_rtt_filter_->min_rtt_ms() ||
*min_rtt_filter_->min_rtt_ms() >= *min_rtt_sample_ms)
min_rtt_filter_->AddRttSample(*min_rtt_sample_ms, now_ms);
}
void BbrBweSender::EnterStartup() {
mode_ = STARTUP;
pacing_gain_ = kHighGain;
congestion_window_gain_ = kHighGain;
}
void BbrBweSender::TryExitingStartup() {
if (full_bandwidth_reached_) {
mode_ = DRAIN;
pacing_gain_ = kDrainGain;
congestion_window_gain_ = kHighGain;
}
}
void BbrBweSender::TryExitingDrain(int64_t now_ms) {
if (congestion_window_->data_inflight() <=
TargetCongestionWindow(kTargetCongestionWindowGain))
EnterProbeBw(now_ms);
}
// Start probing with a random gain value, which is different form 0.75,
// starting with 0.75 doesn't offer any benefits as there are no queues to be
// drained.
void BbrBweSender::EnterProbeBw(int64_t now_ms) {
mode_ = PROBE_BW;
congestion_window_gain_ = kCruisingCongestionWindowGain;
int index = rand_->Rand(kGainCycleLength - 2);
if (index == 1)
index = kGainCycleLength - 1;
pacing_gain_ = kPacingGain[index];
cycle_start_time_ms_ = now_ms;
cycle_index_ = index;
}
void BbrBweSender::TryUpdatingCyclePhase(int64_t now_ms) {
// Each phase should last rougly min_rtt ms time.
bool advance_cycle_phase = false;
if (min_rtt_filter_->min_rtt_ms())
advance_cycle_phase =
now_ms - cycle_start_time_ms_ > *min_rtt_filter_->min_rtt_ms();
// If BBR was probing and it couldn't increase data inflight sufficiently in
// one min_rtt time, continue probing. BBR design doc isn't clear about this,
// but condition helps in quicker ramp-up and performs better.
if (pacing_gain_ > 1.0 &&
congestion_window_->data_inflight() <
TargetCongestionWindow(kTargetCongestionWindowGainForHighGain))
advance_cycle_phase = false;
// If BBR has already drained queues there is no point in continuing draining
// phase.
if (pacing_gain_ < 1.0 &&
congestion_window_->data_inflight() <= TargetCongestionWindow(1))
advance_cycle_phase = true;
if (advance_cycle_phase) {
cycle_index_++;
cycle_index_ %= kGainCycleLength;
pacing_gain_ = kPacingGain[cycle_index_];
cycle_start_time_ms_ = now_ms;
}
}
void BbrBweSender::TryEnteringProbeRtt(int64_t now_ms) {
if (min_rtt_filter_->MinRttExpired(now_ms) && mode_ != PROBE_RTT) {
mode_ = PROBE_RTT;
pacing_gain_ = 1;
congestion_window_gain_ = kProbeRttCongestionWindowGain;
probe_rtt_start_time_ms_ = now_ms;
minimum_congestion_window_start_time_ms_.reset();
}
}
// |minimum_congestion_window_start_time_|'s value is set to the first moment
// when data inflight was less then
// |CongestionWindow::kMinimumCongestionWindowBytes|, we should make sure that
// BBR has been in PROBE_RTT mode for at least one round or 200ms.
void BbrBweSender::TryExitingProbeRtt(int64_t now_ms, int64_t round) {
if (!minimum_congestion_window_start_time_ms_) {
if (congestion_window_->data_inflight() <=
TargetCongestionWindow(kProbeRttCongestionWindowGain)) {
minimum_congestion_window_start_time_ms_.emplace(now_ms);
minimum_congestion_window_start_round_ = round;
}
} else {
if (now_ms - *minimum_congestion_window_start_time_ms_ >=
kProbeRttDurationMs &&
round - minimum_congestion_window_start_round_ >=
kProbeRttDurationRounds) {
EnterProbeBw(now_ms);
}
}
}
void BbrBweSender::TryEnteringRecovery(bool new_round_started) {
if (mode_ == RECOVERY || !new_round_started || !full_bandwidth_reached_ ||
!min_rtt_filter_->min_rtt_ms())
return;
uint64_t increased_rtt_round_counter = 0;
// If average RTT for past |kPastRttsFilterSize| rounds has been more than
// some multiplier of min_rtt_ms enter Recovery.
for (BbrBweSender::AverageRtt i : past_rtts_) {
if (i.sum_of_rtts_ms / (int64_t)i.num_samples >=
*min_rtt_filter_->min_rtt_ms() * kRttIncreaseThreshold)
increased_rtt_round_counter++;
}
if (increased_rtt_round_counter < kPastRttsFilterSize)
return;
mode_ = RECOVERY;
pacing_gain_ = kRecoveryPacingGain;
congestion_window_gain_ = kRecoveryCongestionWindowGain;
}
void BbrBweSender::TryExitingRecovery(bool new_round_started) {
if (mode_ != RECOVERY || !new_round_started || !full_bandwidth_reached_)
return;
// If average RTT for the past round has decreased sufficiently exit Recovery.
if (!past_rtts_.empty()) {
BbrBweSender::AverageRtt last_round_sample = past_rtts_.back();
if (last_round_sample.sum_of_rtts_ms / last_round_sample.num_samples <=
*min_rtt_filter_->min_rtt_ms() * kRttDecreaseThreshold) {
EnterProbeBw(clock_->TimeInMilliseconds());
}
}
}
int64_t BbrBweSender::TimeUntilNextProcess() {
return 50;
}
void BbrBweSender::OnPacketsSent(const Packets& packets) {
for (Packet* packet : packets) {
if (packet->GetPacketType() == Packet::kMedia) {
MediaPacket* media_packet = static_cast<MediaPacket*>(packet);
bytes_sent_ += media_packet->payload_size();
PacketStats packet_stats = PacketStats(
media_packet->sequence_number(), 0,
media_packet->sender_timestamp_ms(), 0, last_packet_ack_time_,
media_packet->payload_size(), 0, bytes_sent_, 0, bytes_acked_);
packet_stats_[media_packet->sequence_number()] = packet_stats;
last_packet_send_time_ = media_packet->sender_timestamp_ms();
last_packet_sent_sequence_number_ = media_packet->sequence_number();
// If this is the first packet sent for high gain phase, save data for it.
if (!first_packet_send_time_during_high_gain_ms_ && pacing_gain_ > 1) {
first_packet_send_time_during_high_gain_ms_.emplace(
last_packet_send_time_);
data_sent_before_high_gain_started_bytes_ =
bytes_sent_ - media_packet->payload_size() / 2;
first_packet_seq_num_during_high_gain_ =
media_packet->sequence_number();
}
// This condition ensures that |last_packet_seq_num_during_high_gain_|
// will contain a sequence number of the last packet sent during high gain
// phase.
if (pacing_gain_ > 1) {
last_packet_send_time_during_high_gain_ms_ = last_packet_send_time_;
data_sent_before_high_gain_ended_bytes_ =
bytes_sent_ - media_packet->payload_size() / 2;
last_packet_seq_num_during_high_gain_ = media_packet->sequence_number();
}
congestion_window_->PacketSent(media_packet->payload_size());
}
}
}
void BbrBweSender::Process() {}
BbrBweReceiver::BbrBweReceiver(int flow_id)
: BweReceiver(flow_id, kReceivingRateTimeWindowMs),
clock_(0),
packet_feedbacks_(),
last_feedback_ms_(0) {}
BbrBweReceiver::~BbrBweReceiver() {}
void BbrBweReceiver::ReceivePacket(int64_t arrival_time_ms,
const MediaPacket& media_packet) {
packet_feedbacks_.push_back(media_packet.sequence_number());
BweReceiver::ReceivePacket(arrival_time_ms, media_packet);
}
FeedbackPacket* BbrBweReceiver::GetFeedback(int64_t now_ms) {
last_feedback_ms_ = now_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;
}
FeedbackPacket* fb = new BbrBweFeedback(
flow_id_, now_ms * 1000, corrected_send_time_ms, packet_feedbacks_);
packet_feedbacks_.clear();
return fb;
}
} // namespace bwe
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,242 @@
/*
* Copyright (c) 2017 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_BBR_H_
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_BBR_H_
#include <list>
#include <map>
#include <memory>
#include <vector>
#include "webrtc/api/optional.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
#include "webrtc/modules/video_coding/sequence_number_util.h"
#include "webrtc/rtc_base/random.h"
namespace webrtc {
namespace testing {
namespace bwe {
class MaxBandwidthFilter;
class MinRttFilter;
class CongestionWindow;
class BbrBweSender : public BweSender {
public:
explicit BbrBweSender(BitrateObserver* observer, Clock* clock);
virtual ~BbrBweSender();
enum Mode {
// Startup phase.
STARTUP,
// Queue draining phase, which where created during startup.
DRAIN,
// Cruising, probing new bandwidth.
PROBE_BW,
// Temporarily limiting congestion window size in order to measure
// minimum RTT.
PROBE_RTT,
// Temporarily reducing pacing rate and congestion window, in order to
// ensure no queues are built.
RECOVERY
};
struct PacketStats {
PacketStats() {}
PacketStats(uint16_t sequence_number_,
int64_t last_sent_packet_send_time_ms_,
int64_t send_time_ms_,
int64_t ack_time_ms_,
int64_t last_acked_packet_ack_time_ms_,
size_t payload_size_bytes_,
size_t data_sent_bytes_,
size_t data_sent_before_last_sent_packet_bytes_,
size_t data_acked_bytes_,
size_t data_acked_before_last_acked_packet_bytes_)
: sequence_number(sequence_number_),
last_sent_packet_send_time_ms(last_sent_packet_send_time_ms_),
send_time_ms(send_time_ms_),
ack_time_ms(ack_time_ms_),
last_acked_packet_ack_time_ms(last_acked_packet_ack_time_ms_),
payload_size_bytes(payload_size_bytes_),
data_sent_bytes(data_sent_bytes_),
data_sent_before_last_sent_packet_bytes(
data_sent_before_last_sent_packet_bytes_),
data_acked_bytes(data_acked_bytes_),
data_acked_before_last_acked_packet_bytes(
data_acked_before_last_acked_packet_bytes_) {}
// Sequence number of this packet.
uint16_t sequence_number;
// Send time of the last sent packet at ack time of this packet.
int64_t last_sent_packet_send_time_ms;
// Send time of this packet.
int64_t send_time_ms;
// Ack time of this packet.
int64_t ack_time_ms;
// Ack time of the last acked packet at send time of this packet.
int64_t last_acked_packet_ack_time_ms;
// Payload size of this packet.
size_t payload_size_bytes;
// Amount of data sent before this packet was sent.
size_t data_sent_bytes;
// Amount of data sent, before last sent packet.
size_t data_sent_before_last_sent_packet_bytes;
// Amount of data acked, before this packet was acked.
size_t data_acked_bytes;
// Amount of data acked, before last acked packet.
size_t data_acked_before_last_acked_packet_bytes;
};
struct AverageRtt {
AverageRtt() {}
AverageRtt(int64_t sum_of_rtts_ms_, int64_t num_samples_, uint64_t round_)
: sum_of_rtts_ms(sum_of_rtts_ms_),
num_samples(num_samples_),
round(round_) {}
// Sum of RTTs over the round.
int64_t sum_of_rtts_ms;
// Number of RTT samples over the round.
int64_t num_samples;
// The number of the round average RTT is recorded for.
uint64_t round;
};
void OnPacketsSent(const Packets& packets) override;
int GetFeedbackIntervalMs() const override;
void GiveFeedback(const FeedbackPacket& feedback) override;
int64_t TimeUntilNextProcess() override;
void Process() override;
private:
void EnterStartup();
void UpdateBandwidthAndMinRtt(int64_t now_ms,
const std::vector<uint16_t>& feedback_vector,
int64_t bytes_acked);
void TryExitingStartup();
void TryExitingDrain(int64_t now_ms);
void EnterProbeBw(int64_t now_ms);
void TryUpdatingCyclePhase(int64_t now_ms);
void TryEnteringProbeRtt(int64_t now_ms);
void TryExitingProbeRtt(int64_t now_ms, int64_t round);
void TryEnteringRecovery(bool new_round_started);
void TryExitingRecovery(bool new_round_started);
size_t TargetCongestionWindow(float gain);
void CalculatePacingRate();
// Calculates and returns bandwidth sample as minimum between send rate and
// ack rate, returns nothing if sample cannot be calculated.
rtc::Optional<int64_t> CalculateBandwidthSample(size_t data_sent,
int64_t send_time_delta_ms,
size_t data_acked,
int64_t ack_time_delta_ms);
// Calculate and add bandwidth sample only for packets' sent during high gain
// phase. Motivation of having a seperate bucket for high gain phase is to
// achieve quicker ramp up. Slight overestimations may happen due to window
// not being as large as usual.
void AddSampleForHighGain();
// Declares lost packets as acked. Implements simple logic by looking at the
// gap between sequence numbers. If there is a gap between sequence numbers we
// declare those packets as lost immediately.
void HandleLoss(uint64_t last_acked_packet, uint64_t recently_acked_packet);
void AddToPastRtts(int64_t rtt_sample_ms);
BitrateObserver* observer_;
Clock* const clock_;
Mode mode_;
std::unique_ptr<MaxBandwidthFilter> max_bandwidth_filter_;
std::unique_ptr<MinRttFilter> min_rtt_filter_;
std::unique_ptr<CongestionWindow> congestion_window_;
std::unique_ptr<Random> rand_;
uint64_t round_count_;
uint64_t round_trip_end_;
float pacing_gain_;
float congestion_window_gain_;
// If optimal bandwidth has been discovered and reached, (for example after
// Startup mode) set this variable to true.
bool full_bandwidth_reached_;
// Entering time for PROBE_BW mode's cycle phase.
int64_t cycle_start_time_ms_;
// Index number of the currently used gain value in PROBE_BW mode, from 0 to
// kGainCycleLength - 1.
int64_t cycle_index_;
size_t bytes_acked_;
// Time we entered PROBE_RTT mode.
int64_t probe_rtt_start_time_ms_;
// First moment of time when data inflight decreased below
// kMinimumCongestionWindow in PROBE_RTT mode.
rtc::Optional<int64_t> minimum_congestion_window_start_time_ms_;
// First round when data inflight decreased below kMinimumCongestionWindow in
// PROBE_RTT mode.
int64_t minimum_congestion_window_start_round_;
size_t bytes_sent_;
uint16_t last_packet_sent_sequence_number_;
uint16_t last_packet_acked_sequence_number_;
int64_t last_packet_ack_time_;
int64_t last_packet_send_time_;
int64_t pacing_rate_bps_;
// Send time of a packet sent first during high gain phase. Also serves as a
// flag, holding value means that we are already in high gain.
rtc::Optional<int64_t> first_packet_send_time_during_high_gain_ms_;
// Send time of a packet sent last during high gain phase.
int64_t last_packet_send_time_during_high_gain_ms_;
// Amount of data sent, before first packet was sent during high gain phase.
int64_t data_sent_before_high_gain_started_bytes_;
// Amount of data sent, before last packet was sent during high gain phase.
int64_t data_sent_before_high_gain_ended_bytes_;
// Ack time of a packet acked first during high gain phase.
int64_t first_packet_ack_time_during_high_gain_ms_;
// Ack time of a packet acked last during high gain phase.
int64_t last_packet_ack_time_during_high_gain_ms_;
// Amount of data acked, before the first packet was acked during high gain
// phase.
int64_t data_acked_before_high_gain_started_bytes_;
// Amount of data acked, before the last packet was acked during high gain
// phase.
int64_t data_acked_before_high_gain_ended_bytes_;
// Sequence number of the first packet sent during high gain phase.
uint16_t first_packet_seq_num_during_high_gain_;
// Sequence number of the last packet sent during high gain phase.
uint16_t last_packet_seq_num_during_high_gain_;
bool high_gain_over_;
std::map<int64_t, PacketStats> packet_stats_;
std::list<AverageRtt> past_rtts_;
};
class BbrBweReceiver : public BweReceiver {
public:
explicit BbrBweReceiver(int flow_id);
virtual ~BbrBweReceiver();
void ReceivePacket(int64_t arrival_time_ms,
const MediaPacket& media_packet) override;
FeedbackPacket* GetFeedback(int64_t now_ms) override;
private:
SimulatedClock clock_;
std::vector<uint16_t> packet_feedbacks_;
int64_t last_feedback_ms_;
};
} // namespace bwe
} // namespace testing
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_BBR_H_

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2017 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/congestion_window.h"
#include <algorithm>
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/bbr.h"
namespace webrtc {
namespace testing {
namespace bwe {
namespace {
// kStartingCongestionWindowBytes is used to set congestion window when
// bandwidth delay product is equal to zero, so that we don't set window to zero
// as well. Chosen randomly by me, because this value shouldn't make any
// significant difference, as bandwidth delay product is more than zero almost
// every time.
const int kStartingCongestionWindowBytes = 6000;
} // namespace
CongestionWindow::CongestionWindow() : data_inflight_bytes_(0) {}
CongestionWindow::~CongestionWindow() {}
int CongestionWindow::GetCongestionWindow(BbrBweSender::Mode mode,
int64_t bandwidth_estimate_bps,
rtc::Optional<int64_t> min_rtt_ms,
float gain) {
return GetTargetCongestionWindow(bandwidth_estimate_bps, min_rtt_ms, gain);
}
void CongestionWindow::PacketSent(size_t sent_packet_size_bytes) {
data_inflight_bytes_ += sent_packet_size_bytes;
}
void CongestionWindow::AckReceived(size_t received_packet_size_bytes) {
RTC_DCHECK_GE(data_inflight_bytes_ >= received_packet_size_bytes, true);
data_inflight_bytes_ -= received_packet_size_bytes;
}
int CongestionWindow::GetTargetCongestionWindow(
int64_t bandwidth_estimate_bps,
rtc::Optional<int64_t> min_rtt_ms,
float gain) {
// If we have no rtt sample yet, return the starting congestion window size.
if (!min_rtt_ms)
return gain * kStartingCongestionWindowBytes;
int bdp = *min_rtt_ms * bandwidth_estimate_bps / 8000;
int congestion_window = bdp * gain;
// Congestion window could be zero in rare cases, when either no bandwidth
// estimate is available, or path's min_rtt value is zero.
if (!congestion_window)
congestion_window = gain * kStartingCongestionWindowBytes;
return congestion_window;
}
} // namespace bwe
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2017 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_CONGESTION_WINDOW_H_
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_CONGESTION_WINDOW_H_
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/bbr.h"
#include "webrtc/api/optional.h"
namespace webrtc {
namespace testing {
namespace bwe {
class CongestionWindow {
public:
CongestionWindow();
~CongestionWindow();
int GetCongestionWindow(BbrBweSender::Mode mode,
int64_t bandwidth_estimate,
rtc::Optional<int64_t> min_rtt,
float gain);
int GetTargetCongestionWindow(int64_t bandwidth_estimate,
rtc::Optional<int64_t> min_rtt,
float gain);
// Packet sent from sender, meaning it is inflight until we receive it and we
// should add packet's size to data_inflight.
void PacketSent(size_t sent_packet_size);
// Ack was received by sender, meaning packet is no longer inflight.
void AckReceived(size_t received_packet_size);
size_t data_inflight() { return data_inflight_bytes_; }
private:
size_t data_inflight_bytes_;
};
} // namespace bwe
} // namespace testing
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_CONGESTION_WINDOW_H_

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2017 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/congestion_window.h"
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/bbr.h"
#include "webrtc/test/gtest.h"
namespace webrtc {
namespace testing {
namespace bwe {
namespace {
// Same value used in CongestionWindow class.
const int64_t kStartingCongestionWindow = 6000;
} // namespace
TEST(CongestionWindowTest, InitializationCheck) {
CongestionWindow congestion_window;
congestion_window.PacketSent(0);
EXPECT_EQ(congestion_window.data_inflight(), 0u);
congestion_window.AckReceived(0);
EXPECT_EQ(congestion_window.data_inflight(), 0u);
}
TEST(CongestionWindowTest, DataInflight) {
CongestionWindow congestion_window;
congestion_window.PacketSent(13);
EXPECT_EQ(congestion_window.data_inflight(), 13u);
congestion_window.AckReceived(12);
EXPECT_EQ(congestion_window.data_inflight(), 1u);
congestion_window.PacketSent(10);
congestion_window.PacketSent(9);
EXPECT_EQ(congestion_window.data_inflight(), 20u);
congestion_window.AckReceived(20);
EXPECT_EQ(congestion_window.data_inflight(), 0u);
}
TEST(CongestionWindowTest, ZeroBandwidthDelayProduct) {
CongestionWindow congestion_window;
int64_t target_congestion_window =
congestion_window.GetTargetCongestionWindow(
100, rtc::Optional<int64_t>(0), 2.885f);
EXPECT_EQ(target_congestion_window, 2.885f * kStartingCongestionWindow);
}
TEST(CongestionWindowTest, CalculateCongestionWindow) {
CongestionWindow congestion_window;
int64_t cwnd = congestion_window.GetCongestionWindow(
BbrBweSender::STARTUP, 800000, rtc::Optional<int64_t>(100l), 2.885f);
EXPECT_EQ(cwnd, 28850);
cwnd = congestion_window.GetCongestionWindow(
BbrBweSender::STARTUP, 400000, rtc::Optional<int64_t>(200l), 2.885f);
EXPECT_EQ(cwnd, 28850);
}
} // namespace bwe
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2017 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/max_bandwidth_filter.h"
namespace webrtc {
namespace testing {
namespace bwe {
const size_t MaxBandwidthFilter::kBandwidthWindowFilterSize;
MaxBandwidthFilter::MaxBandwidthFilter()
: bandwidth_last_round_bytes_per_ms_(0),
max_bandwidth_estimate_bps_(0),
rounds_without_growth_(0) {}
MaxBandwidthFilter::~MaxBandwidthFilter() {}
// For detailed explanation about implementing bandwidth filter this way visit
// "Bbr design" doc. |sample_bps| was measured during |round|.
void MaxBandwidthFilter::AddBandwidthSample(int64_t sample_bps, int64_t round) {
if (bandwidth_samples_[0].first == 0 ||
sample_bps >= bandwidth_samples_[0].first ||
round - bandwidth_samples_[2].second >=
MaxBandwidthFilter::kBandwidthWindowFilterSize)
bandwidth_samples_[0] = bandwidth_samples_[1] =
bandwidth_samples_[2] = {sample_bps, round};
if (sample_bps >= bandwidth_samples_[1].first) {
bandwidth_samples_[1] = {sample_bps, round};
bandwidth_samples_[2] = bandwidth_samples_[1];
} else {
if (sample_bps >= bandwidth_samples_[2].first)
bandwidth_samples_[2] = {sample_bps, round};
}
if (round - bandwidth_samples_[0].second >=
MaxBandwidthFilter::kBandwidthWindowFilterSize) {
bandwidth_samples_[0] = bandwidth_samples_[1];
bandwidth_samples_[1] = bandwidth_samples_[2];
bandwidth_samples_[2] = {sample_bps, round};
if (round - bandwidth_samples_[0].second >=
MaxBandwidthFilter::kBandwidthWindowFilterSize) {
bandwidth_samples_[0] = bandwidth_samples_[1];
bandwidth_samples_[1] = bandwidth_samples_[2];
}
max_bandwidth_estimate_bps_ = bandwidth_samples_[0].first;
return;
}
if (bandwidth_samples_[1].first == bandwidth_samples_[0].first &&
round - bandwidth_samples_[1].second >
MaxBandwidthFilter::kBandwidthWindowFilterSize / 4) {
bandwidth_samples_[2] = bandwidth_samples_[1] = {sample_bps, round};
max_bandwidth_estimate_bps_ = bandwidth_samples_[0].first;
return;
}
if (bandwidth_samples_[2].first == bandwidth_samples_[1].first &&
round - bandwidth_samples_[2].second >
MaxBandwidthFilter::kBandwidthWindowFilterSize / 2)
bandwidth_samples_[2] = {sample_bps, round};
max_bandwidth_estimate_bps_ = bandwidth_samples_[0].first;
}
bool MaxBandwidthFilter::FullBandwidthReached(float growth_target,
int max_rounds_without_growth) {
// Minimal bandwidth necessary to assume that better bandwidth can still be
// found and full bandwidth is not reached.
int64_t minimal_bandwidth =
bandwidth_last_round_bytes_per_ms_ * growth_target;
if (max_bandwidth_estimate_bps_ >= minimal_bandwidth) {
bandwidth_last_round_bytes_per_ms_ = max_bandwidth_estimate_bps_;
rounds_without_growth_ = 0;
return false;
}
rounds_without_growth_++;
return rounds_without_growth_ >= max_rounds_without_growth;
}
} // namespace bwe
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2017 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_MAX_BANDWIDTH_FILTER_H_
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_MAX_BANDWIDTH_FILTER_H_
#include <climits>
#include <list>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "webrtc/logging/rtc_event_log/mock/mock_rtc_event_log.h"
#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 MaxBandwidthFilter {
public:
// Number of rounds for bandwidth estimate to expire.
static const size_t kBandwidthWindowFilterSize = 10;
MaxBandwidthFilter();
~MaxBandwidthFilter();
int64_t max_bandwidth_estimate_bps() { return max_bandwidth_estimate_bps_; }
// Adds bandwidth sample to the bandwidth filter.
void AddBandwidthSample(int64_t sample, int64_t round);
// Checks if bandwidth has grown by certain multiplier for past x rounds,
// to decide whether or full bandwidth was reached.
bool FullBandwidthReached(float growth_target, int max_rounds_without_growth);
private:
int64_t bandwidth_last_round_bytes_per_ms_;
int64_t max_bandwidth_estimate_bps_;
int64_t rounds_without_growth_;
std::pair<int64_t, size_t> bandwidth_samples_[3];
};
} // namespace bwe
} // namespace testing
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_MAX_BANDWIDTH_FILTER_H_

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2017 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/max_bandwidth_filter.h"
#include "webrtc/test/gtest.h"
namespace webrtc {
namespace testing {
namespace bwe {
TEST(MaxBandwidthFilterTest, InitializationCheck) {
MaxBandwidthFilter max_bandwidth_filter;
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 0);
}
TEST(MaxBandwidthFilterTest, AddOneBandwidthSample) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(13, 4);
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 13);
}
TEST(MaxBandwidthFilterTest, AddSeveralBandwidthSamples) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(10, 5);
max_bandwidth_filter.AddBandwidthSample(13, 6);
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 13);
}
TEST(MaxBandwidthFilterTest, FirstSampleTimeOut) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(13, 5);
max_bandwidth_filter.AddBandwidthSample(10, 15);
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 10);
}
TEST(MaxBandwidthFilterTest, SecondSampleBecomesTheFirst) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(4, 5);
max_bandwidth_filter.AddBandwidthSample(3, 10);
max_bandwidth_filter.AddBandwidthSample(2, 15);
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 3);
}
TEST(MaxBandwidthFilterTest, ThirdSampleBecomesTheFirst) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(4, 5);
max_bandwidth_filter.AddBandwidthSample(3, 10);
max_bandwidth_filter.AddBandwidthSample(2, 25);
EXPECT_EQ(max_bandwidth_filter.max_bandwidth_estimate_bps(), 2);
}
TEST(MaxBandwidthFilterTest, FullBandwidthReached) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(100, 1);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(110, 2);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(120, 3);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(124, 4);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), true);
}
TEST(MaxBandwidthFilterTest, FullBandwidthNotReached) {
MaxBandwidthFilter max_bandwidth_filter;
max_bandwidth_filter.AddBandwidthSample(100, 1);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(110, 2);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(120, 3);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
max_bandwidth_filter.AddBandwidthSample(125, 4);
EXPECT_EQ(max_bandwidth_filter.FullBandwidthReached(1.25f, 3), false);
}
} // namespace bwe
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2017 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_MIN_RTT_FILTER_H_
#define WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_MIN_RTT_FILTER_H_
#include <cstdint>
#include <limits>
#include <list>
#include "webrtc/api/optional.h"
namespace webrtc {
namespace testing {
namespace bwe {
// Average rtt for past |kRttFilterSize| packets should grow by
// |kRttIncreaseThresholdForExpiry| in order to enter PROBE_RTT mode and expire.
// old min_rtt estimate.
const float kRttIncreaseThresholdForExpiry = 2.3f;
const size_t kRttFilterSize = 25;
class MinRttFilter {
public:
// This class implements a simple filter to ensure that PROBE_RTT is only
// entered when RTTs start to increase, instead of fixed 10 second window as
// in orginal BBR design doc, to avoid unnecessary freezes in stream.
MinRttFilter() {}
~MinRttFilter() {}
rtc::Optional<int64_t> min_rtt_ms() { return min_rtt_ms_; }
void AddRttSample(int64_t rtt_ms, int64_t now_ms) {
if (!min_rtt_ms_ || rtt_ms <= *min_rtt_ms_ || MinRttExpired(now_ms)) {
min_rtt_ms_.emplace(rtt_ms);
}
rtt_samples_.push_back(rtt_ms);
if (rtt_samples_.size() > kRttFilterSize)
rtt_samples_.pop_front();
}
// Checks whether or not last RTT values for past |kRttFilterSize| packets
// started to increase, meaning we have to update min_rtt estimate.
bool MinRttExpired(int64_t now_ms) {
if (rtt_samples_.size() < kRttFilterSize || !min_rtt_ms_)
return false;
int64_t sum_of_rtts_ms = 0;
for (int64_t i : rtt_samples_)
sum_of_rtts_ms += i;
if (sum_of_rtts_ms >=
*min_rtt_ms_ * kRttIncreaseThresholdForExpiry * kRttFilterSize) {
rtt_samples_.clear();
return true;
}
return false;
}
private:
rtc::Optional<int64_t> min_rtt_ms_;
std::list<int64_t> rtt_samples_;
};
} // namespace bwe
} // namespace testing
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_MIN_RTT_FILTER_H_

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2017 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/min_rtt_filter.h"
#include "webrtc/test/gtest.h"
namespace webrtc {
namespace testing {
namespace bwe {
TEST(MinRttFilterTest, InitializationCheck) {
MinRttFilter min_rtt_filter;
EXPECT_FALSE(min_rtt_filter.min_rtt_ms());
}
TEST(MinRttFilterTest, AddRttSample) {
MinRttFilter min_rtt_filter;
min_rtt_filter.AddRttSample(120, 5);
EXPECT_EQ(*min_rtt_filter.min_rtt_ms(), 120);
min_rtt_filter.AddRttSample(121, 6);
min_rtt_filter.AddRttSample(119, 7);
min_rtt_filter.AddRttSample(140, 10007);
EXPECT_EQ(*min_rtt_filter.min_rtt_ms(), 119);
}
TEST(MinRttFilterTest, MinRttExpired) {
MinRttFilter min_rtt_filter;
for (int i = 1; i <= 25; i++)
min_rtt_filter.AddRttSample(i, i);
EXPECT_EQ(min_rtt_filter.MinRttExpired(25), true);
EXPECT_EQ(min_rtt_filter.MinRttExpired(24), false);
}
} // namespace bwe
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,293 @@
/*
* 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/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
#include "webrtc/modules/remote_bitrate_estimator/test/estimators/nada.h"
#include "webrtc/modules/rtp_rtcp/include/receive_statistics.h"
#include "webrtc/rtc_base/arraysize.h"
namespace webrtc {
namespace testing {
namespace bwe {
namespace {
// Used as an upper bound for calling AcceleratedRampDown.
const float kMaxCongestionSignalMs =
40.0f + NadaBweSender::kMinNadaBitrateKbps / 15;
} // namespace
const int NadaBweSender::kMinNadaBitrateKbps = 50;
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 = arraysize(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(kMinNadaBitrateKbps), // 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 / std::max<int64_t>(1, 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() <
kMinNadaBitrateKbps / kProportionalityDelayBits &&
fb.derivative() < kDerivativeUpperBound &&
fb.receiving_rate() > kMinNadaBitrateKbps) {
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 + kMinNadaBitrateKbps);
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_, kMinNadaBitrateKbps);
observer_->OnNetworkChanged(1000 * bitrate_kbps_, 0, rtt_ms);
}
int64_t NadaBweSender::TimeUntilNextProcess() {
return 100;
}
void NadaBweSender::Process() {
}
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 - kMinNadaBitrateKbps) *
kReferenceDelayMs;
int original_increase = static_cast<int>(
(kKappa * delta_s *
(kTheta - (bitrate_kbps_ - kMinNadaBitrateKbps) * x_hat)) /
(kTauOMs * kTauOMs) +
0.5f);
bitrate_kbps_ = bitrate_kbps_ + smoothing_factor * original_increase;
}
} // namespace bwe
} // namespace testing
} // namespace webrtc

View File

@ -0,0 +1,111 @@
/*
* 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 <memory>
#include "webrtc/modules/include/module_common_types.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
#include "webrtc/rtc_base/constructormagic.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_;
std::unique_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:
static const int kMinNadaBitrateKbps;
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;
void 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_;
// 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_

View File

@ -0,0 +1,497 @@
/*
* 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 <memory>
#include <numeric>
#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
#include "webrtc/modules/remote_bitrate_estimator/test/packet.h"
#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
#include "webrtc/rtc_base/arraysize.h"
#include "webrtc/rtc_base/constructormagic.h"
#include "webrtc/test/gtest.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 * NadaBweSender::kMinNadaBitrateKbps;
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 * NadaBweSender::kMinNadaBitrateKbps;
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(), NadaBweSender::kMinNadaBitrateKbps);
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 = 5 * NadaBweSender::kMinNadaBitrateKbps;
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>(NadaBweSender::kMinNadaBitrateKbps);
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 * NadaBweSender::kMinNadaBitrateKbps);
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(), NadaBweSender::kMinNadaBitrateKbps);
nada_sender_.set_original_operating_mode(false);
nada_sender_.set_bitrate_kbps(5 * NadaBweSender::kMinNadaBitrateKbps);
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(), NadaBweSender::kMinNadaBitrateKbps);
}
// 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(NadaBweSender::kMinNadaBitrateKbps);
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) {
std::unique_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.
std::unique_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 = arraysize(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);
std::unique_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 = arraysize(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);
std::unique_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 = arraysize(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

View File

@ -0,0 +1,155 @@
/*
* 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 "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/include/receive_statistics.h"
#include "webrtc/test/gtest.h"
#include "webrtc/typedefs.h"
namespace webrtc {
namespace testing {
namespace bwe {
RembBweSender::RembBweSender(int kbps, BitrateObserver* observer, Clock* clock)
: bitrate_controller_(
BitrateController::CreateBitrateController(clock,
observer,
&event_log_)),
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();
}
void RembBweSender::Process() {
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());
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)) {
auto report_blocks = recv_stats_->RtcpReportBlocks(1);
if (!report_blocks.empty()) {
const rtcp::ReportBlock& stat = report_blocks.front();
latest_report_block_.fraction_lost = stat.fraction_lost();
latest_report_block_.packets_lost = stat.cumulative_lost();
latest_report_block_.extended_highest_sequence_number =
stat.extended_high_seq_num();
latest_report_block_.jitter = stat.jitter();
}
feedback = new RembFeedback(flow_id_, now_ms * 1000, last_feedback_ms_,
estimated_bps, latest_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<uint32_t>& ssrcs,
uint32_t bitrate) {}
bool RembReceiver::LatestEstimate(uint32_t* estimate_bps) {
if (latest_estimate_bps_ < 0) {
std::vector<uint32_t> ssrcs;
uint32_t 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

View File

@ -0,0 +1,87 @@
/*
* 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 <memory>
#include <string>
#include <vector>
#include "webrtc/logging/rtc_event_log/mock/mock_rtc_event_log.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
#include "webrtc/rtc_base/constructormagic.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;
void Process() override;
protected:
std::unique_ptr<BitrateController> bitrate_controller_;
std::unique_ptr<RtcpBandwidthObserver> feedback_observer_;
private:
Clock* clock_;
::testing::NiceMock<MockRtcEventLog> event_log_;
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<uint32_t>& ssrcs,
uint32_t bitrate) override;
private:
bool LatestEstimate(uint32_t* estimate_bps);
std::string estimate_log_prefix_;
bool plot_estimate_ = false;
SimulatedClock clock_;
std::unique_ptr<ReceiveStatistics> recv_stats_;
int64_t latest_estimate_bps_;
int64_t last_feedback_ms_;
std::unique_ptr<RemoteBitrateEstimator> estimator_;
RTCPReportBlock latest_report_block_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RembReceiver);
};
} // namespace bwe
} // namespace testing
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_REMB_H_

View File

@ -0,0 +1,178 @@
/*
* 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 <algorithm>
#include "webrtc/modules/congestion_controller/delay_based_bwe.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
#include "webrtc/rtc_base/logging.h"
#include "webrtc/rtc_base/ptr_util.h"
namespace webrtc {
namespace testing {
namespace bwe {
const int kFeedbackIntervalMs = 50;
SendSideBweSender::SendSideBweSender(int kbps,
BitrateObserver* observer,
Clock* clock)
: bitrate_controller_(
BitrateController::CreateBitrateController(clock,
observer,
&event_log_)),
acknowledged_bitrate_estimator_(
rtc::MakeUnique<AcknowledgedBitrateEstimator>()),
bwe_(new DelayBasedBwe(nullptr, clock)),
feedback_observer_(bitrate_controller_->CreateRtcpBandwidthObserver()),
clock_(clock),
send_time_history_(clock_, 10000),
has_received_ack_(false),
last_acked_seq_num_(0),
last_log_time_ms_(0) {
assert(kbps >= kMinBitrateKbps);
assert(kbps <= kMaxBitrateKbps);
bitrate_controller_->SetStartBitrate(1000 * kbps);
bitrate_controller_->SetMinMaxBitrate(1000 * kMinBitrateKbps,
1000 * kMaxBitrateKbps);
bwe_->SetMinBitrate(1000 * kMinBitrateKbps);
}
SendSideBweSender::~SendSideBweSender() {}
int SendSideBweSender::GetFeedbackIntervalMs() const {
return kFeedbackIntervalMs;
}
void SendSideBweSender::GiveFeedback(const FeedbackPacket& feedback) {
const SendSideBweFeedback& fb =
static_cast<const SendSideBweFeedback&>(feedback);
if (fb.packet_feedback_vector().empty())
return;
std::vector<PacketFeedback> packet_feedback_vector(
fb.packet_feedback_vector());
for (PacketFeedback& packet_feedback : packet_feedback_vector) {
if (!send_time_history_.GetFeedback(&packet_feedback, true)) {
int64_t now_ms = clock_->TimeInMilliseconds();
if (now_ms - last_log_time_ms_ > 5000) {
LOG(LS_WARNING) << "Ack arrived too late.";
last_log_time_ms_ = now_ms;
}
}
}
int64_t rtt_ms =
clock_->TimeInMilliseconds() - feedback.latest_send_time_ms();
bwe_->OnRttUpdate(rtt_ms, rtt_ms);
BWE_TEST_LOGGING_PLOT(1, "RTT", clock_->TimeInMilliseconds(), rtt_ms);
std::sort(packet_feedback_vector.begin(), packet_feedback_vector.end(),
PacketFeedbackComparator());
acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(
packet_feedback_vector);
DelayBasedBwe::Result result = bwe_->IncomingPacketFeedbackVector(
packet_feedback_vector, acknowledged_bitrate_estimator_->bitrate_bps());
if (result.updated)
bitrate_controller_->OnDelayBasedBweResult(result);
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_.fraction_lost = (lost_packets << 8) / expected_packets;
report_block_.packets_lost += lost_packets;
uint32_t unwrapped = seq_num_unwrapper_.Unwrap(
packet_feedback_vector.back().sequence_number);
report_block_.extended_highest_sequence_number =
std::max(unwrapped, report_block_.extended_highest_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 SendSideBweSender::OnPacketsSent(const Packets& packets) {
for (Packet* packet : packets) {
if (packet->GetPacketType() == Packet::kMedia) {
MediaPacket* media_packet = static_cast<MediaPacket*>(packet);
// TODO(philipel): Add probe_cluster_id to Packet class in order
// to create tests for probing using cluster ids.
PacketFeedback packet_feedback(
clock_->TimeInMilliseconds(), media_packet->header().sequenceNumber,
media_packet->payload_size(), 0, 0, PacedPacketInfo());
send_time_history_.AddAndRemoveOld(packet_feedback);
send_time_history_.OnSentPacket(media_packet->header().sequenceNumber,
media_packet->sender_timestamp_ms());
}
}
}
void SendSideBweSender::OnReceiveBitrateChanged(
const std::vector<uint32_t>& ssrcs,
uint32_t bitrate) {
feedback_observer_->OnReceivedEstimatedBitrate(bitrate);
}
int64_t SendSideBweSender::TimeUntilNextProcess() {
return bitrate_controller_->TimeUntilNextProcess();
}
void SendSideBweSender::Process() {
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(
PacketFeedback(-1, arrival_time_ms, media_packet.sender_timestamp_ms(),
media_packet.header().sequenceNumber,
media_packet.payload_size(), 0, 0, PacedPacketInfo()));
// 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

View File

@ -0,0 +1,76 @@
/*
* 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 <memory>
#include <vector>
#include "webrtc/logging/rtc_event_log/mock/mock_rtc_event_log.h"
#include "webrtc/modules/congestion_controller/acknowledged_bitrate_estimator.h"
#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 SendSideBweSender : public BweSender, public RemoteBitrateObserver {
public:
SendSideBweSender(int kbps, BitrateObserver* observer, Clock* clock);
virtual ~SendSideBweSender();
int GetFeedbackIntervalMs() const override;
void GiveFeedback(const FeedbackPacket& feedback) override;
void OnPacketsSent(const Packets& packets) override;
void OnReceiveBitrateChanged(const std::vector<uint32_t>& ssrcs,
uint32_t bitrate) override;
int64_t TimeUntilNextProcess() override;
void Process() override;
protected:
std::unique_ptr<BitrateController> bitrate_controller_;
std::unique_ptr<AcknowledgedBitrateEstimator> acknowledged_bitrate_estimator_;
std::unique_ptr<DelayBasedBwe> bwe_;
std::unique_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_;
int64_t last_log_time_ms_;
SequenceNumberUnwrapper seq_num_unwrapper_;
::testing::NiceMock<MockRtcEventLog> event_log_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(SendSideBweSender);
};
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<PacketFeedback> packet_feedback_vector_;
};
} // namespace bwe
} // namespace testing
} // namespace webrtc
#endif // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_ESTIMATORS_SEND_SIDE_H_

View File

@ -0,0 +1,53 @@
/*
* 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 "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h"
#include "webrtc/modules/rtp_rtcp/include/receive_statistics.h"
#include "webrtc/test/gtest.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

View File

@ -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_

View File

@ -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 <algorithm>
#include "webrtc/modules/remote_bitrate_estimator/test/packet_sender.h"
#include "webrtc/typedefs.h"
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_AND_SSRC(
0, plot_information_[kTotalAvailable].prefix, now_ms_,
GetTotalAvailableKbps(), flow_id_, "Available");
} else if (metric == kAvailablePerFlow) {
BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(
0, plot_information_[kAvailablePerFlow].prefix, now_ms_,
GetAvailablePerFlowKbps(), flow_id_, "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_AND_SSRC(windows_id, prefix, time_ms,
static_cast<double>(y), flow_id_,
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_;
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(average_delay_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;
// TODO(terelius): Since this does not use the BWE_TEST_LOGGING macros,
// it hasn't been kept up to date with the plot format. Remove or fix?
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

View File

@ -0,0 +1,188 @@
/*
* 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/rtc_base/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_

View File

@ -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 "webrtc/test/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

View File

@ -0,0 +1,220 @@
/*
* 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 <utility>
#include <vector>
#include "webrtc/common_types.h"
#include "webrtc/modules/rtp_rtcp/include/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 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.
};
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 BbrBweFeedback : public FeedbackPacket {
public:
BbrBweFeedback(int flow_id,
int64_t send_time_us,
int64_t latest_send_time_ms,
const std::vector<uint16_t>& packet_feedback_vector);
virtual ~BbrBweFeedback() {}
const std::vector<uint16_t>& packet_feedback_vector() const {
return packet_feedback_vector_;
}
private:
const std::vector<uint16_t> packet_feedback_vector_;
};
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<PacketFeedback>& packet_feedback_vector);
virtual ~SendSideBweFeedback() {}
const std::vector<PacketFeedback>& packet_feedback_vector() const {
return packet_feedback_vector_;
}
private:
const std::vector<PacketFeedback> 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_

View File

@ -0,0 +1,146 @@
/*
* 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 "webrtc/modules/include/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/include/receive_statistics.h"
#include "webrtc/system_wrappers/include/clock.h"
#include "webrtc/test/gtest.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("MetricRecorderThroughput_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("Capacity_kbps#1"); // Total Available.
prefixes.push_back("PerFlowCapacity_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

View File

@ -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 <memory>
#include <string>
#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"
#include "webrtc/rtc_base/constructormagic.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_;
std::unique_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_

View File

@ -0,0 +1,508 @@
/*
* 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/modules/include/module_common_types.h"
#include "webrtc/modules/pacing/pacer.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bbr_paced_sender.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
#include "webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h"
#include "webrtc/rtc_base/checks.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_(
estimator == kBbrEstimator
? static_cast<Pacer*>(new BbrPacedSender(&clock_, this, nullptr))
: static_cast<Pacer*>(new PacedSender(&clock_, this, nullptr))) {
modules_.push_back(pacer_.get());
pacer_->SetEstimatedBitrate(source->bits_per_second());
}
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_size_in_bytes_ += media_packet->payload_size();
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);
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,
const PacedPacketInfo& pacing_info) {
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_size_in_bytes_ -= media_packet->payload_size();
pacer_queue_.erase(it);
return true;
}
}
return false;
}
size_t PacedVideoSender::TimeToSendPadding(size_t bytes,
const PacedPacketInfo& pacing_info) {
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_->SetEstimatedBitrate(target_bitrate_bps);
}
void PacedVideoSender::OnNetworkChanged(uint32_t bitrate_for_encoder_bps,
uint32_t bitrate_for_pacer_bps,
bool in_probe_rtt,
int64_t target_set_time,
uint64_t congestion_window) {
VideoSender::OnNetworkChanged(bitrate_for_encoder_bps, 0u, 0u);
pacer_->SetEstimatedBitrateAndCongestionWindow(
bitrate_for_pacer_bps, in_probe_rtt, congestion_window);
}
void PacedVideoSender::OnBytesAcked(size_t bytes) {
pacer_->OnBytesAcked(bytes);
}
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

View File

@ -0,0 +1,207 @@
/*
* 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 <memory>
#include <set>
#include <string>
#include "webrtc/modules/include/module.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe.h"
#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_framework.h"
#include "webrtc/rtc_base/constructormagic.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_;
std::unique_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::PacketSender {
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,
const PacedPacketInfo& pacing_info) override;
size_t TimeToSendPadding(size_t bytes,
const PacedPacketInfo& pacing_info) override;
// Implements BitrateObserver.
void OnNetworkChanged(uint32_t target_bitrate_bps,
uint8_t fraction_lost,
int64_t rtt) override;
void OnNetworkChanged(uint32_t bitrate_for_encoder_bps,
uint32_t bitrate_for_pacer_bps,
bool in_probe_rtt,
int64_t rtt,
uint64_t congestion_window) override;
size_t pacer_queue_size_in_bytes() override {
return pacer_queue_size_in_bytes_;
}
void OnBytesAcked(size_t bytes) 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);
size_t pacer_queue_size_in_bytes_ = 0;
std::unique_ptr<Pacer> 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:
explicit 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_

View File

@ -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

View File

@ -0,0 +1,182 @@
#!/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. The expected format is
# PLOT <plot_number> <var_name>:<ssrc>@<alg_name> <time> <value>
# <var_name> may optionally be followed by #<axis_alignment> but it is
# deprecated. <plot_number> is also deprecated.
# Each combination <var_name>:<ssrc>@<alg_name> is stored in it's own time
# series. The main function defines which time series should be displayed and
# whether they should should be displayed in the same or separate windows.
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 ParsePlotLineException(Exception):
def __init__(self, reason, line):
super(ParsePlotLineException, self).__init__()
self.reason = reason
self.line = line
def ParsePlotLine(line):
split_line = line.split()
if len(split_line) != 5:
raise ParsePlotLineException("Expected 5 arguments on line", line)
(plot, _, annotated_var, time, value) = split_line
if plot != "PLOT":
raise ParsePlotLineException("Line does not begin with \"PLOT\\t\"", line)
# The variable name can contain any non-whitespace character except "#:@"
match = re.match(r'([^\s#:@]+)(?:#\d)?:(\d+)@(\S+)', annotated_var)
if match == None:
raise ParsePlotLineException("Could not parse variable name, ssrc and \
algorithm name", annotated_var)
var_name = match.group(1)
ssrc = match.group(2)
alg_name = match.group(3).replace('_', ' ')
return (var_name, ssrc, alg_name, time, value)
def GenerateLabel(var_name, ssrc, ssrc_count, alg_name):
label = var_name
if ssrc_count > 1 or ssrc != "0":
label = label + " flow " + ssrc
if alg_name != "-":
label = label + " " + alg_name
return label
class Figure(object):
def __init__(self, name):
self.name = name
self.subplots = []
def AddSubplot(self, var_names, xlabel, ylabel):
self.subplots.append(Subplot(var_names, xlabel, ylabel))
def AddSample(self, var_name, ssrc, alg_name, time, value):
for s in self.subplots:
s.AddSample(var_name, ssrc, alg_name, time, value)
def PlotFigure(self, fig):
n = len(self.subplots)
for i in range(n):
axis = fig.add_subplot(n, 1, i+1)
self.subplots[i].PlotSubplot(axis)
class Subplot(object):
def __init__(self, var_names, xlabel, ylabel):
self.xlabel = xlabel
self.ylabel = ylabel
self.var_names = var_names
self.samples = dict()
def AddSample(self, var_name, ssrc, alg_name, time, value):
if var_name not in self.var_names:
return
if alg_name not in self.samples.keys():
self.samples[alg_name] = {}
if ssrc not in self.samples[alg_name].keys():
self.samples[alg_name][ssrc] = {}
if var_name not in self.samples[alg_name][ssrc].keys():
self.samples[alg_name][ssrc][var_name] = []
self.samples[alg_name][ssrc][var_name].append((time, value))
def PlotSubplot(self, axis):
axis.set_xlabel(self.xlabel)
axis.set_ylabel(self.ylabel)
count = 0
for alg_name in self.samples.keys():
for ssrc in self.samples[alg_name].keys():
for var_name in self.samples[alg_name][ssrc].keys():
x = [sample[0] for sample in self.samples[alg_name][ssrc][var_name]]
y = [sample[1] for sample in self.samples[alg_name][ssrc][var_name]]
x = numpy.array(x)
y = numpy.array(y)
ssrc_count = len(self.samples[alg_name].keys())
l = GenerateLabel(var_name, ssrc, ssrc_count, alg_name)
if l == 'MaxThroughput_':
plt.plot(x, y, label=l, linewidth=4.0)
else:
plt.plot(x, y, label=l, linewidth=2.0)
count += 1
plt.grid(True)
if count > 1:
plt.legend(loc='best')
def main():
receiver = Figure("PacketReceiver")
receiver.AddSubplot(['Throughput_kbps', 'MaxThroughput_', 'Capacity_kbps',
'PerFlowCapacity_kbps', 'MetricRecorderThroughput_kbps'],
"Time (s)", "Throughput (kbps)")
receiver.AddSubplot(['Delay_ms_', 'Delay_ms'], "Time (s)",
"One-way delay (ms)")
receiver.AddSubplot(['Packet_Loss_'], "Time (s)", "Packet Loss Ratio")
kalman_state = Figure("KalmanState")
kalman_state.AddSubplot(['kc', 'km'], "Time (s)", "Kalman gain")
kalman_state.AddSubplot(['slope_1/bps'], "Time (s)", "Slope")
kalman_state.AddSubplot(['var_noise'], "Time (s)", "Var noise")
detector_state = Figure("DetectorState")
detector_state.AddSubplot(['T', 'threshold'], "Time (s)", "Offset")
trendline_state = Figure("TrendlineState")
trendline_state.AddSubplot(["accumulated_delay_ms", "smoothed_delay_ms"],
"Time (s)", "Delay (ms)")
trendline_state.AddSubplot(["trendline_slope"], "Time (s)", "Slope")
target_bitrate = Figure("TargetBitrate")
target_bitrate.AddSubplot(['target_bitrate_bps', 'acknowledged_bitrate'],
"Time (s)", "Bitrate (bps)")
min_rtt_state = Figure("MinRttState")
min_rtt_state.AddSubplot(['MinRtt'], "Time (s)", "Time (ms)")
# Select which figures to plot here.
figures = [receiver, detector_state, trendline_state, target_bitrate,
min_rtt_state]
# Add samples to the figures.
for line in sys.stdin:
if line.startswith("[ RUN ]"):
test_name = re.search(r'\.(\w+)', line).group(1)
if line.startswith("PLOT"):
try:
(var_name, ssrc, alg_name, time, value) = ParsePlotLine(line)
for f in figures:
# The sample will be ignored bv the figures that don't need it.
f.AddSample(var_name, ssrc, alg_name, time, value)
except ParsePlotLineException as e:
print e.reason
print e.line
# Plot figures.
for f in figures:
fig = plt.figure(f.name)
f.PlotFigure(fig)
if SAVE_FIGURE:
fig.savefig(test_name + f.name + ".png")
plt.show()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,143 @@
/*
* 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 <stdio.h>
#include <set>
#include <sstream>
#include <string>
#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/include/rtp_header_parser.h"
#include "webrtc/modules/rtp_rtcp/include/rtp_payload_registry.h"
#include "webrtc/rtc_base/flags.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>(FLAG_extension_type);
}
DEFINE_int(extension_id, 3, "Extension id.");
int ExtensionId() {
return static_cast<int>(FLAG_extension_id);
}
DEFINE_string(input_file, "", "Input file.");
std::string InputFile() {
return static_cast<std::string>(FLAG_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>(FLAG_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;
}
DEFINE_bool(help, false, "Print this message.");
} // 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) {
if (rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true)) {
return 1;
}
if (flags::FLAG_help) {
rtc::FlagList::Print(nullptr, false);
return 0;
}
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;
}

View File

@ -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_

View File

@ -0,0 +1,114 @@
/*
* 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 <memory>
#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/include/rtp_header_parser.h"
#include "webrtc/modules/rtp_rtcp/include/rtp_payload_registry.h"
#include "webrtc/rtc_base/format_macros.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<uint32_t>& ssrcs,
uint32_t 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;
}
std::unique_ptr<webrtc::test::RtpFileReader> rtp_reader(reader);
std::unique_ptr<webrtc::RtpHeaderParser> rtp_parser(parser);
std::unique_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);
++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;
}

View File

@ -0,0 +1,70 @@
/*
* 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 <memory>
#include <sstream>
#include "webrtc/modules/remote_bitrate_estimator/tools/bwe_rtp.h"
#include "webrtc/modules/rtp_rtcp/include/rtp_header_parser.h"
#include "webrtc/modules/rtp_rtcp/include/rtp_payload_registry.h"
#include "webrtc/rtc_base/format_macros.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);
std::unique_ptr<webrtc::test::RtpFileReader> rtp_reader(reader);
std::unique_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;
}