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:
committed by
Commit Bot
parent
6674846b4a
commit
bb547203bf
263
modules/remote_bitrate_estimator/BUILD.gn
Normal file
263
modules/remote_bitrate_estimator/BUILD.gn
Normal 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.
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
10
modules/remote_bitrate_estimator/DEPS
Normal file
10
modules/remote_bitrate_estimator/DEPS
Normal file
@ -0,0 +1,10 @@
|
||||
include_rules = [
|
||||
"+webrtc/logging/rtc_event_log",
|
||||
"+webrtc/system_wrappers",
|
||||
]
|
||||
|
||||
specific_include_rules = {
|
||||
"nada\.h": [
|
||||
"+webrtc/voice_engine",
|
||||
],
|
||||
}
|
||||
10
modules/remote_bitrate_estimator/OWNERS
Normal file
10
modules/remote_bitrate_estimator/OWNERS
Normal 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=*
|
||||
327
modules/remote_bitrate_estimator/aimd_rate_control.cc
Normal file
327
modules/remote_bitrate_estimator/aimd_rate_control.cc
Normal 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
|
||||
92
modules/remote_bitrate_estimator/aimd_rate_control.h
Normal file
92
modules/remote_bitrate_estimator/aimd_rate_control.h
Normal 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_
|
||||
197
modules/remote_bitrate_estimator/aimd_rate_control_unittest.cc
Normal file
197
modules/remote_bitrate_estimator/aimd_rate_control_unittest.cc
Normal 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
|
||||
40
modules/remote_bitrate_estimator/bwe_defines.cc
Normal file
40
modules/remote_bitrate_estimator/bwe_defines.cc
Normal 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
|
||||
554
modules/remote_bitrate_estimator/bwe_simulations.cc
Normal file
554
modules/remote_bitrate_estimator/bwe_simulations.cc
Normal 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
|
||||
|
||||
60
modules/remote_bitrate_estimator/include/bwe_defines.h
Normal file
60
modules/remote_bitrate_estimator/include/bwe_defines.h
Normal 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_
|
||||
@ -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_
|
||||
55
modules/remote_bitrate_estimator/include/send_time_history.h
Normal file
55
modules/remote_bitrate_estimator/include/send_time_history.h
Normal 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_
|
||||
157
modules/remote_bitrate_estimator/inter_arrival.cc
Normal file
157
modules/remote_bitrate_estimator/inter_arrival.cc
Normal 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
|
||||
95
modules/remote_bitrate_estimator/inter_arrival.h
Normal file
95
modules/remote_bitrate_estimator/inter_arrival.h
Normal 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_
|
||||
536
modules/remote_bitrate_estimator/inter_arrival_unittest.cc
Normal file
536
modules/remote_bitrate_estimator/inter_arrival_unittest.cc
Normal 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
|
||||
162
modules/remote_bitrate_estimator/overuse_detector.cc
Normal file
162
modules/remote_bitrate_estimator/overuse_detector.cc
Normal 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
|
||||
62
modules/remote_bitrate_estimator/overuse_detector.h
Normal file
62
modules/remote_bitrate_estimator/overuse_detector.h
Normal 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_
|
||||
779
modules/remote_bitrate_estimator/overuse_detector_unittest.cc
Normal file
779
modules/remote_bitrate_estimator/overuse_detector_unittest.cc
Normal 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,
|
||||
×tamp_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
|
||||
166
modules/remote_bitrate_estimator/overuse_estimator.cc
Normal file
166
modules/remote_bitrate_estimator/overuse_estimator.cc
Normal 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
|
||||
73
modules/remote_bitrate_estimator/overuse_estimator.h
Normal file
73
modules/remote_bitrate_estimator/overuse_estimator.h
Normal 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_
|
||||
@ -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, ¤t);
|
||||
}
|
||||
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, ¤t);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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,
|
||||
×tamp_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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
201
modules/remote_bitrate_estimator/remote_estimator_proxy.cc
Normal file
201
modules/remote_bitrate_estimator/remote_estimator_proxy.cc
Normal 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
|
||||
77
modules/remote_bitrate_estimator/remote_estimator_proxy.h
Normal file
77
modules/remote_bitrate_estimator/remote_estimator_proxy.h
Normal 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_
|
||||
@ -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
|
||||
89
modules/remote_bitrate_estimator/send_time_history.cc
Normal file
89
modules/remote_bitrate_estimator/send_time_history.cc
Normal 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
|
||||
235
modules/remote_bitrate_estimator/send_time_history_unittest.cc
Normal file
235
modules/remote_bitrate_estimator/send_time_history_unittest.cc
Normal 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
|
||||
140
modules/remote_bitrate_estimator/test/bbr_paced_sender.cc
Normal file
140
modules/remote_bitrate_estimator/test/bbr_paced_sender.cc
Normal 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
|
||||
92
modules/remote_bitrate_estimator/test/bbr_paced_sender.h
Normal file
92
modules/remote_bitrate_estimator/test/bbr_paced_sender.h
Normal 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_
|
||||
285
modules/remote_bitrate_estimator/test/bwe.cc
Normal file
285
modules/remote_bitrate_estimator/test/bwe.cc
Normal 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
|
||||
199
modules/remote_bitrate_estimator/test/bwe.h
Normal file
199
modules/remote_bitrate_estimator/test/bwe.h
Normal 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_
|
||||
996
modules/remote_bitrate_estimator/test/bwe_test.cc
Normal file
996
modules/remote_bitrate_estimator/test/bwe_test.cc
Normal 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
|
||||
196
modules/remote_bitrate_estimator/test/bwe_test.h
Normal file
196
modules/remote_bitrate_estimator/test/bwe_test.h
Normal 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_
|
||||
168
modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc
Normal file
168
modules/remote_bitrate_estimator/test/bwe_test_baselinefile.cc
Normal 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
|
||||
@ -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_
|
||||
96
modules/remote_bitrate_estimator/test/bwe_test_fileutils.cc
Normal file
96
modules/remote_bitrate_estimator/test/bwe_test_fileutils.cc
Normal 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
|
||||
59
modules/remote_bitrate_estimator/test/bwe_test_fileutils.h
Normal file
59
modules/remote_bitrate_estimator/test/bwe_test_fileutils.h
Normal 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_
|
||||
829
modules/remote_bitrate_estimator/test/bwe_test_framework.cc
Normal file
829
modules/remote_bitrate_estimator/test/bwe_test_framework.cc
Normal 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
|
||||
473
modules/remote_bitrate_estimator/test/bwe_test_framework.h
Normal file
473
modules/remote_bitrate_estimator/test/bwe_test_framework.h
Normal 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_
|
||||
@ -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
|
||||
259
modules/remote_bitrate_estimator/test/bwe_test_logging.cc
Normal file
259
modules/remote_bitrate_estimator/test/bwe_test_logging.cc
Normal 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
|
||||
351
modules/remote_bitrate_estimator/test/bwe_test_logging.h
Normal file
351
modules/remote_bitrate_estimator/test/bwe_test_logging.h
Normal 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_
|
||||
394
modules/remote_bitrate_estimator/test/bwe_unittest.cc
Normal file
394
modules/remote_bitrate_estimator/test/bwe_unittest.cc
Normal 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
|
||||
553
modules/remote_bitrate_estimator/test/estimators/bbr.cc
Normal file
553
modules/remote_bitrate_estimator/test/estimators/bbr.cc
Normal 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
|
||||
242
modules/remote_bitrate_estimator/test/estimators/bbr.h
Normal file
242
modules/remote_bitrate_estimator/test/estimators/bbr.h
Normal 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_
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
293
modules/remote_bitrate_estimator/test/estimators/nada.cc
Normal file
293
modules/remote_bitrate_estimator/test/estimators/nada.cc
Normal 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
|
||||
111
modules/remote_bitrate_estimator/test/estimators/nada.h
Normal file
111
modules/remote_bitrate_estimator/test/estimators/nada.h
Normal 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_
|
||||
@ -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
|
||||
155
modules/remote_bitrate_estimator/test/estimators/remb.cc
Normal file
155
modules/remote_bitrate_estimator/test/estimators/remb.cc
Normal 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
|
||||
87
modules/remote_bitrate_estimator/test/estimators/remb.h
Normal file
87
modules/remote_bitrate_estimator/test/estimators/remb.h
Normal 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_
|
||||
178
modules/remote_bitrate_estimator/test/estimators/send_side.cc
Normal file
178
modules/remote_bitrate_estimator/test/estimators/send_side.cc
Normal 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
|
||||
76
modules/remote_bitrate_estimator/test/estimators/send_side.h
Normal file
76
modules/remote_bitrate_estimator/test/estimators/send_side.h
Normal 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_
|
||||
53
modules/remote_bitrate_estimator/test/estimators/tcp.cc
Normal file
53
modules/remote_bitrate_estimator/test/estimators/tcp.cc
Normal 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
|
||||
38
modules/remote_bitrate_estimator/test/estimators/tcp.h
Normal file
38
modules/remote_bitrate_estimator/test/estimators/tcp.h
Normal 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_
|
||||
445
modules/remote_bitrate_estimator/test/metric_recorder.cc
Normal file
445
modules/remote_bitrate_estimator/test/metric_recorder.cc
Normal 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
|
||||
188
modules/remote_bitrate_estimator/test/metric_recorder.h
Normal file
188
modules/remote_bitrate_estimator/test/metric_recorder.h
Normal 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_
|
||||
@ -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
|
||||
220
modules/remote_bitrate_estimator/test/packet.h
Normal file
220
modules/remote_bitrate_estimator/test/packet.h
Normal 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_
|
||||
146
modules/remote_bitrate_estimator/test/packet_receiver.cc
Normal file
146
modules/remote_bitrate_estimator/test/packet_receiver.cc
Normal 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
|
||||
71
modules/remote_bitrate_estimator/test/packet_receiver.h
Normal file
71
modules/remote_bitrate_estimator/test/packet_receiver.h
Normal 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_
|
||||
508
modules/remote_bitrate_estimator/test/packet_sender.cc
Normal file
508
modules/remote_bitrate_estimator/test/packet_sender.cc
Normal 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
|
||||
207
modules/remote_bitrate_estimator/test/packet_sender.h
Normal file
207
modules/remote_bitrate_estimator/test/packet_sender.h
Normal 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_
|
||||
286
modules/remote_bitrate_estimator/test/plot_bars.sh
Executable file
286
modules/remote_bitrate_estimator/test/plot_bars.sh
Executable 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
|
||||
182
modules/remote_bitrate_estimator/test/plot_dynamics.py
Executable file
182
modules/remote_bitrate_estimator/test/plot_dynamics.py
Executable 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()
|
||||
143
modules/remote_bitrate_estimator/tools/bwe_rtp.cc
Normal file
143
modules/remote_bitrate_estimator/tools/bwe_rtp.cc
Normal 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;
|
||||
}
|
||||
36
modules/remote_bitrate_estimator/tools/bwe_rtp.h
Normal file
36
modules/remote_bitrate_estimator/tools/bwe_rtp.h
Normal 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_
|
||||
114
modules/remote_bitrate_estimator/tools/bwe_rtp_play.cc
Normal file
114
modules/remote_bitrate_estimator/tools/bwe_rtp_play.cc
Normal 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;
|
||||
}
|
||||
70
modules/remote_bitrate_estimator/tools/rtp_to_text.cc
Normal file
70
modules/remote_bitrate_estimator/tools/rtp_to_text.cc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user