Adding task queue based congestion controller.
To be able to safely move over to the new code, the revised code is added alongside the old code. Most of the files added in this CL are more or less direct copies of the previous code. This new version of send side congestion controller will be activated under a field trial in a followup CL. Bug: webrtc:8415 Change-Id: I034e583cf891a8f6357119739a1517cc0a4abe88 Reviewed-on: https://webrtc-review.googlesource.com/53322 Commit-Queue: Sebastian Jansson <srte@webrtc.org> Reviewed-by: Björn Terelius <terelius@webrtc.org> Cr-Commit-Position: refs/heads/master@{#22212}
This commit is contained in:
parent
1631dc6118
commit
6bcd7f6618
276
modules/congestion_controller/rtp/BUILD.gn
Normal file
276
modules/congestion_controller/rtp/BUILD.gn
Normal file
@ -0,0 +1,276 @@
|
||||
# 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")
|
||||
|
||||
config("bwe_test_logging") {
|
||||
if (rtc_enable_bwe_test_logging) {
|
||||
defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=1" ]
|
||||
} else {
|
||||
defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0" ]
|
||||
}
|
||||
}
|
||||
|
||||
rtc_static_library("congestion_controller") {
|
||||
visibility = [ "*" ]
|
||||
configs += [ ":bwe_test_logging" ]
|
||||
sources = [
|
||||
"include/receive_side_congestion_controller.h",
|
||||
"include/send_side_congestion_controller.h",
|
||||
"pacer_controller.cc",
|
||||
"pacer_controller.h",
|
||||
"receive_side_congestion_controller.cc",
|
||||
"send_side_congestion_controller.cc",
|
||||
]
|
||||
|
||||
# TODO(jschuh): Bug 1348: fix this warning.
|
||||
configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
|
||||
|
||||
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 = [
|
||||
":goog_cc",
|
||||
":transport_feedback",
|
||||
"../..:module_api",
|
||||
"../../..:webrtc_common",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rate_limiter",
|
||||
"../../../rtc_base:rtc_task_queue_api",
|
||||
"../../../rtc_base:sequenced_task_checker",
|
||||
"../../../system_wrappers",
|
||||
"../../../system_wrappers:field_trial_api",
|
||||
"../../../system_wrappers:metrics_api",
|
||||
"../../../system_wrappers:runtime_enabled_features_api",
|
||||
"../../bitrate_controller",
|
||||
"../../pacing",
|
||||
"../../remote_bitrate_estimator",
|
||||
"../../rtp_rtcp:rtp_rtcp_format",
|
||||
"network_control",
|
||||
]
|
||||
|
||||
if (!build_with_mozilla) {
|
||||
deps += [ "../../../rtc_base:rtc_base" ]
|
||||
}
|
||||
}
|
||||
|
||||
rtc_static_library("transport_feedback") {
|
||||
visibility = [ "*" ]
|
||||
sources = [
|
||||
"send_time_history.cc",
|
||||
"send_time_history.h",
|
||||
"transport_feedback_adapter.cc",
|
||||
"transport_feedback_adapter.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../..:module_api",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
"../../../system_wrappers",
|
||||
"../../rtp_rtcp:rtp_rtcp_format",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_static_library("goog_cc") {
|
||||
configs += [ ":bwe_test_logging" ]
|
||||
sources = [
|
||||
"alr_detector.cc",
|
||||
"alr_detector.h",
|
||||
"goog_cc_network_control.cc",
|
||||
"goog_cc_network_control.h",
|
||||
"include/goog_cc_factory.h",
|
||||
"probe_controller.cc",
|
||||
"probe_controller.h",
|
||||
]
|
||||
|
||||
# TODO(jschuh): Bug 1348: fix this warning.
|
||||
configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
|
||||
|
||||
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 = [
|
||||
":delay_based_bwe",
|
||||
":estimators",
|
||||
"../..:module_api",
|
||||
"../../..:webrtc_common",
|
||||
"../../../:typedefs",
|
||||
"../../../api:optional",
|
||||
"../../../logging:rtc_event_log_api",
|
||||
"../../../logging:rtc_event_pacing",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
"../../../rtc_base/experiments:alr_experiment",
|
||||
"../../../system_wrappers",
|
||||
"../../../system_wrappers:field_trial_api",
|
||||
"../../../system_wrappers:metrics_api",
|
||||
"../../bitrate_controller",
|
||||
"../../pacing",
|
||||
"../../remote_bitrate_estimator",
|
||||
"../../rtp_rtcp:rtp_rtcp_format",
|
||||
"network_control",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_source_set("estimators") {
|
||||
configs += [ ":bwe_test_logging" ]
|
||||
sources = [
|
||||
"acknowledged_bitrate_estimator.cc",
|
||||
"acknowledged_bitrate_estimator.h",
|
||||
"bitrate_estimator.cc",
|
||||
"bitrate_estimator.h",
|
||||
"delay_increase_detector_interface.h",
|
||||
"median_slope_estimator.cc",
|
||||
"median_slope_estimator.h",
|
||||
"probe_bitrate_estimator.cc",
|
||||
"probe_bitrate_estimator.h",
|
||||
"trendline_estimator.cc",
|
||||
"trendline_estimator.h",
|
||||
]
|
||||
|
||||
# TODO(jschuh): Bug 1348: fix this warning.
|
||||
configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
|
||||
|
||||
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 = [
|
||||
"../../../api:optional",
|
||||
"../../../logging:rtc_event_bwe",
|
||||
"../../../logging:rtc_event_log_api",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
"../../../rtc_base:rtc_numerics",
|
||||
"../../../system_wrappers:field_trial_api",
|
||||
"../../../system_wrappers:metrics_api",
|
||||
"../../remote_bitrate_estimator",
|
||||
"../../rtp_rtcp:rtp_rtcp_format",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_source_set("delay_based_bwe") {
|
||||
configs += [ ":bwe_test_logging" ]
|
||||
sources = [
|
||||
"delay_based_bwe.cc",
|
||||
"delay_based_bwe.h",
|
||||
]
|
||||
deps = [
|
||||
":estimators",
|
||||
"../../../:typedefs",
|
||||
"../../../logging:rtc_event_bwe",
|
||||
"../../../logging:rtc_event_log_api",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
"../../../system_wrappers:field_trial_api",
|
||||
"../../../system_wrappers:metrics_api",
|
||||
"../../pacing",
|
||||
"../../remote_bitrate_estimator",
|
||||
]
|
||||
|
||||
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 (rtc_include_tests) {
|
||||
rtc_source_set("congestion_controller_unittests") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"congestion_controller_unittests_helper.cc",
|
||||
"congestion_controller_unittests_helper.h",
|
||||
"receive_side_congestion_controller_unittest.cc",
|
||||
"send_side_congestion_controller_unittest.cc",
|
||||
"send_time_history_unittest.cc",
|
||||
"transport_feedback_adapter_unittest.cc",
|
||||
]
|
||||
deps = [
|
||||
":congestion_controller",
|
||||
":goog_cc_unittests",
|
||||
":mock_congestion_controller",
|
||||
":transport_feedback",
|
||||
"../../../logging:mocks",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
"../../../rtc_base:rtc_base_tests_utils",
|
||||
"../../../system_wrappers",
|
||||
"../../../test:field_trial",
|
||||
"../../../test:test_support",
|
||||
"../../bitrate_controller:mocks",
|
||||
"../../pacing:mock_paced_sender",
|
||||
"../../pacing:pacing",
|
||||
"../../remote_bitrate_estimator:remote_bitrate_estimator",
|
||||
"../../rtp_rtcp:rtp_rtcp_format",
|
||||
"network_control",
|
||||
"//testing/gmock",
|
||||
]
|
||||
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("goog_cc_unittests") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"acknowledged_bitrate_estimator_unittest.cc",
|
||||
"alr_detector_unittest.cc",
|
||||
"delay_based_bwe_unittest.cc",
|
||||
"delay_based_bwe_unittest_helper.cc",
|
||||
"delay_based_bwe_unittest_helper.h",
|
||||
"median_slope_estimator_unittest.cc",
|
||||
"probe_bitrate_estimator_unittest.cc",
|
||||
"probe_controller_unittest.cc",
|
||||
"trendline_estimator_unittest.cc",
|
||||
]
|
||||
deps = [
|
||||
":delay_based_bwe",
|
||||
":estimators",
|
||||
":goog_cc",
|
||||
"../../../rtc_base:checks",
|
||||
"../../../rtc_base:rtc_base_approved",
|
||||
"../../../rtc_base:rtc_base_tests_utils",
|
||||
"../../../rtc_base/experiments:alr_experiment",
|
||||
"../../../system_wrappers",
|
||||
"../../../test:field_trial",
|
||||
"../../../test:test_support",
|
||||
"../../pacing",
|
||||
"../../remote_bitrate_estimator",
|
||||
"../../rtp_rtcp:rtp_rtcp_format",
|
||||
"network_control",
|
||||
"network_control:network_control_unittests",
|
||||
"//testing/gmock",
|
||||
]
|
||||
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("mock_congestion_controller") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"include/mock/mock_congestion_observer.h",
|
||||
"include/mock/mock_send_side_congestion_controller.h",
|
||||
]
|
||||
deps = [
|
||||
":congestion_controller",
|
||||
"../../../test:test_support",
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -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 "modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
#include "rtc_base/ptr_util.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
bool IsInSendTimeHistory(const PacketFeedback& packet) {
|
||||
return packet.send_time_ms != PacketFeedback::kNoSendTime;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator()
|
||||
: AcknowledgedBitrateEstimator(rtc::MakeUnique<BitrateEstimator>()) {}
|
||||
|
||||
AcknowledgedBitrateEstimator::~AcknowledgedBitrateEstimator() {}
|
||||
|
||||
AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator(
|
||||
std::unique_ptr<BitrateEstimator> bitrate_estimator)
|
||||
: bitrate_estimator_(std::move(bitrate_estimator)) {}
|
||||
|
||||
void AcknowledgedBitrateEstimator::IncomingPacketFeedbackVector(
|
||||
const std::vector<PacketFeedback>& packet_feedback_vector) {
|
||||
RTC_DCHECK(std::is_sorted(packet_feedback_vector.begin(),
|
||||
packet_feedback_vector.end(),
|
||||
PacketFeedbackComparator()));
|
||||
for (const auto& packet : packet_feedback_vector) {
|
||||
if (IsInSendTimeHistory(packet)) {
|
||||
MaybeExpectFastRateChange(packet.send_time_ms);
|
||||
bitrate_estimator_->Update(packet.arrival_time_ms,
|
||||
rtc::dchecked_cast<int>(packet.payload_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rtc::Optional<uint32_t> AcknowledgedBitrateEstimator::bitrate_bps() const {
|
||||
return bitrate_estimator_->bitrate_bps();
|
||||
}
|
||||
|
||||
void AcknowledgedBitrateEstimator::SetAlrEndedTimeMs(
|
||||
int64_t alr_ended_time_ms) {
|
||||
alr_ended_time_ms_.emplace(alr_ended_time_ms);
|
||||
}
|
||||
|
||||
void AcknowledgedBitrateEstimator::MaybeExpectFastRateChange(
|
||||
int64_t packet_send_time_ms) {
|
||||
if (alr_ended_time_ms_ && packet_send_time_ms > *alr_ended_time_ms_) {
|
||||
bitrate_estimator_->ExpectFastRateChange();
|
||||
alr_ended_time_ms_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 MODULES_CONGESTION_CONTROLLER_RTP_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "api/optional.h"
|
||||
#include "modules/congestion_controller/rtp/bitrate_estimator.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct PacketFeedback;
|
||||
|
||||
class AcknowledgedBitrateEstimator {
|
||||
public:
|
||||
explicit AcknowledgedBitrateEstimator(
|
||||
std::unique_ptr<BitrateEstimator> bitrate_estimator);
|
||||
|
||||
AcknowledgedBitrateEstimator();
|
||||
~AcknowledgedBitrateEstimator();
|
||||
|
||||
void IncomingPacketFeedbackVector(
|
||||
const std::vector<PacketFeedback>& packet_feedback_vector);
|
||||
rtc::Optional<uint32_t> bitrate_bps() const;
|
||||
void SetAlrEndedTimeMs(int64_t alr_ended_time_ms);
|
||||
|
||||
private:
|
||||
void MaybeExpectFastRateChange(int64_t packet_arrival_time_ms);
|
||||
rtc::Optional<int64_t> alr_ended_time_ms_;
|
||||
std::unique_ptr<BitrateEstimator> bitrate_estimator_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_
|
||||
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "rtc_base/fakeclock.h"
|
||||
#include "rtc_base/ptr_util.h"
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
using testing::_;
|
||||
using testing::NiceMock;
|
||||
using testing::InSequence;
|
||||
using testing::Return;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int64_t kFirstArrivalTimeMs = 10;
|
||||
constexpr int64_t kFirstSendTimeMs = 10;
|
||||
constexpr uint16_t kSequenceNumber = 1;
|
||||
constexpr size_t kPayloadSize = 10;
|
||||
|
||||
class MockBitrateEstimator : public BitrateEstimator {
|
||||
public:
|
||||
MOCK_METHOD2(Update, void(int64_t now_ms, int bytes));
|
||||
MOCK_CONST_METHOD0(bitrate_bps, rtc::Optional<uint32_t>());
|
||||
MOCK_METHOD0(ExpectFastRateChange, void());
|
||||
};
|
||||
|
||||
struct AcknowledgedBitrateEstimatorTestStates {
|
||||
std::unique_ptr<AcknowledgedBitrateEstimator> acknowledged_bitrate_estimator;
|
||||
MockBitrateEstimator* mock_bitrate_estimator;
|
||||
};
|
||||
|
||||
AcknowledgedBitrateEstimatorTestStates CreateTestStates() {
|
||||
AcknowledgedBitrateEstimatorTestStates states;
|
||||
auto mock_bitrate_estimator = rtc::MakeUnique<MockBitrateEstimator>();
|
||||
states.mock_bitrate_estimator = mock_bitrate_estimator.get();
|
||||
states.acknowledged_bitrate_estimator =
|
||||
rtc::MakeUnique<AcknowledgedBitrateEstimator>(
|
||||
std::move(mock_bitrate_estimator));
|
||||
return states;
|
||||
}
|
||||
|
||||
std::vector<PacketFeedback> CreateFeedbackVector() {
|
||||
std::vector<PacketFeedback> packet_feedback_vector;
|
||||
const PacedPacketInfo pacing_info;
|
||||
packet_feedback_vector.push_back(
|
||||
PacketFeedback(kFirstArrivalTimeMs, kFirstSendTimeMs, kSequenceNumber,
|
||||
kPayloadSize, pacing_info));
|
||||
packet_feedback_vector.push_back(
|
||||
PacketFeedback(kFirstArrivalTimeMs + 10, kFirstSendTimeMs + 10,
|
||||
kSequenceNumber, kPayloadSize + 10, pacing_info));
|
||||
return packet_feedback_vector;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST(TestAcknowledgedBitrateEstimator, DontAddPacketsWhichAreNotInSendHistory) {
|
||||
auto states = CreateTestStates();
|
||||
std::vector<PacketFeedback> packet_feedback_vector;
|
||||
packet_feedback_vector.push_back(
|
||||
PacketFeedback(kFirstArrivalTimeMs, kSequenceNumber));
|
||||
EXPECT_CALL(*states.mock_bitrate_estimator, Update(_, _)).Times(0);
|
||||
states.acknowledged_bitrate_estimator->IncomingPacketFeedbackVector(
|
||||
packet_feedback_vector);
|
||||
}
|
||||
|
||||
TEST(TestAcknowledgedBitrateEstimator, UpdateBandwith) {
|
||||
auto states = CreateTestStates();
|
||||
auto packet_feedback_vector = CreateFeedbackVector();
|
||||
{
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(
|
||||
*states.mock_bitrate_estimator,
|
||||
Update(packet_feedback_vector[0].arrival_time_ms,
|
||||
static_cast<int>(packet_feedback_vector[0].payload_size)))
|
||||
.Times(1);
|
||||
EXPECT_CALL(
|
||||
*states.mock_bitrate_estimator,
|
||||
Update(packet_feedback_vector[1].arrival_time_ms,
|
||||
static_cast<int>(packet_feedback_vector[1].payload_size)))
|
||||
.Times(1);
|
||||
}
|
||||
states.acknowledged_bitrate_estimator->IncomingPacketFeedbackVector(
|
||||
packet_feedback_vector);
|
||||
}
|
||||
|
||||
TEST(TestAcknowledgedBitrateEstimator, ExpectFastRateChangeWhenLeftAlr) {
|
||||
auto states = CreateTestStates();
|
||||
auto packet_feedback_vector = CreateFeedbackVector();
|
||||
{
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(
|
||||
*states.mock_bitrate_estimator,
|
||||
Update(packet_feedback_vector[0].arrival_time_ms,
|
||||
static_cast<int>(packet_feedback_vector[0].payload_size)))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*states.mock_bitrate_estimator, ExpectFastRateChange())
|
||||
.Times(1);
|
||||
EXPECT_CALL(
|
||||
*states.mock_bitrate_estimator,
|
||||
Update(packet_feedback_vector[1].arrival_time_ms,
|
||||
static_cast<int>(packet_feedback_vector[1].payload_size)))
|
||||
.Times(1);
|
||||
}
|
||||
states.acknowledged_bitrate_estimator->SetAlrEndedTimeMs(kFirstArrivalTimeMs +
|
||||
1);
|
||||
states.acknowledged_bitrate_estimator->IncomingPacketFeedbackVector(
|
||||
packet_feedback_vector);
|
||||
}
|
||||
|
||||
TEST(TestAcknowledgedBitrateEstimator, ReturnBitrate) {
|
||||
auto states = CreateTestStates();
|
||||
rtc::Optional<uint32_t> return_value(42);
|
||||
EXPECT_CALL(*states.mock_bitrate_estimator, bitrate_bps())
|
||||
.Times(1)
|
||||
.WillOnce(Return(return_value));
|
||||
EXPECT_EQ(return_value, states.acknowledged_bitrate_estimator->bitrate_bps());
|
||||
}
|
||||
|
||||
} // namespace webrtc*/
|
||||
95
modules/congestion_controller/rtp/alr_detector.cc
Normal file
95
modules/congestion_controller/rtp/alr_detector.cc
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/alr_detector.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "logging/rtc_event_log/events/rtc_event_alr_state.h"
|
||||
#include "logging/rtc_event_log/rtc_event_log.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/experiments/alr_experiment.h"
|
||||
#include "rtc_base/format_macros.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/ptr_util.h"
|
||||
#include "rtc_base/timeutils.h"
|
||||
#include "system_wrappers/include/field_trial.h"
|
||||
|
||||
namespace webrtc {
|
||||
AlrDetector::AlrDetector() : AlrDetector(nullptr) {}
|
||||
|
||||
AlrDetector::AlrDetector(RtcEventLog* event_log)
|
||||
: bandwidth_usage_percent_(kDefaultAlrBandwidthUsagePercent),
|
||||
alr_start_budget_level_percent_(kDefaultAlrStartBudgetLevelPercent),
|
||||
alr_stop_budget_level_percent_(kDefaultAlrStopBudgetLevelPercent),
|
||||
alr_budget_(0, true),
|
||||
event_log_(event_log) {
|
||||
RTC_CHECK(AlrExperimentSettings::MaxOneFieldTrialEnabled());
|
||||
rtc::Optional<AlrExperimentSettings> experiment_settings =
|
||||
AlrExperimentSettings::CreateFromFieldTrial(
|
||||
AlrExperimentSettings::kScreenshareProbingBweExperimentName);
|
||||
if (!experiment_settings) {
|
||||
experiment_settings = AlrExperimentSettings::CreateFromFieldTrial(
|
||||
AlrExperimentSettings::kStrictPacingAndProbingExperimentName);
|
||||
}
|
||||
if (experiment_settings) {
|
||||
alr_stop_budget_level_percent_ =
|
||||
experiment_settings->alr_stop_budget_level_percent;
|
||||
alr_start_budget_level_percent_ =
|
||||
experiment_settings->alr_start_budget_level_percent;
|
||||
bandwidth_usage_percent_ = experiment_settings->alr_bandwidth_usage_percent;
|
||||
}
|
||||
}
|
||||
|
||||
AlrDetector::~AlrDetector() {}
|
||||
|
||||
void AlrDetector::OnBytesSent(size_t bytes_sent, int64_t send_time_ms) {
|
||||
if (!last_send_time_ms_.has_value()) {
|
||||
last_send_time_ms_ = send_time_ms;
|
||||
// Since the duration for sending the bytes is unknwon, return without
|
||||
// updating alr state.
|
||||
return;
|
||||
}
|
||||
int64_t delta_time_ms = send_time_ms - *last_send_time_ms_;
|
||||
last_send_time_ms_ = send_time_ms;
|
||||
|
||||
alr_budget_.UseBudget(bytes_sent);
|
||||
alr_budget_.IncreaseBudget(delta_time_ms);
|
||||
bool state_changed = false;
|
||||
if (alr_budget_.budget_level_percent() > alr_start_budget_level_percent_ &&
|
||||
!alr_started_time_ms_) {
|
||||
alr_started_time_ms_.emplace(rtc::TimeMillis());
|
||||
state_changed = true;
|
||||
} else if (alr_budget_.budget_level_percent() <
|
||||
alr_stop_budget_level_percent_ &&
|
||||
alr_started_time_ms_) {
|
||||
state_changed = true;
|
||||
alr_started_time_ms_.reset();
|
||||
}
|
||||
if (event_log_ && state_changed) {
|
||||
event_log_->Log(
|
||||
rtc::MakeUnique<RtcEventAlrState>(alr_started_time_ms_.has_value()));
|
||||
}
|
||||
}
|
||||
|
||||
void AlrDetector::SetEstimatedBitrate(int bitrate_bps) {
|
||||
RTC_DCHECK(bitrate_bps);
|
||||
const auto target_rate_kbps = static_cast<int64_t>(bitrate_bps) *
|
||||
bandwidth_usage_percent_ / (1000 * 100);
|
||||
alr_budget_.set_target_rate_kbps(rtc::dchecked_cast<int>(target_rate_kbps));
|
||||
}
|
||||
|
||||
rtc::Optional<int64_t> AlrDetector::GetApplicationLimitedRegionStartTime()
|
||||
const {
|
||||
return alr_started_time_ms_;
|
||||
}
|
||||
} // namespace webrtc
|
||||
74
modules/congestion_controller/rtp/alr_detector.h
Normal file
74
modules/congestion_controller/rtp/alr_detector.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_RTP_ALR_DETECTOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_ALR_DETECTOR_H_
|
||||
|
||||
#include "api/optional.h"
|
||||
#include "common_types.h" // NOLINT(build/include)
|
||||
#include "modules/pacing/interval_budget.h"
|
||||
#include "modules/pacing/paced_sender.h"
|
||||
#include "rtc_base/rate_statistics.h"
|
||||
#include "typedefs.h" // NOLINT(build/include)
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class RtcEventLog;
|
||||
|
||||
// Application limited region detector is a class that utilizes signals of
|
||||
// elapsed time and bytes sent to estimate whether network traffic is
|
||||
// currently limited by the application's ability to generate traffic.
|
||||
//
|
||||
// AlrDetector provides a signal that can be utilized to adjust
|
||||
// estimate bandwidth.
|
||||
// Note: This class is not thread-safe.
|
||||
class AlrDetector {
|
||||
public:
|
||||
AlrDetector();
|
||||
explicit AlrDetector(RtcEventLog* event_log);
|
||||
~AlrDetector();
|
||||
|
||||
void OnBytesSent(size_t bytes_sent, int64_t send_time_ms);
|
||||
|
||||
// Set current estimated bandwidth.
|
||||
void SetEstimatedBitrate(int bitrate_bps);
|
||||
|
||||
// Returns time in milliseconds when the current application-limited region
|
||||
// started or empty result if the sender is currently not application-limited.
|
||||
rtc::Optional<int64_t> GetApplicationLimitedRegionStartTime() const;
|
||||
|
||||
// Sent traffic percentage as a function of network capacity used to determine
|
||||
// application-limited region. ALR region start when bandwidth usage drops
|
||||
// below kAlrStartUsagePercent and ends when it raises above
|
||||
// kAlrEndUsagePercent. NOTE: This is intentionally conservative at the moment
|
||||
// until BW adjustments of application limited region is fine tuned.
|
||||
static constexpr int kDefaultAlrBandwidthUsagePercent = 65;
|
||||
static constexpr int kDefaultAlrStartBudgetLevelPercent = 80;
|
||||
static constexpr int kDefaultAlrStopBudgetLevelPercent = 50;
|
||||
|
||||
void UpdateBudgetWithElapsedTime(int64_t delta_time_ms);
|
||||
void UpdateBudgetWithBytesSent(size_t bytes_sent);
|
||||
|
||||
private:
|
||||
int bandwidth_usage_percent_;
|
||||
int alr_start_budget_level_percent_;
|
||||
int alr_stop_budget_level_percent_;
|
||||
|
||||
rtc::Optional<int64_t> last_send_time_ms_;
|
||||
|
||||
IntervalBudget alr_budget_;
|
||||
rtc::Optional<int64_t> alr_started_time_ms_;
|
||||
|
||||
RtcEventLog* event_log_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_ALR_DETECTOR_H_
|
||||
175
modules/congestion_controller/rtp/alr_detector_unittest.cc
Normal file
175
modules/congestion_controller/rtp/alr_detector_unittest.cc
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/alr_detector.h"
|
||||
|
||||
#include "rtc_base/experiments/alr_experiment.h"
|
||||
#include "test/field_trial.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kEstimatedBitrateBps = 300000;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
class SimulateOutgoingTrafficIn {
|
||||
public:
|
||||
explicit SimulateOutgoingTrafficIn(AlrDetector* alr_detector,
|
||||
int64_t* timestamp_ms)
|
||||
: alr_detector_(alr_detector), timestamp_ms_(timestamp_ms) {
|
||||
RTC_CHECK(alr_detector_);
|
||||
}
|
||||
|
||||
SimulateOutgoingTrafficIn& ForTimeMs(int time_ms) {
|
||||
interval_ms_ = time_ms;
|
||||
ProduceTraffic();
|
||||
return *this;
|
||||
}
|
||||
|
||||
SimulateOutgoingTrafficIn& AtPercentOfEstimatedBitrate(int usage_percentage) {
|
||||
usage_percentage_.emplace(usage_percentage);
|
||||
ProduceTraffic();
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
void ProduceTraffic() {
|
||||
if (!interval_ms_ || !usage_percentage_)
|
||||
return;
|
||||
const int kTimeStepMs = 10;
|
||||
for (int t = 0; t < *interval_ms_; t += kTimeStepMs) {
|
||||
*timestamp_ms_ += kTimeStepMs;
|
||||
alr_detector_->OnBytesSent(kEstimatedBitrateBps * *usage_percentage_ *
|
||||
kTimeStepMs / (8 * 100 * 1000),
|
||||
*timestamp_ms_);
|
||||
}
|
||||
int remainder_ms = *interval_ms_ % kTimeStepMs;
|
||||
if (remainder_ms > 0) {
|
||||
*timestamp_ms_ += kTimeStepMs;
|
||||
alr_detector_->OnBytesSent(kEstimatedBitrateBps * *usage_percentage_ *
|
||||
remainder_ms / (8 * 100 * 1000),
|
||||
*timestamp_ms_);
|
||||
}
|
||||
}
|
||||
AlrDetector* const alr_detector_;
|
||||
int64_t* timestamp_ms_;
|
||||
rtc::Optional<int> interval_ms_;
|
||||
rtc::Optional<int> usage_percentage_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class AlrDetectorTest : public testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
alr_detector_.SetEstimatedBitrate(kEstimatedBitrateBps);
|
||||
}
|
||||
|
||||
protected:
|
||||
AlrDetector alr_detector_;
|
||||
int64_t timestamp_ms_ = 1000;
|
||||
};
|
||||
|
||||
TEST_F(AlrDetectorTest, AlrDetection) {
|
||||
// Start in non-ALR state.
|
||||
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
|
||||
// Stay in non-ALR state when usage is close to 100%.
|
||||
SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_)
|
||||
.ForTimeMs(1000)
|
||||
.AtPercentOfEstimatedBitrate(90);
|
||||
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
|
||||
// Verify that we ALR starts when bitrate drops below 20%.
|
||||
SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_)
|
||||
.ForTimeMs(1500)
|
||||
.AtPercentOfEstimatedBitrate(20);
|
||||
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
|
||||
// Verify that ALR ends when usage is above 65%.
|
||||
SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_)
|
||||
.ForTimeMs(4000)
|
||||
.AtPercentOfEstimatedBitrate(100);
|
||||
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
}
|
||||
|
||||
TEST_F(AlrDetectorTest, ShortSpike) {
|
||||
// Start in non-ALR state.
|
||||
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
|
||||
// Verify that we ALR starts when bitrate drops below 20%.
|
||||
SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_)
|
||||
.ForTimeMs(1000)
|
||||
.AtPercentOfEstimatedBitrate(20);
|
||||
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
|
||||
// Verify that we stay in ALR region even after a short bitrate spike.
|
||||
SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_)
|
||||
.ForTimeMs(100)
|
||||
.AtPercentOfEstimatedBitrate(150);
|
||||
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
|
||||
// ALR ends when usage is above 65%.
|
||||
SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_)
|
||||
.ForTimeMs(3000)
|
||||
.AtPercentOfEstimatedBitrate(100);
|
||||
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
}
|
||||
|
||||
TEST_F(AlrDetectorTest, BandwidthEstimateChanges) {
|
||||
// Start in non-ALR state.
|
||||
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
|
||||
// ALR starts when bitrate drops below 20%.
|
||||
SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_)
|
||||
.ForTimeMs(1000)
|
||||
.AtPercentOfEstimatedBitrate(20);
|
||||
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
|
||||
// When bandwidth estimate drops the detector should stay in ALR mode and quit
|
||||
// it shortly afterwards as the sender continues sending the same amount of
|
||||
// traffic. This is necessary to ensure that ProbeController can still react
|
||||
// to the BWE drop by initiating a new probe.
|
||||
alr_detector_.SetEstimatedBitrate(kEstimatedBitrateBps / 5);
|
||||
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
SimulateOutgoingTrafficIn(&alr_detector_, ×tamp_ms_)
|
||||
.ForTimeMs(1000)
|
||||
.AtPercentOfEstimatedBitrate(50);
|
||||
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
|
||||
}
|
||||
|
||||
TEST_F(AlrDetectorTest, ParseControlFieldTrial) {
|
||||
webrtc::test::ScopedFieldTrials field_trial(
|
||||
"WebRTC-ProbingScreenshareBwe/Control/");
|
||||
rtc::Optional<AlrExperimentSettings> parsed_params =
|
||||
AlrExperimentSettings::CreateFromFieldTrial(
|
||||
"WebRTC-ProbingScreenshareBwe");
|
||||
EXPECT_FALSE(static_cast<bool>(parsed_params));
|
||||
}
|
||||
|
||||
TEST_F(AlrDetectorTest, ParseActiveFieldTrial) {
|
||||
webrtc::test::ScopedFieldTrials field_trial(
|
||||
"WebRTC-ProbingScreenshareBwe/1.1,2875,85,20,-20,1/");
|
||||
rtc::Optional<AlrExperimentSettings> parsed_params =
|
||||
AlrExperimentSettings::CreateFromFieldTrial(
|
||||
"WebRTC-ProbingScreenshareBwe");
|
||||
ASSERT_TRUE(static_cast<bool>(parsed_params));
|
||||
EXPECT_EQ(1.1f, parsed_params->pacing_factor);
|
||||
EXPECT_EQ(2875, parsed_params->max_paced_queue_time);
|
||||
EXPECT_EQ(85, parsed_params->alr_bandwidth_usage_percent);
|
||||
EXPECT_EQ(20, parsed_params->alr_start_budget_level_percent);
|
||||
EXPECT_EQ(-20, parsed_params->alr_stop_budget_level_percent);
|
||||
EXPECT_EQ(1, parsed_params->group_id);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
107
modules/congestion_controller/rtp/bitrate_estimator.cc
Normal file
107
modules/congestion_controller/rtp/bitrate_estimator.cc
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/bitrate_estimator.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
constexpr int kInitialRateWindowMs = 500;
|
||||
constexpr int kRateWindowMs = 150;
|
||||
} // namespace
|
||||
|
||||
BitrateEstimator::BitrateEstimator()
|
||||
: sum_(0),
|
||||
current_win_ms_(0),
|
||||
prev_time_ms_(-1),
|
||||
bitrate_estimate_(-1.0f),
|
||||
bitrate_estimate_var_(50.0f) {}
|
||||
|
||||
BitrateEstimator::~BitrateEstimator() = default;
|
||||
|
||||
void BitrateEstimator::Update(int64_t now_ms, int bytes) {
|
||||
int rate_window_ms = kRateWindowMs;
|
||||
// We use a larger window at the beginning to get a more stable sample that
|
||||
// we can use to initialize the estimate.
|
||||
if (bitrate_estimate_ < 0.f)
|
||||
rate_window_ms = kInitialRateWindowMs;
|
||||
float bitrate_sample = UpdateWindow(now_ms, bytes, rate_window_ms);
|
||||
if (bitrate_sample < 0.0f)
|
||||
return;
|
||||
if (bitrate_estimate_ < 0.0f) {
|
||||
// This is the very first sample we get. Use it to initialize the estimate.
|
||||
bitrate_estimate_ = bitrate_sample;
|
||||
return;
|
||||
}
|
||||
// Define the sample uncertainty as a function of how far away it is from the
|
||||
// current estimate.
|
||||
float sample_uncertainty =
|
||||
10.0f * std::abs(bitrate_estimate_ - bitrate_sample) / bitrate_estimate_;
|
||||
float sample_var = sample_uncertainty * sample_uncertainty;
|
||||
// Update a bayesian estimate of the rate, weighting it lower if the sample
|
||||
// uncertainty is large.
|
||||
// The bitrate estimate uncertainty is increased with each update to model
|
||||
// that the bitrate changes over time.
|
||||
float pred_bitrate_estimate_var = bitrate_estimate_var_ + 5.f;
|
||||
bitrate_estimate_ = (sample_var * bitrate_estimate_ +
|
||||
pred_bitrate_estimate_var * bitrate_sample) /
|
||||
(sample_var + pred_bitrate_estimate_var);
|
||||
bitrate_estimate_var_ = sample_var * pred_bitrate_estimate_var /
|
||||
(sample_var + pred_bitrate_estimate_var);
|
||||
BWE_TEST_LOGGING_PLOT(1, "acknowledged_bitrate", now_ms,
|
||||
bitrate_estimate_ * 1000);
|
||||
}
|
||||
|
||||
float BitrateEstimator::UpdateWindow(int64_t now_ms,
|
||||
int bytes,
|
||||
int rate_window_ms) {
|
||||
// Reset if time moves backwards.
|
||||
if (now_ms < prev_time_ms_) {
|
||||
prev_time_ms_ = -1;
|
||||
sum_ = 0;
|
||||
current_win_ms_ = 0;
|
||||
}
|
||||
if (prev_time_ms_ >= 0) {
|
||||
current_win_ms_ += now_ms - prev_time_ms_;
|
||||
// Reset if nothing has been received for more than a full window.
|
||||
if (now_ms - prev_time_ms_ > rate_window_ms) {
|
||||
sum_ = 0;
|
||||
current_win_ms_ %= rate_window_ms;
|
||||
}
|
||||
}
|
||||
prev_time_ms_ = now_ms;
|
||||
float bitrate_sample = -1.0f;
|
||||
if (current_win_ms_ >= rate_window_ms) {
|
||||
bitrate_sample = 8.0f * sum_ / static_cast<float>(rate_window_ms);
|
||||
current_win_ms_ -= rate_window_ms;
|
||||
sum_ = 0;
|
||||
}
|
||||
sum_ += bytes;
|
||||
return bitrate_sample;
|
||||
}
|
||||
|
||||
rtc::Optional<uint32_t> BitrateEstimator::bitrate_bps() const {
|
||||
if (bitrate_estimate_ < 0.f)
|
||||
return rtc::nullopt;
|
||||
return bitrate_estimate_ * 1000;
|
||||
}
|
||||
|
||||
void BitrateEstimator::ExpectFastRateChange() {
|
||||
// By setting the bitrate-estimate variance to a higher value we allow the
|
||||
// bitrate to change fast for the next few samples.
|
||||
bitrate_estimate_var_ += 200;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
46
modules/congestion_controller/rtp/bitrate_estimator.h
Normal file
46
modules/congestion_controller/rtp/bitrate_estimator.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 MODULES_CONGESTION_CONTROLLER_RTP_BITRATE_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_BITRATE_ESTIMATOR_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "api/optional.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Computes a bayesian estimate of the throughput given acks containing
|
||||
// the arrival time and payload size. Samples which are far from the current
|
||||
// estimate or are based on few packets are given a smaller weight, as they
|
||||
// are considered to be more likely to have been caused by, e.g., delay spikes
|
||||
// unrelated to congestion.
|
||||
class BitrateEstimator {
|
||||
public:
|
||||
BitrateEstimator();
|
||||
virtual ~BitrateEstimator();
|
||||
virtual void Update(int64_t now_ms, int bytes);
|
||||
|
||||
virtual rtc::Optional<uint32_t> bitrate_bps() const;
|
||||
|
||||
virtual void ExpectFastRateChange();
|
||||
|
||||
private:
|
||||
float UpdateWindow(int64_t now_ms, int bytes, int rate_window_ms);
|
||||
int sum_;
|
||||
int64_t current_win_ms_;
|
||||
int64_t prev_time_ms_;
|
||||
float bitrate_estimate_;
|
||||
float bitrate_estimate_var_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_BITRATE_ESTIMATOR_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 "modules/congestion_controller/rtp/congestion_controller_unittests_helper.h"
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
void ComparePacketFeedbackVectors(const std::vector<PacketFeedback>& truth,
|
||||
const std::vector<PacketFeedback>& input) {
|
||||
ASSERT_EQ(truth.size(), input.size());
|
||||
size_t len = truth.size();
|
||||
// truth contains the input data for the test, and input is what will be
|
||||
// sent to the bandwidth estimator. truth.arrival_tims_ms is used to
|
||||
// populate the transport feedback messages. As these times may be changed
|
||||
// (because of resolution limits in the packets, and because of the time
|
||||
// base adjustment performed by the TransportFeedbackAdapter at the first
|
||||
// packet, the truth[x].arrival_time and input[x].arrival_time may not be
|
||||
// equal. However, the difference must be the same for all x.
|
||||
int64_t arrival_time_delta =
|
||||
truth[0].arrival_time_ms - input[0].arrival_time_ms;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
RTC_CHECK(truth[i].arrival_time_ms != PacketFeedback::kNotReceived);
|
||||
if (input[i].arrival_time_ms != PacketFeedback::kNotReceived) {
|
||||
EXPECT_EQ(truth[i].arrival_time_ms,
|
||||
input[i].arrival_time_ms + arrival_time_delta);
|
||||
}
|
||||
EXPECT_EQ(truth[i].send_time_ms, input[i].send_time_ms);
|
||||
EXPECT_EQ(truth[i].sequence_number, input[i].sequence_number);
|
||||
EXPECT_EQ(truth[i].payload_size, input[i].payload_size);
|
||||
EXPECT_EQ(truth[i].pacing_info, input[i].pacing_info);
|
||||
}
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 MODULES_CONGESTION_CONTROLLER_RTP_CONGESTION_CONTROLLER_UNITTESTS_HELPER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_CONGESTION_CONTROLLER_UNITTESTS_HELPER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
|
||||
namespace webrtc {
|
||||
void ComparePacketFeedbackVectors(const std::vector<PacketFeedback>& truth,
|
||||
const std::vector<PacketFeedback>& input);
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_CONGESTION_CONTROLLER_UNITTESTS_HELPER_H_
|
||||
329
modules/congestion_controller/rtp/delay_based_bwe.cc
Normal file
329
modules/congestion_controller/rtp/delay_based_bwe.cc
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/delay_based_bwe.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "logging/rtc_event_log/events/rtc_event_bwe_update_delay_based.h"
|
||||
#include "logging/rtc_event_log/rtc_event_log.h"
|
||||
#include "modules/congestion_controller/rtp/trendline_estimator.h"
|
||||
#include "modules/pacing/paced_sender.h"
|
||||
#include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
|
||||
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/ptr_util.h"
|
||||
#include "rtc_base/thread_annotations.h"
|
||||
#include "system_wrappers/include/field_trial.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
#include "typedefs.h" // NOLINT(build/include)
|
||||
|
||||
namespace {
|
||||
constexpr int kTimestampGroupLengthMs = 5;
|
||||
constexpr int kAbsSendTimeFraction = 18;
|
||||
constexpr int kAbsSendTimeInterArrivalUpshift = 8;
|
||||
constexpr int kInterArrivalShift =
|
||||
kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift;
|
||||
constexpr double kTimestampToMs =
|
||||
1000.0 / static_cast<double>(1 << kInterArrivalShift);
|
||||
// This ssrc is used to fulfill the current API but will be removed
|
||||
// after the API has been changed.
|
||||
constexpr uint32_t kFixedSsrc = 0;
|
||||
|
||||
// Parameters for linear least squares fit of regression line to noisy data.
|
||||
constexpr size_t kDefaultTrendlineWindowSize = 20;
|
||||
constexpr double kDefaultTrendlineSmoothingCoeff = 0.9;
|
||||
constexpr double kDefaultTrendlineThresholdGain = 4.0;
|
||||
|
||||
constexpr int kMaxConsecutiveFailedLookups = 5;
|
||||
|
||||
const char kBweWindowSizeInPacketsExperiment[] =
|
||||
"WebRTC-BweWindowSizeInPackets";
|
||||
|
||||
size_t ReadTrendlineFilterWindowSize() {
|
||||
std::string experiment_string =
|
||||
webrtc::field_trial::FindFullName(kBweWindowSizeInPacketsExperiment);
|
||||
size_t window_size;
|
||||
int parsed_values =
|
||||
sscanf(experiment_string.c_str(), "Enabled-%zu", &window_size);
|
||||
if (parsed_values == 1) {
|
||||
if (window_size > 1)
|
||||
return window_size;
|
||||
RTC_LOG(WARNING) << "Window size must be greater than 1.";
|
||||
}
|
||||
RTC_LOG(LS_WARNING) << "Failed to parse parameters for BweTrendlineFilter "
|
||||
"experiment from field trial string. Using default.";
|
||||
return kDefaultTrendlineWindowSize;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
DelayBasedBwe::Result::Result()
|
||||
: updated(false),
|
||||
probe(false),
|
||||
target_bitrate_bps(0),
|
||||
recovered_from_overuse(false) {}
|
||||
|
||||
DelayBasedBwe::Result::Result(bool probe, uint32_t target_bitrate_bps)
|
||||
: updated(true),
|
||||
probe(probe),
|
||||
target_bitrate_bps(target_bitrate_bps),
|
||||
recovered_from_overuse(false) {}
|
||||
|
||||
DelayBasedBwe::Result::~Result() {}
|
||||
|
||||
DelayBasedBwe::DelayBasedBwe(RtcEventLog* event_log)
|
||||
: event_log_(event_log),
|
||||
inter_arrival_(),
|
||||
delay_detector_(),
|
||||
last_seen_packet_ms_(-1),
|
||||
uma_recorded_(false),
|
||||
probe_bitrate_estimator_(event_log),
|
||||
trendline_window_size_(
|
||||
webrtc::field_trial::IsEnabled(kBweWindowSizeInPacketsExperiment)
|
||||
? ReadTrendlineFilterWindowSize()
|
||||
: kDefaultTrendlineWindowSize),
|
||||
trendline_smoothing_coeff_(kDefaultTrendlineSmoothingCoeff),
|
||||
trendline_threshold_gain_(kDefaultTrendlineThresholdGain),
|
||||
consecutive_delayed_feedbacks_(0),
|
||||
prev_bitrate_(0),
|
||||
prev_state_(BandwidthUsage::kBwNormal) {
|
||||
RTC_LOG(LS_INFO)
|
||||
<< "Using Trendline filter for delay change estimation with window size "
|
||||
<< trendline_window_size_;
|
||||
delay_detector_.reset(new TrendlineEstimator(trendline_window_size_,
|
||||
trendline_smoothing_coeff_,
|
||||
trendline_threshold_gain_));
|
||||
}
|
||||
|
||||
DelayBasedBwe::~DelayBasedBwe() {}
|
||||
|
||||
DelayBasedBwe::Result DelayBasedBwe::IncomingPacketFeedbackVector(
|
||||
const std::vector<PacketFeedback>& packet_feedback_vector,
|
||||
rtc::Optional<uint32_t> acked_bitrate_bps,
|
||||
int64_t at_time_ms) {
|
||||
RTC_DCHECK(std::is_sorted(packet_feedback_vector.begin(),
|
||||
packet_feedback_vector.end(),
|
||||
PacketFeedbackComparator()));
|
||||
RTC_DCHECK_RUNS_SERIALIZED(&network_race_);
|
||||
|
||||
// TOOD(holmer): An empty feedback vector here likely means that
|
||||
// all acks were too late and that the send time history had
|
||||
// timed out. We should reduce the rate when this occurs.
|
||||
if (packet_feedback_vector.empty()) {
|
||||
RTC_LOG(LS_WARNING) << "Very late feedback received.";
|
||||
return DelayBasedBwe::Result();
|
||||
}
|
||||
|
||||
if (!uma_recorded_) {
|
||||
RTC_HISTOGRAM_ENUMERATION(kBweTypeHistogram,
|
||||
BweNames::kSendSideTransportSeqNum,
|
||||
BweNames::kBweNamesMax);
|
||||
uma_recorded_ = true;
|
||||
}
|
||||
bool delayed_feedback = true;
|
||||
bool recovered_from_overuse = false;
|
||||
BandwidthUsage prev_detector_state = delay_detector_->State();
|
||||
for (const auto& packet_feedback : packet_feedback_vector) {
|
||||
if (packet_feedback.send_time_ms < 0)
|
||||
continue;
|
||||
delayed_feedback = false;
|
||||
IncomingPacketFeedback(packet_feedback, at_time_ms);
|
||||
if (prev_detector_state == BandwidthUsage::kBwUnderusing &&
|
||||
delay_detector_->State() == BandwidthUsage::kBwNormal) {
|
||||
recovered_from_overuse = true;
|
||||
}
|
||||
prev_detector_state = delay_detector_->State();
|
||||
}
|
||||
|
||||
if (delayed_feedback) {
|
||||
++consecutive_delayed_feedbacks_;
|
||||
if (consecutive_delayed_feedbacks_ >= kMaxConsecutiveFailedLookups) {
|
||||
consecutive_delayed_feedbacks_ = 0;
|
||||
return OnLongFeedbackDelay(packet_feedback_vector.back().arrival_time_ms);
|
||||
}
|
||||
} else {
|
||||
consecutive_delayed_feedbacks_ = 0;
|
||||
return MaybeUpdateEstimate(acked_bitrate_bps, recovered_from_overuse,
|
||||
at_time_ms);
|
||||
}
|
||||
return Result();
|
||||
}
|
||||
|
||||
DelayBasedBwe::Result DelayBasedBwe::OnLongFeedbackDelay(
|
||||
int64_t arrival_time_ms) {
|
||||
// Estimate should always be valid since a start bitrate always is set in the
|
||||
// Call constructor. An alternative would be to return an empty Result here,
|
||||
// or to estimate the throughput based on the feedback we received.
|
||||
RTC_DCHECK(rate_control_.ValidEstimate());
|
||||
rate_control_.SetEstimate(rate_control_.LatestEstimate() / 2,
|
||||
arrival_time_ms);
|
||||
Result result;
|
||||
result.updated = true;
|
||||
result.probe = false;
|
||||
result.target_bitrate_bps = rate_control_.LatestEstimate();
|
||||
RTC_LOG(LS_WARNING) << "Long feedback delay detected, reducing BWE to "
|
||||
<< result.target_bitrate_bps;
|
||||
return result;
|
||||
}
|
||||
|
||||
void DelayBasedBwe::IncomingPacketFeedback(
|
||||
const PacketFeedback& packet_feedback,
|
||||
int64_t at_time_ms) {
|
||||
int64_t now_ms = at_time_ms;
|
||||
// Reset if the stream has timed out.
|
||||
if (last_seen_packet_ms_ == -1 ||
|
||||
now_ms - last_seen_packet_ms_ > kStreamTimeOutMs) {
|
||||
inter_arrival_.reset(
|
||||
new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000,
|
||||
kTimestampToMs, true));
|
||||
delay_detector_.reset(new TrendlineEstimator(trendline_window_size_,
|
||||
trendline_smoothing_coeff_,
|
||||
trendline_threshold_gain_));
|
||||
}
|
||||
last_seen_packet_ms_ = now_ms;
|
||||
|
||||
uint32_t send_time_24bits =
|
||||
static_cast<uint32_t>(
|
||||
((static_cast<uint64_t>(packet_feedback.send_time_ms)
|
||||
<< kAbsSendTimeFraction) +
|
||||
500) /
|
||||
1000) &
|
||||
0x00FFFFFF;
|
||||
// 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;
|
||||
|
||||
uint32_t ts_delta = 0;
|
||||
int64_t t_delta = 0;
|
||||
int size_delta = 0;
|
||||
if (inter_arrival_->ComputeDeltas(timestamp, packet_feedback.arrival_time_ms,
|
||||
now_ms, packet_feedback.payload_size,
|
||||
&ts_delta, &t_delta, &size_delta)) {
|
||||
double ts_delta_ms = (1000.0 * ts_delta) / (1 << kInterArrivalShift);
|
||||
delay_detector_->Update(t_delta, ts_delta_ms,
|
||||
packet_feedback.arrival_time_ms);
|
||||
}
|
||||
if (packet_feedback.pacing_info.probe_cluster_id !=
|
||||
PacedPacketInfo::kNotAProbe) {
|
||||
probe_bitrate_estimator_.HandleProbeAndEstimateBitrate(packet_feedback);
|
||||
}
|
||||
}
|
||||
|
||||
DelayBasedBwe::Result DelayBasedBwe::MaybeUpdateEstimate(
|
||||
rtc::Optional<uint32_t> acked_bitrate_bps,
|
||||
bool recovered_from_overuse,
|
||||
int64_t at_time_ms) {
|
||||
Result result;
|
||||
int64_t now_ms = at_time_ms;
|
||||
|
||||
rtc::Optional<int> probe_bitrate_bps =
|
||||
probe_bitrate_estimator_.FetchAndResetLastEstimatedBitrateBps();
|
||||
// Currently overusing the bandwidth.
|
||||
if (delay_detector_->State() == BandwidthUsage::kBwOverusing) {
|
||||
if (acked_bitrate_bps &&
|
||||
rate_control_.TimeToReduceFurther(now_ms, *acked_bitrate_bps)) {
|
||||
result.updated =
|
||||
UpdateEstimate(now_ms, acked_bitrate_bps, &result.target_bitrate_bps);
|
||||
} else if (!acked_bitrate_bps && rate_control_.ValidEstimate() &&
|
||||
rate_control_.TimeToReduceFurther(
|
||||
now_ms, rate_control_.LatestEstimate() / 2 - 1)) {
|
||||
// Overusing before we have a measured acknowledged bitrate. We check
|
||||
// TimeToReduceFurther (with a fake acknowledged bitrate) to avoid
|
||||
// reducing too often.
|
||||
// TODO(tschumim): Improve this and/or the acknowledged bitrate estimator
|
||||
// so that we (almost) always have a bitrate estimate.
|
||||
rate_control_.SetEstimate(rate_control_.LatestEstimate() / 2, now_ms);
|
||||
result.updated = true;
|
||||
result.probe = false;
|
||||
result.target_bitrate_bps = rate_control_.LatestEstimate();
|
||||
}
|
||||
} else {
|
||||
if (probe_bitrate_bps) {
|
||||
result.probe = true;
|
||||
result.updated = true;
|
||||
result.target_bitrate_bps = *probe_bitrate_bps;
|
||||
rate_control_.SetEstimate(*probe_bitrate_bps, now_ms);
|
||||
} else {
|
||||
result.updated =
|
||||
UpdateEstimate(now_ms, acked_bitrate_bps, &result.target_bitrate_bps);
|
||||
result.recovered_from_overuse = recovered_from_overuse;
|
||||
}
|
||||
}
|
||||
BandwidthUsage detector_state = delay_detector_->State();
|
||||
if ((result.updated && prev_bitrate_ != result.target_bitrate_bps) ||
|
||||
detector_state != prev_state_) {
|
||||
uint32_t bitrate_bps =
|
||||
result.updated ? result.target_bitrate_bps : prev_bitrate_;
|
||||
|
||||
BWE_TEST_LOGGING_PLOT(1, "target_bitrate_bps", now_ms, bitrate_bps);
|
||||
|
||||
if (event_log_) {
|
||||
event_log_->Log(rtc::MakeUnique<RtcEventBweUpdateDelayBased>(
|
||||
bitrate_bps, detector_state));
|
||||
}
|
||||
|
||||
prev_bitrate_ = bitrate_bps;
|
||||
prev_state_ = detector_state;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DelayBasedBwe::UpdateEstimate(int64_t now_ms,
|
||||
rtc::Optional<uint32_t> acked_bitrate_bps,
|
||||
uint32_t* target_bitrate_bps) {
|
||||
// TODO(terelius): RateControlInput::noise_var is deprecated and will be
|
||||
// removed. In the meantime, we set it to zero.
|
||||
const RateControlInput input(delay_detector_->State(), acked_bitrate_bps, 0);
|
||||
*target_bitrate_bps = rate_control_.Update(&input, now_ms);
|
||||
return rate_control_.ValidEstimate();
|
||||
}
|
||||
|
||||
void DelayBasedBwe::OnRttUpdate(int64_t avg_rtt_ms) {
|
||||
rate_control_.SetRtt(avg_rtt_ms);
|
||||
}
|
||||
|
||||
bool DelayBasedBwe::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);
|
||||
if (!rate_control_.ValidEstimate())
|
||||
return false;
|
||||
|
||||
*ssrcs = {kFixedSsrc};
|
||||
*bitrate_bps = rate_control_.LatestEstimate();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DelayBasedBwe::SetStartBitrate(int start_bitrate_bps) {
|
||||
RTC_LOG(LS_WARNING) << "BWE Setting start bitrate to: " << start_bitrate_bps;
|
||||
rate_control_.SetStartBitrate(start_bitrate_bps);
|
||||
}
|
||||
|
||||
void DelayBasedBwe::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.
|
||||
rate_control_.SetMinBitrate(min_bitrate_bps);
|
||||
}
|
||||
|
||||
int64_t DelayBasedBwe::GetExpectedBwePeriodMs() const {
|
||||
return rate_control_.GetExpectedBandwidthPeriodMs();
|
||||
}
|
||||
} // namespace webrtc
|
||||
92
modules/congestion_controller/rtp/delay_based_bwe.h
Normal file
92
modules/congestion_controller/rtp/delay_based_bwe.h
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/congestion_controller/rtp/delay_increase_detector_interface.h"
|
||||
#include "modules/congestion_controller/rtp/probe_bitrate_estimator.h"
|
||||
#include "modules/remote_bitrate_estimator/aimd_rate_control.h"
|
||||
#include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
|
||||
#include "modules/remote_bitrate_estimator/inter_arrival.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
#include "rtc_base/race_checker.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class RtcEventLog;
|
||||
|
||||
class DelayBasedBwe {
|
||||
public:
|
||||
static const int64_t kStreamTimeOutMs = 2000;
|
||||
|
||||
struct Result {
|
||||
Result();
|
||||
Result(bool probe, uint32_t target_bitrate_bps);
|
||||
~Result();
|
||||
bool updated;
|
||||
bool probe;
|
||||
uint32_t target_bitrate_bps;
|
||||
bool recovered_from_overuse;
|
||||
};
|
||||
|
||||
explicit DelayBasedBwe(RtcEventLog* event_log);
|
||||
virtual ~DelayBasedBwe();
|
||||
|
||||
Result IncomingPacketFeedbackVector(
|
||||
const std::vector<PacketFeedback>& packet_feedback_vector,
|
||||
rtc::Optional<uint32_t> acked_bitrate_bps,
|
||||
int64_t at_time_ms);
|
||||
void OnRttUpdate(int64_t avg_rtt_ms);
|
||||
bool LatestEstimate(std::vector<uint32_t>* ssrcs,
|
||||
uint32_t* bitrate_bps) const;
|
||||
void SetStartBitrate(int start_bitrate_bps);
|
||||
void SetMinBitrate(int min_bitrate_bps);
|
||||
int64_t GetExpectedBwePeriodMs() const;
|
||||
|
||||
private:
|
||||
void IncomingPacketFeedback(const PacketFeedback& packet_feedback,
|
||||
int64_t at_time_ms);
|
||||
Result OnLongFeedbackDelay(int64_t arrival_time_ms);
|
||||
Result MaybeUpdateEstimate(rtc::Optional<uint32_t> acked_bitrate_bps,
|
||||
bool request_probe,
|
||||
int64_t at_time_ms);
|
||||
// Updates the current remote rate estimate and returns true if a valid
|
||||
// estimate exists.
|
||||
bool UpdateEstimate(int64_t now_ms,
|
||||
rtc::Optional<uint32_t> acked_bitrate_bps,
|
||||
uint32_t* target_bitrate_bps);
|
||||
|
||||
rtc::RaceChecker network_race_;
|
||||
RtcEventLog* const event_log_;
|
||||
std::unique_ptr<InterArrival> inter_arrival_;
|
||||
std::unique_ptr<DelayIncreaseDetectorInterface> delay_detector_;
|
||||
int64_t last_seen_packet_ms_;
|
||||
bool uma_recorded_;
|
||||
AimdRateControl rate_control_;
|
||||
ProbeBitrateEstimator probe_bitrate_estimator_;
|
||||
size_t trendline_window_size_;
|
||||
double trendline_smoothing_coeff_;
|
||||
double trendline_threshold_gain_;
|
||||
int consecutive_delayed_feedbacks_;
|
||||
uint32_t prev_bitrate_;
|
||||
BandwidthUsage prev_state_;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DelayBasedBwe);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_H_
|
||||
237
modules/congestion_controller/rtp/delay_based_bwe_unittest.cc
Normal file
237
modules/congestion_controller/rtp/delay_based_bwe_unittest.cc
Normal file
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/delay_based_bwe.h"
|
||||
#include "modules/congestion_controller/rtp/delay_based_bwe_unittest_helper.h"
|
||||
#include "modules/pacing/paced_sender.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
#include "test/field_trial.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
constexpr int kNumProbesCluster0 = 5;
|
||||
constexpr int kNumProbesCluster1 = 8;
|
||||
const PacedPacketInfo kPacingInfo0(0, kNumProbesCluster0, 2000);
|
||||
const PacedPacketInfo kPacingInfo1(1, kNumProbesCluster1, 4000);
|
||||
constexpr float kTargetUtilizationFraction = 0.95f;
|
||||
constexpr int64_t kDummyTimestamp = 1000;
|
||||
} // namespace
|
||||
|
||||
TEST_F(DelayBasedBweTest, NoCrashEmptyFeedback) {
|
||||
std::vector<PacketFeedback> packet_feedback_vector;
|
||||
bitrate_estimator_->IncomingPacketFeedbackVector(
|
||||
packet_feedback_vector, rtc::nullopt, kDummyTimestamp);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, NoCrashOnlyLostFeedback) {
|
||||
std::vector<PacketFeedback> packet_feedback_vector;
|
||||
packet_feedback_vector.push_back(PacketFeedback(PacketFeedback::kNotReceived,
|
||||
PacketFeedback::kNoSendTime,
|
||||
0, 1500, PacedPacketInfo()));
|
||||
packet_feedback_vector.push_back(PacketFeedback(PacketFeedback::kNotReceived,
|
||||
PacketFeedback::kNoSendTime,
|
||||
1, 1500, PacedPacketInfo()));
|
||||
bitrate_estimator_->IncomingPacketFeedbackVector(
|
||||
packet_feedback_vector, rtc::nullopt, kDummyTimestamp);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, ProbeDetection) {
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
uint16_t seq_num = 0;
|
||||
|
||||
// First burst sent at 8 * 1000 / 10 = 800 kbps.
|
||||
for (int i = 0; i < kNumProbesCluster0; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(10);
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, now_ms, seq_num++, 1000, kPacingInfo0);
|
||||
}
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
|
||||
// Second burst sent at 8 * 1000 / 5 = 1600 kbps.
|
||||
for (int i = 0; i < kNumProbesCluster1; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(5);
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, now_ms, seq_num++, 1000, kPacingInfo1);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_GT(bitrate_observer_.latest_bitrate(), 1500000u);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, ProbeDetectionNonPacedPackets) {
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
uint16_t seq_num = 0;
|
||||
// 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 < kNumProbesCluster0; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(5);
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, now_ms, seq_num++, 1000, kPacingInfo0);
|
||||
// Non-paced packet, arriving 5 ms after.
|
||||
clock_.AdvanceTimeMilliseconds(5);
|
||||
IncomingFeedback(now_ms, now_ms, seq_num++, 100, PacedPacketInfo());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_GT(bitrate_observer_.latest_bitrate(), 800000u);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, ProbeDetectionFasterArrival) {
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
uint16_t seq_num = 0;
|
||||
// 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 < kNumProbesCluster0; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(1);
|
||||
send_time_ms += 10;
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, send_time_ms, seq_num++, 1000, kPacingInfo0);
|
||||
}
|
||||
|
||||
EXPECT_FALSE(bitrate_observer_.updated());
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, ProbeDetectionSlowerArrival) {
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
uint16_t seq_num = 0;
|
||||
// First burst sent at 8 * 1000 / 5 = 1600 kbps.
|
||||
// Arriving at 8 * 1000 / 7 = 1142 kbps.
|
||||
// Since the receive rate is significantly below the send rate, we expect to
|
||||
// use 95% of the estimated capacity.
|
||||
int64_t send_time_ms = 0;
|
||||
for (int i = 0; i < kNumProbesCluster1; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(7);
|
||||
send_time_ms += 5;
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, send_time_ms, seq_num++, 1000, kPacingInfo1);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_NEAR(bitrate_observer_.latest_bitrate(),
|
||||
kTargetUtilizationFraction * 1140000u, 10000u);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, ProbeDetectionSlowerArrivalHighBitrate) {
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
uint16_t seq_num = 0;
|
||||
// Burst sent at 8 * 1000 / 1 = 8000 kbps.
|
||||
// Arriving at 8 * 1000 / 2 = 4000 kbps.
|
||||
// Since the receive rate is significantly below the send rate, we expect to
|
||||
// use 95% of the estimated capacity.
|
||||
int64_t send_time_ms = 0;
|
||||
for (int i = 0; i < kNumProbesCluster1; ++i) {
|
||||
clock_.AdvanceTimeMilliseconds(2);
|
||||
send_time_ms += 1;
|
||||
now_ms = clock_.TimeInMilliseconds();
|
||||
IncomingFeedback(now_ms, send_time_ms, seq_num++, 1000, kPacingInfo1);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_NEAR(bitrate_observer_.latest_bitrate(),
|
||||
kTargetUtilizationFraction * 4000000u, 10000u);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, GetExpectedBwePeriodMs) {
|
||||
int64_t default_interval_ms = bitrate_estimator_->GetExpectedBwePeriodMs();
|
||||
EXPECT_GT(default_interval_ms, 0);
|
||||
CapacityDropTestHelper(1, true, 333, 0);
|
||||
int64_t interval_ms = bitrate_estimator_->GetExpectedBwePeriodMs();
|
||||
EXPECT_GT(interval_ms, 0);
|
||||
EXPECT_NE(interval_ms, default_interval_ms);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, InitialBehavior) {
|
||||
InitialBehaviorTestHelper(730000);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, RateIncreaseReordering) {
|
||||
RateIncreaseReorderingTestHelper(730000);
|
||||
}
|
||||
TEST_F(DelayBasedBweTest, RateIncreaseRtpTimestamps) {
|
||||
RateIncreaseRtpTimestampsTestHelper(627);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, CapacityDropOneStream) {
|
||||
CapacityDropTestHelper(1, false, 300, 0);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, CapacityDropPosOffsetChange) {
|
||||
CapacityDropTestHelper(1, false, 867, 30000);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, CapacityDropNegOffsetChange) {
|
||||
CapacityDropTestHelper(1, false, 933, -30000);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, CapacityDropOneStreamWrap) {
|
||||
CapacityDropTestHelper(1, true, 333, 0);
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, TestTimestampGrouping) {
|
||||
TestTimestampGroupingTestHelper();
|
||||
}
|
||||
|
||||
TEST_F(DelayBasedBweTest, 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(DelayBasedBweTest, 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(DelayBasedBweTest, TestInitialOveruse) {
|
||||
const uint32_t kStartBitrate = 300e3;
|
||||
const uint32_t kInitialCapacityBps = 200e3;
|
||||
const uint32_t kDummySsrc = 0;
|
||||
// High FPS to ensure that we send a lot of packets in a short time.
|
||||
const int kFps = 90;
|
||||
|
||||
stream_generator_->AddStream(new test::RtpStream(kFps, kStartBitrate));
|
||||
stream_generator_->set_capacity_bps(kInitialCapacityBps);
|
||||
|
||||
// Needed to initialize the AimdRateControl.
|
||||
bitrate_estimator_->SetStartBitrate(kStartBitrate);
|
||||
|
||||
// Produce 30 frames (in 1/3 second) and give them to the estimator.
|
||||
uint32_t bitrate_bps = kStartBitrate;
|
||||
bool seen_overuse = false;
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
bool overuse = GenerateAndProcessFrame(kDummySsrc, bitrate_bps);
|
||||
// The purpose of this test is to ensure that we back down even if we don't
|
||||
// have any acknowledged bitrate estimate yet. Hence, if the test works
|
||||
// as expected, we should not have a measured bitrate yet.
|
||||
EXPECT_FALSE(acknowledged_bitrate_estimator_->bitrate_bps().has_value());
|
||||
if (overuse) {
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
EXPECT_NEAR(bitrate_observer_.latest_bitrate(), kStartBitrate / 2, 15000);
|
||||
bitrate_bps = bitrate_observer_.latest_bitrate();
|
||||
seen_overuse = true;
|
||||
break;
|
||||
} else if (bitrate_observer_.updated()) {
|
||||
bitrate_bps = bitrate_observer_.latest_bitrate();
|
||||
bitrate_observer_.Reset();
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(seen_overuse);
|
||||
EXPECT_NEAR(bitrate_observer_.latest_bitrate(), kStartBitrate / 2, 15000);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,515 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/delay_based_bwe_unittest_helper.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include "modules/congestion_controller/rtp/delay_based_bwe.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/ptr_util.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
constexpr size_t kMtu = 1200;
|
||||
constexpr uint32_t kAcceptedBitrateErrorBps = 50000;
|
||||
|
||||
// Number of packets needed before we have a valid estimate.
|
||||
constexpr int kNumInitialPackets = 2;
|
||||
|
||||
constexpr int kInitialProbingPackets = 5;
|
||||
|
||||
namespace test {
|
||||
|
||||
void TestBitrateObserver::OnReceiveBitrateChanged(
|
||||
const std::vector<uint32_t>& ssrcs,
|
||||
uint32_t bitrate) {
|
||||
latest_bitrate_ = bitrate;
|
||||
updated_ = true;
|
||||
}
|
||||
|
||||
RtpStream::RtpStream(int fps, int bitrate_bps)
|
||||
: fps_(fps),
|
||||
bitrate_bps_(bitrate_bps),
|
||||
next_rtp_time_(0),
|
||||
sequence_number_(0) {
|
||||
RTC_CHECK_GT(fps_, 0);
|
||||
}
|
||||
|
||||
// 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,
|
||||
std::vector<PacketFeedback>* packets) {
|
||||
if (time_now_us < next_rtp_time_) {
|
||||
return next_rtp_time_;
|
||||
}
|
||||
RTC_CHECK(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 payload_size = (bits_per_frame + 4 * n_packets) / (8 * n_packets);
|
||||
for (size_t i = 0; i < n_packets; ++i) {
|
||||
PacketFeedback packet(-1, sequence_number_++);
|
||||
packet.send_time_ms = (time_now_us + kSendSideOffsetUs) / 1000;
|
||||
packet.payload_size = payload_size;
|
||||
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_;
|
||||
}
|
||||
|
||||
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_;
|
||||
}
|
||||
|
||||
bool RtpStream::Compare(const std::unique_ptr<RtpStream>& lhs,
|
||||
const std::unique_ptr<RtpStream>& rhs) {
|
||||
return lhs->next_rtp_time_ < rhs->next_rtp_time_;
|
||||
}
|
||||
|
||||
StreamGenerator::StreamGenerator(int capacity, int64_t time_now)
|
||||
: capacity_(capacity), prev_arrival_time_us_(time_now) {}
|
||||
|
||||
// Add a new stream.
|
||||
void StreamGenerator::AddStream(RtpStream* stream) {
|
||||
streams_.push_back(std::unique_ptr<RtpStream>(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 (const auto& stream : streams_) {
|
||||
total_bitrate_before += stream->bitrate_bps();
|
||||
}
|
||||
int64_t bitrate_before = 0;
|
||||
int total_bitrate_after = 0;
|
||||
for (const auto& stream : streams_) {
|
||||
bitrate_before += stream->bitrate_bps();
|
||||
int64_t bitrate_after =
|
||||
(bitrate_before * bitrate_bps + total_bitrate_before / 2) /
|
||||
total_bitrate_before;
|
||||
stream->set_bitrate_bps(bitrate_after - total_bitrate_after);
|
||||
total_bitrate_after += stream->bitrate_bps();
|
||||
}
|
||||
ASSERT_EQ(bitrate_before, total_bitrate_before);
|
||||
EXPECT_EQ(total_bitrate_after, bitrate_bps);
|
||||
}
|
||||
|
||||
// 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(std::vector<PacketFeedback>* packets,
|
||||
int64_t time_now_us) {
|
||||
RTC_CHECK(packets != NULL);
|
||||
RTC_CHECK(packets->empty());
|
||||
RTC_CHECK_GT(capacity_, 0);
|
||||
auto it =
|
||||
std::min_element(streams_.begin(), streams_.end(), RtpStream::Compare);
|
||||
(*it)->GenerateFrame(time_now_us, packets);
|
||||
int i = 0;
|
||||
for (PacketFeedback& packet : *packets) {
|
||||
int capacity_bpus = capacity_ / 1000;
|
||||
int64_t required_network_time_us =
|
||||
(8 * 1000 * packet.payload_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.arrival_time_ms = prev_arrival_time_us_ / 1000;
|
||||
++i;
|
||||
}
|
||||
it = std::min_element(streams_.begin(), streams_.end(), RtpStream::Compare);
|
||||
return std::max((*it)->next_rtp_time(), time_now_us);
|
||||
}
|
||||
} // namespace test
|
||||
|
||||
DelayBasedBweTest::DelayBasedBweTest()
|
||||
: clock_(100000000),
|
||||
acknowledged_bitrate_estimator_(
|
||||
rtc::MakeUnique<AcknowledgedBitrateEstimator>()),
|
||||
bitrate_estimator_(new DelayBasedBwe(nullptr)),
|
||||
stream_generator_(new test::StreamGenerator(1e6, // Capacity.
|
||||
clock_.TimeInMicroseconds())),
|
||||
arrival_time_offset_ms_(0),
|
||||
first_update_(true) {}
|
||||
|
||||
DelayBasedBweTest::~DelayBasedBweTest() {}
|
||||
|
||||
void DelayBasedBweTest::AddDefaultStream() {
|
||||
stream_generator_->AddStream(new test::RtpStream(30, 3e5));
|
||||
}
|
||||
|
||||
const uint32_t DelayBasedBweTest::kDefaultSsrc = 0;
|
||||
|
||||
void DelayBasedBweTest::IncomingFeedback(int64_t arrival_time_ms,
|
||||
int64_t send_time_ms,
|
||||
uint16_t sequence_number,
|
||||
size_t payload_size) {
|
||||
IncomingFeedback(arrival_time_ms, send_time_ms, sequence_number, payload_size,
|
||||
PacedPacketInfo());
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::IncomingFeedback(int64_t arrival_time_ms,
|
||||
int64_t send_time_ms,
|
||||
uint16_t sequence_number,
|
||||
size_t payload_size,
|
||||
const PacedPacketInfo& pacing_info) {
|
||||
RTC_CHECK_GE(arrival_time_ms + arrival_time_offset_ms_, 0);
|
||||
PacketFeedback packet(arrival_time_ms + arrival_time_offset_ms_, send_time_ms,
|
||||
sequence_number, payload_size, pacing_info);
|
||||
std::vector<PacketFeedback> packets;
|
||||
packets.push_back(packet);
|
||||
acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(packets);
|
||||
DelayBasedBwe::Result result =
|
||||
bitrate_estimator_->IncomingPacketFeedbackVector(
|
||||
packets, acknowledged_bitrate_estimator_->bitrate_bps(),
|
||||
clock_.TimeInMilliseconds());
|
||||
const uint32_t kDummySsrc = 0;
|
||||
if (result.updated) {
|
||||
bitrate_observer_.OnReceiveBitrateChanged({kDummySsrc},
|
||||
result.target_bitrate_bps);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 DelayBasedBweTest::GenerateAndProcessFrame(uint32_t ssrc,
|
||||
uint32_t bitrate_bps) {
|
||||
stream_generator_->SetBitrateBps(bitrate_bps);
|
||||
std::vector<PacketFeedback> packets;
|
||||
int64_t next_time_us =
|
||||
stream_generator_->GenerateFrame(&packets, clock_.TimeInMicroseconds());
|
||||
if (packets.empty())
|
||||
return false;
|
||||
|
||||
bool overuse = false;
|
||||
bitrate_observer_.Reset();
|
||||
clock_.AdvanceTimeMicroseconds(1000 * packets.back().arrival_time_ms -
|
||||
clock_.TimeInMicroseconds());
|
||||
for (auto& packet : packets) {
|
||||
RTC_CHECK_GE(packet.arrival_time_ms + arrival_time_offset_ms_, 0);
|
||||
packet.arrival_time_ms += arrival_time_offset_ms_;
|
||||
}
|
||||
|
||||
acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(packets);
|
||||
DelayBasedBwe::Result result =
|
||||
bitrate_estimator_->IncomingPacketFeedbackVector(
|
||||
packets, acknowledged_bitrate_estimator_->bitrate_bps(),
|
||||
clock_.TimeInMilliseconds());
|
||||
const uint32_t kDummySsrc = 0;
|
||||
if (result.updated) {
|
||||
bitrate_observer_.OnReceiveBitrateChanged({kDummySsrc},
|
||||
result.target_bitrate_bps);
|
||||
if (!first_update_ && result.target_bitrate_bps < bitrate_bps)
|
||||
overuse = true;
|
||||
first_update_ = false;
|
||||
}
|
||||
|
||||
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 DelayBasedBweTest::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 DelayBasedBweTest::InitialBehaviorTestHelper(
|
||||
uint32_t expected_converge_bitrate) {
|
||||
const int kFramerate = 50; // 50 fps to avoid rounding errors.
|
||||
const int kFrameIntervalMs = 1000 / kFramerate;
|
||||
const PacedPacketInfo kPacingInfo(0, 5, 5000);
|
||||
uint32_t bitrate_bps = 0;
|
||||
int64_t send_time_ms = 0;
|
||||
uint16_t sequence_number = 0;
|
||||
std::vector<uint32_t> ssrcs;
|
||||
EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps));
|
||||
EXPECT_EQ(0u, ssrcs.size());
|
||||
clock_.AdvanceTimeMilliseconds(1000);
|
||||
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) {
|
||||
// NOTE!!! If the following line is moved under the if case then this test
|
||||
// wont work on windows realease bots.
|
||||
PacedPacketInfo pacing_info =
|
||||
i < kInitialProbingPackets ? kPacingInfo : PacedPacketInfo();
|
||||
|
||||
if (i == kNumInitialPackets) {
|
||||
EXPECT_FALSE(bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_bps));
|
||||
EXPECT_EQ(0u, ssrcs.size());
|
||||
EXPECT_FALSE(bitrate_observer_.updated());
|
||||
bitrate_observer_.Reset();
|
||||
}
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms,
|
||||
sequence_number++, kMtu, pacing_info);
|
||||
clock_.AdvanceTimeMilliseconds(1000 / kFramerate);
|
||||
send_time_ms += kFrameIntervalMs;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::RateIncreaseReorderingTestHelper(
|
||||
uint32_t expected_bitrate_bps) {
|
||||
const int kFramerate = 50; // 50 fps to avoid rounding errors.
|
||||
const int kFrameIntervalMs = 1000 / kFramerate;
|
||||
const PacedPacketInfo kPacingInfo(0, 5, 5000);
|
||||
int64_t send_time_ms = 0;
|
||||
uint16_t sequence_number = 0;
|
||||
// Inserting packets for five seconds to get a valid estimate.
|
||||
for (int i = 0; i < 5 * kFramerate + 1 + kNumInitialPackets; ++i) {
|
||||
// NOTE!!! If the following line is moved under the if case then this test
|
||||
// wont work on windows realease bots.
|
||||
PacedPacketInfo pacing_info =
|
||||
i < kInitialProbingPackets ? kPacingInfo : PacedPacketInfo();
|
||||
|
||||
// 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.
|
||||
|
||||
EXPECT_FALSE(bitrate_observer_.updated()); // No valid estimate.
|
||||
}
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms,
|
||||
sequence_number++, kMtu, pacing_info);
|
||||
clock_.AdvanceTimeMilliseconds(kFrameIntervalMs);
|
||||
send_time_ms += kFrameIntervalMs;
|
||||
}
|
||||
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);
|
||||
send_time_ms += 2 * kFrameIntervalMs;
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms,
|
||||
sequence_number + 2, 1000);
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(),
|
||||
send_time_ms - kFrameIntervalMs, sequence_number + 1,
|
||||
1000);
|
||||
sequence_number += 2;
|
||||
}
|
||||
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 DelayBasedBweTest::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_EQ(expected_iterations, iterations);
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::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;
|
||||
}
|
||||
stream_generator_->AddStream(new test::RtpStream(kFramerate, bitrate));
|
||||
bitrate_sum += bitrate;
|
||||
}
|
||||
ASSERT_EQ(bitrate_sum, kStartBitrate);
|
||||
}
|
||||
|
||||
// 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, 180000u);
|
||||
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);
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::TestTimestampGroupingTestHelper() {
|
||||
const int kFramerate = 50; // 50 fps to avoid rounding errors.
|
||||
const int kFrameIntervalMs = 1000 / kFramerate;
|
||||
int64_t send_time_ms = 0;
|
||||
uint16_t sequence_number = 0;
|
||||
// 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) {
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms,
|
||||
sequence_number++, 1000);
|
||||
|
||||
clock_.AdvanceTimeMilliseconds(kFrameIntervalMs);
|
||||
send_time_ms += kFrameIntervalMs;
|
||||
}
|
||||
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;
|
||||
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.
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms,
|
||||
sequence_number++, 100);
|
||||
clock_.AdvanceTimeMilliseconds(kFrameIntervalMs / kTimestampGroupLength);
|
||||
send_time_ms += 1;
|
||||
}
|
||||
// Increase time until next batch to simulate over-use.
|
||||
clock_.AdvanceTimeMilliseconds(10);
|
||||
send_time_ms += kFrameIntervalMs - kTimestampGroupLength;
|
||||
}
|
||||
EXPECT_TRUE(bitrate_observer_.updated());
|
||||
// Should have reduced the estimate.
|
||||
EXPECT_LT(bitrate_observer_.latest_bitrate(), 400000u);
|
||||
}
|
||||
|
||||
void DelayBasedBweTest::TestWrappingHelper(int silence_time_s) {
|
||||
const int kFramerate = 100;
|
||||
const int kFrameIntervalMs = 1000 / kFramerate;
|
||||
int64_t send_time_ms = 0;
|
||||
uint16_t sequence_number = 0;
|
||||
|
||||
for (size_t i = 0; i < 3000; ++i) {
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms,
|
||||
sequence_number++, 1000);
|
||||
clock_.AdvanceTimeMilliseconds(kFrameIntervalMs);
|
||||
send_time_ms += kFrameIntervalMs;
|
||||
}
|
||||
uint32_t bitrate_before = 0;
|
||||
std::vector<uint32_t> ssrcs;
|
||||
bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_before);
|
||||
|
||||
clock_.AdvanceTimeMilliseconds(silence_time_s * 1000);
|
||||
send_time_ms += silence_time_s * 1000;
|
||||
|
||||
for (size_t i = 0; i < 24; ++i) {
|
||||
IncomingFeedback(clock_.TimeInMilliseconds(), send_time_ms,
|
||||
sequence_number++, 1000);
|
||||
clock_.AdvanceTimeMilliseconds(2 * kFrameIntervalMs);
|
||||
send_time_ms += kFrameIntervalMs;
|
||||
}
|
||||
uint32_t bitrate_after = 0;
|
||||
bitrate_estimator_->LatestEstimate(&ssrcs, &bitrate_after);
|
||||
EXPECT_LT(bitrate_after, bitrate_before);
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_UNITTEST_HELPER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_UNITTEST_HELPER_H_
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h"
|
||||
#include "modules/congestion_controller/rtp/delay_based_bwe.h"
|
||||
#include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
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:
|
||||
enum { kSendSideOffsetUs = 1000000 };
|
||||
|
||||
RtpStream(int fps, int bitrate_bps);
|
||||
|
||||
// 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,
|
||||
std::vector<PacketFeedback>* packets);
|
||||
|
||||
// The send-side time when the next frame can be generated.
|
||||
int64_t next_rtp_time() const;
|
||||
|
||||
void set_bitrate_bps(int bitrate_bps);
|
||||
|
||||
int bitrate_bps() const;
|
||||
|
||||
static bool Compare(const std::unique_ptr<RtpStream>& lhs,
|
||||
const std::unique_ptr<RtpStream>& rhs);
|
||||
|
||||
private:
|
||||
int fps_;
|
||||
int bitrate_bps_;
|
||||
int64_t next_rtp_time_;
|
||||
uint16_t sequence_number_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(RtpStream);
|
||||
};
|
||||
|
||||
class StreamGenerator {
|
||||
public:
|
||||
StreamGenerator(int capacity, int64_t time_now);
|
||||
|
||||
// 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(std::vector<PacketFeedback>* packets,
|
||||
int64_t time_now_us);
|
||||
|
||||
private:
|
||||
// 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.
|
||||
std::vector<std::unique_ptr<RtpStream>> streams_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(StreamGenerator);
|
||||
};
|
||||
} // namespace test
|
||||
|
||||
class DelayBasedBweTest : public ::testing::Test {
|
||||
public:
|
||||
DelayBasedBweTest();
|
||||
virtual ~DelayBasedBweTest();
|
||||
|
||||
protected:
|
||||
void AddDefaultStream();
|
||||
|
||||
// Helpers to insert a single packet into the delay-based BWE.
|
||||
void IncomingFeedback(int64_t arrival_time_ms,
|
||||
int64_t send_time_ms,
|
||||
uint16_t sequence_number,
|
||||
size_t payload_size);
|
||||
void IncomingFeedback(int64_t arrival_time_ms,
|
||||
int64_t send_time_ms,
|
||||
uint16_t sequence_number,
|
||||
size_t payload_size,
|
||||
const PacedPacketInfo& pacing_info);
|
||||
|
||||
// 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.
|
||||
test::TestBitrateObserver bitrate_observer_;
|
||||
std::unique_ptr<AcknowledgedBitrateEstimator> acknowledged_bitrate_estimator_;
|
||||
std::unique_ptr<DelayBasedBwe> bitrate_estimator_;
|
||||
std::unique_ptr<test::StreamGenerator> stream_generator_;
|
||||
int64_t arrival_time_offset_ms_;
|
||||
bool first_update_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(DelayBasedBweTest);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_DELAY_BASED_BWE_UNITTEST_HELPER_H_
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_DELAY_INCREASE_DETECTOR_INTERFACE_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_DELAY_INCREASE_DETECTOR_INTERFACE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class DelayIncreaseDetectorInterface {
|
||||
public:
|
||||
DelayIncreaseDetectorInterface() {}
|
||||
virtual ~DelayIncreaseDetectorInterface() {}
|
||||
|
||||
// Update the detector with a new sample. The deltas should represent deltas
|
||||
// between timestamp groups as defined by the InterArrival class.
|
||||
virtual void Update(double recv_delta_ms,
|
||||
double send_delta_ms,
|
||||
int64_t arrival_time_ms) = 0;
|
||||
|
||||
virtual BandwidthUsage State() const = 0;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(DelayIncreaseDetectorInterface);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_DELAY_INCREASE_DETECTOR_INTERFACE_H_
|
||||
425
modules/congestion_controller/rtp/goog_cc_network_control.cc
Normal file
425
modules/congestion_controller/rtp/goog_cc_network_control.cc
Normal file
@ -0,0 +1,425 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/rtp/goog_cc_network_control.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h"
|
||||
#include "modules/congestion_controller/rtp/alr_detector.h"
|
||||
#include "modules/congestion_controller/rtp/include/goog_cc_factory.h"
|
||||
#include "modules/congestion_controller/rtp/probe_controller.h"
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/format_macros.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/ptr_util.h"
|
||||
#include "rtc_base/timeutils.h"
|
||||
#include "system_wrappers/include/field_trial.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
const char kCwndExperiment[] = "WebRTC-CwndExperiment";
|
||||
const int64_t kDefaultAcceptedQueueMs = 250;
|
||||
|
||||
// Pacing-rate relative to our target send rate.
|
||||
// Multiplicative factor that is applied to the target bitrate to calculate
|
||||
// the number of bytes that can be transmitted per interval.
|
||||
// Increasing this factor will result in lower delays in cases of bitrate
|
||||
// overshoots from the encoder.
|
||||
const float kDefaultPaceMultiplier = 2.5f;
|
||||
|
||||
bool CwndExperimentEnabled() {
|
||||
std::string experiment_string =
|
||||
webrtc::field_trial::FindFullName(kCwndExperiment);
|
||||
// The experiment is enabled iff the field trial string begins with "Enabled".
|
||||
return experiment_string.find("Enabled") == 0;
|
||||
}
|
||||
|
||||
bool ReadCwndExperimentParameter(int64_t* accepted_queue_ms) {
|
||||
RTC_DCHECK(accepted_queue_ms);
|
||||
std::string experiment_string =
|
||||
webrtc::field_trial::FindFullName(kCwndExperiment);
|
||||
int parsed_values =
|
||||
sscanf(experiment_string.c_str(), "Enabled-%" PRId64, accepted_queue_ms);
|
||||
if (parsed_values == 1) {
|
||||
RTC_CHECK_GE(*accepted_queue_ms, 0)
|
||||
<< "Accepted must be greater than or equal to 0.";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Makes sure that the bitrate and the min, max values are in valid range.
|
||||
static void ClampBitrates(int64_t* bitrate_bps,
|
||||
int64_t* min_bitrate_bps,
|
||||
int64_t* max_bitrate_bps) {
|
||||
// TODO(holmer): We should make sure the default bitrates are set to 10 kbps,
|
||||
// and that we don't try to set the min bitrate to 0 from any applications.
|
||||
// The congestion controller should allow a min bitrate of 0.
|
||||
if (*min_bitrate_bps < congestion_controller::GetMinBitrateBps())
|
||||
*min_bitrate_bps = congestion_controller::GetMinBitrateBps();
|
||||
if (*max_bitrate_bps > 0)
|
||||
*max_bitrate_bps = std::max(*min_bitrate_bps, *max_bitrate_bps);
|
||||
if (*bitrate_bps > 0)
|
||||
*bitrate_bps = std::max(*min_bitrate_bps, *bitrate_bps);
|
||||
}
|
||||
|
||||
std::vector<PacketFeedback> ReceivedPacketsFeedbackAsRtp(
|
||||
const TransportPacketsFeedback report) {
|
||||
std::vector<PacketFeedback> packet_feedback_vector;
|
||||
for (auto& fb : report.PacketsWithFeedback()) {
|
||||
if (fb.receive_time.IsFinite()) {
|
||||
PacketFeedback pf(fb.receive_time.ms(), 0);
|
||||
pf.creation_time_ms = report.feedback_time.ms();
|
||||
if (fb.sent_packet.has_value()) {
|
||||
pf.payload_size = fb.sent_packet->size.bytes();
|
||||
pf.pacing_info = fb.sent_packet->pacing_info;
|
||||
pf.send_time_ms = fb.sent_packet->send_time.ms();
|
||||
} else {
|
||||
pf.send_time_ms = PacketFeedback::kNoSendTime;
|
||||
}
|
||||
packet_feedback_vector.push_back(pf);
|
||||
}
|
||||
}
|
||||
return packet_feedback_vector;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory(
|
||||
RtcEventLog* event_log)
|
||||
: event_log_(event_log) {}
|
||||
|
||||
NetworkControllerInterface::uptr GoogCcNetworkControllerFactory::Create(
|
||||
NetworkControllerObserver* observer) {
|
||||
return rtc::MakeUnique<GoogCcNetworkController>(event_log_, observer);
|
||||
}
|
||||
|
||||
TimeDelta GoogCcNetworkControllerFactory::GetProcessInterval() const {
|
||||
const int64_t kUpdateIntervalMs = 25;
|
||||
return TimeDelta::ms(kUpdateIntervalMs);
|
||||
}
|
||||
|
||||
GoogCcNetworkController::GoogCcNetworkController(
|
||||
RtcEventLog* event_log,
|
||||
NetworkControllerObserver* observer)
|
||||
: event_log_(event_log),
|
||||
observer_(observer),
|
||||
probe_controller_(new ProbeController(observer_)),
|
||||
bandwidth_estimation_(
|
||||
rtc::MakeUnique<SendSideBandwidthEstimation>(event_log_)),
|
||||
alr_detector_(rtc::MakeUnique<AlrDetector>()),
|
||||
delay_based_bwe_(new DelayBasedBwe(event_log_)),
|
||||
acknowledged_bitrate_estimator_(
|
||||
rtc::MakeUnique<AcknowledgedBitrateEstimator>()),
|
||||
pacing_factor_(kDefaultPaceMultiplier),
|
||||
min_pacing_rate_(DataRate::Zero()),
|
||||
max_padding_rate_(DataRate::Zero()),
|
||||
in_cwnd_experiment_(CwndExperimentEnabled()),
|
||||
accepted_queue_ms_(kDefaultAcceptedQueueMs) {
|
||||
delay_based_bwe_->SetMinBitrate(congestion_controller::GetMinBitrateBps());
|
||||
if (in_cwnd_experiment_ &&
|
||||
!ReadCwndExperimentParameter(&accepted_queue_ms_)) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to parse parameters for CwndExperiment "
|
||||
"from field trial string. Experiment disabled.";
|
||||
in_cwnd_experiment_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
GoogCcNetworkController::~GoogCcNetworkController() {}
|
||||
|
||||
void GoogCcNetworkController::OnNetworkAvailability(NetworkAvailability msg) {
|
||||
probe_controller_->OnNetworkAvailability(msg);
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::OnNetworkRouteChange(NetworkRouteChange msg) {
|
||||
int64_t min_bitrate_bps = msg.constraints.min_data_rate.bps();
|
||||
int64_t max_bitrate_bps = -1;
|
||||
int64_t start_bitrate_bps = -1;
|
||||
|
||||
if (msg.constraints.max_data_rate.IsFinite())
|
||||
max_bitrate_bps = msg.constraints.max_data_rate.bps();
|
||||
if (msg.constraints.starting_rate.IsFinite())
|
||||
start_bitrate_bps = msg.constraints.starting_rate.bps();
|
||||
|
||||
ClampBitrates(&start_bitrate_bps, &min_bitrate_bps, &max_bitrate_bps);
|
||||
|
||||
bandwidth_estimation_ =
|
||||
rtc::MakeUnique<SendSideBandwidthEstimation>(event_log_);
|
||||
bandwidth_estimation_->SetBitrates(start_bitrate_bps, min_bitrate_bps,
|
||||
max_bitrate_bps);
|
||||
delay_based_bwe_.reset(new DelayBasedBwe(event_log_));
|
||||
acknowledged_bitrate_estimator_.reset(new AcknowledgedBitrateEstimator());
|
||||
delay_based_bwe_->SetStartBitrate(start_bitrate_bps);
|
||||
delay_based_bwe_->SetMinBitrate(min_bitrate_bps);
|
||||
|
||||
probe_controller_->Reset(msg.at_time.ms());
|
||||
probe_controller_->SetBitrates(min_bitrate_bps, start_bitrate_bps,
|
||||
max_bitrate_bps, msg.at_time.ms());
|
||||
|
||||
MaybeTriggerOnNetworkChanged(msg.at_time);
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::OnProcessInterval(ProcessInterval msg) {
|
||||
bandwidth_estimation_->UpdateEstimate(msg.at_time.ms());
|
||||
rtc::Optional<int64_t> start_time_ms =
|
||||
alr_detector_->GetApplicationLimitedRegionStartTime();
|
||||
probe_controller_->SetAlrStartTimeMs(start_time_ms);
|
||||
probe_controller_->Process(msg.at_time.ms());
|
||||
MaybeTriggerOnNetworkChanged(msg.at_time);
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::OnRemoteBitrateReport(RemoteBitrateReport msg) {
|
||||
bandwidth_estimation_->UpdateReceiverEstimate(msg.receive_time.ms(),
|
||||
msg.bandwidth.bps());
|
||||
BWE_TEST_LOGGING_PLOT(1, "REMB_kbps", msg.receive_time.ms(),
|
||||
msg.bandwidth.bps() / 1000);
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::OnRoundTripTimeUpdate(RoundTripTimeUpdate msg) {
|
||||
if (msg.smoothed) {
|
||||
delay_based_bwe_->OnRttUpdate(msg.round_trip_time.ms());
|
||||
} else {
|
||||
bandwidth_estimation_->UpdateRtt(msg.round_trip_time.ms(),
|
||||
msg.receive_time.ms());
|
||||
}
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::OnSentPacket(SentPacket sent_packet) {
|
||||
alr_detector_->OnBytesSent(sent_packet.size.bytes(),
|
||||
sent_packet.send_time.ms());
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::OnStreamsConfig(StreamsConfig msg) {
|
||||
probe_controller_->EnablePeriodicAlrProbing(msg.requests_alr_probing);
|
||||
|
||||
bool pacing_changed = false;
|
||||
if (msg.pacing_factor && *msg.pacing_factor != pacing_factor_) {
|
||||
pacing_factor_ = *msg.pacing_factor;
|
||||
pacing_changed = true;
|
||||
}
|
||||
if (msg.min_pacing_rate && *msg.min_pacing_rate != min_pacing_rate_) {
|
||||
min_pacing_rate_ = *msg.min_pacing_rate;
|
||||
pacing_changed = true;
|
||||
}
|
||||
if (msg.max_padding_rate && *msg.max_padding_rate != max_padding_rate_) {
|
||||
max_padding_rate_ = *msg.max_padding_rate;
|
||||
pacing_changed = true;
|
||||
}
|
||||
if (pacing_changed)
|
||||
UpdatePacingRates(msg.at_time);
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::OnTargetRateConstraints(
|
||||
TargetRateConstraints constraints) {
|
||||
int64_t min_bitrate_bps = constraints.min_data_rate.bps();
|
||||
int64_t max_bitrate_bps = -1;
|
||||
int64_t start_bitrate_bps = -1;
|
||||
|
||||
if (constraints.max_data_rate.IsFinite())
|
||||
max_bitrate_bps = constraints.max_data_rate.bps();
|
||||
if (constraints.starting_rate.IsFinite())
|
||||
start_bitrate_bps = constraints.starting_rate.bps();
|
||||
|
||||
ClampBitrates(&start_bitrate_bps, &min_bitrate_bps, &max_bitrate_bps);
|
||||
|
||||
probe_controller_->SetBitrates(min_bitrate_bps, start_bitrate_bps,
|
||||
max_bitrate_bps, constraints.at_time.ms());
|
||||
|
||||
bandwidth_estimation_->SetBitrates(start_bitrate_bps, min_bitrate_bps,
|
||||
max_bitrate_bps);
|
||||
if (start_bitrate_bps > 0)
|
||||
delay_based_bwe_->SetStartBitrate(start_bitrate_bps);
|
||||
delay_based_bwe_->SetMinBitrate(min_bitrate_bps);
|
||||
|
||||
MaybeTriggerOnNetworkChanged(constraints.at_time);
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::OnTransportLossReport(TransportLossReport msg) {
|
||||
int64_t total_packets_delta =
|
||||
msg.packets_received_delta + msg.packets_lost_delta;
|
||||
bandwidth_estimation_->UpdatePacketsLost(
|
||||
msg.packets_lost_delta, total_packets_delta, msg.receive_time.ms());
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::OnTransportPacketsFeedback(
|
||||
TransportPacketsFeedback report) {
|
||||
int64_t feedback_rtt = -1;
|
||||
for (const auto& packet_feedback : report.PacketsWithFeedback()) {
|
||||
if (packet_feedback.sent_packet.has_value() &&
|
||||
packet_feedback.receive_time.IsFinite()) {
|
||||
int64_t rtt = report.feedback_time.ms() -
|
||||
packet_feedback.sent_packet->send_time.ms();
|
||||
// max() is used to account for feedback being delayed by the
|
||||
// receiver.
|
||||
feedback_rtt = std::max(rtt, feedback_rtt);
|
||||
}
|
||||
}
|
||||
if (feedback_rtt > -1) {
|
||||
feedback_rtts_.push_back(feedback_rtt);
|
||||
const size_t kFeedbackRttWindow = 32;
|
||||
if (feedback_rtts_.size() > kFeedbackRttWindow)
|
||||
feedback_rtts_.pop_front();
|
||||
min_feedback_rtt_ms_.emplace(
|
||||
*std::min_element(feedback_rtts_.begin(), feedback_rtts_.end()));
|
||||
}
|
||||
|
||||
std::vector<PacketFeedback> received_feedback_vector =
|
||||
ReceivedPacketsFeedbackAsRtp(report);
|
||||
|
||||
rtc::Optional<int64_t> alr_start_time =
|
||||
alr_detector_->GetApplicationLimitedRegionStartTime();
|
||||
|
||||
if (previously_in_alr && !alr_start_time.has_value()) {
|
||||
int64_t now_ms = report.feedback_time.ms();
|
||||
acknowledged_bitrate_estimator_->SetAlrEndedTimeMs(now_ms);
|
||||
probe_controller_->SetAlrEndedTimeMs(now_ms);
|
||||
}
|
||||
previously_in_alr = alr_start_time.has_value();
|
||||
acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(
|
||||
received_feedback_vector);
|
||||
DelayBasedBwe::Result result;
|
||||
result = delay_based_bwe_->IncomingPacketFeedbackVector(
|
||||
received_feedback_vector, acknowledged_bitrate_estimator_->bitrate_bps(),
|
||||
report.feedback_time.ms());
|
||||
if (result.updated) {
|
||||
if (result.probe) {
|
||||
bandwidth_estimation_->SetSendBitrate(result.target_bitrate_bps);
|
||||
}
|
||||
// Since SetSendBitrate now resets the delay-based estimate, we have to call
|
||||
// UpdateDelayBasedEstimate after SetSendBitrate.
|
||||
bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time.ms(),
|
||||
result.target_bitrate_bps);
|
||||
// Update the estimate in the ProbeController, in case we want to probe.
|
||||
MaybeTriggerOnNetworkChanged(report.feedback_time);
|
||||
}
|
||||
if (result.recovered_from_overuse) {
|
||||
probe_controller_->SetAlrStartTimeMs(alr_start_time);
|
||||
probe_controller_->RequestProbe(report.feedback_time.ms());
|
||||
}
|
||||
MaybeUpdateCongestionWindow();
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::MaybeUpdateCongestionWindow() {
|
||||
if (!in_cwnd_experiment_)
|
||||
return;
|
||||
// No valid RTT. Could be because send-side BWE isn't used, in which case
|
||||
// we don't try to limit the outstanding packets.
|
||||
if (!min_feedback_rtt_ms_)
|
||||
return;
|
||||
if (!last_estimate_.has_value())
|
||||
return;
|
||||
const DataSize kMinCwnd = DataSize::bytes(2 * 1500);
|
||||
TimeDelta time_window =
|
||||
TimeDelta::ms(*min_feedback_rtt_ms_ + accepted_queue_ms_);
|
||||
DataSize data_window = last_estimate_->bandwidth * time_window;
|
||||
CongestionWindow msg;
|
||||
msg.enabled = true;
|
||||
msg.data_window = std::max(kMinCwnd, data_window);
|
||||
observer_->OnCongestionWindow(msg);
|
||||
RTC_LOG(LS_INFO) << "Feedback rtt: " << *min_feedback_rtt_ms_
|
||||
<< " Bitrate: " << last_estimate_->bandwidth.bps();
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::MaybeTriggerOnNetworkChanged(Timestamp at_time) {
|
||||
int32_t estimated_bitrate_bps;
|
||||
uint8_t fraction_loss;
|
||||
int64_t rtt_ms;
|
||||
|
||||
bool estimate_changed = GetNetworkParameters(
|
||||
&estimated_bitrate_bps, &fraction_loss, &rtt_ms, at_time);
|
||||
if (estimate_changed) {
|
||||
TimeDelta bwe_period =
|
||||
TimeDelta::ms(delay_based_bwe_->GetExpectedBwePeriodMs());
|
||||
|
||||
NetworkEstimate new_estimate;
|
||||
new_estimate.at_time = at_time;
|
||||
new_estimate.round_trip_time = TimeDelta::ms(rtt_ms);
|
||||
new_estimate.bandwidth = DataRate::bps(estimated_bitrate_bps);
|
||||
new_estimate.loss_rate_ratio = fraction_loss / 255.0f;
|
||||
new_estimate.bwe_period = bwe_period;
|
||||
new_estimate.changed = true;
|
||||
last_estimate_ = new_estimate;
|
||||
OnNetworkEstimate(new_estimate);
|
||||
}
|
||||
}
|
||||
|
||||
bool GoogCcNetworkController::GetNetworkParameters(
|
||||
int32_t* estimated_bitrate_bps,
|
||||
uint8_t* fraction_loss,
|
||||
int64_t* rtt_ms,
|
||||
Timestamp at_time) {
|
||||
bandwidth_estimation_->CurrentEstimate(estimated_bitrate_bps, fraction_loss,
|
||||
rtt_ms);
|
||||
*estimated_bitrate_bps = std::max<int32_t>(
|
||||
*estimated_bitrate_bps, bandwidth_estimation_->GetMinBitrate());
|
||||
|
||||
bool estimate_changed = false;
|
||||
if ((*estimated_bitrate_bps != last_estimated_bitrate_bps_) ||
|
||||
(*fraction_loss != last_estimated_fraction_loss_) ||
|
||||
(*rtt_ms != last_estimated_rtt_ms_)) {
|
||||
last_estimated_bitrate_bps_ = *estimated_bitrate_bps;
|
||||
last_estimated_fraction_loss_ = *fraction_loss;
|
||||
last_estimated_rtt_ms_ = *rtt_ms;
|
||||
estimate_changed = true;
|
||||
}
|
||||
|
||||
BWE_TEST_LOGGING_PLOT(1, "fraction_loss_%", at_time.ms(),
|
||||
(*fraction_loss * 100) / 256);
|
||||
BWE_TEST_LOGGING_PLOT(1, "rtt_ms", at_time.ms(), *rtt_ms);
|
||||
BWE_TEST_LOGGING_PLOT(1, "Target_bitrate_kbps", at_time.ms(),
|
||||
*estimated_bitrate_bps / 1000);
|
||||
|
||||
return estimate_changed;
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::OnNetworkEstimate(NetworkEstimate estimate) {
|
||||
if (!estimate.changed)
|
||||
return;
|
||||
|
||||
UpdatePacingRates(estimate.at_time);
|
||||
alr_detector_->SetEstimatedBitrate(estimate.bandwidth.bps());
|
||||
probe_controller_->SetEstimatedBitrate(estimate.bandwidth.bps(),
|
||||
estimate.at_time.ms());
|
||||
|
||||
TargetTransferRate target_rate;
|
||||
target_rate.at_time = estimate.at_time;
|
||||
// Set the target rate to the full estimated bandwidth since the estimation
|
||||
// for legacy reasons includes target rate constraints.
|
||||
target_rate.target_rate = estimate.bandwidth;
|
||||
target_rate.network_estimate = estimate;
|
||||
observer_->OnTargetTransferRate(target_rate);
|
||||
}
|
||||
|
||||
void GoogCcNetworkController::UpdatePacingRates(Timestamp at_time) {
|
||||
if (!last_estimate_)
|
||||
return;
|
||||
DataRate pacing_rate =
|
||||
std::max(min_pacing_rate_, last_estimate_->bandwidth) * pacing_factor_;
|
||||
DataRate padding_rate =
|
||||
std::min(max_padding_rate_, last_estimate_->bandwidth);
|
||||
PacerConfig msg;
|
||||
msg.at_time = at_time;
|
||||
msg.time_window = TimeDelta::s(1);
|
||||
msg.data_window = pacing_rate * msg.time_window;
|
||||
msg.pad_window = padding_rate * msg.time_window;
|
||||
observer_->OnPacerConfig(msg);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
92
modules/congestion_controller/rtp/goog_cc_network_control.h
Normal file
92
modules/congestion_controller/rtp/goog_cc_network_control.h
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_GOOG_CC_NETWORK_CONTROL_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_GOOG_CC_NETWORK_CONTROL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "api/optional.h"
|
||||
#include "logging/rtc_event_log/rtc_event_log.h"
|
||||
#include "modules/bitrate_controller/send_side_bandwidth_estimation.h"
|
||||
#include "modules/congestion_controller/rtp/acknowledged_bitrate_estimator.h"
|
||||
#include "modules/congestion_controller/rtp/alr_detector.h"
|
||||
#include "modules/congestion_controller/rtp/delay_based_bwe.h"
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_control.h"
|
||||
#include "modules/congestion_controller/rtp/probe_controller.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class GoogCcNetworkController : public NetworkControllerInterface {
|
||||
public:
|
||||
GoogCcNetworkController(RtcEventLog* event_log,
|
||||
NetworkControllerObserver* observer);
|
||||
~GoogCcNetworkController() override;
|
||||
|
||||
// NetworkControllerInterface
|
||||
void OnNetworkAvailability(NetworkAvailability msg) override;
|
||||
void OnNetworkRouteChange(NetworkRouteChange msg) override;
|
||||
void OnProcessInterval(ProcessInterval msg) override;
|
||||
void OnRemoteBitrateReport(RemoteBitrateReport msg) override;
|
||||
void OnRoundTripTimeUpdate(RoundTripTimeUpdate msg) override;
|
||||
void OnSentPacket(SentPacket msg) override;
|
||||
void OnStreamsConfig(StreamsConfig msg) override;
|
||||
void OnTargetRateConstraints(TargetRateConstraints msg) override;
|
||||
void OnTransportLossReport(TransportLossReport msg) override;
|
||||
void OnTransportPacketsFeedback(TransportPacketsFeedback msg) override;
|
||||
|
||||
private:
|
||||
void MaybeUpdateCongestionWindow();
|
||||
void MaybeTriggerOnNetworkChanged(Timestamp at_time);
|
||||
bool GetNetworkParameters(int32_t* estimated_bitrate_bps,
|
||||
uint8_t* fraction_loss,
|
||||
int64_t* rtt_ms,
|
||||
Timestamp at_time);
|
||||
void OnNetworkEstimate(NetworkEstimate msg);
|
||||
void UpdatePacingRates(Timestamp at_time);
|
||||
|
||||
RtcEventLog* const event_log_;
|
||||
NetworkControllerObserver* const observer_;
|
||||
|
||||
const std::unique_ptr<ProbeController> probe_controller_;
|
||||
|
||||
std::unique_ptr<SendSideBandwidthEstimation> bandwidth_estimation_;
|
||||
std::unique_ptr<AlrDetector> alr_detector_;
|
||||
std::unique_ptr<DelayBasedBwe> delay_based_bwe_;
|
||||
std::unique_ptr<AcknowledgedBitrateEstimator> acknowledged_bitrate_estimator_;
|
||||
|
||||
std::deque<int64_t> feedback_rtts_;
|
||||
rtc::Optional<int64_t> min_feedback_rtt_ms_;
|
||||
|
||||
rtc::Optional<NetworkEstimate> last_estimate_;
|
||||
rtc::Optional<TargetTransferRate> last_target_rate_;
|
||||
|
||||
int32_t last_estimated_bitrate_bps_ = 0;
|
||||
uint8_t last_estimated_fraction_loss_ = 0;
|
||||
int64_t last_estimated_rtt_ms_ = 0;
|
||||
|
||||
double pacing_factor_;
|
||||
DataRate min_pacing_rate_;
|
||||
DataRate max_padding_rate_;
|
||||
|
||||
bool in_cwnd_experiment_;
|
||||
int64_t accepted_queue_ms_;
|
||||
bool previously_in_alr = false;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(GoogCcNetworkController);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_GOOG_CC_NETWORK_CONTROL_H_
|
||||
32
modules/congestion_controller/rtp/include/goog_cc_factory.h
Normal file
32
modules/congestion_controller/rtp/include/goog_cc_factory.h
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_GOOG_CC_FACTORY_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_GOOG_CC_FACTORY_H_
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_control.h"
|
||||
|
||||
namespace webrtc {
|
||||
class Clock;
|
||||
class RtcEventLog;
|
||||
|
||||
class GoogCcNetworkControllerFactory
|
||||
: public NetworkControllerFactoryInterface {
|
||||
public:
|
||||
explicit GoogCcNetworkControllerFactory(RtcEventLog*);
|
||||
NetworkControllerInterface::uptr Create(
|
||||
NetworkControllerObserver* observer) override;
|
||||
TimeDelta GetProcessInterval() const override;
|
||||
|
||||
private:
|
||||
RtcEventLog* const event_log_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_GOOG_CC_FACTORY_H_
|
||||
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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 MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_SEND_SIDE_CONGESTION_CONTROLLER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_SEND_SIDE_CONGESTION_CONTROLLER_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common_types.h" // NOLINT(build/include)
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_control.h"
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_types.h"
|
||||
#include "modules/congestion_controller/rtp/pacer_controller.h"
|
||||
#include "modules/congestion_controller/rtp/transport_feedback_adapter.h"
|
||||
#include "modules/include/module.h"
|
||||
#include "modules/include/module_common_types.h"
|
||||
#include "modules/pacing/paced_sender.h"
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
#include "rtc_base/criticalsection.h"
|
||||
#include "rtc_base/networkroute.h"
|
||||
#include "rtc_base/race_checker.h"
|
||||
#include "rtc_base/task_queue.h"
|
||||
|
||||
namespace rtc {
|
||||
struct SentPacket;
|
||||
}
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class Clock;
|
||||
class RateLimiter;
|
||||
class RtcEventLog;
|
||||
|
||||
namespace send_side_cc_internal {
|
||||
// This is used to observe the network controller state and route calls to
|
||||
// the proper handler. It also keeps cached values for safe asynchronous use.
|
||||
// This makes sure that things running on the worker queue can't access state
|
||||
// in SendSideCongestionController, which would risk causing data race on
|
||||
// destruction unless members are properly ordered.
|
||||
class ControlHandler;
|
||||
} // namespace send_side_cc_internal
|
||||
|
||||
class SendSideCongestionController : public CallStatsObserver,
|
||||
public Module,
|
||||
public TransportFeedbackObserver,
|
||||
public RtcpBandwidthObserver {
|
||||
public:
|
||||
// Observer class for bitrate changes announced due to change in bandwidth
|
||||
// estimate or due to that the send pacer is full. Fraction loss and rtt is
|
||||
// also part of this callback to allow the observer to optimize its settings
|
||||
// for different types of network environments. The bitrate does not include
|
||||
// packet headers and is measured in bits per second.
|
||||
class Observer {
|
||||
public:
|
||||
virtual void OnNetworkChanged(uint32_t bitrate_bps,
|
||||
uint8_t fraction_loss, // 0 - 255.
|
||||
int64_t rtt_ms,
|
||||
int64_t probing_interval_ms) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Observer() {}
|
||||
};
|
||||
SendSideCongestionController(const Clock* clock,
|
||||
Observer* observer,
|
||||
RtcEventLog* event_log,
|
||||
PacedSender* pacer);
|
||||
~SendSideCongestionController() override;
|
||||
|
||||
void RegisterPacketFeedbackObserver(PacketFeedbackObserver* observer);
|
||||
void DeRegisterPacketFeedbackObserver(PacketFeedbackObserver* observer);
|
||||
|
||||
// Currently, there can be at most one observer.
|
||||
// TODO(nisse): The RegisterNetworkObserver method is needed because we first
|
||||
// construct this object (as part of RtpTransportControllerSend), then pass a
|
||||
// reference to Call, which then registers itself as the observer. We should
|
||||
// try to break this circular chain of references, and make the observer a
|
||||
// construction time constant.
|
||||
void RegisterNetworkObserver(Observer* observer);
|
||||
void DeRegisterNetworkObserver(Observer* observer);
|
||||
|
||||
virtual void SetBweBitrates(int min_bitrate_bps,
|
||||
int start_bitrate_bps,
|
||||
int max_bitrate_bps);
|
||||
// Resets the BWE state. Note the first argument is the bitrate_bps.
|
||||
virtual void OnNetworkRouteChanged(const rtc::NetworkRoute& network_route,
|
||||
int bitrate_bps,
|
||||
int min_bitrate_bps,
|
||||
int max_bitrate_bps);
|
||||
virtual void SignalNetworkState(NetworkState state);
|
||||
virtual void SetTransportOverhead(size_t transport_overhead_bytes_per_packet);
|
||||
|
||||
virtual RtcpBandwidthObserver* GetBandwidthObserver();
|
||||
|
||||
virtual bool AvailableBandwidth(uint32_t* bandwidth) const;
|
||||
virtual int64_t GetPacerQueuingDelayMs() const;
|
||||
virtual int64_t GetFirstPacketTimeMs() const;
|
||||
|
||||
virtual TransportFeedbackObserver* GetTransportFeedbackObserver();
|
||||
|
||||
RateLimiter* GetRetransmissionRateLimiter();
|
||||
void EnablePeriodicAlrProbing(bool enable);
|
||||
|
||||
virtual void OnSentPacket(const rtc::SentPacket& sent_packet);
|
||||
|
||||
// Implements RtcpBandwidthObserver
|
||||
void OnReceivedEstimatedBitrate(uint32_t bitrate) override;
|
||||
void OnReceivedRtcpReceiverReport(const ReportBlockList& report_blocks,
|
||||
int64_t rtt,
|
||||
int64_t now_ms) override;
|
||||
// Implements CallStatsObserver.
|
||||
void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override;
|
||||
|
||||
// Implements Module.
|
||||
int64_t TimeUntilNextProcess() override;
|
||||
void Process() override;
|
||||
|
||||
// Implements TransportFeedbackObserver.
|
||||
void AddPacket(uint32_t ssrc,
|
||||
uint16_t sequence_number,
|
||||
size_t length,
|
||||
const PacedPacketInfo& pacing_info) override;
|
||||
void OnTransportFeedback(const rtcp::TransportFeedback& feedback) override;
|
||||
std::vector<PacketFeedback> GetTransportFeedbackVector() const override;
|
||||
|
||||
// Sets the minimum send bitrate and maximum padding bitrate requested by send
|
||||
// streams.
|
||||
// |min_send_bitrate_bps| might be higher that the estimated available network
|
||||
// bitrate and if so, the pacer will send with |min_send_bitrate_bps|.
|
||||
// |max_padding_bitrate_bps| might be higher than the estimate available
|
||||
// network bitrate and if so, the pacer will send padding packets to reach
|
||||
// the min of the estimated available bitrate and |max_padding_bitrate_bps|.
|
||||
void SetSendBitrateLimits(int64_t min_send_bitrate_bps,
|
||||
int64_t max_padding_bitrate_bps);
|
||||
void SetPacingFactor(float pacing_factor);
|
||||
|
||||
protected:
|
||||
// Waits long enough that any outstanding tasks should be finished.
|
||||
void WaitOnTasks();
|
||||
|
||||
private:
|
||||
SendSideCongestionController(
|
||||
const Clock* clock,
|
||||
RtcEventLog* event_log,
|
||||
PacedSender* pacer,
|
||||
NetworkControllerFactoryInterface::uptr controller_factory);
|
||||
|
||||
void UpdateStreamsConfig();
|
||||
void WaitOnTask(std::function<void()> closure);
|
||||
void MaybeUpdateOutstandingData();
|
||||
void OnReceivedRtcpReceiverReportBlocks(const ReportBlockList& report_blocks,
|
||||
int64_t now_ms);
|
||||
|
||||
const Clock* const clock_;
|
||||
PacedSender* const pacer_;
|
||||
TransportFeedbackAdapter transport_feedback_adapter_;
|
||||
|
||||
const std::unique_ptr<PacerController> pacer_controller_;
|
||||
const std::unique_ptr<send_side_cc_internal::ControlHandler> control_handler;
|
||||
const std::unique_ptr<NetworkControllerInterface> controller_;
|
||||
|
||||
TimeDelta process_interval_;
|
||||
int64_t last_process_update_ms_ = 0;
|
||||
|
||||
std::map<uint32_t, RTCPReportBlock> last_report_blocks_;
|
||||
Timestamp last_report_block_time_;
|
||||
|
||||
StreamsConfig streams_config_;
|
||||
const bool send_side_bwe_with_overhead_;
|
||||
std::atomic<size_t> transport_overhead_bytes_per_packet_;
|
||||
std::atomic<bool> network_available_;
|
||||
|
||||
rtc::RaceChecker worker_race_;
|
||||
|
||||
// Note that moving ownership of the task queue makes it neccessary to make
|
||||
// sure that there is no outstanding tasks on it using destructed objects.
|
||||
// This is currently guranteed by using explicit reset in the destructor of
|
||||
// this class. It is declared last to indicate that it's lifetime is shorter
|
||||
// than all other members.
|
||||
std::unique_ptr<rtc::TaskQueue> task_queue_;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(SendSideCongestionController);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_INCLUDE_SEND_SIDE_CONGESTION_CONTROLLER_H_
|
||||
93
modules/congestion_controller/rtp/median_slope_estimator.cc
Normal file
93
modules/congestion_controller/rtp/median_slope_estimator.cc
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/median_slope_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
constexpr unsigned int kDeltaCounterMax = 1000;
|
||||
|
||||
MedianSlopeEstimator::MedianSlopeEstimator(size_t window_size,
|
||||
double threshold_gain)
|
||||
: window_size_(window_size),
|
||||
threshold_gain_(threshold_gain),
|
||||
num_of_deltas_(0),
|
||||
accumulated_delay_(0),
|
||||
delay_hist_(),
|
||||
median_filter_(0.5),
|
||||
trendline_(0) {}
|
||||
|
||||
MedianSlopeEstimator::~MedianSlopeEstimator() {}
|
||||
|
||||
MedianSlopeEstimator::DelayInfo::DelayInfo(int64_t time,
|
||||
double delay,
|
||||
size_t slope_count)
|
||||
: time(time), delay(delay) {
|
||||
slopes.reserve(slope_count);
|
||||
}
|
||||
|
||||
MedianSlopeEstimator::DelayInfo::~DelayInfo() = default;
|
||||
|
||||
void MedianSlopeEstimator::Update(double recv_delta_ms,
|
||||
double send_delta_ms,
|
||||
int64_t arrival_time_ms) {
|
||||
const double delta_ms = recv_delta_ms - send_delta_ms;
|
||||
++num_of_deltas_;
|
||||
if (num_of_deltas_ > kDeltaCounterMax)
|
||||
num_of_deltas_ = kDeltaCounterMax;
|
||||
|
||||
accumulated_delay_ += delta_ms;
|
||||
BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", arrival_time_ms,
|
||||
accumulated_delay_);
|
||||
|
||||
// If the window is full, remove the |window_size_| - 1 slopes that belong to
|
||||
// the oldest point.
|
||||
if (delay_hist_.size() == window_size_) {
|
||||
for (double slope : delay_hist_.front().slopes) {
|
||||
const bool success = median_filter_.Erase(slope);
|
||||
RTC_CHECK(success);
|
||||
}
|
||||
delay_hist_.pop_front();
|
||||
}
|
||||
// Add |window_size_| - 1 new slopes.
|
||||
for (auto& old_delay : delay_hist_) {
|
||||
if (arrival_time_ms - old_delay.time != 0) {
|
||||
// The C99 standard explicitly states that casts and assignments must
|
||||
// perform the associated conversions. This means that |slope| will be
|
||||
// a 64-bit double even if the division is computed using, e.g., 80-bit
|
||||
// extended precision. I believe this also holds in C++ even though the
|
||||
// C++11 standard isn't as explicit. Furthermore, there are good reasons
|
||||
// to believe that compilers couldn't perform optimizations that break
|
||||
// this assumption even if they wanted to.
|
||||
double slope = (accumulated_delay_ - old_delay.delay) /
|
||||
static_cast<double>(arrival_time_ms - old_delay.time);
|
||||
median_filter_.Insert(slope);
|
||||
// We want to avoid issues with different rounding mode / precision
|
||||
// which we might get if we recomputed the slope when we remove it.
|
||||
old_delay.slopes.push_back(slope);
|
||||
}
|
||||
}
|
||||
delay_hist_.emplace_back(arrival_time_ms, accumulated_delay_,
|
||||
window_size_ - 1);
|
||||
// Recompute the median slope.
|
||||
if (delay_hist_.size() == window_size_)
|
||||
trendline_ = median_filter_.GetPercentileValue();
|
||||
|
||||
BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trendline_);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
72
modules/congestion_controller/rtp/median_slope_estimator.h
Normal file
72
modules/congestion_controller/rtp/median_slope_estimator.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_RTP_MEDIAN_SLOPE_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_MEDIAN_SLOPE_ESTIMATOR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
#include "rtc_base/constructormagic.h"
|
||||
#include "rtc_base/numerics/percentile_filter.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class MedianSlopeEstimator {
|
||||
public:
|
||||
// |window_size| is the number of points required to compute a trend line.
|
||||
// |threshold_gain| is used to scale the trendline slope for comparison to
|
||||
// the old threshold. Once the old estimator has been removed (or the
|
||||
// thresholds been merged into the estimators), we can just set the
|
||||
// threshold instead of setting a gain.
|
||||
MedianSlopeEstimator(size_t window_size, double threshold_gain);
|
||||
~MedianSlopeEstimator();
|
||||
|
||||
// Update the estimator with a new sample. The deltas should represent deltas
|
||||
// between timestamp groups as defined by the InterArrival class.
|
||||
void Update(double recv_delta_ms,
|
||||
double send_delta_ms,
|
||||
int64_t arrival_time_ms);
|
||||
|
||||
// Returns the estimated trend k multiplied by some gain.
|
||||
// 0 < k < 1 -> the delay increases, queues are filling up
|
||||
// k == 0 -> the delay does not change
|
||||
// k < 0 -> the delay decreases, queues are being emptied
|
||||
double trendline_slope() const { return trendline_ * threshold_gain_; }
|
||||
|
||||
// Returns the number of deltas which the current estimator state is based on.
|
||||
unsigned int num_of_deltas() const { return num_of_deltas_; }
|
||||
|
||||
private:
|
||||
struct DelayInfo {
|
||||
DelayInfo(int64_t time, double delay, size_t slope_count);
|
||||
~DelayInfo();
|
||||
int64_t time;
|
||||
double delay;
|
||||
std::vector<double> slopes;
|
||||
};
|
||||
// Parameters.
|
||||
const size_t window_size_;
|
||||
const double threshold_gain_;
|
||||
// Used by the existing threshold.
|
||||
unsigned int num_of_deltas_;
|
||||
// Theil-Sen robust line fitting
|
||||
double accumulated_delay_;
|
||||
std::deque<DelayInfo> delay_hist_;
|
||||
PercentileFilter<double> median_filter_;
|
||||
double trendline_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(MedianSlopeEstimator);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_MEDIAN_SLOPE_ESTIMATOR_H_
|
||||
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/median_slope_estimator.h"
|
||||
#include "rtc_base/random.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
constexpr size_t kWindowSize = 20;
|
||||
constexpr double kGain = 1;
|
||||
constexpr int64_t kAvgTimeBetweenPackets = 10;
|
||||
constexpr size_t kPacketCount = 2 * kWindowSize + 1;
|
||||
|
||||
void TestEstimator(double slope, double jitter_stddev, double tolerance) {
|
||||
MedianSlopeEstimator estimator(kWindowSize, kGain);
|
||||
Random random(0x1234567);
|
||||
int64_t send_times[kPacketCount];
|
||||
int64_t recv_times[kPacketCount];
|
||||
int64_t send_start_time = random.Rand(1000000);
|
||||
int64_t recv_start_time = random.Rand(1000000);
|
||||
for (size_t i = 0; i < kPacketCount; ++i) {
|
||||
send_times[i] = send_start_time + i * kAvgTimeBetweenPackets;
|
||||
double latency = i * kAvgTimeBetweenPackets / (1 - slope);
|
||||
double jitter = random.Gaussian(0, jitter_stddev);
|
||||
recv_times[i] = recv_start_time + latency + jitter;
|
||||
}
|
||||
for (size_t i = 1; i < kPacketCount; ++i) {
|
||||
double recv_delta = recv_times[i] - recv_times[i - 1];
|
||||
double send_delta = send_times[i] - send_times[i - 1];
|
||||
estimator.Update(recv_delta, send_delta, recv_times[i]);
|
||||
if (i < kWindowSize)
|
||||
EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001);
|
||||
else
|
||||
EXPECT_NEAR(estimator.trendline_slope(), slope, tolerance);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(MedianSlopeEstimator, PerfectLineSlopeOneHalf) {
|
||||
TestEstimator(0.5, 0, 0.001);
|
||||
}
|
||||
|
||||
TEST(MedianSlopeEstimator, PerfectLineSlopeMinusOne) {
|
||||
TestEstimator(-1, 0, 0.001);
|
||||
}
|
||||
|
||||
TEST(MedianSlopeEstimator, PerfectLineSlopeZero) {
|
||||
TestEstimator(0, 0, 0.001);
|
||||
}
|
||||
|
||||
TEST(MedianSlopeEstimator, JitteryLineSlopeOneHalf) {
|
||||
TestEstimator(0.5, kAvgTimeBetweenPackets / 3.0, 0.01);
|
||||
}
|
||||
|
||||
TEST(MedianSlopeEstimator, JitteryLineSlopeMinusOne) {
|
||||
TestEstimator(-1, kAvgTimeBetweenPackets / 3.0, 0.05);
|
||||
}
|
||||
|
||||
TEST(MedianSlopeEstimator, JitteryLineSlopeZero) {
|
||||
TestEstimator(0, kAvgTimeBetweenPackets / 3.0, 0.02);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
40
modules/congestion_controller/rtp/network_control/BUILD.gn
Normal file
40
modules/congestion_controller/rtp/network_control/BUILD.gn
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright (c) 2018 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("network_control") {
|
||||
sources = [
|
||||
"include/network_control.h",
|
||||
"include/network_types.h",
|
||||
"include/network_units.h",
|
||||
"network_types.cc",
|
||||
"network_units.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../../../:module_api",
|
||||
"../../../../api:optional",
|
||||
"../../../../rtc_base:checks",
|
||||
"../../../../rtc_base:rtc_base_approved",
|
||||
]
|
||||
}
|
||||
|
||||
if (rtc_include_tests) {
|
||||
rtc_source_set("network_control_unittests") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"network_units_unittest.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":network_control",
|
||||
"../../../../test:test_support",
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_CONTROL_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_CONTROL_H_
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_types.h"
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_units.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// NetworkControllerObserver is an interface implemented by observers of network
|
||||
// controllers. It contains declarations of the possible configuration messages
|
||||
// that can be sent from a network controller implementation.
|
||||
class NetworkControllerObserver {
|
||||
public:
|
||||
// Called when congestion window configutation is changed.
|
||||
virtual void OnCongestionWindow(CongestionWindow) = 0;
|
||||
// Called when pacer configuration has changed.
|
||||
virtual void OnPacerConfig(PacerConfig) = 0;
|
||||
// Called to indicate that a new probe should be sent.
|
||||
virtual void OnProbeClusterConfig(ProbeClusterConfig) = 0;
|
||||
// Called to indicate target transfer rate as well as giving information about
|
||||
// the current estimate of network parameters.
|
||||
virtual void OnTargetTransferRate(TargetTransferRate) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~NetworkControllerObserver() = default;
|
||||
};
|
||||
|
||||
// NetworkControllerInterface is implemented by network controllers. A network
|
||||
// controller is a class that uses information about network state and traffic
|
||||
// to estimate network parameters such as round trip time and bandwidth. Network
|
||||
// controllers does not guarantee thread safety, the interface must be used in a
|
||||
// non-concurrent fashion.
|
||||
class NetworkControllerInterface {
|
||||
public:
|
||||
using uptr = std::unique_ptr<NetworkControllerInterface>;
|
||||
virtual ~NetworkControllerInterface() = default;
|
||||
|
||||
// Called when network availabilty changes.
|
||||
virtual void OnNetworkAvailability(NetworkAvailability) = 0;
|
||||
// Called when the receiving or sending endpoint changes address.
|
||||
virtual void OnNetworkRouteChange(NetworkRouteChange) = 0;
|
||||
// Called periodically with a periodicy as specified by
|
||||
// NetworkControllerFactoryInterface::GetProcessInterval.
|
||||
virtual void OnProcessInterval(ProcessInterval) = 0;
|
||||
// Called when remotely calculated bitrate is received.
|
||||
virtual void OnRemoteBitrateReport(RemoteBitrateReport) = 0;
|
||||
// Called round trip time has been calculated by protocol specific mechanisms.
|
||||
virtual void OnRoundTripTimeUpdate(RoundTripTimeUpdate) = 0;
|
||||
// Called when a packet is sent on the network.
|
||||
virtual void OnSentPacket(SentPacket) = 0;
|
||||
// Called when the stream specific configuration has been updated.
|
||||
virtual void OnStreamsConfig(StreamsConfig) = 0;
|
||||
// Called when target transfer rate constraints has been changed.
|
||||
virtual void OnTargetRateConstraints(TargetRateConstraints) = 0;
|
||||
// Called when a protocol specific calculation of packet loss has been made.
|
||||
virtual void OnTransportLossReport(TransportLossReport) = 0;
|
||||
// Called with per packet feedback regarding receive time.
|
||||
virtual void OnTransportPacketsFeedback(TransportPacketsFeedback) = 0;
|
||||
};
|
||||
|
||||
// NetworkControllerFactoryInterface is an interface for creating a network
|
||||
// controller.
|
||||
class NetworkControllerFactoryInterface {
|
||||
public:
|
||||
using uptr = std::unique_ptr<NetworkControllerFactoryInterface>;
|
||||
// Used to create a new network controller, requires an observer to be
|
||||
// provided to handle callbacks.
|
||||
virtual NetworkControllerInterface::uptr Create(
|
||||
NetworkControllerObserver* observer) = 0;
|
||||
// Returns the interval by which the network controller expects
|
||||
// OnProcessInterval calls.
|
||||
virtual TimeDelta GetProcessInterval() const = 0;
|
||||
virtual ~NetworkControllerFactoryInterface() = default;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_CONTROL_H_
|
||||
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_TYPES_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_TYPES_H_
|
||||
#include <stdint.h>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_units.h"
|
||||
#include "modules/include/module_common_types.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Configuration
|
||||
|
||||
// Use StreamsConfig for information about streams that is required for specific
|
||||
// adjustments to the algorithms in network controllers. Especially useful
|
||||
// for experiments.
|
||||
struct StreamsConfig {
|
||||
StreamsConfig();
|
||||
StreamsConfig(const StreamsConfig&);
|
||||
~StreamsConfig();
|
||||
Timestamp at_time;
|
||||
bool requests_alr_probing = false;
|
||||
rtc::Optional<double> pacing_factor;
|
||||
rtc::Optional<DataRate> min_pacing_rate;
|
||||
rtc::Optional<DataRate> max_padding_rate;
|
||||
};
|
||||
|
||||
struct TargetRateConstraints {
|
||||
Timestamp at_time;
|
||||
DataRate starting_rate;
|
||||
DataRate min_data_rate;
|
||||
DataRate max_data_rate;
|
||||
};
|
||||
|
||||
// Send side information
|
||||
|
||||
struct NetworkAvailability {
|
||||
Timestamp at_time;
|
||||
bool network_available = false;
|
||||
};
|
||||
|
||||
struct NetworkRouteChange {
|
||||
Timestamp at_time;
|
||||
// The TargetRateConstraints are set here so they can be changed synchronously
|
||||
// when network route changes.
|
||||
TargetRateConstraints constraints;
|
||||
};
|
||||
|
||||
struct SentPacket {
|
||||
Timestamp send_time;
|
||||
DataSize size;
|
||||
PacedPacketInfo pacing_info;
|
||||
};
|
||||
|
||||
struct PacerQueueUpdate {
|
||||
TimeDelta expected_queue_time;
|
||||
};
|
||||
|
||||
// Transport level feedback
|
||||
|
||||
struct RemoteBitrateReport {
|
||||
Timestamp receive_time;
|
||||
DataRate bandwidth;
|
||||
};
|
||||
|
||||
struct RoundTripTimeUpdate {
|
||||
Timestamp receive_time;
|
||||
TimeDelta round_trip_time;
|
||||
bool smoothed = false;
|
||||
};
|
||||
|
||||
struct TransportLossReport {
|
||||
Timestamp receive_time;
|
||||
Timestamp start_time;
|
||||
Timestamp end_time;
|
||||
uint64_t packets_lost_delta = 0;
|
||||
uint64_t packets_received_delta = 0;
|
||||
};
|
||||
|
||||
struct OutstandingData {
|
||||
DataSize in_flight_data;
|
||||
};
|
||||
|
||||
// Packet level feedback
|
||||
|
||||
struct PacketResult {
|
||||
PacketResult();
|
||||
PacketResult(const PacketResult&);
|
||||
~PacketResult();
|
||||
|
||||
rtc::Optional<SentPacket> sent_packet;
|
||||
Timestamp receive_time;
|
||||
};
|
||||
|
||||
struct TransportPacketsFeedback {
|
||||
TransportPacketsFeedback();
|
||||
TransportPacketsFeedback(const TransportPacketsFeedback& other);
|
||||
~TransportPacketsFeedback();
|
||||
|
||||
Timestamp feedback_time;
|
||||
DataSize data_in_flight;
|
||||
DataSize prior_in_flight;
|
||||
std::vector<PacketResult> packet_feedbacks;
|
||||
|
||||
std::vector<PacketResult> ReceivedWithSendInfo() const;
|
||||
std::vector<PacketResult> LostWithSendInfo() const;
|
||||
std::vector<PacketResult> PacketsWithFeedback() const;
|
||||
};
|
||||
|
||||
// Network estimation
|
||||
|
||||
struct NetworkEstimate {
|
||||
Timestamp at_time;
|
||||
DataRate bandwidth;
|
||||
TimeDelta round_trip_time;
|
||||
TimeDelta bwe_period;
|
||||
|
||||
float loss_rate_ratio = 0;
|
||||
bool changed = true;
|
||||
};
|
||||
|
||||
// Network control
|
||||
struct CongestionWindow {
|
||||
bool enabled = true;
|
||||
DataSize data_window;
|
||||
};
|
||||
|
||||
struct PacerConfig {
|
||||
Timestamp at_time;
|
||||
// Pacer should send at most data_window data over time_window duration.
|
||||
DataSize data_window;
|
||||
TimeDelta time_window;
|
||||
// Pacer should send at least pad_window data over time_window duration.
|
||||
DataSize pad_window;
|
||||
DataRate data_rate() const { return data_window / time_window; }
|
||||
};
|
||||
|
||||
struct ProbeClusterConfig {
|
||||
Timestamp at_time;
|
||||
DataRate target_data_rate;
|
||||
TimeDelta target_duration;
|
||||
uint32_t target_probe_count;
|
||||
};
|
||||
|
||||
struct TargetTransferRate {
|
||||
Timestamp at_time;
|
||||
DataRate target_rate;
|
||||
// The estimate on which the target rate is based on.
|
||||
NetworkEstimate network_estimate;
|
||||
};
|
||||
|
||||
// Process control
|
||||
struct ProcessInterval {
|
||||
Timestamp at_time;
|
||||
};
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os,
|
||||
const ProbeClusterConfig& config);
|
||||
::std::ostream& operator<<(::std::ostream& os, const PacerConfig& config);
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_TYPES_H_
|
||||
@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_UNITS_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_UNITS_H_
|
||||
#include <stdint.h>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace units_internal {
|
||||
inline int64_t DivideAndRound(int64_t numerator, int64_t denominators) {
|
||||
if (numerator >= 0) {
|
||||
return (numerator + (denominators / 2)) / denominators;
|
||||
} else {
|
||||
return (numerator + (denominators / 2)) / denominators - 1;
|
||||
}
|
||||
}
|
||||
} // namespace units_internal
|
||||
|
||||
// TimeDelta represents the difference between two timestamps. Connomly this can
|
||||
// be a duration. However since two Timestamps are not guaranteed to have the
|
||||
// same epoch (they might come from different computers, making exact
|
||||
// synchronisation infeasible), the duration covered by a TimeDelta can be
|
||||
// undefined. To simplify usage, it can be constructed and converted to
|
||||
// different units, specifically seconds (s), milliseconds (ms) and
|
||||
// microseconds (us).
|
||||
class TimeDelta {
|
||||
public:
|
||||
static const TimeDelta kPlusInfinity;
|
||||
static const TimeDelta kMinusInfinity;
|
||||
static const TimeDelta kNotInitialized;
|
||||
static const TimeDelta kZero;
|
||||
TimeDelta() : TimeDelta(kNotInitialized) {}
|
||||
static TimeDelta Zero() { return kZero; }
|
||||
static TimeDelta Infinity() { return kPlusInfinity; }
|
||||
static TimeDelta seconds(int64_t seconds) { return TimeDelta::s(seconds); }
|
||||
static TimeDelta s(int64_t seconds) {
|
||||
return TimeDelta::us(seconds * 1000000);
|
||||
}
|
||||
static TimeDelta ms(int64_t milliseconds) {
|
||||
return TimeDelta::us(milliseconds * 1000);
|
||||
}
|
||||
static TimeDelta us(int64_t microseconds) {
|
||||
// Infinities only allowed via use of explicit constants.
|
||||
RTC_DCHECK(microseconds > std::numeric_limits<int64_t>::min());
|
||||
RTC_DCHECK(microseconds < std::numeric_limits<int64_t>::max());
|
||||
return TimeDelta(microseconds);
|
||||
}
|
||||
int64_t s() const { return units_internal::DivideAndRound(us(), 1000000); }
|
||||
int64_t ms() const { return units_internal::DivideAndRound(us(), 1000); }
|
||||
int64_t us() const {
|
||||
RTC_DCHECK(IsFinite());
|
||||
return microseconds_;
|
||||
}
|
||||
TimeDelta Abs() const { return TimeDelta::us(std::abs(us())); }
|
||||
bool IsZero() const { return microseconds_ == 0; }
|
||||
bool IsFinite() const { return IsInitialized() && !IsInfinite(); }
|
||||
bool IsInitialized() const {
|
||||
return microseconds_ != kNotInitialized.microseconds_;
|
||||
}
|
||||
bool IsInfinite() const {
|
||||
return *this == kPlusInfinity || *this == kMinusInfinity;
|
||||
}
|
||||
TimeDelta operator+(const TimeDelta& other) const {
|
||||
return TimeDelta::us(us() + other.us());
|
||||
}
|
||||
TimeDelta operator-(const TimeDelta& other) const {
|
||||
return TimeDelta::us(us() - other.us());
|
||||
}
|
||||
TimeDelta operator*(double scalar) const {
|
||||
return TimeDelta::us(us() * scalar);
|
||||
}
|
||||
TimeDelta operator*(int64_t scalar) const {
|
||||
return TimeDelta::us(us() * scalar);
|
||||
}
|
||||
TimeDelta operator*(int32_t scalar) const {
|
||||
return TimeDelta::us(us() * scalar);
|
||||
}
|
||||
bool operator==(const TimeDelta& other) const {
|
||||
return microseconds_ == other.microseconds_;
|
||||
}
|
||||
bool operator!=(const TimeDelta& other) const {
|
||||
return microseconds_ != other.microseconds_;
|
||||
}
|
||||
bool operator<=(const TimeDelta& other) const {
|
||||
return microseconds_ <= other.microseconds_;
|
||||
}
|
||||
bool operator>=(const TimeDelta& other) const {
|
||||
return microseconds_ >= other.microseconds_;
|
||||
}
|
||||
bool operator>(const TimeDelta& other) const {
|
||||
return microseconds_ > other.microseconds_;
|
||||
}
|
||||
bool operator<(const TimeDelta& other) const {
|
||||
return microseconds_ < other.microseconds_;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit TimeDelta(int64_t us) : microseconds_(us) {}
|
||||
int64_t microseconds_;
|
||||
};
|
||||
inline TimeDelta operator*(const double& scalar, const TimeDelta& delta) {
|
||||
return delta * scalar;
|
||||
}
|
||||
inline TimeDelta operator*(const int64_t& scalar, const TimeDelta& delta) {
|
||||
return delta * scalar;
|
||||
}
|
||||
inline TimeDelta operator*(const int32_t& scalar, const TimeDelta& delta) {
|
||||
return delta * scalar;
|
||||
}
|
||||
|
||||
// Timestamp represents the time that has passed since some unspecified epoch.
|
||||
// The epoch is assumed to be before any represented timestamps, this means that
|
||||
// negative values are not valid. The most notable feature is that the
|
||||
// difference of of two Timestamps results in a TimeDelta.
|
||||
class Timestamp {
|
||||
public:
|
||||
static const Timestamp kPlusInfinity;
|
||||
static const Timestamp kNotInitialized;
|
||||
Timestamp() : Timestamp(kNotInitialized) {}
|
||||
static Timestamp Infinity() { return kPlusInfinity; }
|
||||
static Timestamp s(int64_t seconds) { return Timestamp(seconds * 1000000); }
|
||||
static Timestamp ms(int64_t millis) { return Timestamp(millis * 1000); }
|
||||
static Timestamp us(int64_t micros) { return Timestamp(micros); }
|
||||
int64_t s() const { return units_internal::DivideAndRound(us(), 1000000); }
|
||||
int64_t ms() const { return units_internal::DivideAndRound(us(), 1000); }
|
||||
int64_t us() const {
|
||||
RTC_DCHECK(IsFinite());
|
||||
return microseconds_;
|
||||
}
|
||||
bool IsInfinite() const {
|
||||
return microseconds_ == kPlusInfinity.microseconds_;
|
||||
}
|
||||
bool IsInitialized() const {
|
||||
return microseconds_ != kNotInitialized.microseconds_;
|
||||
}
|
||||
bool IsFinite() const { return IsInitialized() && !IsInfinite(); }
|
||||
TimeDelta operator-(const Timestamp& other) const {
|
||||
return TimeDelta::us(us() - other.us());
|
||||
}
|
||||
Timestamp operator-(const TimeDelta& delta) const {
|
||||
return Timestamp::us(us() - delta.us());
|
||||
}
|
||||
Timestamp operator+(const TimeDelta& delta) const {
|
||||
return Timestamp::us(us() + delta.us());
|
||||
}
|
||||
bool operator==(const Timestamp& other) const {
|
||||
return microseconds_ == other.microseconds_;
|
||||
}
|
||||
bool operator!=(const Timestamp& other) const {
|
||||
return microseconds_ != other.microseconds_;
|
||||
}
|
||||
bool operator<=(const Timestamp& other) const { return us() <= other.us(); }
|
||||
bool operator>=(const Timestamp& other) const { return us() >= other.us(); }
|
||||
bool operator>(const Timestamp& other) const { return us() > other.us(); }
|
||||
bool operator<(const Timestamp& other) const { return us() < other.us(); }
|
||||
|
||||
private:
|
||||
explicit Timestamp(int64_t us) : microseconds_(us) {}
|
||||
int64_t microseconds_;
|
||||
};
|
||||
|
||||
// DataSize is a class represeting a count of bytes. Note that while it can be
|
||||
// initialized by a number of bits, it does not guarantee that the resolution is
|
||||
// kept and the internal storage is in bytes. The number of bits will be
|
||||
// truncated to fit.
|
||||
class DataSize {
|
||||
public:
|
||||
static const DataSize kZero;
|
||||
static const DataSize kPlusInfinity;
|
||||
static const DataSize kNotInitialized;
|
||||
DataSize() : DataSize(kNotInitialized) {}
|
||||
static DataSize Zero() { return kZero; }
|
||||
static DataSize Infinity() { return kPlusInfinity; }
|
||||
static DataSize bytes(int64_t bytes) { return DataSize(bytes); }
|
||||
static DataSize bits(int64_t bits) { return DataSize(bits / 8); }
|
||||
int64_t bytes() const {
|
||||
RTC_DCHECK(IsFinite());
|
||||
return bytes_;
|
||||
}
|
||||
int64_t kilobytes() const {
|
||||
return units_internal::DivideAndRound(bytes(), 1000);
|
||||
}
|
||||
int64_t bits() const { return bytes() * 8; }
|
||||
int64_t kilobits() const {
|
||||
return units_internal::DivideAndRound(bits(), 1000);
|
||||
}
|
||||
bool IsZero() const { return bytes_ == 0; }
|
||||
bool IsInfinite() const { return bytes_ == kPlusInfinity.bytes_; }
|
||||
bool IsInitialized() const { return bytes_ != kNotInitialized.bytes_; }
|
||||
bool IsFinite() const { return IsInitialized() && !IsInfinite(); }
|
||||
DataSize operator-(const DataSize& other) const {
|
||||
return DataSize::bytes(bytes() - other.bytes());
|
||||
}
|
||||
DataSize operator+(const DataSize& other) const {
|
||||
return DataSize::bytes(bytes() + other.bytes());
|
||||
}
|
||||
DataSize operator*(double scalar) const {
|
||||
return DataSize::bytes(bytes() * scalar);
|
||||
}
|
||||
DataSize operator*(int64_t scalar) const {
|
||||
return DataSize::bytes(bytes() * scalar);
|
||||
}
|
||||
DataSize operator*(int32_t scalar) const {
|
||||
return DataSize::bytes(bytes() * scalar);
|
||||
}
|
||||
DataSize operator/(int64_t scalar) const {
|
||||
return DataSize::bytes(bytes() / scalar);
|
||||
}
|
||||
DataSize& operator-=(const DataSize& other) {
|
||||
bytes_ -= other.bytes();
|
||||
return *this;
|
||||
}
|
||||
DataSize& operator+=(const DataSize& other) {
|
||||
bytes_ += other.bytes();
|
||||
return *this;
|
||||
}
|
||||
bool operator==(const DataSize& other) const {
|
||||
return bytes_ == other.bytes_;
|
||||
}
|
||||
bool operator!=(const DataSize& other) const {
|
||||
return bytes_ != other.bytes_;
|
||||
}
|
||||
bool operator<=(const DataSize& other) const {
|
||||
return bytes_ <= other.bytes_;
|
||||
}
|
||||
bool operator>=(const DataSize& other) const {
|
||||
return bytes_ >= other.bytes_;
|
||||
}
|
||||
bool operator>(const DataSize& other) const { return bytes_ > other.bytes_; }
|
||||
bool operator<(const DataSize& other) const { return bytes_ < other.bytes_; }
|
||||
|
||||
private:
|
||||
explicit DataSize(int64_t bytes) : bytes_(bytes) {}
|
||||
int64_t bytes_;
|
||||
};
|
||||
inline DataSize operator*(const double& scalar, const DataSize& size) {
|
||||
return size * scalar;
|
||||
}
|
||||
inline DataSize operator*(const int64_t& scalar, const DataSize& size) {
|
||||
return size * scalar;
|
||||
}
|
||||
inline DataSize operator*(const int32_t& scalar, const DataSize& size) {
|
||||
return size * scalar;
|
||||
}
|
||||
|
||||
// DataRate is a class that represents a given data rate. This can be used to
|
||||
// represent bandwidth, encoding bitrate, etc. The internal storage is currently
|
||||
// bits per second (bps) since this makes it easier to intepret the raw value
|
||||
// when debugging. The promised precision, however is only that it will
|
||||
// represent bytes per second accurately. Any implementation depending on bps
|
||||
// resolution should document this by changing this comment.
|
||||
class DataRate {
|
||||
public:
|
||||
static const DataRate kZero;
|
||||
static const DataRate kPlusInfinity;
|
||||
static const DataRate kNotInitialized;
|
||||
DataRate() : DataRate(kNotInitialized) {}
|
||||
static DataRate Zero() { return kZero; }
|
||||
static DataRate Infinity() { return kPlusInfinity; }
|
||||
static DataRate bytes_per_second(int64_t bytes_per_sec) {
|
||||
return DataRate(bytes_per_sec * 8);
|
||||
}
|
||||
static DataRate bits_per_second(int64_t bits_per_sec) {
|
||||
return DataRate(bits_per_sec);
|
||||
}
|
||||
static DataRate bps(int64_t bits_per_sec) {
|
||||
return DataRate::bits_per_second(bits_per_sec);
|
||||
}
|
||||
static DataRate kbps(int64_t kilobits_per_sec) {
|
||||
return DataRate::bits_per_second(kilobits_per_sec * 1000);
|
||||
}
|
||||
int64_t bits_per_second() const {
|
||||
RTC_DCHECK(IsFinite());
|
||||
return bits_per_sec_;
|
||||
}
|
||||
int64_t bytes_per_second() const { return bits_per_second() / 8; }
|
||||
int64_t bps() const { return bits_per_second(); }
|
||||
int64_t kbps() const { return units_internal::DivideAndRound(bps(), 1000); }
|
||||
bool IsZero() const { return bits_per_sec_ == 0; }
|
||||
bool IsInfinite() const {
|
||||
return bits_per_sec_ == kPlusInfinity.bits_per_sec_;
|
||||
}
|
||||
bool IsInitialized() const {
|
||||
return bits_per_sec_ != kNotInitialized.bits_per_sec_;
|
||||
}
|
||||
bool IsFinite() const { return IsInitialized() && !IsInfinite(); }
|
||||
DataRate operator*(double scalar) const {
|
||||
return DataRate::bytes_per_second(bytes_per_second() * scalar);
|
||||
}
|
||||
DataRate operator*(int64_t scalar) const {
|
||||
return DataRate::bytes_per_second(bytes_per_second() * scalar);
|
||||
}
|
||||
DataRate operator*(int32_t scalar) const {
|
||||
return DataRate::bytes_per_second(bytes_per_second() * scalar);
|
||||
}
|
||||
bool operator==(const DataRate& other) const {
|
||||
return bits_per_sec_ == other.bits_per_sec_;
|
||||
}
|
||||
bool operator!=(const DataRate& other) const {
|
||||
return bits_per_sec_ != other.bits_per_sec_;
|
||||
}
|
||||
bool operator<=(const DataRate& other) const {
|
||||
return bits_per_sec_ <= other.bits_per_sec_;
|
||||
}
|
||||
bool operator>=(const DataRate& other) const {
|
||||
return bits_per_sec_ >= other.bits_per_sec_;
|
||||
}
|
||||
bool operator>(const DataRate& other) const {
|
||||
return bits_per_sec_ > other.bits_per_sec_;
|
||||
}
|
||||
bool operator<(const DataRate& other) const {
|
||||
return bits_per_sec_ < other.bits_per_sec_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Bits per second used internally to simplify debugging by making the value
|
||||
// more recognizable.
|
||||
explicit DataRate(int64_t bits_per_second) : bits_per_sec_(bits_per_second) {}
|
||||
int64_t bits_per_sec_;
|
||||
};
|
||||
inline DataRate operator*(const double& scalar, const DataRate& rate) {
|
||||
return rate * scalar;
|
||||
}
|
||||
inline DataRate operator*(const int64_t& scalar, const DataRate& rate) {
|
||||
return rate * scalar;
|
||||
}
|
||||
inline DataRate operator*(const int32_t& scalar, const DataRate& rate) {
|
||||
return rate * scalar;
|
||||
}
|
||||
|
||||
DataRate operator/(const DataSize& size, const TimeDelta& duration);
|
||||
TimeDelta operator/(const DataSize& size, const DataRate& rate);
|
||||
DataSize operator*(const DataRate& rate, const TimeDelta& duration);
|
||||
DataSize operator*(const TimeDelta& duration, const DataRate& rate);
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os, const DataRate& datarate);
|
||||
::std::ostream& operator<<(::std::ostream& os, const DataSize& datasize);
|
||||
::std::ostream& operator<<(::std::ostream& os, const Timestamp& timestamp);
|
||||
::std::ostream& operator<<(::std::ostream& os, const TimeDelta& delta);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_NETWORK_CONTROL_INCLUDE_NETWORK_UNITS_H_
|
||||
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_types.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
StreamsConfig::StreamsConfig() = default;
|
||||
StreamsConfig::StreamsConfig(const StreamsConfig&) = default;
|
||||
StreamsConfig::~StreamsConfig() = default;
|
||||
|
||||
PacketResult::PacketResult() = default;
|
||||
PacketResult::PacketResult(const PacketResult& other) = default;
|
||||
PacketResult::~PacketResult() = default;
|
||||
|
||||
TransportPacketsFeedback::TransportPacketsFeedback() = default;
|
||||
TransportPacketsFeedback::TransportPacketsFeedback(
|
||||
const TransportPacketsFeedback& other) = default;
|
||||
TransportPacketsFeedback::~TransportPacketsFeedback() = default;
|
||||
|
||||
std::vector<PacketResult> TransportPacketsFeedback::ReceivedWithSendInfo()
|
||||
const {
|
||||
std::vector<PacketResult> res;
|
||||
for (const PacketResult& fb : packet_feedbacks) {
|
||||
if (fb.receive_time.IsFinite() && fb.sent_packet.has_value()) {
|
||||
res.push_back(fb);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<PacketResult> TransportPacketsFeedback::LostWithSendInfo() const {
|
||||
std::vector<PacketResult> res;
|
||||
for (const PacketResult& fb : packet_feedbacks) {
|
||||
if (fb.receive_time.IsInfinite() && fb.sent_packet.has_value()) {
|
||||
res.push_back(fb);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<PacketResult> TransportPacketsFeedback::PacketsWithFeedback()
|
||||
const {
|
||||
return packet_feedbacks;
|
||||
}
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os,
|
||||
const ProbeClusterConfig& config) {
|
||||
return os << "ProbeClusterConfig(...)";
|
||||
}
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os, const PacerConfig& config) {
|
||||
return os << "PacerConfig(...)";
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_units.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
int64_t kPlusInfinityVal = std::numeric_limits<int64_t>::max();
|
||||
int64_t kMinusInfinityVal = std::numeric_limits<int64_t>::min();
|
||||
int64_t kSignedNotInitializedVal = kMinusInfinityVal + 1;
|
||||
int64_t kNotInitializedVal = -1;
|
||||
} // namespace
|
||||
const TimeDelta TimeDelta::kZero = TimeDelta(0);
|
||||
const TimeDelta TimeDelta::kMinusInfinity = TimeDelta(kMinusInfinityVal);
|
||||
const TimeDelta TimeDelta::kPlusInfinity = TimeDelta(kPlusInfinityVal);
|
||||
const TimeDelta TimeDelta::kNotInitialized =
|
||||
TimeDelta(kSignedNotInitializedVal);
|
||||
|
||||
const Timestamp Timestamp::kPlusInfinity = Timestamp(kPlusInfinityVal);
|
||||
const Timestamp Timestamp::kNotInitialized = Timestamp(kNotInitializedVal);
|
||||
|
||||
const DataRate DataRate::kZero = DataRate(0);
|
||||
const DataRate DataRate::kPlusInfinity = DataRate(kPlusInfinityVal);
|
||||
const DataRate DataRate::kNotInitialized = DataRate(kNotInitializedVal);
|
||||
|
||||
const DataSize DataSize::kZero = DataSize(0);
|
||||
const DataSize DataSize::kPlusInfinity = DataSize(kPlusInfinityVal);
|
||||
const DataSize DataSize::kNotInitialized = DataSize(kNotInitializedVal);
|
||||
|
||||
DataRate operator/(const DataSize& size, const TimeDelta& duration) {
|
||||
RTC_DCHECK(size.bytes() < std::numeric_limits<int64_t>::max() / 1000000)
|
||||
<< "size is too large, size: " << size.bytes() << " is not less than "
|
||||
<< std::numeric_limits<int64_t>::max() / 1000000;
|
||||
auto bytes_per_sec = size.bytes() * 1000000 / duration.us();
|
||||
return DataRate::bytes_per_second(bytes_per_sec);
|
||||
}
|
||||
|
||||
TimeDelta operator/(const DataSize& size, const DataRate& rate) {
|
||||
RTC_DCHECK(size.bytes() < std::numeric_limits<int64_t>::max() / 1000000)
|
||||
<< "size is too large, size: " << size.bytes() << " is not less than "
|
||||
<< std::numeric_limits<int64_t>::max() / 1000000;
|
||||
auto microseconds = size.bytes() * 1000000 / rate.bytes_per_second();
|
||||
return TimeDelta::us(microseconds);
|
||||
}
|
||||
|
||||
DataSize operator*(const DataRate& rate, const TimeDelta& duration) {
|
||||
auto micro_bytes = rate.bytes_per_second() * duration.us();
|
||||
auto bytes = units_internal::DivideAndRound(micro_bytes, 1000000);
|
||||
return DataSize::bytes(bytes);
|
||||
}
|
||||
|
||||
DataSize operator*(const TimeDelta& duration, const DataRate& rate) {
|
||||
return rate * duration;
|
||||
}
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os, const DataRate& value) {
|
||||
if (value == DataRate::kPlusInfinity) {
|
||||
return os << "inf bps";
|
||||
} else if (value == DataRate::kNotInitialized) {
|
||||
return os << "? bps";
|
||||
} else {
|
||||
return os << value.bps() << " bps";
|
||||
}
|
||||
}
|
||||
::std::ostream& operator<<(::std::ostream& os, const DataSize& value) {
|
||||
if (value == DataSize::kPlusInfinity) {
|
||||
return os << "inf bytes";
|
||||
} else if (value == DataSize::kNotInitialized) {
|
||||
return os << "? bytes";
|
||||
} else {
|
||||
return os << value.bytes() << " bytes";
|
||||
}
|
||||
}
|
||||
::std::ostream& operator<<(::std::ostream& os, const Timestamp& value) {
|
||||
if (value == Timestamp::kPlusInfinity) {
|
||||
return os << "inf ms";
|
||||
} else if (value == Timestamp::kNotInitialized) {
|
||||
return os << "? ms";
|
||||
} else {
|
||||
return os << value.ms() << " ms";
|
||||
}
|
||||
}
|
||||
::std::ostream& operator<<(::std::ostream& os, const TimeDelta& value) {
|
||||
if (value == TimeDelta::kPlusInfinity) {
|
||||
return os << "+inf ms";
|
||||
} else if (value == TimeDelta::kMinusInfinity) {
|
||||
return os << "-inf ms";
|
||||
} else if (value == TimeDelta::kNotInitialized) {
|
||||
return os << "? ms";
|
||||
} else {
|
||||
return os << value.ms() << " ms";
|
||||
}
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_units.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
TEST(TimeDeltaTest, GetBackSameValues) {
|
||||
const int64_t kValue = 499;
|
||||
for (int sign = -1; sign <= 1; ++sign) {
|
||||
int64_t value = kValue * sign;
|
||||
EXPECT_EQ(TimeDelta::ms(value).ms(), value);
|
||||
EXPECT_EQ(TimeDelta::us(value).us(), value);
|
||||
EXPECT_EQ(TimeDelta::s(value).s(), value);
|
||||
EXPECT_EQ(TimeDelta::seconds(value).s(), value);
|
||||
}
|
||||
EXPECT_EQ(TimeDelta::Zero().us(), 0);
|
||||
}
|
||||
|
||||
TEST(TimeDeltaTest, GetDifferentPrefix) {
|
||||
const int64_t kValue = 3000000;
|
||||
EXPECT_EQ(TimeDelta::us(kValue).s(), kValue / 1000000);
|
||||
EXPECT_EQ(TimeDelta::ms(kValue).s(), kValue / 1000);
|
||||
EXPECT_EQ(TimeDelta::us(kValue).ms(), kValue / 1000);
|
||||
|
||||
EXPECT_EQ(TimeDelta::ms(kValue).us(), kValue * 1000);
|
||||
EXPECT_EQ(TimeDelta::s(kValue).ms(), kValue * 1000);
|
||||
EXPECT_EQ(TimeDelta::s(kValue).us(), kValue * 1000000);
|
||||
}
|
||||
|
||||
TEST(TimeDeltaTest, IdentityChecks) {
|
||||
const int64_t kValue = 3000;
|
||||
EXPECT_TRUE(TimeDelta::Zero().IsZero());
|
||||
EXPECT_FALSE(TimeDelta::ms(kValue).IsZero());
|
||||
|
||||
EXPECT_TRUE(TimeDelta::Infinity().IsInfinite());
|
||||
EXPECT_TRUE(TimeDelta::kPlusInfinity.IsInfinite());
|
||||
EXPECT_TRUE(TimeDelta::kMinusInfinity.IsInfinite());
|
||||
EXPECT_FALSE(TimeDelta::Zero().IsInfinite());
|
||||
EXPECT_FALSE(TimeDelta::ms(-kValue).IsInfinite());
|
||||
EXPECT_FALSE(TimeDelta::ms(kValue).IsInfinite());
|
||||
|
||||
EXPECT_FALSE(TimeDelta::Infinity().IsFinite());
|
||||
EXPECT_FALSE(TimeDelta::kPlusInfinity.IsFinite());
|
||||
EXPECT_FALSE(TimeDelta::kMinusInfinity.IsFinite());
|
||||
EXPECT_TRUE(TimeDelta::ms(-kValue).IsFinite());
|
||||
EXPECT_TRUE(TimeDelta::ms(kValue).IsFinite());
|
||||
EXPECT_TRUE(TimeDelta::Zero().IsFinite());
|
||||
}
|
||||
|
||||
TEST(TimeDeltaTest, ComparisonOperators) {
|
||||
const int64_t kSmall = 450;
|
||||
const int64_t kLarge = 451;
|
||||
const TimeDelta small = TimeDelta::ms(kSmall);
|
||||
const TimeDelta large = TimeDelta::ms(kLarge);
|
||||
|
||||
EXPECT_EQ(TimeDelta::Zero(), TimeDelta::Zero());
|
||||
EXPECT_EQ(TimeDelta::Infinity(), TimeDelta::Infinity());
|
||||
EXPECT_EQ(small, TimeDelta::ms(kSmall));
|
||||
EXPECT_LE(small, TimeDelta::ms(kSmall));
|
||||
EXPECT_GE(small, TimeDelta::ms(kSmall));
|
||||
EXPECT_NE(small, TimeDelta::ms(kLarge));
|
||||
EXPECT_LE(small, TimeDelta::ms(kLarge));
|
||||
EXPECT_LT(small, TimeDelta::ms(kLarge));
|
||||
EXPECT_GE(large, TimeDelta::ms(kSmall));
|
||||
EXPECT_GT(large, TimeDelta::ms(kSmall));
|
||||
EXPECT_LT(TimeDelta::kZero, small);
|
||||
EXPECT_GT(TimeDelta::kZero, TimeDelta::ms(-kSmall));
|
||||
EXPECT_GT(TimeDelta::kZero, TimeDelta::ms(-kSmall));
|
||||
|
||||
EXPECT_GT(TimeDelta::kPlusInfinity, large);
|
||||
EXPECT_LT(TimeDelta::kMinusInfinity, TimeDelta::kZero);
|
||||
}
|
||||
|
||||
TEST(TimeDeltaTest, MathOperations) {
|
||||
const int64_t kValueA = 267;
|
||||
const int64_t kValueB = 450;
|
||||
const TimeDelta delta_a = TimeDelta::ms(kValueA);
|
||||
const TimeDelta delta_b = TimeDelta::ms(kValueB);
|
||||
EXPECT_EQ((delta_a + delta_b).ms(), kValueA + kValueB);
|
||||
EXPECT_EQ((delta_a - delta_b).ms(), kValueA - kValueB);
|
||||
|
||||
const int32_t kInt32Value = 123;
|
||||
const double kFloatValue = 123.0;
|
||||
EXPECT_EQ((TimeDelta::us(kValueA) * kValueB).us(), kValueA * kValueB);
|
||||
EXPECT_EQ((TimeDelta::us(kValueA) * kInt32Value).us(), kValueA * kInt32Value);
|
||||
EXPECT_EQ((TimeDelta::us(kValueA) * kFloatValue).us(), kValueA * kFloatValue);
|
||||
|
||||
EXPECT_EQ(TimeDelta::us(-kValueA).Abs().us(), kValueA);
|
||||
EXPECT_EQ(TimeDelta::us(kValueA).Abs().us(), kValueA);
|
||||
}
|
||||
|
||||
TEST(TimestampTest, GetBackSameValues) {
|
||||
const int64_t kValue = 499;
|
||||
EXPECT_EQ(Timestamp::ms(kValue).ms(), kValue);
|
||||
EXPECT_EQ(Timestamp::us(kValue).us(), kValue);
|
||||
EXPECT_EQ(Timestamp::s(kValue).s(), kValue);
|
||||
}
|
||||
|
||||
TEST(TimestampTest, GetDifferentPrefix) {
|
||||
const int64_t kValue = 3000000;
|
||||
EXPECT_EQ(Timestamp::us(kValue).s(), kValue / 1000000);
|
||||
EXPECT_EQ(Timestamp::ms(kValue).s(), kValue / 1000);
|
||||
EXPECT_EQ(Timestamp::us(kValue).ms(), kValue / 1000);
|
||||
|
||||
EXPECT_EQ(Timestamp::ms(kValue).us(), kValue * 1000);
|
||||
EXPECT_EQ(Timestamp::s(kValue).ms(), kValue * 1000);
|
||||
EXPECT_EQ(Timestamp::s(kValue).us(), kValue * 1000000);
|
||||
}
|
||||
|
||||
TEST(TimestampTest, IdentityChecks) {
|
||||
const int64_t kValue = 3000;
|
||||
|
||||
EXPECT_TRUE(Timestamp::Infinity().IsInfinite());
|
||||
EXPECT_FALSE(Timestamp::ms(kValue).IsInfinite());
|
||||
|
||||
EXPECT_FALSE(Timestamp::kNotInitialized.IsFinite());
|
||||
EXPECT_FALSE(Timestamp::Infinity().IsFinite());
|
||||
EXPECT_TRUE(Timestamp::ms(kValue).IsFinite());
|
||||
}
|
||||
|
||||
TEST(TimestampTest, ComparisonOperators) {
|
||||
const int64_t kSmall = 450;
|
||||
const int64_t kLarge = 451;
|
||||
|
||||
EXPECT_EQ(Timestamp::Infinity(), Timestamp::Infinity());
|
||||
EXPECT_EQ(Timestamp::ms(kSmall), Timestamp::ms(kSmall));
|
||||
EXPECT_LE(Timestamp::ms(kSmall), Timestamp::ms(kSmall));
|
||||
EXPECT_GE(Timestamp::ms(kSmall), Timestamp::ms(kSmall));
|
||||
EXPECT_NE(Timestamp::ms(kSmall), Timestamp::ms(kLarge));
|
||||
EXPECT_LE(Timestamp::ms(kSmall), Timestamp::ms(kLarge));
|
||||
EXPECT_LT(Timestamp::ms(kSmall), Timestamp::ms(kLarge));
|
||||
EXPECT_GE(Timestamp::ms(kLarge), Timestamp::ms(kSmall));
|
||||
EXPECT_GT(Timestamp::ms(kLarge), Timestamp::ms(kSmall));
|
||||
}
|
||||
|
||||
TEST(UnitConversionTest, TimestampAndTimeDeltaMath) {
|
||||
const int64_t kValueA = 267;
|
||||
const int64_t kValueB = 450;
|
||||
const Timestamp time_a = Timestamp::ms(kValueA);
|
||||
const Timestamp time_b = Timestamp::ms(kValueB);
|
||||
const TimeDelta delta_a = TimeDelta::ms(kValueA);
|
||||
|
||||
EXPECT_EQ((time_a - time_b), TimeDelta::ms(kValueA - kValueB));
|
||||
EXPECT_EQ((time_b - delta_a), Timestamp::ms(kValueB - kValueA));
|
||||
EXPECT_EQ((time_b + delta_a), Timestamp::ms(kValueB + kValueA));
|
||||
}
|
||||
|
||||
TEST(DataSizeTest, GetBackSameValues) {
|
||||
const int64_t kValue = 123 * 8;
|
||||
EXPECT_EQ(DataSize::bytes(kValue).bytes(), kValue);
|
||||
EXPECT_EQ(DataSize::bits(kValue).bits(), kValue);
|
||||
}
|
||||
|
||||
TEST(DataSizeTest, GetDifferentPrefix) {
|
||||
const int64_t kValue = 123 * 8000;
|
||||
EXPECT_EQ(DataSize::bytes(kValue).bits(), kValue * 8);
|
||||
EXPECT_EQ(DataSize::bits(kValue).bytes(), kValue / 8);
|
||||
EXPECT_EQ(DataSize::bits(kValue).kilobits(), kValue / 1000);
|
||||
EXPECT_EQ(DataSize::bytes(kValue).kilobytes(), kValue / 1000);
|
||||
}
|
||||
|
||||
TEST(DataSizeTest, IdentityChecks) {
|
||||
const int64_t kValue = 3000;
|
||||
EXPECT_TRUE(DataSize::Zero().IsZero());
|
||||
EXPECT_FALSE(DataSize::bytes(kValue).IsZero());
|
||||
|
||||
EXPECT_TRUE(DataSize::Infinity().IsInfinite());
|
||||
EXPECT_TRUE(DataSize::kPlusInfinity.IsInfinite());
|
||||
EXPECT_FALSE(DataSize::Zero().IsInfinite());
|
||||
EXPECT_FALSE(DataSize::bytes(kValue).IsInfinite());
|
||||
|
||||
EXPECT_FALSE(DataSize::Infinity().IsFinite());
|
||||
EXPECT_FALSE(DataSize::kPlusInfinity.IsFinite());
|
||||
EXPECT_TRUE(DataSize::bytes(kValue).IsFinite());
|
||||
EXPECT_TRUE(DataSize::Zero().IsFinite());
|
||||
}
|
||||
|
||||
TEST(DataSizeTest, ComparisonOperators) {
|
||||
const int64_t kSmall = 450;
|
||||
const int64_t kLarge = 451;
|
||||
const DataSize small = DataSize::bytes(kSmall);
|
||||
const DataSize large = DataSize::bytes(kLarge);
|
||||
|
||||
EXPECT_EQ(DataSize::Zero(), DataSize::Zero());
|
||||
EXPECT_EQ(DataSize::Infinity(), DataSize::Infinity());
|
||||
EXPECT_EQ(small, small);
|
||||
EXPECT_LE(small, small);
|
||||
EXPECT_GE(small, small);
|
||||
EXPECT_NE(small, large);
|
||||
EXPECT_LE(small, large);
|
||||
EXPECT_LT(small, large);
|
||||
EXPECT_GE(large, small);
|
||||
EXPECT_GT(large, small);
|
||||
EXPECT_LT(DataSize::kZero, small);
|
||||
|
||||
EXPECT_GT(DataSize::kPlusInfinity, large);
|
||||
}
|
||||
|
||||
TEST(DataSizeTest, MathOperations) {
|
||||
const int64_t kValueA = 450;
|
||||
const int64_t kValueB = 267;
|
||||
const DataSize size_a = DataSize::bytes(kValueA);
|
||||
const DataSize size_b = DataSize::bytes(kValueB);
|
||||
EXPECT_EQ((size_a + size_b).bytes(), kValueA + kValueB);
|
||||
EXPECT_EQ((size_a - size_b).bytes(), kValueA - kValueB);
|
||||
|
||||
const int32_t kInt32Value = 123;
|
||||
const double kFloatValue = 123.0;
|
||||
EXPECT_EQ((size_a * kValueB).bytes(), kValueA * kValueB);
|
||||
EXPECT_EQ((size_a * kInt32Value).bytes(), kValueA * kInt32Value);
|
||||
EXPECT_EQ((size_a * kFloatValue).bytes(), kValueA * kFloatValue);
|
||||
|
||||
EXPECT_EQ((size_a / 10).bytes(), kValueA / 10);
|
||||
|
||||
DataSize mutable_size = DataSize::bytes(kValueA);
|
||||
mutable_size += size_b;
|
||||
EXPECT_EQ(mutable_size.bytes(), kValueA + kValueB);
|
||||
mutable_size -= size_a;
|
||||
EXPECT_EQ(mutable_size.bytes(), kValueB);
|
||||
}
|
||||
|
||||
TEST(DataRateTest, GetBackSameValues) {
|
||||
const int64_t kValue = 123 * 8;
|
||||
EXPECT_EQ(DataRate::bytes_per_second(kValue).bytes_per_second(), kValue);
|
||||
EXPECT_EQ(DataRate::bits_per_second(kValue).bits_per_second(), kValue);
|
||||
EXPECT_EQ(DataRate::bps(kValue).bps(), kValue);
|
||||
EXPECT_EQ(DataRate::kbps(kValue).kbps(), kValue);
|
||||
}
|
||||
|
||||
TEST(DataRateTest, GetDifferentPrefix) {
|
||||
const int64_t kValue = 123 * 8000;
|
||||
EXPECT_EQ(DataRate::bytes_per_second(kValue).bps(), kValue * 8);
|
||||
EXPECT_EQ(DataRate::bits_per_second(kValue).bytes_per_second(), kValue / 8);
|
||||
EXPECT_EQ(DataRate::bps(kValue).kbps(), kValue / 1000);
|
||||
}
|
||||
|
||||
TEST(DataRateTest, IdentityChecks) {
|
||||
const int64_t kValue = 3000;
|
||||
EXPECT_TRUE(DataRate::Zero().IsZero());
|
||||
EXPECT_FALSE(DataRate::bytes_per_second(kValue).IsZero());
|
||||
|
||||
EXPECT_TRUE(DataRate::Infinity().IsInfinite());
|
||||
EXPECT_TRUE(DataRate::kPlusInfinity.IsInfinite());
|
||||
EXPECT_FALSE(DataRate::Zero().IsInfinite());
|
||||
EXPECT_FALSE(DataRate::bytes_per_second(kValue).IsInfinite());
|
||||
|
||||
EXPECT_FALSE(DataRate::Infinity().IsFinite());
|
||||
EXPECT_FALSE(DataRate::kPlusInfinity.IsFinite());
|
||||
EXPECT_TRUE(DataRate::bytes_per_second(kValue).IsFinite());
|
||||
EXPECT_TRUE(DataRate::Zero().IsFinite());
|
||||
}
|
||||
|
||||
TEST(DataRateTest, ComparisonOperators) {
|
||||
const int64_t kSmall = 450;
|
||||
const int64_t kLarge = 451;
|
||||
const DataRate small = DataRate::bytes_per_second(kSmall);
|
||||
const DataRate large = DataRate::bytes_per_second(kLarge);
|
||||
|
||||
EXPECT_EQ(DataRate::Zero(), DataRate::Zero());
|
||||
EXPECT_EQ(DataRate::Infinity(), DataRate::Infinity());
|
||||
EXPECT_EQ(small, small);
|
||||
EXPECT_LE(small, small);
|
||||
EXPECT_GE(small, small);
|
||||
EXPECT_NE(small, large);
|
||||
EXPECT_LE(small, large);
|
||||
EXPECT_LT(small, large);
|
||||
EXPECT_GE(large, small);
|
||||
EXPECT_GT(large, small);
|
||||
EXPECT_LT(DataRate::kZero, small);
|
||||
EXPECT_GT(DataRate::kPlusInfinity, large);
|
||||
}
|
||||
|
||||
TEST(DataRateTest, MathOperations) {
|
||||
const int64_t kValueA = 450;
|
||||
const int64_t kValueB = 267;
|
||||
const DataRate size_a = DataRate::bytes_per_second(kValueA);
|
||||
const int32_t kInt32Value = 123;
|
||||
const double kFloatValue = 123.0;
|
||||
EXPECT_EQ((size_a * kValueB).bytes_per_second(), kValueA * kValueB);
|
||||
EXPECT_EQ((size_a * kInt32Value).bytes_per_second(), kValueA * kInt32Value);
|
||||
EXPECT_EQ((size_a * kFloatValue).bytes_per_second(), kValueA * kFloatValue);
|
||||
}
|
||||
|
||||
TEST(UnitConversionTest, DataRateAndDataSizeAndTimeDelta) {
|
||||
const int64_t kValueA = 5;
|
||||
const int64_t kValueB = 450;
|
||||
const int64_t kValueC = 45000;
|
||||
const TimeDelta delta_a = TimeDelta::seconds(kValueA);
|
||||
const DataRate rate_b = DataRate::bytes_per_second(kValueB);
|
||||
const DataSize size_c = DataSize::bytes(kValueC);
|
||||
EXPECT_EQ((delta_a * rate_b).bytes(), kValueA * kValueB);
|
||||
EXPECT_EQ((size_c / delta_a).bytes_per_second(), kValueC / kValueA);
|
||||
EXPECT_EQ((size_c / rate_b).s(), kValueC / kValueB);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
83
modules/congestion_controller/rtp/pacer_controller.cc
Normal file
83
modules/congestion_controller/rtp/pacer_controller.cc
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/congestion_controller/rtp/pacer_controller.h"
|
||||
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_units.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
PacerController::PacerController(PacedSender* pacer) : pacer_(pacer) {
|
||||
sequenced_checker_.Detach();
|
||||
}
|
||||
|
||||
PacerController::~PacerController() = default;
|
||||
|
||||
void PacerController::OnCongestionWindow(CongestionWindow congestion_window) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
if (congestion_window.enabled) {
|
||||
congestion_window_ = congestion_window;
|
||||
} else {
|
||||
congestion_window_ = rtc::nullopt;
|
||||
congested_ = false;
|
||||
UpdatePacerState();
|
||||
}
|
||||
}
|
||||
|
||||
void PacerController::OnNetworkAvailability(NetworkAvailability msg) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
network_available_ = msg.network_available;
|
||||
congested_ = false;
|
||||
UpdatePacerState();
|
||||
}
|
||||
|
||||
void PacerController::OnNetworkRouteChange(NetworkRouteChange) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
congested_ = false;
|
||||
UpdatePacerState();
|
||||
}
|
||||
|
||||
void PacerController::OnPacerConfig(PacerConfig msg) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
DataRate pacing_rate = msg.data_window / msg.time_window;
|
||||
DataRate padding_rate = msg.pad_window / msg.time_window;
|
||||
pacer_->SetPacingRates(pacing_rate.bps(), padding_rate.bps());
|
||||
}
|
||||
|
||||
void PacerController::OnProbeClusterConfig(ProbeClusterConfig config) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
int64_t bitrate_bps = config.target_data_rate.bps();
|
||||
pacer_->CreateProbeCluster(bitrate_bps);
|
||||
}
|
||||
|
||||
void PacerController::OnOutstandingData(OutstandingData msg) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
if (congestion_window_.has_value()) {
|
||||
congested_ = msg.in_flight_data > congestion_window_->data_window;
|
||||
}
|
||||
UpdatePacerState();
|
||||
}
|
||||
|
||||
void PacerController::UpdatePacerState() {
|
||||
bool pause = congested_ || !network_available_;
|
||||
SetPacerState(pause);
|
||||
}
|
||||
|
||||
void PacerController::SetPacerState(bool paused) {
|
||||
if (paused && !pacer_paused_)
|
||||
pacer_->Pause();
|
||||
else if (!paused && pacer_paused_)
|
||||
pacer_->Resume();
|
||||
pacer_paused_ = paused;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
53
modules/congestion_controller/rtp/pacer_controller.h
Normal file
53
modules/congestion_controller/rtp/pacer_controller.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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 MODULES_CONGESTION_CONTROLLER_RTP_PACER_CONTROLLER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_PACER_CONTROLLER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_types.h"
|
||||
#include "modules/pacing/paced_sender.h"
|
||||
#include "rtc_base/sequenced_task_checker.h"
|
||||
|
||||
namespace webrtc {
|
||||
class Clock;
|
||||
|
||||
// Wrapper class to control pacer using task queues. Note that this class is
|
||||
// only designed to be used from a single task queue and has no built in
|
||||
// concurrency safety.
|
||||
// TODO(srte): Integrate this interface directly into PacedSender.
|
||||
class PacerController {
|
||||
public:
|
||||
explicit PacerController(PacedSender* pacer);
|
||||
~PacerController();
|
||||
void OnCongestionWindow(CongestionWindow msg);
|
||||
void OnNetworkAvailability(NetworkAvailability msg);
|
||||
void OnNetworkRouteChange(NetworkRouteChange msg);
|
||||
void OnOutstandingData(OutstandingData msg);
|
||||
void OnPacerConfig(PacerConfig msg);
|
||||
void OnProbeClusterConfig(ProbeClusterConfig msg);
|
||||
|
||||
private:
|
||||
void UpdatePacerState();
|
||||
void SetPacerState(bool paused);
|
||||
PacedSender* const pacer_;
|
||||
|
||||
rtc::Optional<PacerConfig> current_pacer_config_;
|
||||
rtc::Optional<CongestionWindow> congestion_window_;
|
||||
bool congested_ = false;
|
||||
bool pacer_paused_ = false;
|
||||
bool network_available_ = true;
|
||||
|
||||
rtc::SequencedTaskChecker sequenced_checker_;
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(PacerController);
|
||||
};
|
||||
} // namespace webrtc
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_PACER_CONTROLLER_H_
|
||||
189
modules/congestion_controller/rtp/probe_bitrate_estimator.cc
Normal file
189
modules/congestion_controller/rtp/probe_bitrate_estimator.cc
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/probe_bitrate_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "logging/rtc_event_log/events/rtc_event_probe_result_failure.h"
|
||||
#include "logging/rtc_event_log/events/rtc_event_probe_result_success.h"
|
||||
#include "logging/rtc_event_log/rtc_event_log.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
#include "rtc_base/ptr_util.h"
|
||||
|
||||
namespace {
|
||||
// The minumum number of probes we need to receive feedback about in percent
|
||||
// in order to have a valid estimate.
|
||||
constexpr int kMinReceivedProbesPercent = 80;
|
||||
|
||||
// The minumum number of bytes we need to receive feedback about in percent
|
||||
// in order to have a valid estimate.
|
||||
constexpr int kMinReceivedBytesPercent = 80;
|
||||
|
||||
// The maximum |receive rate| / |send rate| ratio for a valid estimate.
|
||||
constexpr float kMaxValidRatio = 2.0f;
|
||||
|
||||
// The minimum |receive rate| / |send rate| ratio assuming that the link is
|
||||
// not saturated, i.e. we assume that we will receive at least
|
||||
// kMinRatioForUnsaturatedLink * |send rate| if |send rate| is less than the
|
||||
// link capacity.
|
||||
constexpr float kMinRatioForUnsaturatedLink = 0.9f;
|
||||
|
||||
// The target utilization of the link. If we know true link capacity
|
||||
// we'd like to send at 95% of that rate.
|
||||
constexpr float kTargetUtilizationFraction = 0.95f;
|
||||
|
||||
// The maximum time period over which the cluster history is retained.
|
||||
// This is also the maximum time period beyond which a probing burst is not
|
||||
// expected to last.
|
||||
constexpr int kMaxClusterHistoryMs = 1000;
|
||||
|
||||
// The maximum time interval between first and the last probe on a cluster
|
||||
// on the sender side as well as the receive side.
|
||||
constexpr int kMaxProbeIntervalMs = 1000;
|
||||
} // namespace
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
ProbeBitrateEstimator::ProbeBitrateEstimator(RtcEventLog* event_log)
|
||||
: event_log_(event_log) {}
|
||||
|
||||
ProbeBitrateEstimator::~ProbeBitrateEstimator() = default;
|
||||
|
||||
int ProbeBitrateEstimator::HandleProbeAndEstimateBitrate(
|
||||
const PacketFeedback& packet_feedback) {
|
||||
int cluster_id = packet_feedback.pacing_info.probe_cluster_id;
|
||||
RTC_DCHECK_NE(cluster_id, PacedPacketInfo::kNotAProbe);
|
||||
|
||||
EraseOldClusters(packet_feedback.arrival_time_ms - kMaxClusterHistoryMs);
|
||||
|
||||
int payload_size_bits =
|
||||
rtc::dchecked_cast<int>(packet_feedback.payload_size * 8);
|
||||
AggregatedCluster* cluster = &clusters_[cluster_id];
|
||||
|
||||
if (packet_feedback.send_time_ms < cluster->first_send_ms) {
|
||||
cluster->first_send_ms = packet_feedback.send_time_ms;
|
||||
}
|
||||
if (packet_feedback.send_time_ms > cluster->last_send_ms) {
|
||||
cluster->last_send_ms = packet_feedback.send_time_ms;
|
||||
cluster->size_last_send = payload_size_bits;
|
||||
}
|
||||
if (packet_feedback.arrival_time_ms < cluster->first_receive_ms) {
|
||||
cluster->first_receive_ms = packet_feedback.arrival_time_ms;
|
||||
cluster->size_first_receive = payload_size_bits;
|
||||
}
|
||||
if (packet_feedback.arrival_time_ms > cluster->last_receive_ms) {
|
||||
cluster->last_receive_ms = packet_feedback.arrival_time_ms;
|
||||
}
|
||||
cluster->size_total += payload_size_bits;
|
||||
cluster->num_probes += 1;
|
||||
|
||||
RTC_DCHECK_GT(packet_feedback.pacing_info.probe_cluster_min_probes, 0);
|
||||
RTC_DCHECK_GT(packet_feedback.pacing_info.probe_cluster_min_bytes, 0);
|
||||
|
||||
int min_probes = packet_feedback.pacing_info.probe_cluster_min_probes *
|
||||
kMinReceivedProbesPercent / 100;
|
||||
int min_bytes = packet_feedback.pacing_info.probe_cluster_min_bytes *
|
||||
kMinReceivedBytesPercent / 100;
|
||||
if (cluster->num_probes < min_probes || cluster->size_total < min_bytes * 8)
|
||||
return -1;
|
||||
|
||||
float send_interval_ms = cluster->last_send_ms - cluster->first_send_ms;
|
||||
float receive_interval_ms =
|
||||
cluster->last_receive_ms - cluster->first_receive_ms;
|
||||
|
||||
if (send_interval_ms <= 0 || send_interval_ms > kMaxProbeIntervalMs ||
|
||||
receive_interval_ms <= 0 || receive_interval_ms > kMaxProbeIntervalMs) {
|
||||
RTC_LOG(LS_INFO) << "Probing unsuccessful, invalid send/receive interval"
|
||||
<< " [cluster id: " << cluster_id
|
||||
<< "] [send interval: " << send_interval_ms << " ms]"
|
||||
<< " [receive interval: " << receive_interval_ms << " ms]";
|
||||
if (event_log_) {
|
||||
event_log_->Log(rtc::MakeUnique<RtcEventProbeResultFailure>(
|
||||
cluster_id, ProbeFailureReason::kInvalidSendReceiveInterval));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
// Since the |send_interval_ms| does not include the time it takes to actually
|
||||
// send the last packet the size of the last sent packet should not be
|
||||
// included when calculating the send bitrate.
|
||||
RTC_DCHECK_GT(cluster->size_total, cluster->size_last_send);
|
||||
float send_size = cluster->size_total - cluster->size_last_send;
|
||||
float send_bps = send_size / send_interval_ms * 1000;
|
||||
|
||||
// Since the |receive_interval_ms| does not include the time it takes to
|
||||
// actually receive the first packet the size of the first received packet
|
||||
// should not be included when calculating the receive bitrate.
|
||||
RTC_DCHECK_GT(cluster->size_total, cluster->size_first_receive);
|
||||
float receive_size = cluster->size_total - cluster->size_first_receive;
|
||||
float receive_bps = receive_size / receive_interval_ms * 1000;
|
||||
|
||||
float ratio = receive_bps / send_bps;
|
||||
if (ratio > kMaxValidRatio) {
|
||||
RTC_LOG(LS_INFO) << "Probing unsuccessful, receive/send ratio too high"
|
||||
<< " [cluster id: " << cluster_id
|
||||
<< "] [send: " << send_size << " bytes / "
|
||||
<< send_interval_ms << " ms = " << send_bps / 1000
|
||||
<< " kb/s]"
|
||||
<< " [receive: " << receive_size << " bytes / "
|
||||
<< receive_interval_ms << " ms = " << receive_bps / 1000
|
||||
<< " kb/s]"
|
||||
<< " [ratio: " << receive_bps / 1000 << " / "
|
||||
<< send_bps / 1000 << " = " << ratio
|
||||
<< " > kMaxValidRatio (" << kMaxValidRatio << ")]";
|
||||
if (event_log_) {
|
||||
event_log_->Log(rtc::MakeUnique<RtcEventProbeResultFailure>(
|
||||
cluster_id, ProbeFailureReason::kInvalidSendReceiveRatio));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
RTC_LOG(LS_INFO) << "Probing successful"
|
||||
<< " [cluster id: " << cluster_id << "] [send: " << send_size
|
||||
<< " bytes / " << send_interval_ms
|
||||
<< " ms = " << send_bps / 1000 << " kb/s]"
|
||||
<< " [receive: " << receive_size << " bytes / "
|
||||
<< receive_interval_ms << " ms = " << receive_bps / 1000
|
||||
<< " kb/s]";
|
||||
|
||||
float res = std::min(send_bps, receive_bps);
|
||||
// If we're receiving at significantly lower bitrate than we were sending at,
|
||||
// it suggests that we've found the true capacity of the link. In this case,
|
||||
// set the target bitrate slightly lower to not immediately overuse.
|
||||
if (receive_bps < kMinRatioForUnsaturatedLink * send_bps) {
|
||||
RTC_DCHECK_GT(send_bps, receive_bps);
|
||||
res = kTargetUtilizationFraction * receive_bps;
|
||||
}
|
||||
if (event_log_) {
|
||||
event_log_->Log(
|
||||
rtc::MakeUnique<RtcEventProbeResultSuccess>(cluster_id, res));
|
||||
}
|
||||
estimated_bitrate_bps_ = res;
|
||||
return *estimated_bitrate_bps_;
|
||||
}
|
||||
|
||||
rtc::Optional<int>
|
||||
ProbeBitrateEstimator::FetchAndResetLastEstimatedBitrateBps() {
|
||||
rtc::Optional<int> estimated_bitrate_bps = estimated_bitrate_bps_;
|
||||
estimated_bitrate_bps_.reset();
|
||||
return estimated_bitrate_bps;
|
||||
}
|
||||
|
||||
void ProbeBitrateEstimator::EraseOldClusters(int64_t timestamp_ms) {
|
||||
for (auto it = clusters_.begin(); it != clusters_.end();) {
|
||||
if (it->second.last_receive_ms < timestamp_ms) {
|
||||
it = clusters_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace webrtc
|
||||
55
modules/congestion_controller/rtp/probe_bitrate_estimator.h
Normal file
55
modules/congestion_controller/rtp/probe_bitrate_estimator.h
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_RTP_PROBE_BITRATE_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_PROBE_BITRATE_ESTIMATOR_H_
|
||||
|
||||
#include <limits>
|
||||
#include <map>
|
||||
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
|
||||
namespace webrtc {
|
||||
class RtcEventLog;
|
||||
|
||||
class ProbeBitrateEstimator {
|
||||
public:
|
||||
explicit ProbeBitrateEstimator(RtcEventLog* event_log);
|
||||
~ProbeBitrateEstimator();
|
||||
|
||||
// Should be called for every probe packet we receive feedback about.
|
||||
// Returns the estimated bitrate if the probe completes a valid cluster.
|
||||
int HandleProbeAndEstimateBitrate(const PacketFeedback& packet_feedback);
|
||||
|
||||
rtc::Optional<int> FetchAndResetLastEstimatedBitrateBps();
|
||||
|
||||
private:
|
||||
struct AggregatedCluster {
|
||||
int num_probes = 0;
|
||||
int64_t first_send_ms = std::numeric_limits<int64_t>::max();
|
||||
int64_t last_send_ms = 0;
|
||||
int64_t first_receive_ms = std::numeric_limits<int64_t>::max();
|
||||
int64_t last_receive_ms = 0;
|
||||
int size_last_send = 0;
|
||||
int size_first_receive = 0;
|
||||
int size_total = 0;
|
||||
};
|
||||
|
||||
// Erases old cluster data that was seen before |timestamp_ms|.
|
||||
void EraseOldClusters(int64_t timestamp_ms);
|
||||
|
||||
std::map<int, AggregatedCluster> clusters_;
|
||||
RtcEventLog* const event_log_;
|
||||
rtc::Optional<int> estimated_bitrate_bps_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_PROBE_BITRATE_ESTIMATOR_H_
|
||||
@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/probe_bitrate_estimator.h"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/remote_bitrate_estimator/aimd_rate_control.h"
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
constexpr int kInvalidBitrate = -1;
|
||||
constexpr int kDefaultMinProbes = 5;
|
||||
constexpr int kDefaultMinBytes = 5000;
|
||||
constexpr float kTargetUtilizationFraction = 0.95f;
|
||||
} // anonymous namespace
|
||||
|
||||
class TestProbeBitrateEstimator : public ::testing::Test {
|
||||
public:
|
||||
TestProbeBitrateEstimator() : probe_bitrate_estimator_(nullptr) {}
|
||||
|
||||
// TODO(philipel): Use PacedPacketInfo when ProbeBitrateEstimator is rewritten
|
||||
// to use that information.
|
||||
void AddPacketFeedback(int probe_cluster_id,
|
||||
size_t size_bytes,
|
||||
int64_t send_time_ms,
|
||||
int64_t arrival_time_ms,
|
||||
int min_probes = kDefaultMinProbes,
|
||||
int min_bytes = kDefaultMinBytes) {
|
||||
PacedPacketInfo pacing_info(probe_cluster_id, min_probes, min_bytes);
|
||||
PacketFeedback packet_feedback(arrival_time_ms, send_time_ms, 0, size_bytes,
|
||||
pacing_info);
|
||||
measured_bps_ =
|
||||
probe_bitrate_estimator_.HandleProbeAndEstimateBitrate(packet_feedback);
|
||||
}
|
||||
|
||||
protected:
|
||||
int measured_bps_ = kInvalidBitrate;
|
||||
ProbeBitrateEstimator probe_bitrate_estimator_;
|
||||
};
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, OneCluster) {
|
||||
AddPacketFeedback(0, 1000, 0, 10);
|
||||
AddPacketFeedback(0, 1000, 10, 20);
|
||||
AddPacketFeedback(0, 1000, 20, 30);
|
||||
AddPacketFeedback(0, 1000, 30, 40);
|
||||
|
||||
EXPECT_NEAR(measured_bps_, 800000, 10);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, OneClusterTooFewProbes) {
|
||||
AddPacketFeedback(0, 2000, 0, 10);
|
||||
AddPacketFeedback(0, 2000, 10, 20);
|
||||
AddPacketFeedback(0, 2000, 20, 30);
|
||||
|
||||
EXPECT_EQ(kInvalidBitrate, measured_bps_);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, OneClusterTooFewBytes) {
|
||||
const int kMinBytes = 6000;
|
||||
AddPacketFeedback(0, 800, 0, 10, kDefaultMinProbes, kMinBytes);
|
||||
AddPacketFeedback(0, 800, 10, 20, kDefaultMinProbes, kMinBytes);
|
||||
AddPacketFeedback(0, 800, 20, 30, kDefaultMinProbes, kMinBytes);
|
||||
AddPacketFeedback(0, 800, 30, 40, kDefaultMinProbes, kMinBytes);
|
||||
AddPacketFeedback(0, 800, 40, 50, kDefaultMinProbes, kMinBytes);
|
||||
|
||||
EXPECT_EQ(kInvalidBitrate, measured_bps_);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, SmallCluster) {
|
||||
const int kMinBytes = 1000;
|
||||
AddPacketFeedback(0, 150, 0, 10, kDefaultMinProbes, kMinBytes);
|
||||
AddPacketFeedback(0, 150, 10, 20, kDefaultMinProbes, kMinBytes);
|
||||
AddPacketFeedback(0, 150, 20, 30, kDefaultMinProbes, kMinBytes);
|
||||
AddPacketFeedback(0, 150, 30, 40, kDefaultMinProbes, kMinBytes);
|
||||
AddPacketFeedback(0, 150, 40, 50, kDefaultMinProbes, kMinBytes);
|
||||
AddPacketFeedback(0, 150, 50, 60, kDefaultMinProbes, kMinBytes);
|
||||
EXPECT_NEAR(measured_bps_, 120000, 10);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, LargeCluster) {
|
||||
const int kMinProbes = 30;
|
||||
const int kMinBytes = 312500;
|
||||
|
||||
int64_t send_time = 0;
|
||||
int64_t receive_time = 5;
|
||||
for (int i = 0; i < 25; ++i) {
|
||||
AddPacketFeedback(0, 12500, send_time, receive_time, kMinProbes, kMinBytes);
|
||||
++send_time;
|
||||
++receive_time;
|
||||
}
|
||||
EXPECT_NEAR(measured_bps_, 100000000, 10);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, FastReceive) {
|
||||
AddPacketFeedback(0, 1000, 0, 15);
|
||||
AddPacketFeedback(0, 1000, 10, 30);
|
||||
AddPacketFeedback(0, 1000, 20, 35);
|
||||
AddPacketFeedback(0, 1000, 30, 40);
|
||||
|
||||
EXPECT_NEAR(measured_bps_, 800000, 10);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, TooFastReceive) {
|
||||
AddPacketFeedback(0, 1000, 0, 19);
|
||||
AddPacketFeedback(0, 1000, 10, 22);
|
||||
AddPacketFeedback(0, 1000, 20, 25);
|
||||
AddPacketFeedback(0, 1000, 40, 27);
|
||||
|
||||
EXPECT_EQ(measured_bps_, kInvalidBitrate);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, SlowReceive) {
|
||||
AddPacketFeedback(0, 1000, 0, 10);
|
||||
AddPacketFeedback(0, 1000, 10, 40);
|
||||
AddPacketFeedback(0, 1000, 20, 70);
|
||||
AddPacketFeedback(0, 1000, 30, 85);
|
||||
// Expected send rate = 800 kbps, expected receive rate = 320 kbps.
|
||||
|
||||
EXPECT_NEAR(measured_bps_, kTargetUtilizationFraction * 320000, 10);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, BurstReceive) {
|
||||
AddPacketFeedback(0, 1000, 0, 50);
|
||||
AddPacketFeedback(0, 1000, 10, 50);
|
||||
AddPacketFeedback(0, 1000, 20, 50);
|
||||
AddPacketFeedback(0, 1000, 40, 50);
|
||||
|
||||
EXPECT_EQ(measured_bps_, kInvalidBitrate);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, MultipleClusters) {
|
||||
AddPacketFeedback(0, 1000, 0, 10);
|
||||
AddPacketFeedback(0, 1000, 10, 20);
|
||||
AddPacketFeedback(0, 1000, 20, 30);
|
||||
AddPacketFeedback(0, 1000, 40, 60);
|
||||
// Expected send rate = 600 kbps, expected receive rate = 480 kbps.
|
||||
EXPECT_NEAR(measured_bps_, kTargetUtilizationFraction * 480000, 10);
|
||||
|
||||
AddPacketFeedback(0, 1000, 50, 60);
|
||||
// Expected send rate = 640 kbps, expected receive rate = 640 kbps.
|
||||
EXPECT_NEAR(measured_bps_, 640000, 10);
|
||||
|
||||
AddPacketFeedback(1, 1000, 60, 70);
|
||||
AddPacketFeedback(1, 1000, 65, 77);
|
||||
AddPacketFeedback(1, 1000, 70, 84);
|
||||
AddPacketFeedback(1, 1000, 75, 90);
|
||||
// Expected send rate = 1600 kbps, expected receive rate = 1200 kbps.
|
||||
|
||||
EXPECT_NEAR(measured_bps_, kTargetUtilizationFraction * 1200000, 10);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, IgnoreOldClusters) {
|
||||
AddPacketFeedback(0, 1000, 0, 10);
|
||||
AddPacketFeedback(0, 1000, 10, 20);
|
||||
AddPacketFeedback(0, 1000, 20, 30);
|
||||
|
||||
AddPacketFeedback(1, 1000, 60, 70);
|
||||
AddPacketFeedback(1, 1000, 65, 77);
|
||||
AddPacketFeedback(1, 1000, 70, 84);
|
||||
AddPacketFeedback(1, 1000, 75, 90);
|
||||
// Expected send rate = 1600 kbps, expected receive rate = 1200 kbps.
|
||||
|
||||
EXPECT_NEAR(measured_bps_, kTargetUtilizationFraction * 1200000, 10);
|
||||
|
||||
// Coming in 6s later
|
||||
AddPacketFeedback(0, 1000, 40 + 6000, 60 + 6000);
|
||||
|
||||
EXPECT_EQ(measured_bps_, kInvalidBitrate);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, IgnoreSizeLastSendPacket) {
|
||||
AddPacketFeedback(0, 1000, 0, 10);
|
||||
AddPacketFeedback(0, 1000, 10, 20);
|
||||
AddPacketFeedback(0, 1000, 20, 30);
|
||||
AddPacketFeedback(0, 1000, 30, 40);
|
||||
AddPacketFeedback(0, 1500, 40, 50);
|
||||
// Expected send rate = 800 kbps, expected receive rate = 900 kbps.
|
||||
|
||||
EXPECT_NEAR(measured_bps_, 800000, 10);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, IgnoreSizeFirstReceivePacket) {
|
||||
AddPacketFeedback(0, 1500, 0, 10);
|
||||
AddPacketFeedback(0, 1000, 10, 20);
|
||||
AddPacketFeedback(0, 1000, 20, 30);
|
||||
AddPacketFeedback(0, 1000, 30, 40);
|
||||
// Expected send rate = 933 kbps, expected receive rate = 800 kbps.
|
||||
|
||||
EXPECT_NEAR(measured_bps_, kTargetUtilizationFraction * 800000, 10);
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, NoLastEstimatedBitrateBps) {
|
||||
EXPECT_FALSE(probe_bitrate_estimator_.FetchAndResetLastEstimatedBitrateBps());
|
||||
}
|
||||
|
||||
TEST_F(TestProbeBitrateEstimator, FetchLastEstimatedBitrateBps) {
|
||||
AddPacketFeedback(0, 1000, 0, 10);
|
||||
AddPacketFeedback(0, 1000, 10, 20);
|
||||
AddPacketFeedback(0, 1000, 20, 30);
|
||||
AddPacketFeedback(0, 1000, 30, 40);
|
||||
|
||||
auto estimated_bitrate_bps =
|
||||
probe_bitrate_estimator_.FetchAndResetLastEstimatedBitrateBps();
|
||||
EXPECT_TRUE(estimated_bitrate_bps);
|
||||
EXPECT_NEAR(*estimated_bitrate_bps, 800000, 10);
|
||||
EXPECT_FALSE(probe_bitrate_estimator_.FetchAndResetLastEstimatedBitrateBps());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
308
modules/congestion_controller/rtp/probe_controller.cc
Normal file
308
modules/congestion_controller/rtp/probe_controller.cc
Normal file
@ -0,0 +1,308 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/probe_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
#include "system_wrappers/include/field_trial.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
// The minimum number probing packets used.
|
||||
constexpr int kMinProbePacketsSent = 5;
|
||||
|
||||
// The minimum probing duration in ms.
|
||||
constexpr int kMinProbeDurationMs = 15;
|
||||
|
||||
// Maximum waiting time from the time of initiating probing to getting
|
||||
// the measured results back.
|
||||
constexpr int64_t kMaxWaitingTimeForProbingResultMs = 1000;
|
||||
|
||||
// Value of |min_bitrate_to_probe_further_bps_| that indicates
|
||||
// further probing is disabled.
|
||||
constexpr int kExponentialProbingDisabled = 0;
|
||||
|
||||
// Default probing bitrate limit. Applied only when the application didn't
|
||||
// specify max bitrate.
|
||||
constexpr int64_t kDefaultMaxProbingBitrateBps = 5000000;
|
||||
|
||||
// Interval between probes when ALR periodic probing is enabled.
|
||||
constexpr int64_t kAlrPeriodicProbingIntervalMs = 5000;
|
||||
|
||||
// Minimum probe bitrate percentage to probe further for repeated probes,
|
||||
// relative to the previous probe. For example, if 1Mbps probe results in
|
||||
// 80kbps, then we'll probe again at 1.6Mbps. In that case second probe won't be
|
||||
// sent if we get 600kbps from the first one.
|
||||
constexpr int kRepeatedProbeMinPercentage = 70;
|
||||
|
||||
// If the bitrate drops to a factor |kBitrateDropThreshold| or lower
|
||||
// and we recover within |kBitrateDropTimeoutMs|, then we'll send
|
||||
// a probe at a fraction |kProbeFractionAfterDrop| of the original bitrate.
|
||||
constexpr double kBitrateDropThreshold = 0.66;
|
||||
constexpr int kBitrateDropTimeoutMs = 5000;
|
||||
constexpr double kProbeFractionAfterDrop = 0.85;
|
||||
|
||||
// Timeout for probing after leaving ALR. If the bitrate drops significantly,
|
||||
// (as determined by the delay based estimator) and we leave ALR, then we will
|
||||
// send a probe if we recover within |kLeftAlrTimeoutMs| ms.
|
||||
constexpr int kAlrEndedTimeoutMs = 3000;
|
||||
|
||||
// The expected uncertainty of probe result (as a fraction of the target probe
|
||||
// This is a limit on how often probing can be done when there is a BW
|
||||
// drop detected in ALR.
|
||||
constexpr int64_t kMinTimeBetweenAlrProbesMs = 5000;
|
||||
|
||||
// bitrate). Used to avoid probing if the probe bitrate is close to our current
|
||||
// estimate.
|
||||
constexpr double kProbeUncertainty = 0.05;
|
||||
|
||||
// Use probing to recover faster after large bitrate estimate drops.
|
||||
constexpr char kBweRapidRecoveryExperiment[] =
|
||||
"WebRTC-BweRapidRecoveryExperiment";
|
||||
|
||||
} // namespace
|
||||
|
||||
ProbeController::ProbeController(NetworkControllerObserver* observer)
|
||||
: observer_(observer), enable_periodic_alr_probing_(false) {
|
||||
Reset(0);
|
||||
in_rapid_recovery_experiment_ = webrtc::field_trial::FindFullName(
|
||||
kBweRapidRecoveryExperiment) == "Enabled";
|
||||
}
|
||||
|
||||
ProbeController::~ProbeController() {}
|
||||
|
||||
void ProbeController::SetBitrates(int64_t min_bitrate_bps,
|
||||
int64_t start_bitrate_bps,
|
||||
int64_t max_bitrate_bps,
|
||||
int64_t at_time_ms) {
|
||||
if (start_bitrate_bps > 0) {
|
||||
start_bitrate_bps_ = start_bitrate_bps;
|
||||
estimated_bitrate_bps_ = start_bitrate_bps;
|
||||
} else if (start_bitrate_bps_ == 0) {
|
||||
start_bitrate_bps_ = min_bitrate_bps;
|
||||
}
|
||||
|
||||
// The reason we use the variable |old_max_bitrate_pbs| is because we
|
||||
// need to set |max_bitrate_bps_| before we call InitiateProbing.
|
||||
int64_t old_max_bitrate_bps = max_bitrate_bps_;
|
||||
max_bitrate_bps_ = max_bitrate_bps;
|
||||
|
||||
switch (state_) {
|
||||
case State::kInit:
|
||||
if (network_available_)
|
||||
InitiateExponentialProbing(at_time_ms);
|
||||
break;
|
||||
|
||||
case State::kWaitingForProbingResult:
|
||||
break;
|
||||
|
||||
case State::kProbingComplete:
|
||||
// If the new max bitrate is higher than the old max bitrate and the
|
||||
// estimate is lower than the new max bitrate then initiate probing.
|
||||
if (estimated_bitrate_bps_ != 0 &&
|
||||
old_max_bitrate_bps < max_bitrate_bps_ &&
|
||||
estimated_bitrate_bps_ < max_bitrate_bps_) {
|
||||
// The assumption is that if we jump more than 20% in the bandwidth
|
||||
// estimate or if the bandwidth estimate is within 90% of the new
|
||||
// max bitrate then the probing attempt was successful.
|
||||
mid_call_probing_succcess_threshold_ =
|
||||
std::min(estimated_bitrate_bps_ * 1.2, max_bitrate_bps_ * 0.9);
|
||||
mid_call_probing_waiting_for_result_ = true;
|
||||
mid_call_probing_bitrate_bps_ = max_bitrate_bps_;
|
||||
|
||||
RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.MidCallProbing.Initiated",
|
||||
max_bitrate_bps_ / 1000);
|
||||
|
||||
InitiateProbing(at_time_ms, {max_bitrate_bps}, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ProbeController::OnNetworkAvailability(NetworkAvailability msg) {
|
||||
network_available_ = msg.network_available;
|
||||
if (network_available_ && state_ == State::kInit && start_bitrate_bps_ > 0)
|
||||
InitiateExponentialProbing(msg.at_time.ms());
|
||||
}
|
||||
|
||||
void ProbeController::InitiateExponentialProbing(int64_t at_time_ms) {
|
||||
RTC_DCHECK(network_available_);
|
||||
RTC_DCHECK(state_ == State::kInit);
|
||||
RTC_DCHECK_GT(start_bitrate_bps_, 0);
|
||||
|
||||
// When probing at 1.8 Mbps ( 6x 300), this represents a threshold of
|
||||
// 1.2 Mbps to continue probing.
|
||||
InitiateProbing(at_time_ms, {3 * start_bitrate_bps_, 6 * start_bitrate_bps_},
|
||||
true);
|
||||
}
|
||||
|
||||
void ProbeController::SetEstimatedBitrate(int64_t bitrate_bps,
|
||||
int64_t at_time_ms) {
|
||||
int64_t now_ms = at_time_ms;
|
||||
|
||||
if (mid_call_probing_waiting_for_result_ &&
|
||||
bitrate_bps >= mid_call_probing_succcess_threshold_) {
|
||||
RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.MidCallProbing.Success",
|
||||
mid_call_probing_bitrate_bps_ / 1000);
|
||||
RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.MidCallProbing.ProbedKbps",
|
||||
bitrate_bps / 1000);
|
||||
mid_call_probing_waiting_for_result_ = false;
|
||||
}
|
||||
|
||||
if (state_ == State::kWaitingForProbingResult) {
|
||||
// Continue probing if probing results indicate channel has greater
|
||||
// capacity.
|
||||
RTC_LOG(LS_INFO) << "Measured bitrate: " << bitrate_bps
|
||||
<< " Minimum to probe further: "
|
||||
<< min_bitrate_to_probe_further_bps_;
|
||||
|
||||
if (min_bitrate_to_probe_further_bps_ != kExponentialProbingDisabled &&
|
||||
bitrate_bps > min_bitrate_to_probe_further_bps_) {
|
||||
// Double the probing bitrate.
|
||||
InitiateProbing(now_ms, {2 * bitrate_bps}, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (bitrate_bps < kBitrateDropThreshold * estimated_bitrate_bps_) {
|
||||
time_of_last_large_drop_ms_ = now_ms;
|
||||
bitrate_before_last_large_drop_bps_ = estimated_bitrate_bps_;
|
||||
}
|
||||
|
||||
estimated_bitrate_bps_ = bitrate_bps;
|
||||
}
|
||||
|
||||
void ProbeController::EnablePeriodicAlrProbing(bool enable) {
|
||||
enable_periodic_alr_probing_ = enable;
|
||||
}
|
||||
|
||||
void ProbeController::SetAlrStartTimeMs(
|
||||
rtc::Optional<int64_t> alr_start_time_ms) {
|
||||
alr_start_time_ms_ = alr_start_time_ms;
|
||||
}
|
||||
void ProbeController::SetAlrEndedTimeMs(int64_t alr_end_time_ms) {
|
||||
alr_end_time_ms_.emplace(alr_end_time_ms);
|
||||
}
|
||||
|
||||
void ProbeController::RequestProbe(int64_t at_time_ms) {
|
||||
// Called once we have returned to normal state after a large drop in
|
||||
// estimated bandwidth. The current response is to initiate a single probe
|
||||
// session (if not already probing) at the previous bitrate.
|
||||
//
|
||||
// If the probe session fails, the assumption is that this drop was a
|
||||
// real one from a competing flow or a network change.
|
||||
bool in_alr = alr_start_time_ms_.has_value();
|
||||
bool alr_ended_recently =
|
||||
(alr_end_time_ms_.has_value() &&
|
||||
at_time_ms - alr_end_time_ms_.value() < kAlrEndedTimeoutMs);
|
||||
if (in_alr || alr_ended_recently || in_rapid_recovery_experiment_) {
|
||||
if (state_ == State::kProbingComplete) {
|
||||
uint32_t suggested_probe_bps =
|
||||
kProbeFractionAfterDrop * bitrate_before_last_large_drop_bps_;
|
||||
uint32_t min_expected_probe_result_bps =
|
||||
(1 - kProbeUncertainty) * suggested_probe_bps;
|
||||
int64_t time_since_drop_ms = at_time_ms - time_of_last_large_drop_ms_;
|
||||
int64_t time_since_probe_ms = at_time_ms - last_bwe_drop_probing_time_ms_;
|
||||
if (min_expected_probe_result_bps > estimated_bitrate_bps_ &&
|
||||
time_since_drop_ms < kBitrateDropTimeoutMs &&
|
||||
time_since_probe_ms > kMinTimeBetweenAlrProbesMs) {
|
||||
RTC_LOG(LS_INFO) << "Detected big bandwidth drop, start probing.";
|
||||
// Track how often we probe in response to bandwidth drop in ALR.
|
||||
RTC_HISTOGRAM_COUNTS_10000(
|
||||
"WebRTC.BWE.BweDropProbingIntervalInS",
|
||||
(at_time_ms - last_bwe_drop_probing_time_ms_) / 1000);
|
||||
InitiateProbing(at_time_ms, {suggested_probe_bps}, false);
|
||||
last_bwe_drop_probing_time_ms_ = at_time_ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProbeController::Reset(int64_t at_time_ms) {
|
||||
network_available_ = true;
|
||||
state_ = State::kInit;
|
||||
min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled;
|
||||
time_last_probing_initiated_ms_ = 0;
|
||||
estimated_bitrate_bps_ = 0;
|
||||
start_bitrate_bps_ = 0;
|
||||
max_bitrate_bps_ = 0;
|
||||
int64_t now_ms = at_time_ms;
|
||||
last_bwe_drop_probing_time_ms_ = now_ms;
|
||||
alr_end_time_ms_.reset();
|
||||
mid_call_probing_waiting_for_result_ = false;
|
||||
time_of_last_large_drop_ms_ = now_ms;
|
||||
bitrate_before_last_large_drop_bps_ = 0;
|
||||
}
|
||||
|
||||
void ProbeController::Process(int64_t at_time_ms) {
|
||||
int64_t now_ms = at_time_ms;
|
||||
|
||||
if (now_ms - time_last_probing_initiated_ms_ >
|
||||
kMaxWaitingTimeForProbingResultMs) {
|
||||
mid_call_probing_waiting_for_result_ = false;
|
||||
|
||||
if (state_ == State::kWaitingForProbingResult) {
|
||||
RTC_LOG(LS_INFO) << "kWaitingForProbingResult: timeout";
|
||||
state_ = State::kProbingComplete;
|
||||
min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
if (state_ != State::kProbingComplete || !enable_periodic_alr_probing_)
|
||||
return;
|
||||
|
||||
// Probe bandwidth periodically when in ALR state.
|
||||
if (alr_start_time_ms_ && estimated_bitrate_bps_ > 0) {
|
||||
int64_t next_probe_time_ms =
|
||||
std::max(*alr_start_time_ms_, time_last_probing_initiated_ms_) +
|
||||
kAlrPeriodicProbingIntervalMs;
|
||||
if (now_ms >= next_probe_time_ms) {
|
||||
InitiateProbing(now_ms, {estimated_bitrate_bps_ * 2}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProbeController::InitiateProbing(
|
||||
int64_t now_ms,
|
||||
std::initializer_list<int64_t> bitrates_to_probe,
|
||||
bool probe_further) {
|
||||
for (int64_t bitrate : bitrates_to_probe) {
|
||||
RTC_DCHECK_GT(bitrate, 0);
|
||||
int64_t max_probe_bitrate_bps =
|
||||
max_bitrate_bps_ > 0 ? max_bitrate_bps_ : kDefaultMaxProbingBitrateBps;
|
||||
if (bitrate > max_probe_bitrate_bps) {
|
||||
bitrate = max_probe_bitrate_bps;
|
||||
probe_further = false;
|
||||
}
|
||||
|
||||
ProbeClusterConfig config;
|
||||
config.at_time = Timestamp::ms(now_ms);
|
||||
config.target_data_rate = DataRate::bps(rtc::dchecked_cast<int>(bitrate));
|
||||
config.target_duration = TimeDelta::ms(kMinProbeDurationMs);
|
||||
config.target_probe_count = kMinProbePacketsSent;
|
||||
observer_->OnProbeClusterConfig(config);
|
||||
}
|
||||
time_last_probing_initiated_ms_ = now_ms;
|
||||
if (probe_further) {
|
||||
state_ = State::kWaitingForProbingResult;
|
||||
min_bitrate_to_probe_further_bps_ =
|
||||
(*(bitrates_to_probe.end() - 1)) * kRepeatedProbeMinPercentage / 100;
|
||||
} else {
|
||||
state_ = State::kProbingComplete;
|
||||
min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
97
modules/congestion_controller/rtp/probe_controller.h
Normal file
97
modules/congestion_controller/rtp/probe_controller.h
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_RTP_PROBE_CONTROLLER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_PROBE_CONTROLLER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
#include "api/optional.h"
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_control.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class Clock;
|
||||
|
||||
// This class controls initiation of probing to estimate initial channel
|
||||
// capacity. There is also support for probing during a session when max
|
||||
// bitrate is adjusted by an application.
|
||||
class ProbeController {
|
||||
public:
|
||||
explicit ProbeController(NetworkControllerObserver* observer);
|
||||
~ProbeController();
|
||||
|
||||
void SetBitrates(int64_t min_bitrate_bps,
|
||||
int64_t start_bitrate_bps,
|
||||
int64_t max_bitrate_bps,
|
||||
int64_t at_time_ms);
|
||||
|
||||
void OnNetworkAvailability(NetworkAvailability msg);
|
||||
|
||||
void SetEstimatedBitrate(int64_t bitrate_bps, int64_t at_time_ms);
|
||||
|
||||
void EnablePeriodicAlrProbing(bool enable);
|
||||
|
||||
void SetAlrStartTimeMs(rtc::Optional<int64_t> alr_start_time);
|
||||
void SetAlrEndedTimeMs(int64_t alr_end_time);
|
||||
|
||||
void RequestProbe(int64_t at_time_ms);
|
||||
|
||||
// Resets the ProbeController to a state equivalent to as if it was just
|
||||
// created EXCEPT for |enable_periodic_alr_probing_|.
|
||||
void Reset(int64_t at_time_ms);
|
||||
|
||||
void Process(int64_t at_time_ms);
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
// Initial state where no probing has been triggered yet.
|
||||
kInit,
|
||||
// Waiting for probing results to continue further probing.
|
||||
kWaitingForProbingResult,
|
||||
// Probing is complete.
|
||||
kProbingComplete,
|
||||
};
|
||||
|
||||
void InitiateExponentialProbing(int64_t at_time_ms);
|
||||
void InitiateProbing(int64_t now_ms,
|
||||
std::initializer_list<int64_t> bitrates_to_probe,
|
||||
bool probe_further);
|
||||
|
||||
NetworkControllerObserver* const observer_;
|
||||
|
||||
bool network_available_;
|
||||
State state_;
|
||||
int64_t min_bitrate_to_probe_further_bps_;
|
||||
int64_t time_last_probing_initiated_ms_;
|
||||
int64_t estimated_bitrate_bps_;
|
||||
int64_t start_bitrate_bps_;
|
||||
int64_t max_bitrate_bps_;
|
||||
int64_t last_bwe_drop_probing_time_ms_;
|
||||
rtc::Optional<int64_t> alr_start_time_ms_;
|
||||
rtc::Optional<int64_t> alr_end_time_ms_;
|
||||
bool enable_periodic_alr_probing_;
|
||||
int64_t time_of_last_large_drop_ms_;
|
||||
int64_t bitrate_before_last_large_drop_bps_;
|
||||
|
||||
bool in_rapid_recovery_experiment_;
|
||||
// For WebRTC.BWE.MidCallProbing.* metric.
|
||||
bool mid_call_probing_waiting_for_result_;
|
||||
int64_t mid_call_probing_bitrate_bps_;
|
||||
int64_t mid_call_probing_succcess_threshold_;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ProbeController);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_PROBE_CONTROLLER_H_
|
||||
292
modules/congestion_controller/rtp/probe_controller_unittest.cc
Normal file
292
modules/congestion_controller/rtp/probe_controller_unittest.cc
Normal file
@ -0,0 +1,292 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/network_control/include/network_types.h"
|
||||
#include "modules/congestion_controller/rtp/probe_controller.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
using testing::_;
|
||||
using testing::AtLeast;
|
||||
using testing::Field;
|
||||
using testing::Matcher;
|
||||
using testing::NiceMock;
|
||||
using testing::Return;
|
||||
|
||||
using webrtc::ProbeClusterConfig;
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kMinBitrateBps = 100;
|
||||
constexpr int kStartBitrateBps = 300;
|
||||
constexpr int kMaxBitrateBps = 10000;
|
||||
|
||||
constexpr int kExponentialProbingTimeoutMs = 5000;
|
||||
|
||||
constexpr int kAlrProbeInterval = 5000;
|
||||
constexpr int kAlrEndedTimeoutMs = 3000;
|
||||
constexpr int kBitrateDropTimeoutMs = 5000;
|
||||
|
||||
inline Matcher<ProbeClusterConfig> DataRateEqBps(int bps) {
|
||||
return Field(&ProbeClusterConfig::target_data_rate, DataRate::bps(bps));
|
||||
}
|
||||
class MockNetworkControllerObserver : public NetworkControllerObserver {
|
||||
public:
|
||||
MOCK_METHOD1(OnCongestionWindow, void(CongestionWindow));
|
||||
MOCK_METHOD1(OnPacerConfig, void(PacerConfig));
|
||||
MOCK_METHOD1(OnProbeClusterConfig, void(ProbeClusterConfig));
|
||||
MOCK_METHOD1(OnTargetTransferRate, void(TargetTransferRate));
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class ProbeControllerTest : public ::testing::Test {
|
||||
protected:
|
||||
ProbeControllerTest() : clock_(100000000L) {
|
||||
probe_controller_.reset(new ProbeController(&cluster_handler_));
|
||||
}
|
||||
~ProbeControllerTest() override {}
|
||||
|
||||
void SetNetworkAvailable(bool available) {
|
||||
NetworkAvailability msg;
|
||||
msg.at_time = Timestamp::ms(NowMs());
|
||||
msg.network_available = available;
|
||||
probe_controller_->OnNetworkAvailability(msg);
|
||||
}
|
||||
|
||||
int64_t NowMs() { return clock_.TimeInMilliseconds(); }
|
||||
|
||||
SimulatedClock clock_;
|
||||
NiceMock<MockNetworkControllerObserver> cluster_handler_;
|
||||
std::unique_ptr<ProbeController> probe_controller_;
|
||||
};
|
||||
|
||||
TEST_F(ProbeControllerTest, InitiatesProbingAtStart) {
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(AtLeast(2));
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, ProbeOnlyWhenNetworkIsUp) {
|
||||
SetNetworkAvailable(false);
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0);
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(AtLeast(2));
|
||||
SetNetworkAvailable(true);
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, InitiatesProbingOnMaxBitrateIncrease) {
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(AtLeast(2));
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
// Long enough to time out exponential probing.
|
||||
clock_.AdvanceTimeMilliseconds(kExponentialProbingTimeoutMs);
|
||||
probe_controller_->SetEstimatedBitrate(kStartBitrateBps, NowMs());
|
||||
probe_controller_->Process(NowMs());
|
||||
|
||||
EXPECT_CALL(cluster_handler_,
|
||||
OnProbeClusterConfig(DataRateEqBps(kMaxBitrateBps + 100)));
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps + 100, NowMs());
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, InitiatesProbingOnMaxBitrateIncreaseAtMaxBitrate) {
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(AtLeast(2));
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
// Long enough to time out exponential probing.
|
||||
clock_.AdvanceTimeMilliseconds(kExponentialProbingTimeoutMs);
|
||||
probe_controller_->SetEstimatedBitrate(kStartBitrateBps, NowMs());
|
||||
probe_controller_->Process(NowMs());
|
||||
|
||||
probe_controller_->SetEstimatedBitrate(kMaxBitrateBps, NowMs());
|
||||
EXPECT_CALL(cluster_handler_,
|
||||
OnProbeClusterConfig(DataRateEqBps(kMaxBitrateBps + 100)));
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps + 100, NowMs());
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, TestExponentialProbing) {
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
|
||||
// Repeated probe should only be sent when estimated bitrate climbs above
|
||||
// 0.7 * 6 * kStartBitrateBps = 1260.
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0);
|
||||
probe_controller_->SetEstimatedBitrate(1000, NowMs());
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(DataRateEqBps(2 * 1800)));
|
||||
probe_controller_->SetEstimatedBitrate(1800, NowMs());
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, TestExponentialProbingTimeout) {
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
|
||||
// Advance far enough to cause a time out in waiting for probing result.
|
||||
clock_.AdvanceTimeMilliseconds(kExponentialProbingTimeoutMs);
|
||||
probe_controller_->Process(NowMs());
|
||||
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0);
|
||||
probe_controller_->SetEstimatedBitrate(1800, NowMs());
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, RequestProbeInAlr) {
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(2);
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(500, NowMs());
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(DataRateEqBps(0.85 * 500)))
|
||||
.Times(1);
|
||||
probe_controller_->SetAlrStartTimeMs(clock_.TimeInMilliseconds());
|
||||
clock_.AdvanceTimeMilliseconds(kAlrProbeInterval + 1);
|
||||
probe_controller_->Process(NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(250, NowMs());
|
||||
probe_controller_->RequestProbe(NowMs());
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, RequestProbeWhenAlrEndedRecently) {
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(2);
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(500, NowMs());
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(DataRateEqBps(0.85 * 500)))
|
||||
.Times(1);
|
||||
probe_controller_->SetAlrStartTimeMs(rtc::nullopt);
|
||||
clock_.AdvanceTimeMilliseconds(kAlrProbeInterval + 1);
|
||||
probe_controller_->Process(NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(250, NowMs());
|
||||
probe_controller_->SetAlrEndedTimeMs(clock_.TimeInMilliseconds());
|
||||
clock_.AdvanceTimeMilliseconds(kAlrEndedTimeoutMs - 1);
|
||||
probe_controller_->RequestProbe(NowMs());
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, RequestProbeWhenAlrNotEndedRecently) {
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(2);
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(500, NowMs());
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0);
|
||||
probe_controller_->SetAlrStartTimeMs(rtc::nullopt);
|
||||
clock_.AdvanceTimeMilliseconds(kAlrProbeInterval + 1);
|
||||
probe_controller_->Process(NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(250, NowMs());
|
||||
probe_controller_->SetAlrEndedTimeMs(clock_.TimeInMilliseconds());
|
||||
clock_.AdvanceTimeMilliseconds(kAlrEndedTimeoutMs + 1);
|
||||
probe_controller_->RequestProbe(NowMs());
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, RequestProbeWhenBweDropNotRecent) {
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(2);
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(500, NowMs());
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0);
|
||||
probe_controller_->SetAlrStartTimeMs(clock_.TimeInMilliseconds());
|
||||
clock_.AdvanceTimeMilliseconds(kAlrProbeInterval + 1);
|
||||
probe_controller_->Process(NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(250, NowMs());
|
||||
clock_.AdvanceTimeMilliseconds(kBitrateDropTimeoutMs + 1);
|
||||
probe_controller_->RequestProbe(NowMs());
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, PeriodicProbing) {
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(2);
|
||||
probe_controller_->EnablePeriodicAlrProbing(true);
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(500, NowMs());
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
|
||||
int64_t start_time = clock_.TimeInMilliseconds();
|
||||
|
||||
// Expect the controller to send a new probe after 5s has passed.
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(DataRateEqBps(1000)))
|
||||
.Times(1);
|
||||
probe_controller_->SetAlrStartTimeMs(start_time);
|
||||
clock_.AdvanceTimeMilliseconds(5000);
|
||||
probe_controller_->Process(NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(500, NowMs());
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
|
||||
// The following probe should be sent at 10s into ALR.
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0);
|
||||
probe_controller_->SetAlrStartTimeMs(start_time);
|
||||
clock_.AdvanceTimeMilliseconds(4000);
|
||||
probe_controller_->Process(NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(500, NowMs());
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(1);
|
||||
probe_controller_->SetAlrStartTimeMs(start_time);
|
||||
clock_.AdvanceTimeMilliseconds(1000);
|
||||
probe_controller_->Process(NowMs());
|
||||
probe_controller_->SetEstimatedBitrate(500, NowMs());
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, PeriodicProbingAfterReset) {
|
||||
NiceMock<MockNetworkControllerObserver> local_handler;
|
||||
probe_controller_.reset(new ProbeController(&local_handler));
|
||||
int64_t alr_start_time = clock_.TimeInMilliseconds();
|
||||
|
||||
probe_controller_->SetAlrStartTimeMs(alr_start_time);
|
||||
EXPECT_CALL(local_handler, OnProbeClusterConfig(_)).Times(2);
|
||||
probe_controller_->EnablePeriodicAlrProbing(true);
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
probe_controller_->Reset(NowMs());
|
||||
|
||||
clock_.AdvanceTimeMilliseconds(10000);
|
||||
probe_controller_->Process(NowMs());
|
||||
|
||||
EXPECT_CALL(local_handler, OnProbeClusterConfig(_)).Times(2);
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
|
||||
kMaxBitrateBps, NowMs());
|
||||
|
||||
// Make sure we use |kStartBitrateBps| as the estimated bitrate
|
||||
// until SetEstimatedBitrate is called with an updated estimate.
|
||||
clock_.AdvanceTimeMilliseconds(10000);
|
||||
EXPECT_CALL(local_handler,
|
||||
OnProbeClusterConfig(DataRateEqBps(kStartBitrateBps * 2)));
|
||||
probe_controller_->Process(NowMs());
|
||||
}
|
||||
|
||||
TEST_F(ProbeControllerTest, TestExponentialProbingOverflow) {
|
||||
const int64_t kMbpsMultiplier = 1000000;
|
||||
probe_controller_->SetBitrates(kMinBitrateBps, 10 * kMbpsMultiplier,
|
||||
100 * kMbpsMultiplier, NowMs());
|
||||
|
||||
// Verify that probe bitrate is capped at the specified max bitrate.
|
||||
EXPECT_CALL(cluster_handler_,
|
||||
OnProbeClusterConfig(DataRateEqBps(100 * kMbpsMultiplier)));
|
||||
probe_controller_->SetEstimatedBitrate(60 * kMbpsMultiplier, NowMs());
|
||||
testing::Mock::VerifyAndClearExpectations(&cluster_handler_);
|
||||
|
||||
// Verify that repeated probes aren't sent.
|
||||
EXPECT_CALL(cluster_handler_, OnProbeClusterConfig(_)).Times(0);
|
||||
probe_controller_->SetEstimatedBitrate(100 * kMbpsMultiplier, NowMs());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,661 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/include/send_side_congestion_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "modules/congestion_controller/rtp/include/goog_cc_factory.h"
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_types.h"
|
||||
#include "modules/congestion_controller/rtp/network_control/include/network_units.h"
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/format_macros.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
#include "rtc_base/numerics/safe_minmax.h"
|
||||
#include "rtc_base/ptr_util.h"
|
||||
#include "rtc_base/rate_limiter.h"
|
||||
#include "rtc_base/sequenced_task_checker.h"
|
||||
#include "rtc_base/socket.h"
|
||||
#include "rtc_base/timeutils.h"
|
||||
#include "system_wrappers/include/field_trial.h"
|
||||
#include "system_wrappers/include/runtime_enabled_features.h"
|
||||
|
||||
using rtc::MakeUnique;
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
static const int64_t kRetransmitWindowSizeMs = 500;
|
||||
|
||||
const char kPacerPushbackExperiment[] = "WebRTC-PacerPushbackExperiment";
|
||||
|
||||
bool IsPacerPushbackExperimentEnabled() {
|
||||
return webrtc::field_trial::IsEnabled(kPacerPushbackExperiment) ||
|
||||
(!webrtc::field_trial::IsDisabled(kPacerPushbackExperiment) &&
|
||||
webrtc::runtime_enabled_features::IsFeatureEnabled(
|
||||
webrtc::runtime_enabled_features::kDualStreamModeFeatureName));
|
||||
}
|
||||
|
||||
NetworkControllerFactoryInterface::uptr ControllerFactory(
|
||||
RtcEventLog* event_log) {
|
||||
return rtc::MakeUnique<GoogCcNetworkControllerFactory>(event_log);
|
||||
}
|
||||
|
||||
void SortPacketFeedbackVector(std::vector<webrtc::PacketFeedback>* input) {
|
||||
std::sort(input->begin(), input->end(), PacketFeedbackComparator());
|
||||
}
|
||||
|
||||
PacketResult NetworkPacketFeedbackFromRtpPacketFeedback(
|
||||
const webrtc::PacketFeedback& pf) {
|
||||
PacketResult feedback;
|
||||
if (pf.arrival_time_ms == webrtc::PacketFeedback::kNotReceived)
|
||||
feedback.receive_time = Timestamp::Infinity();
|
||||
else
|
||||
feedback.receive_time = Timestamp::ms(pf.arrival_time_ms);
|
||||
if (pf.send_time_ms != webrtc::PacketFeedback::kNoSendTime) {
|
||||
feedback.sent_packet = SentPacket();
|
||||
feedback.sent_packet->send_time = Timestamp::ms(pf.send_time_ms);
|
||||
feedback.sent_packet->size = DataSize::bytes(pf.payload_size);
|
||||
feedback.sent_packet->pacing_info = pf.pacing_info;
|
||||
}
|
||||
return feedback;
|
||||
}
|
||||
|
||||
std::vector<PacketResult> PacketResultsFromRtpFeedbackVector(
|
||||
const std::vector<PacketFeedback>& feedback_vector) {
|
||||
RTC_DCHECK(std::is_sorted(feedback_vector.begin(), feedback_vector.end(),
|
||||
PacketFeedbackComparator()));
|
||||
|
||||
std::vector<PacketResult> packet_feedbacks;
|
||||
packet_feedbacks.reserve(feedback_vector.size());
|
||||
for (const PacketFeedback& rtp_feedback : feedback_vector) {
|
||||
auto feedback = NetworkPacketFeedbackFromRtpPacketFeedback(rtp_feedback);
|
||||
packet_feedbacks.push_back(feedback);
|
||||
}
|
||||
return packet_feedbacks;
|
||||
}
|
||||
|
||||
TargetRateConstraints ConvertConstraints(int min_bitrate_bps,
|
||||
int max_bitrate_bps,
|
||||
int start_bitrate_bps,
|
||||
const Clock* clock) {
|
||||
TargetRateConstraints msg;
|
||||
msg.at_time = Timestamp::ms(clock->TimeInMilliseconds());
|
||||
msg.min_data_rate =
|
||||
min_bitrate_bps >= 0 ? DataRate::bps(min_bitrate_bps) : DataRate::Zero();
|
||||
msg.starting_rate = start_bitrate_bps > 0 ? DataRate::bps(start_bitrate_bps)
|
||||
: DataRate::kNotInitialized;
|
||||
msg.max_data_rate = max_bitrate_bps > 0 ? DataRate::bps(max_bitrate_bps)
|
||||
: DataRate::Infinity();
|
||||
return msg;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace send_side_cc_internal {
|
||||
class ControlHandler : public NetworkControllerObserver {
|
||||
public:
|
||||
ControlHandler(PacerController* pacer_controller, const Clock* clock);
|
||||
|
||||
void OnCongestionWindow(CongestionWindow window) override;
|
||||
void OnPacerConfig(PacerConfig config) override;
|
||||
void OnProbeClusterConfig(ProbeClusterConfig config) override;
|
||||
void OnTargetTransferRate(TargetTransferRate target_rate) override;
|
||||
|
||||
void OnNetworkAvailability(NetworkAvailability msg);
|
||||
void OnPacerQueueUpdate(PacerQueueUpdate msg);
|
||||
|
||||
void RegisterNetworkObserver(
|
||||
SendSideCongestionController::Observer* observer);
|
||||
void DeRegisterNetworkObserver(
|
||||
SendSideCongestionController::Observer* observer);
|
||||
|
||||
rtc::Optional<TargetTransferRate> last_transfer_rate();
|
||||
bool pacer_configured();
|
||||
RateLimiter* retransmission_rate_limiter();
|
||||
|
||||
private:
|
||||
void OnNetworkInvalidation();
|
||||
bool GetNetworkParameters(int32_t* estimated_bitrate_bps,
|
||||
uint8_t* fraction_loss,
|
||||
int64_t* rtt_ms);
|
||||
bool IsSendQueueFull() const;
|
||||
bool HasNetworkParametersToReportChanged(int64_t bitrate_bps,
|
||||
uint8_t fraction_loss,
|
||||
int64_t rtt);
|
||||
PacerController* pacer_controller_;
|
||||
RateLimiter retransmission_rate_limiter_;
|
||||
|
||||
rtc::CriticalSection state_lock_;
|
||||
rtc::Optional<TargetTransferRate> last_target_rate_
|
||||
RTC_GUARDED_BY(state_lock_);
|
||||
bool pacer_configured_ RTC_GUARDED_BY(state_lock_) = false;
|
||||
|
||||
SendSideCongestionController::Observer* observer_ = nullptr;
|
||||
rtc::Optional<TargetTransferRate> current_target_rate_msg_;
|
||||
bool network_available_ = true;
|
||||
int64_t last_reported_target_bitrate_bps_ = 0;
|
||||
uint8_t last_reported_fraction_loss_ = 0;
|
||||
int64_t last_reported_rtt_ms_ = 0;
|
||||
const bool pacer_pushback_experiment_ = false;
|
||||
int64_t pacer_expected_queue_ms_ = 0;
|
||||
float encoding_rate_ratio_ = 1.0;
|
||||
|
||||
rtc::SequencedTaskChecker sequenced_checker_;
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ControlHandler);
|
||||
};
|
||||
|
||||
ControlHandler::ControlHandler(PacerController* pacer_controller,
|
||||
const Clock* clock)
|
||||
: pacer_controller_(pacer_controller),
|
||||
retransmission_rate_limiter_(clock, kRetransmitWindowSizeMs),
|
||||
pacer_pushback_experiment_(IsPacerPushbackExperimentEnabled()) {
|
||||
sequenced_checker_.Detach();
|
||||
}
|
||||
|
||||
void ControlHandler::OnCongestionWindow(CongestionWindow window) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
pacer_controller_->OnCongestionWindow(window);
|
||||
}
|
||||
|
||||
void ControlHandler::OnPacerConfig(PacerConfig config) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
pacer_controller_->OnPacerConfig(config);
|
||||
rtc::CritScope cs(&state_lock_);
|
||||
pacer_configured_ = true;
|
||||
}
|
||||
|
||||
void ControlHandler::OnProbeClusterConfig(ProbeClusterConfig config) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
pacer_controller_->OnProbeClusterConfig(config);
|
||||
}
|
||||
|
||||
void ControlHandler::OnTargetTransferRate(TargetTransferRate target_rate) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
retransmission_rate_limiter_.SetMaxRate(
|
||||
target_rate.network_estimate.bandwidth.bps());
|
||||
|
||||
current_target_rate_msg_ = target_rate;
|
||||
OnNetworkInvalidation();
|
||||
rtc::CritScope cs(&state_lock_);
|
||||
last_target_rate_ = target_rate;
|
||||
}
|
||||
|
||||
void ControlHandler::OnNetworkAvailability(NetworkAvailability msg) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
network_available_ = msg.network_available;
|
||||
OnNetworkInvalidation();
|
||||
}
|
||||
|
||||
void ControlHandler::OnPacerQueueUpdate(PacerQueueUpdate msg) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
pacer_expected_queue_ms_ = msg.expected_queue_time.ms();
|
||||
OnNetworkInvalidation();
|
||||
}
|
||||
|
||||
void ControlHandler::RegisterNetworkObserver(
|
||||
SendSideCongestionController::Observer* observer) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
RTC_DCHECK(observer_ == nullptr);
|
||||
observer_ = observer;
|
||||
}
|
||||
|
||||
void ControlHandler::DeRegisterNetworkObserver(
|
||||
SendSideCongestionController::Observer* observer) {
|
||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_checker_);
|
||||
RTC_DCHECK_EQ(observer_, observer);
|
||||
observer_ = nullptr;
|
||||
}
|
||||
|
||||
void ControlHandler::OnNetworkInvalidation() {
|
||||
if (!current_target_rate_msg_.has_value())
|
||||
return;
|
||||
|
||||
uint32_t target_bitrate_bps = current_target_rate_msg_->target_rate.bps();
|
||||
int64_t rtt_ms =
|
||||
current_target_rate_msg_->network_estimate.round_trip_time.ms();
|
||||
float loss_rate_ratio =
|
||||
current_target_rate_msg_->network_estimate.loss_rate_ratio;
|
||||
|
||||
int loss_ratio_255 = loss_rate_ratio * 255;
|
||||
uint8_t fraction_loss =
|
||||
rtc::dchecked_cast<uint8_t>(rtc::SafeClamp(loss_ratio_255, 0, 255));
|
||||
|
||||
int64_t probing_interval_ms =
|
||||
current_target_rate_msg_->network_estimate.bwe_period.ms();
|
||||
|
||||
if (!network_available_) {
|
||||
target_bitrate_bps = 0;
|
||||
} else if (!pacer_pushback_experiment_) {
|
||||
target_bitrate_bps = IsSendQueueFull() ? 0 : target_bitrate_bps;
|
||||
} else {
|
||||
int64_t queue_length_ms = pacer_expected_queue_ms_;
|
||||
|
||||
if (queue_length_ms == 0) {
|
||||
encoding_rate_ratio_ = 1.0;
|
||||
} else if (queue_length_ms > 50) {
|
||||
float encoding_ratio = 1.0 - queue_length_ms / 1000.0;
|
||||
encoding_rate_ratio_ = std::min(encoding_rate_ratio_, encoding_ratio);
|
||||
encoding_rate_ratio_ = std::max(encoding_rate_ratio_, 0.0f);
|
||||
}
|
||||
|
||||
target_bitrate_bps *= encoding_rate_ratio_;
|
||||
target_bitrate_bps = target_bitrate_bps < 50000 ? 0 : target_bitrate_bps;
|
||||
}
|
||||
if (HasNetworkParametersToReportChanged(target_bitrate_bps, fraction_loss,
|
||||
rtt_ms)) {
|
||||
if (observer_) {
|
||||
observer_->OnNetworkChanged(target_bitrate_bps, fraction_loss, rtt_ms,
|
||||
probing_interval_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool ControlHandler::HasNetworkParametersToReportChanged(
|
||||
int64_t target_bitrate_bps,
|
||||
uint8_t fraction_loss,
|
||||
int64_t rtt_ms) {
|
||||
bool changed = last_reported_target_bitrate_bps_ != target_bitrate_bps ||
|
||||
(target_bitrate_bps > 0 &&
|
||||
(last_reported_fraction_loss_ != fraction_loss ||
|
||||
last_reported_rtt_ms_ != rtt_ms));
|
||||
if (changed &&
|
||||
(last_reported_target_bitrate_bps_ == 0 || target_bitrate_bps == 0)) {
|
||||
RTC_LOG(LS_INFO) << "Bitrate estimate state changed, BWE: "
|
||||
<< target_bitrate_bps << " bps.";
|
||||
}
|
||||
last_reported_target_bitrate_bps_ = target_bitrate_bps;
|
||||
last_reported_fraction_loss_ = fraction_loss;
|
||||
last_reported_rtt_ms_ = rtt_ms;
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool ControlHandler::IsSendQueueFull() const {
|
||||
return pacer_expected_queue_ms_ > PacedSender::kMaxQueueLengthMs;
|
||||
}
|
||||
|
||||
rtc::Optional<TargetTransferRate> ControlHandler::last_transfer_rate() {
|
||||
rtc::CritScope cs(&state_lock_);
|
||||
return last_target_rate_;
|
||||
}
|
||||
|
||||
bool ControlHandler::pacer_configured() {
|
||||
rtc::CritScope cs(&state_lock_);
|
||||
return pacer_configured_;
|
||||
}
|
||||
|
||||
RateLimiter* ControlHandler::retransmission_rate_limiter() {
|
||||
return &retransmission_rate_limiter_;
|
||||
}
|
||||
} // namespace send_side_cc_internal
|
||||
|
||||
SendSideCongestionController::SendSideCongestionController(
|
||||
const Clock* clock,
|
||||
Observer* observer,
|
||||
RtcEventLog* event_log,
|
||||
PacedSender* pacer)
|
||||
: SendSideCongestionController(clock,
|
||||
event_log,
|
||||
pacer,
|
||||
ControllerFactory(event_log)) {
|
||||
if (observer != nullptr)
|
||||
RegisterNetworkObserver(observer);
|
||||
}
|
||||
|
||||
SendSideCongestionController::SendSideCongestionController(
|
||||
const Clock* clock,
|
||||
RtcEventLog* event_log,
|
||||
PacedSender* pacer,
|
||||
NetworkControllerFactoryInterface::uptr controller_factory)
|
||||
: clock_(clock),
|
||||
pacer_(pacer),
|
||||
transport_feedback_adapter_(clock_),
|
||||
pacer_controller_(MakeUnique<PacerController>(pacer_)),
|
||||
control_handler(MakeUnique<send_side_cc_internal::ControlHandler>(
|
||||
pacer_controller_.get(),
|
||||
clock_)),
|
||||
controller_(controller_factory->Create(control_handler.get())),
|
||||
process_interval_(controller_factory->GetProcessInterval()),
|
||||
send_side_bwe_with_overhead_(
|
||||
webrtc::field_trial::IsEnabled("WebRTC-SendSideBwe-WithOverhead")),
|
||||
transport_overhead_bytes_per_packet_(0),
|
||||
network_available_(true),
|
||||
task_queue_(MakeUnique<rtc::TaskQueue>("SendSideCCQueue")) {}
|
||||
|
||||
SendSideCongestionController::~SendSideCongestionController() {
|
||||
// Must be destructed before any objects used by calls on the task queue.
|
||||
task_queue_.reset();
|
||||
}
|
||||
|
||||
void SendSideCongestionController::RegisterPacketFeedbackObserver(
|
||||
PacketFeedbackObserver* observer) {
|
||||
transport_feedback_adapter_.RegisterPacketFeedbackObserver(observer);
|
||||
}
|
||||
|
||||
void SendSideCongestionController::DeRegisterPacketFeedbackObserver(
|
||||
PacketFeedbackObserver* observer) {
|
||||
transport_feedback_adapter_.DeRegisterPacketFeedbackObserver(observer);
|
||||
}
|
||||
|
||||
void SendSideCongestionController::RegisterNetworkObserver(Observer* observer) {
|
||||
WaitOnTask([this, observer]() {
|
||||
control_handler->RegisterNetworkObserver(observer);
|
||||
});
|
||||
}
|
||||
|
||||
void SendSideCongestionController::DeRegisterNetworkObserver(
|
||||
Observer* observer) {
|
||||
WaitOnTask([this, observer]() {
|
||||
control_handler->DeRegisterNetworkObserver(observer);
|
||||
});
|
||||
}
|
||||
|
||||
void SendSideCongestionController::SetBweBitrates(int min_bitrate_bps,
|
||||
int start_bitrate_bps,
|
||||
int max_bitrate_bps) {
|
||||
TargetRateConstraints msg = ConvertConstraints(
|
||||
min_bitrate_bps, max_bitrate_bps, start_bitrate_bps, clock_);
|
||||
WaitOnTask([this, msg]() { controller_->OnTargetRateConstraints(msg); });
|
||||
}
|
||||
|
||||
// TODO(holmer): Split this up and use SetBweBitrates in combination with
|
||||
// OnNetworkRouteChanged.
|
||||
void SendSideCongestionController::OnNetworkRouteChanged(
|
||||
const rtc::NetworkRoute& network_route,
|
||||
int start_bitrate_bps,
|
||||
int min_bitrate_bps,
|
||||
int max_bitrate_bps) {
|
||||
transport_feedback_adapter_.SetNetworkIds(network_route.local_network_id,
|
||||
network_route.remote_network_id);
|
||||
|
||||
NetworkRouteChange msg;
|
||||
msg.at_time = Timestamp::ms(clock_->TimeInMilliseconds());
|
||||
msg.constraints = ConvertConstraints(min_bitrate_bps, max_bitrate_bps,
|
||||
start_bitrate_bps, clock_);
|
||||
WaitOnTask([this, msg]() {
|
||||
controller_->OnNetworkRouteChange(msg);
|
||||
pacer_controller_->OnNetworkRouteChange(msg);
|
||||
});
|
||||
}
|
||||
|
||||
bool SendSideCongestionController::AvailableBandwidth(
|
||||
uint32_t* bandwidth) const {
|
||||
// TODO(srte): Remove this interface and push information about bandwidth
|
||||
// estimation to users of this class, thereby reducing synchronous calls.
|
||||
if (control_handler->last_transfer_rate().has_value()) {
|
||||
*bandwidth =
|
||||
control_handler->last_transfer_rate()->network_estimate.bandwidth.bps();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RtcpBandwidthObserver* SendSideCongestionController::GetBandwidthObserver() {
|
||||
return this;
|
||||
}
|
||||
|
||||
RateLimiter* SendSideCongestionController::GetRetransmissionRateLimiter() {
|
||||
return control_handler->retransmission_rate_limiter();
|
||||
}
|
||||
|
||||
void SendSideCongestionController::EnablePeriodicAlrProbing(bool enable) {
|
||||
WaitOnTask([this, enable]() {
|
||||
streams_config_.requests_alr_probing = enable;
|
||||
UpdateStreamsConfig();
|
||||
});
|
||||
}
|
||||
|
||||
void SendSideCongestionController::UpdateStreamsConfig() {
|
||||
RTC_DCHECK(task_queue_->IsCurrent());
|
||||
streams_config_.at_time = Timestamp::ms(clock_->TimeInMilliseconds());
|
||||
controller_->OnStreamsConfig(streams_config_);
|
||||
}
|
||||
|
||||
int64_t SendSideCongestionController::GetPacerQueuingDelayMs() const {
|
||||
// TODO(srte): This should be made less synchronous. Now it grabs a lock in
|
||||
// the pacer just for stats usage. Some kind of push interface might make
|
||||
// sense.
|
||||
return network_available_ ? pacer_->QueueInMs() : 0;
|
||||
}
|
||||
|
||||
int64_t SendSideCongestionController::GetFirstPacketTimeMs() const {
|
||||
return pacer_->FirstSentPacketTimeMs();
|
||||
}
|
||||
|
||||
TransportFeedbackObserver*
|
||||
SendSideCongestionController::GetTransportFeedbackObserver() {
|
||||
return this;
|
||||
}
|
||||
|
||||
void SendSideCongestionController::SignalNetworkState(NetworkState state) {
|
||||
RTC_LOG(LS_INFO) << "SignalNetworkState "
|
||||
<< (state == kNetworkUp ? "Up" : "Down");
|
||||
NetworkAvailability msg;
|
||||
msg.at_time = Timestamp::ms(clock_->TimeInMilliseconds());
|
||||
msg.network_available = state == kNetworkUp;
|
||||
network_available_ = msg.network_available;
|
||||
WaitOnTask([this, msg]() {
|
||||
controller_->OnNetworkAvailability(msg);
|
||||
pacer_controller_->OnNetworkAvailability(msg);
|
||||
control_handler->OnNetworkAvailability(msg);
|
||||
});
|
||||
}
|
||||
|
||||
void SendSideCongestionController::SetTransportOverhead(
|
||||
size_t transport_overhead_bytes_per_packet) {
|
||||
transport_overhead_bytes_per_packet_ = transport_overhead_bytes_per_packet;
|
||||
}
|
||||
|
||||
void SendSideCongestionController::OnSentPacket(
|
||||
const rtc::SentPacket& sent_packet) {
|
||||
// We're not interested in packets without an id, which may be stun packets,
|
||||
// etc, sent on the same transport.
|
||||
if (sent_packet.packet_id == -1)
|
||||
return;
|
||||
transport_feedback_adapter_.OnSentPacket(sent_packet.packet_id,
|
||||
sent_packet.send_time_ms);
|
||||
MaybeUpdateOutstandingData();
|
||||
auto packet = transport_feedback_adapter_.GetPacket(sent_packet.packet_id);
|
||||
if (packet.has_value()) {
|
||||
SentPacket msg;
|
||||
msg.size = DataSize::bytes(packet->payload_size);
|
||||
msg.send_time = Timestamp::ms(packet->send_time_ms);
|
||||
task_queue_->PostTask([this, msg]() { controller_->OnSentPacket(msg); });
|
||||
}
|
||||
}
|
||||
|
||||
void SendSideCongestionController::OnRttUpdate(int64_t avg_rtt_ms,
|
||||
int64_t max_rtt_ms) {
|
||||
int64_t now_ms = clock_->TimeInMilliseconds();
|
||||
RoundTripTimeUpdate report;
|
||||
report.receive_time = Timestamp::ms(now_ms);
|
||||
report.round_trip_time = TimeDelta::ms(avg_rtt_ms);
|
||||
report.smoothed = true;
|
||||
task_queue_->PostTask(
|
||||
[this, report]() { controller_->OnRoundTripTimeUpdate(report); });
|
||||
}
|
||||
|
||||
int64_t SendSideCongestionController::TimeUntilNextProcess() {
|
||||
const int kMaxProcessInterval = 60 * 1000;
|
||||
if (process_interval_.IsInfinite())
|
||||
return kMaxProcessInterval;
|
||||
int64_t next_process_ms = last_process_update_ms_ + process_interval_.ms();
|
||||
int64_t time_until_next_process =
|
||||
next_process_ms - clock_->TimeInMilliseconds();
|
||||
return std::max<int64_t>(time_until_next_process, 0);
|
||||
}
|
||||
|
||||
void SendSideCongestionController::Process() {
|
||||
int64_t now_ms = clock_->TimeInMilliseconds();
|
||||
last_process_update_ms_ = now_ms;
|
||||
{
|
||||
ProcessInterval msg;
|
||||
msg.at_time = Timestamp::ms(now_ms);
|
||||
task_queue_->PostTask(
|
||||
[this, msg]() { controller_->OnProcessInterval(msg); });
|
||||
}
|
||||
if (control_handler->pacer_configured()) {
|
||||
PacerQueueUpdate msg;
|
||||
msg.expected_queue_time = TimeDelta::ms(pacer_->ExpectedQueueTimeMs());
|
||||
task_queue_->PostTask(
|
||||
[this, msg]() { control_handler->OnPacerQueueUpdate(msg); });
|
||||
}
|
||||
}
|
||||
|
||||
void SendSideCongestionController::AddPacket(
|
||||
uint32_t ssrc,
|
||||
uint16_t sequence_number,
|
||||
size_t length,
|
||||
const PacedPacketInfo& pacing_info) {
|
||||
if (send_side_bwe_with_overhead_) {
|
||||
length += transport_overhead_bytes_per_packet_;
|
||||
}
|
||||
transport_feedback_adapter_.AddPacket(ssrc, sequence_number, length,
|
||||
pacing_info);
|
||||
}
|
||||
|
||||
void SendSideCongestionController::OnTransportFeedback(
|
||||
const rtcp::TransportFeedback& feedback) {
|
||||
RTC_DCHECK_RUNS_SERIALIZED(&worker_race_);
|
||||
int64_t feedback_time_ms = clock_->TimeInMilliseconds();
|
||||
|
||||
DataSize prior_in_flight =
|
||||
DataSize::bytes(transport_feedback_adapter_.GetOutstandingBytes());
|
||||
transport_feedback_adapter_.OnTransportFeedback(feedback);
|
||||
MaybeUpdateOutstandingData();
|
||||
|
||||
std::vector<PacketFeedback> feedback_vector =
|
||||
transport_feedback_adapter_.GetTransportFeedbackVector();
|
||||
SortPacketFeedbackVector(&feedback_vector);
|
||||
|
||||
if (!feedback_vector.empty()) {
|
||||
TransportPacketsFeedback msg;
|
||||
msg.packet_feedbacks = PacketResultsFromRtpFeedbackVector(feedback_vector);
|
||||
msg.feedback_time = Timestamp::ms(feedback_time_ms);
|
||||
msg.prior_in_flight = prior_in_flight;
|
||||
msg.data_in_flight =
|
||||
DataSize::bytes(transport_feedback_adapter_.GetOutstandingBytes());
|
||||
task_queue_->PostTask(
|
||||
[this, msg]() { controller_->OnTransportPacketsFeedback(msg); });
|
||||
}
|
||||
}
|
||||
|
||||
void SendSideCongestionController::MaybeUpdateOutstandingData() {
|
||||
OutstandingData msg;
|
||||
msg.in_flight_data =
|
||||
DataSize::bytes(transport_feedback_adapter_.GetOutstandingBytes());
|
||||
task_queue_->PostTask(
|
||||
[this, msg]() { pacer_controller_->OnOutstandingData(msg); });
|
||||
}
|
||||
|
||||
std::vector<PacketFeedback>
|
||||
SendSideCongestionController::GetTransportFeedbackVector() const {
|
||||
RTC_DCHECK_RUNS_SERIALIZED(&worker_race_);
|
||||
return transport_feedback_adapter_.GetTransportFeedbackVector();
|
||||
}
|
||||
|
||||
void SendSideCongestionController::WaitOnTasks() {
|
||||
rtc::Event event(false, false);
|
||||
task_queue_->PostTask([&event]() { event.Set(); });
|
||||
event.Wait(rtc::Event::kForever);
|
||||
}
|
||||
|
||||
void SendSideCongestionController::WaitOnTask(std::function<void()> closure) {
|
||||
rtc::Event done(false, false);
|
||||
task_queue_->PostTask(rtc::NewClosure(closure, [&done] { done.Set(); }));
|
||||
done.Wait(rtc::Event::kForever);
|
||||
}
|
||||
|
||||
void SendSideCongestionController::SetSendBitrateLimits(
|
||||
int64_t min_send_bitrate_bps,
|
||||
int64_t max_padding_bitrate_bps) {
|
||||
WaitOnTask([this, min_send_bitrate_bps, max_padding_bitrate_bps]() {
|
||||
streams_config_.min_pacing_rate = DataRate::bps(min_send_bitrate_bps);
|
||||
streams_config_.max_padding_rate = DataRate::bps(max_padding_bitrate_bps);
|
||||
UpdateStreamsConfig();
|
||||
});
|
||||
}
|
||||
|
||||
void SendSideCongestionController::SetPacingFactor(float pacing_factor) {
|
||||
WaitOnTask([this, pacing_factor]() {
|
||||
streams_config_.pacing_factor = pacing_factor;
|
||||
UpdateStreamsConfig();
|
||||
});
|
||||
}
|
||||
|
||||
void SendSideCongestionController::OnReceivedEstimatedBitrate(
|
||||
uint32_t bitrate) {
|
||||
RemoteBitrateReport msg;
|
||||
msg.receive_time = Timestamp::ms(clock_->TimeInMilliseconds());
|
||||
msg.bandwidth = DataRate::bps(bitrate);
|
||||
task_queue_->PostTask(
|
||||
[this, msg]() { controller_->OnRemoteBitrateReport(msg); });
|
||||
}
|
||||
|
||||
void SendSideCongestionController::OnReceivedRtcpReceiverReport(
|
||||
const webrtc::ReportBlockList& report_blocks,
|
||||
int64_t rtt_ms,
|
||||
int64_t now_ms) {
|
||||
OnReceivedRtcpReceiverReportBlocks(report_blocks, now_ms);
|
||||
|
||||
RoundTripTimeUpdate report;
|
||||
report.receive_time = Timestamp::ms(now_ms);
|
||||
report.round_trip_time = TimeDelta::ms(rtt_ms);
|
||||
report.smoothed = false;
|
||||
task_queue_->PostTask(
|
||||
[this, report]() { controller_->OnRoundTripTimeUpdate(report); });
|
||||
}
|
||||
|
||||
void SendSideCongestionController::OnReceivedRtcpReceiverReportBlocks(
|
||||
const ReportBlockList& report_blocks,
|
||||
int64_t now_ms) {
|
||||
if (report_blocks.empty())
|
||||
return;
|
||||
|
||||
int total_packets_lost_delta = 0;
|
||||
int total_packets_delta = 0;
|
||||
|
||||
// Compute the packet loss from all report blocks.
|
||||
for (const RTCPReportBlock& report_block : report_blocks) {
|
||||
auto it = last_report_blocks_.find(report_block.source_ssrc);
|
||||
if (it != last_report_blocks_.end()) {
|
||||
auto number_of_packets = report_block.extended_highest_sequence_number -
|
||||
it->second.extended_highest_sequence_number;
|
||||
total_packets_delta += number_of_packets;
|
||||
auto lost_delta = report_block.packets_lost - it->second.packets_lost;
|
||||
total_packets_lost_delta += lost_delta;
|
||||
}
|
||||
last_report_blocks_[report_block.source_ssrc] = report_block;
|
||||
}
|
||||
// Can only compute delta if there has been previous blocks to compare to. If
|
||||
// not, total_packets_delta will be unchanged and there's nothing more to do.
|
||||
if (!total_packets_delta)
|
||||
return;
|
||||
int packets_received_delta = total_packets_delta - total_packets_lost_delta;
|
||||
// To detect lost packets, at least one packet has to be received. This check
|
||||
// is needed to avoid bandwith detection update in
|
||||
// VideoSendStreamTest.SuspendBelowMinBitrate
|
||||
|
||||
if (packets_received_delta < 1)
|
||||
return;
|
||||
Timestamp now = Timestamp::ms(now_ms);
|
||||
TransportLossReport msg;
|
||||
msg.packets_lost_delta = total_packets_lost_delta;
|
||||
msg.packets_received_delta = packets_received_delta;
|
||||
msg.receive_time = now;
|
||||
msg.start_time = last_report_block_time_;
|
||||
msg.end_time = now;
|
||||
task_queue_->PostTask(
|
||||
[this, msg]() { controller_->OnTransportLossReport(msg); });
|
||||
last_report_block_time_ = now;
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,518 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/include/send_side_congestion_controller.h"
|
||||
#include "logging/rtc_event_log/mock/mock_rtc_event_log.h"
|
||||
#include "modules/congestion_controller/rtp/congestion_controller_unittests_helper.h"
|
||||
#include "modules/congestion_controller/rtp/include/mock/mock_congestion_observer.h"
|
||||
#include "modules/pacing/mock/mock_paced_sender.h"
|
||||
#include "modules/pacing/packet_router.h"
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
|
||||
#include "rtc_base/socket.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
#include "test/field_trial.h"
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
using testing::_;
|
||||
using testing::AtLeast;
|
||||
using testing::Ge;
|
||||
using testing::NiceMock;
|
||||
using testing::Return;
|
||||
using testing::SaveArg;
|
||||
using testing::StrictMock;
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
const webrtc::PacedPacketInfo kPacingInfo0(0, 5, 2000);
|
||||
const webrtc::PacedPacketInfo kPacingInfo1(1, 8, 4000);
|
||||
|
||||
const uint32_t kInitialBitrateBps = 60000;
|
||||
const float kDefaultPacingRate = 2.5f;
|
||||
|
||||
class SendSideCongestionControllerForTest
|
||||
: public SendSideCongestionController {
|
||||
public:
|
||||
SendSideCongestionControllerForTest(const Clock* clock,
|
||||
Observer* observer,
|
||||
RtcEventLog* event_log,
|
||||
PacedSender* pacer)
|
||||
: SendSideCongestionController(clock, observer, event_log, pacer) {}
|
||||
~SendSideCongestionControllerForTest() {}
|
||||
void Process() override {
|
||||
SendSideCongestionController::Process();
|
||||
SendSideCongestionController::WaitOnTasks();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
class SendSideCongestionControllerTest : public ::testing::Test {
|
||||
protected:
|
||||
SendSideCongestionControllerTest()
|
||||
: clock_(123456), target_bitrate_observer_(this) {}
|
||||
~SendSideCongestionControllerTest() override {}
|
||||
|
||||
void SetUp() override {
|
||||
pacer_.reset(new NiceMock<MockPacedSender>());
|
||||
controller_.reset(new SendSideCongestionControllerForTest(
|
||||
&clock_, &observer_, &event_log_, pacer_.get()));
|
||||
bandwidth_observer_ = controller_->GetBandwidthObserver();
|
||||
|
||||
// Set the initial bitrate estimate and expect the |observer| and |pacer_|
|
||||
// to be updated.
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _));
|
||||
EXPECT_CALL(*pacer_,
|
||||
SetPacingRates(kInitialBitrateBps * kDefaultPacingRate, _));
|
||||
EXPECT_CALL(*pacer_, CreateProbeCluster(kInitialBitrateBps * 3));
|
||||
EXPECT_CALL(*pacer_, CreateProbeCluster(kInitialBitrateBps * 5));
|
||||
controller_->SetBweBitrates(0, kInitialBitrateBps, 5 * kInitialBitrateBps);
|
||||
}
|
||||
|
||||
// Custom setup - use an observer that tracks the target bitrate, without
|
||||
// prescribing on which iterations it must change (like a mock would).
|
||||
void TargetBitrateTrackingSetup() {
|
||||
bandwidth_observer_ = nullptr;
|
||||
pacer_.reset(new NiceMock<MockPacedSender>());
|
||||
controller_.reset(new SendSideCongestionControllerForTest(
|
||||
&clock_, &target_bitrate_observer_, &event_log_, pacer_.get()));
|
||||
controller_->SetBweBitrates(0, kInitialBitrateBps, 5 * kInitialBitrateBps);
|
||||
}
|
||||
|
||||
void OnSentPacket(const PacketFeedback& packet_feedback) {
|
||||
constexpr uint32_t ssrc = 0;
|
||||
controller_->AddPacket(ssrc, packet_feedback.sequence_number,
|
||||
packet_feedback.payload_size,
|
||||
packet_feedback.pacing_info);
|
||||
controller_->OnSentPacket(rtc::SentPacket(packet_feedback.sequence_number,
|
||||
packet_feedback.send_time_ms));
|
||||
}
|
||||
|
||||
// Allows us to track the target bitrate, without prescribing the exact
|
||||
// iterations when this would hapen, like a mock would.
|
||||
class TargetBitrateObserver : public SendSideCongestionController::Observer {
|
||||
public:
|
||||
explicit TargetBitrateObserver(SendSideCongestionControllerTest* owner)
|
||||
: owner_(owner) {}
|
||||
~TargetBitrateObserver() override = default;
|
||||
void OnNetworkChanged(uint32_t bitrate_bps,
|
||||
uint8_t fraction_loss, // 0 - 255.
|
||||
int64_t rtt_ms,
|
||||
int64_t probing_interval_ms) override {
|
||||
owner_->target_bitrate_bps_ = bitrate_bps;
|
||||
}
|
||||
|
||||
private:
|
||||
SendSideCongestionControllerTest* owner_;
|
||||
};
|
||||
|
||||
void PacketTransmissionAndFeedbackBlock(uint16_t* seq_num,
|
||||
int64_t runtime_ms,
|
||||
int64_t delay) {
|
||||
int64_t delay_buildup = 0;
|
||||
int64_t start_time_ms = clock_.TimeInMilliseconds();
|
||||
while (clock_.TimeInMilliseconds() - start_time_ms < runtime_ms) {
|
||||
constexpr size_t kPayloadSize = 1000;
|
||||
PacketFeedback packet(clock_.TimeInMilliseconds() + delay_buildup,
|
||||
clock_.TimeInMilliseconds(), *seq_num, kPayloadSize,
|
||||
PacedPacketInfo());
|
||||
delay_buildup += delay; // Delay has to increase, or it's just RTT.
|
||||
OnSentPacket(packet);
|
||||
// Create expected feedback and send into adapter.
|
||||
std::unique_ptr<rtcp::TransportFeedback> feedback(
|
||||
new rtcp::TransportFeedback());
|
||||
feedback->SetBase(packet.sequence_number, packet.arrival_time_ms * 1000);
|
||||
EXPECT_TRUE(feedback->AddReceivedPacket(packet.sequence_number,
|
||||
packet.arrival_time_ms * 1000));
|
||||
rtc::Buffer raw_packet = feedback->Build();
|
||||
feedback = rtcp::TransportFeedback::ParseFrom(raw_packet.data(),
|
||||
raw_packet.size());
|
||||
EXPECT_TRUE(feedback.get() != nullptr);
|
||||
controller_->OnTransportFeedback(*feedback.get());
|
||||
clock_.AdvanceTimeMilliseconds(50);
|
||||
controller_->Process();
|
||||
++(*seq_num);
|
||||
}
|
||||
}
|
||||
|
||||
SimulatedClock clock_;
|
||||
StrictMock<MockCongestionObserver> observer_;
|
||||
TargetBitrateObserver target_bitrate_observer_;
|
||||
NiceMock<MockRtcEventLog> event_log_;
|
||||
RtcpBandwidthObserver* bandwidth_observer_;
|
||||
PacketRouter packet_router_;
|
||||
std::unique_ptr<NiceMock<MockPacedSender>> pacer_;
|
||||
std::unique_ptr<SendSideCongestionControllerForTest> controller_;
|
||||
|
||||
rtc::Optional<uint32_t> target_bitrate_bps_;
|
||||
};
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest, OnNetworkChanged) {
|
||||
// Test no change.
|
||||
clock_.AdvanceTimeMilliseconds(25);
|
||||
controller_->Process();
|
||||
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps * 2, _, _, _));
|
||||
EXPECT_CALL(*pacer_,
|
||||
SetPacingRates(kInitialBitrateBps * 2 * kDefaultPacingRate, _));
|
||||
bandwidth_observer_->OnReceivedEstimatedBitrate(kInitialBitrateBps * 2);
|
||||
clock_.AdvanceTimeMilliseconds(25);
|
||||
controller_->Process();
|
||||
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _));
|
||||
EXPECT_CALL(*pacer_,
|
||||
SetPacingRates(kInitialBitrateBps * kDefaultPacingRate, _));
|
||||
bandwidth_observer_->OnReceivedEstimatedBitrate(kInitialBitrateBps);
|
||||
clock_.AdvanceTimeMilliseconds(25);
|
||||
controller_->Process();
|
||||
}
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest, OnSendQueueFull) {
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs())
|
||||
.WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs + 1));
|
||||
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _));
|
||||
controller_->Process();
|
||||
|
||||
// Let the pacer not be full next time the controller checks.
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs())
|
||||
.WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs - 1));
|
||||
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _));
|
||||
controller_->Process();
|
||||
}
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest, OnSendQueueFullAndEstimateChange) {
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs())
|
||||
.WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs + 1));
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _));
|
||||
controller_->Process();
|
||||
|
||||
// Receive new estimate but let the queue still be full.
|
||||
bandwidth_observer_->OnReceivedEstimatedBitrate(kInitialBitrateBps * 2);
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs())
|
||||
.WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs + 1));
|
||||
// The send pacer should get the new estimate though.
|
||||
EXPECT_CALL(*pacer_,
|
||||
SetPacingRates(kInitialBitrateBps * 2 * kDefaultPacingRate, _));
|
||||
clock_.AdvanceTimeMilliseconds(25);
|
||||
controller_->Process();
|
||||
|
||||
// Let the pacer not be full next time the controller checks.
|
||||
// |OnNetworkChanged| should be called with the new estimate.
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs())
|
||||
.WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs - 1));
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps * 2, _, _, _));
|
||||
clock_.AdvanceTimeMilliseconds(25);
|
||||
controller_->Process();
|
||||
}
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest, SignalNetworkState) {
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _));
|
||||
controller_->SignalNetworkState(kNetworkDown);
|
||||
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _));
|
||||
controller_->SignalNetworkState(kNetworkUp);
|
||||
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _));
|
||||
controller_->SignalNetworkState(kNetworkDown);
|
||||
}
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest, OnNetworkRouteChanged) {
|
||||
int new_bitrate = 200000;
|
||||
testing::Mock::VerifyAndClearExpectations(pacer_.get());
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(new_bitrate, _, _, _));
|
||||
EXPECT_CALL(*pacer_, SetPacingRates(new_bitrate * kDefaultPacingRate, _));
|
||||
rtc::NetworkRoute route;
|
||||
route.local_network_id = 1;
|
||||
controller_->OnNetworkRouteChanged(route, new_bitrate, -1, -1);
|
||||
|
||||
// If the bitrate is reset to -1, the new starting bitrate will be
|
||||
// the minimum default bitrate kMinBitrateBps.
|
||||
EXPECT_CALL(
|
||||
observer_,
|
||||
OnNetworkChanged(congestion_controller::GetMinBitrateBps(), _, _, _));
|
||||
EXPECT_CALL(
|
||||
*pacer_,
|
||||
SetPacingRates(
|
||||
congestion_controller::GetMinBitrateBps() * kDefaultPacingRate, _));
|
||||
route.local_network_id = 2;
|
||||
controller_->OnNetworkRouteChanged(route, -1, -1, -1);
|
||||
}
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest, OldFeedback) {
|
||||
int new_bitrate = 200000;
|
||||
testing::Mock::VerifyAndClearExpectations(pacer_.get());
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(new_bitrate, _, _, _));
|
||||
EXPECT_CALL(*pacer_, SetPacingRates(new_bitrate * kDefaultPacingRate, _));
|
||||
|
||||
// Send a few packets on the first network route.
|
||||
std::vector<PacketFeedback> packets;
|
||||
packets.push_back(PacketFeedback(0, 0, 0, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(10, 10, 1, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(20, 20, 2, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(30, 30, 3, 1500, kPacingInfo1));
|
||||
packets.push_back(PacketFeedback(40, 40, 4, 1500, kPacingInfo1));
|
||||
|
||||
for (const PacketFeedback& packet : packets)
|
||||
OnSentPacket(packet);
|
||||
|
||||
// Change route and then insert a number of feedback packets.
|
||||
rtc::NetworkRoute route;
|
||||
route.local_network_id = 1;
|
||||
controller_->OnNetworkRouteChanged(route, new_bitrate, -1, -1);
|
||||
|
||||
for (const PacketFeedback& packet : packets) {
|
||||
rtcp::TransportFeedback feedback;
|
||||
feedback.SetBase(packet.sequence_number, packet.arrival_time_ms * 1000);
|
||||
|
||||
EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number,
|
||||
packet.arrival_time_ms * 1000));
|
||||
feedback.Build();
|
||||
controller_->OnTransportFeedback(feedback);
|
||||
}
|
||||
|
||||
// If the bitrate is reset to -1, the new starting bitrate will be
|
||||
// the minimum default bitrate kMinBitrateBps.
|
||||
EXPECT_CALL(
|
||||
observer_,
|
||||
OnNetworkChanged(congestion_controller::GetMinBitrateBps(), _, _, _));
|
||||
EXPECT_CALL(
|
||||
*pacer_,
|
||||
SetPacingRates(
|
||||
congestion_controller::GetMinBitrateBps() * kDefaultPacingRate, _));
|
||||
route.local_network_id = 2;
|
||||
controller_->OnNetworkRouteChanged(route, -1, -1, -1);
|
||||
}
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest,
|
||||
SignalNetworkStateAndQueueIsFullAndEstimateChange) {
|
||||
// Send queue is full.
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs())
|
||||
.WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs + 1));
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _));
|
||||
controller_->Process();
|
||||
|
||||
// Queue is full and network is down. Expect no bitrate change.
|
||||
controller_->SignalNetworkState(kNetworkDown);
|
||||
controller_->Process();
|
||||
|
||||
// Queue is full but network is up. Expect no bitrate change.
|
||||
controller_->SignalNetworkState(kNetworkUp);
|
||||
controller_->Process();
|
||||
|
||||
// Receive new estimate but let the queue still be full.
|
||||
EXPECT_CALL(*pacer_,
|
||||
SetPacingRates(kInitialBitrateBps * 2 * kDefaultPacingRate, _));
|
||||
bandwidth_observer_->OnReceivedEstimatedBitrate(kInitialBitrateBps * 2);
|
||||
clock_.AdvanceTimeMilliseconds(25);
|
||||
controller_->Process();
|
||||
|
||||
// Let the pacer not be full next time the controller checks.
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs())
|
||||
.WillRepeatedly(Return(PacedSender::kMaxQueueLengthMs - 1));
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps * 2, _, _, _));
|
||||
controller_->Process();
|
||||
}
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest, GetPacerQueuingDelayMs) {
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(_, _, _, _)).Times(AtLeast(1));
|
||||
|
||||
const int64_t kQueueTimeMs = 123;
|
||||
EXPECT_CALL(*pacer_, QueueInMs()).WillRepeatedly(Return(kQueueTimeMs));
|
||||
EXPECT_EQ(kQueueTimeMs, controller_->GetPacerQueuingDelayMs());
|
||||
|
||||
// Expect zero pacer delay when network is down.
|
||||
controller_->SignalNetworkState(kNetworkDown);
|
||||
EXPECT_EQ(0, controller_->GetPacerQueuingDelayMs());
|
||||
|
||||
// Network is up, pacer delay should be reported.
|
||||
controller_->SignalNetworkState(kNetworkUp);
|
||||
EXPECT_EQ(kQueueTimeMs, controller_->GetPacerQueuingDelayMs());
|
||||
}
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest, GetProbingInterval) {
|
||||
clock_.AdvanceTimeMilliseconds(25);
|
||||
controller_->Process();
|
||||
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(_, _, _, testing::Ne(0)));
|
||||
EXPECT_CALL(*pacer_, SetPacingRates(_, _));
|
||||
bandwidth_observer_->OnReceivedEstimatedBitrate(kInitialBitrateBps * 2);
|
||||
clock_.AdvanceTimeMilliseconds(25);
|
||||
controller_->Process();
|
||||
}
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest, ProbeOnRouteChange) {
|
||||
testing::Mock::VerifyAndClearExpectations(pacer_.get());
|
||||
EXPECT_CALL(*pacer_, CreateProbeCluster(kInitialBitrateBps * 6));
|
||||
EXPECT_CALL(*pacer_, CreateProbeCluster(kInitialBitrateBps * 12));
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps * 2, _, _, _));
|
||||
rtc::NetworkRoute route;
|
||||
route.local_network_id = 1;
|
||||
controller_->OnNetworkRouteChanged(route, 2 * kInitialBitrateBps, 0,
|
||||
20 * kInitialBitrateBps);
|
||||
}
|
||||
|
||||
// Estimated bitrate reduced when the feedbacks arrive with such a long delay,
|
||||
// that the send-time-history no longer holds the feedbacked packets.
|
||||
TEST_F(SendSideCongestionControllerTest, LongFeedbackDelays) {
|
||||
TargetBitrateTrackingSetup();
|
||||
|
||||
const int64_t kFeedbackTimeoutMs = 60001;
|
||||
const int kMaxConsecutiveFailedLookups = 5;
|
||||
for (int i = 0; i < kMaxConsecutiveFailedLookups; ++i) {
|
||||
std::vector<PacketFeedback> packets;
|
||||
packets.push_back(
|
||||
PacketFeedback(i * 100, 2 * i * 100, 0, 1500, kPacingInfo0));
|
||||
packets.push_back(
|
||||
PacketFeedback(i * 100 + 10, 2 * i * 100 + 10, 1, 1500, kPacingInfo0));
|
||||
packets.push_back(
|
||||
PacketFeedback(i * 100 + 20, 2 * i * 100 + 20, 2, 1500, kPacingInfo0));
|
||||
packets.push_back(
|
||||
PacketFeedback(i * 100 + 30, 2 * i * 100 + 30, 3, 1500, kPacingInfo1));
|
||||
packets.push_back(
|
||||
PacketFeedback(i * 100 + 40, 2 * i * 100 + 40, 4, 1500, kPacingInfo1));
|
||||
|
||||
for (const PacketFeedback& packet : packets)
|
||||
OnSentPacket(packet);
|
||||
|
||||
rtcp::TransportFeedback feedback;
|
||||
feedback.SetBase(packets[0].sequence_number,
|
||||
packets[0].arrival_time_ms * 1000);
|
||||
|
||||
for (const PacketFeedback& packet : packets) {
|
||||
EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number,
|
||||
packet.arrival_time_ms * 1000));
|
||||
}
|
||||
|
||||
feedback.Build();
|
||||
|
||||
clock_.AdvanceTimeMilliseconds(kFeedbackTimeoutMs);
|
||||
PacketFeedback later_packet(kFeedbackTimeoutMs + i * 100 + 40,
|
||||
kFeedbackTimeoutMs + i * 200 + 40, 5, 1500,
|
||||
kPacingInfo1);
|
||||
OnSentPacket(later_packet);
|
||||
|
||||
controller_->OnTransportFeedback(feedback);
|
||||
|
||||
// Check that packets have timed out.
|
||||
for (PacketFeedback& packet : packets) {
|
||||
packet.send_time_ms = PacketFeedback::kNoSendTime;
|
||||
packet.payload_size = 0;
|
||||
packet.pacing_info = PacedPacketInfo();
|
||||
}
|
||||
ComparePacketFeedbackVectors(packets,
|
||||
controller_->GetTransportFeedbackVector());
|
||||
}
|
||||
|
||||
controller_->Process();
|
||||
|
||||
EXPECT_EQ(kInitialBitrateBps / 2, target_bitrate_bps_);
|
||||
|
||||
// Test with feedback that isn't late enough to time out.
|
||||
{
|
||||
std::vector<PacketFeedback> packets;
|
||||
packets.push_back(PacketFeedback(100, 200, 0, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(110, 210, 1, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(120, 220, 2, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(130, 230, 3, 1500, kPacingInfo1));
|
||||
packets.push_back(PacketFeedback(140, 240, 4, 1500, kPacingInfo1));
|
||||
|
||||
for (const PacketFeedback& packet : packets)
|
||||
OnSentPacket(packet);
|
||||
|
||||
rtcp::TransportFeedback feedback;
|
||||
feedback.SetBase(packets[0].sequence_number,
|
||||
packets[0].arrival_time_ms * 1000);
|
||||
|
||||
for (const PacketFeedback& packet : packets) {
|
||||
EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number,
|
||||
packet.arrival_time_ms * 1000));
|
||||
}
|
||||
|
||||
feedback.Build();
|
||||
|
||||
clock_.AdvanceTimeMilliseconds(kFeedbackTimeoutMs - 1);
|
||||
PacketFeedback later_packet(kFeedbackTimeoutMs + 140,
|
||||
kFeedbackTimeoutMs + 240, 5, 1500,
|
||||
kPacingInfo1);
|
||||
OnSentPacket(later_packet);
|
||||
|
||||
controller_->OnTransportFeedback(feedback);
|
||||
ComparePacketFeedbackVectors(packets,
|
||||
controller_->GetTransportFeedbackVector());
|
||||
}
|
||||
}
|
||||
|
||||
// Bandwidth estimation is updated when feedbacks are received.
|
||||
// Feedbacks which show an increasing delay cause the estimation to be reduced.
|
||||
TEST_F(SendSideCongestionControllerTest, UpdatesDelayBasedEstimate) {
|
||||
TargetBitrateTrackingSetup();
|
||||
|
||||
const int64_t kRunTimeMs = 6000;
|
||||
uint16_t seq_num = 0;
|
||||
|
||||
// The test must run and insert packets/feedback long enough that the
|
||||
// BWE computes a valid estimate. This is first done in an environment which
|
||||
// simulates no bandwidth limitation, and therefore not built-up delay.
|
||||
PacketTransmissionAndFeedbackBlock(&seq_num, kRunTimeMs, 0);
|
||||
ASSERT_TRUE(target_bitrate_bps_);
|
||||
|
||||
// Repeat, but this time with a building delay, and make sure that the
|
||||
// estimation is adjusted downwards.
|
||||
uint32_t bitrate_before_delay = *target_bitrate_bps_;
|
||||
PacketTransmissionAndFeedbackBlock(&seq_num, kRunTimeMs, 50);
|
||||
EXPECT_LT(*target_bitrate_bps_, bitrate_before_delay);
|
||||
}
|
||||
|
||||
TEST_F(SendSideCongestionControllerTest, PacerQueueEncodeRatePushback) {
|
||||
ScopedFieldTrials pushback_field_trial(
|
||||
"WebRTC-PacerPushbackExperiment/Enabled/");
|
||||
SetUp();
|
||||
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()).WillOnce(Return(0));
|
||||
controller_->Process();
|
||||
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()).WillOnce(Return(100));
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps * 0.9, _, _, _));
|
||||
controller_->Process();
|
||||
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()).WillOnce(Return(50));
|
||||
controller_->Process();
|
||||
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()).WillOnce(Return(0));
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _));
|
||||
controller_->Process();
|
||||
|
||||
const uint32_t kMinAdjustedBps = 50000;
|
||||
int expected_queue_threshold =
|
||||
1000 - kMinAdjustedBps * 1000.0 / kInitialBitrateBps;
|
||||
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs())
|
||||
.WillOnce(Return(expected_queue_threshold));
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(Ge(kMinAdjustedBps), _, _, _));
|
||||
controller_->Process();
|
||||
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs())
|
||||
.WillOnce(Return(expected_queue_threshold + 1));
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(0, _, _, _));
|
||||
controller_->Process();
|
||||
|
||||
EXPECT_CALL(*pacer_, ExpectedQueueTimeMs()).WillOnce(Return(0));
|
||||
EXPECT_CALL(observer_, OnNetworkChanged(kInitialBitrateBps, _, _, _));
|
||||
controller_->Process();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
103
modules/congestion_controller/rtp/send_time_history.cc
Normal file
103
modules/congestion_controller/rtp/send_time_history.cc
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/send_time_history.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "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;
|
||||
}
|
||||
|
||||
rtc::Optional<PacketFeedback> SendTimeHistory::GetPacket(
|
||||
uint16_t sequence_number) const {
|
||||
int64_t unwrapped_seq_num =
|
||||
seq_num_unwrapper_.UnwrapWithoutUpdate(sequence_number);
|
||||
rtc::Optional<PacketFeedback> optional_feedback;
|
||||
auto it = history_.find(unwrapped_seq_num);
|
||||
if (it != history_.end())
|
||||
optional_feedback.emplace(it->second);
|
||||
return optional_feedback;
|
||||
}
|
||||
|
||||
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
|
||||
58
modules/congestion_controller/rtp/send_time_history.h
Normal file
58
modules/congestion_controller/rtp/send_time_history.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "modules/include/module_common_types.h"
|
||||
#include "rtc_base/basictypes.h"
|
||||
#include "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);
|
||||
|
||||
// Retrieves packet info identified by |sequence_number|.
|
||||
rtc::Optional<PacketFeedback> GetPacket(uint16_t sequence_number) const;
|
||||
|
||||
// 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 // MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_
|
||||
256
modules/congestion_controller/rtp/send_time_history_unittest.cc
Normal file
256
modules/congestion_controller/rtp/send_time_history_unittest.cc
Normal file
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* 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 <random>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/congestion_controller/rtp/send_time_history.h"
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
#include "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, GetPacketReturnsSentPacket) {
|
||||
const uint16_t kSeqNo = 10;
|
||||
const PacedPacketInfo kPacingInfo(0, 5, 1200);
|
||||
const PacketFeedback kSentPacket(0, -1, 1, kSeqNo, 123, 0, 0, kPacingInfo);
|
||||
AddPacketWithSendTime(kSeqNo, 123, 1, kPacingInfo);
|
||||
auto sent_packet = history_.GetPacket(kSeqNo);
|
||||
EXPECT_EQ(kSentPacket, *sent_packet);
|
||||
}
|
||||
|
||||
TEST_F(SendTimeHistoryTest, GetPacketEmptyForRemovedPacket) {
|
||||
const uint16_t kSeqNo = 10;
|
||||
const PacedPacketInfo kPacingInfo(0, 5, 1200);
|
||||
AddPacketWithSendTime(kSeqNo, 123, 1, kPacingInfo);
|
||||
auto sent_packet = history_.GetPacket(kSeqNo);
|
||||
PacketFeedback received_packet(0, 0, kSeqNo, 0, kPacingInfo);
|
||||
EXPECT_TRUE(history_.GetFeedback(&received_packet, true));
|
||||
sent_packet = history_.GetPacket(kSeqNo);
|
||||
EXPECT_FALSE(sent_packet.has_value());
|
||||
}
|
||||
|
||||
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 = PacketFeedback::kNotReceived;
|
||||
packet.send_time_ms = PacketFeedback::kNoSendTime;
|
||||
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::shuffle(received_packets.begin(), received_packets.end(),
|
||||
std::mt19937(std::random_device()()));
|
||||
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
|
||||
191
modules/congestion_controller/rtp/transport_feedback_adapter.cc
Normal file
191
modules/congestion_controller/rtp/transport_feedback_adapter.cc
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/transport_feedback_adapter.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/mod_ops.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
const int64_t kNoTimestamp = -1;
|
||||
const int64_t kSendTimeHistoryWindowMs = 60000;
|
||||
const int64_t kBaseTimestampScaleFactor =
|
||||
rtcp::TransportFeedback::kDeltaScaleFactor * (1 << 8);
|
||||
const int64_t kBaseTimestampRangeSizeUs = kBaseTimestampScaleFactor * (1 << 24);
|
||||
|
||||
TransportFeedbackAdapter::TransportFeedbackAdapter(const Clock* clock)
|
||||
: send_time_history_(clock, kSendTimeHistoryWindowMs),
|
||||
clock_(clock),
|
||||
current_offset_ms_(kNoTimestamp),
|
||||
last_timestamp_us_(kNoTimestamp),
|
||||
local_net_id_(0),
|
||||
remote_net_id_(0) {}
|
||||
|
||||
TransportFeedbackAdapter::~TransportFeedbackAdapter() {
|
||||
RTC_DCHECK(observers_.empty());
|
||||
}
|
||||
|
||||
void TransportFeedbackAdapter::RegisterPacketFeedbackObserver(
|
||||
PacketFeedbackObserver* observer) {
|
||||
rtc::CritScope cs(&observers_lock_);
|
||||
RTC_DCHECK(observer);
|
||||
RTC_DCHECK(std::find(observers_.begin(), observers_.end(), observer) ==
|
||||
observers_.end());
|
||||
observers_.push_back(observer);
|
||||
}
|
||||
|
||||
void TransportFeedbackAdapter::DeRegisterPacketFeedbackObserver(
|
||||
PacketFeedbackObserver* observer) {
|
||||
rtc::CritScope cs(&observers_lock_);
|
||||
RTC_DCHECK(observer);
|
||||
const auto it = std::find(observers_.begin(), observers_.end(), observer);
|
||||
RTC_DCHECK(it != observers_.end());
|
||||
observers_.erase(it);
|
||||
}
|
||||
|
||||
void TransportFeedbackAdapter::AddPacket(uint32_t ssrc,
|
||||
uint16_t sequence_number,
|
||||
size_t length,
|
||||
const PacedPacketInfo& pacing_info) {
|
||||
{
|
||||
rtc::CritScope cs(&lock_);
|
||||
const int64_t creation_time_ms = clock_->TimeInMilliseconds();
|
||||
send_time_history_.AddAndRemoveOld(
|
||||
PacketFeedback(creation_time_ms, sequence_number, length, local_net_id_,
|
||||
remote_net_id_, pacing_info));
|
||||
}
|
||||
|
||||
{
|
||||
rtc::CritScope cs(&observers_lock_);
|
||||
for (auto* observer : observers_) {
|
||||
observer->OnPacketAdded(ssrc, sequence_number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TransportFeedbackAdapter::OnSentPacket(uint16_t sequence_number,
|
||||
int64_t send_time_ms) {
|
||||
rtc::CritScope cs(&lock_);
|
||||
send_time_history_.OnSentPacket(sequence_number, send_time_ms);
|
||||
}
|
||||
|
||||
rtc::Optional<PacketFeedback> TransportFeedbackAdapter::GetPacket(
|
||||
uint16_t sequence_number) const {
|
||||
rtc::CritScope cs(&lock_);
|
||||
return send_time_history_.GetPacket(sequence_number);
|
||||
}
|
||||
|
||||
void TransportFeedbackAdapter::SetNetworkIds(uint16_t local_id,
|
||||
uint16_t remote_id) {
|
||||
rtc::CritScope cs(&lock_);
|
||||
local_net_id_ = local_id;
|
||||
remote_net_id_ = remote_id;
|
||||
}
|
||||
|
||||
std::vector<PacketFeedback> TransportFeedbackAdapter::GetPacketFeedbackVector(
|
||||
const rtcp::TransportFeedback& feedback) {
|
||||
int64_t timestamp_us = feedback.GetBaseTimeUs();
|
||||
int64_t now_ms = clock_->TimeInMilliseconds();
|
||||
// Add timestamp deltas to a local time base selected on first packet arrival.
|
||||
// This won't be the true time base, but makes it easier to manually inspect
|
||||
// time stamps.
|
||||
if (last_timestamp_us_ == kNoTimestamp) {
|
||||
current_offset_ms_ = now_ms;
|
||||
} else {
|
||||
int64_t delta = timestamp_us - last_timestamp_us_;
|
||||
|
||||
// Detect and compensate for wrap-arounds in base time.
|
||||
if (std::abs(delta - kBaseTimestampRangeSizeUs) < std::abs(delta)) {
|
||||
delta -= kBaseTimestampRangeSizeUs; // Wrap backwards.
|
||||
} else if (std::abs(delta + kBaseTimestampRangeSizeUs) < std::abs(delta)) {
|
||||
delta += kBaseTimestampRangeSizeUs; // Wrap forwards.
|
||||
}
|
||||
|
||||
current_offset_ms_ += delta / 1000;
|
||||
}
|
||||
last_timestamp_us_ = timestamp_us;
|
||||
|
||||
std::vector<PacketFeedback> packet_feedback_vector;
|
||||
if (feedback.GetPacketStatusCount() == 0) {
|
||||
RTC_LOG(LS_INFO) << "Empty transport feedback packet received.";
|
||||
return packet_feedback_vector;
|
||||
}
|
||||
packet_feedback_vector.reserve(feedback.GetPacketStatusCount());
|
||||
{
|
||||
rtc::CritScope cs(&lock_);
|
||||
size_t failed_lookups = 0;
|
||||
int64_t offset_us = 0;
|
||||
int64_t timestamp_ms = 0;
|
||||
uint16_t seq_num = feedback.GetBaseSequence();
|
||||
for (const auto& packet : feedback.GetReceivedPackets()) {
|
||||
// Insert into the vector those unreceived packets which precede this
|
||||
// iteration's received packet.
|
||||
for (; seq_num != packet.sequence_number(); ++seq_num) {
|
||||
PacketFeedback packet_feedback(PacketFeedback::kNotReceived, seq_num);
|
||||
// Note: Element not removed from history because it might be reported
|
||||
// as received by another feedback.
|
||||
if (!send_time_history_.GetFeedback(&packet_feedback, false))
|
||||
++failed_lookups;
|
||||
if (packet_feedback.local_net_id == local_net_id_ &&
|
||||
packet_feedback.remote_net_id == remote_net_id_) {
|
||||
packet_feedback_vector.push_back(packet_feedback);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle this iteration's received packet.
|
||||
offset_us += packet.delta_us();
|
||||
timestamp_ms = current_offset_ms_ + (offset_us / 1000);
|
||||
PacketFeedback packet_feedback(timestamp_ms, packet.sequence_number());
|
||||
if (!send_time_history_.GetFeedback(&packet_feedback, true))
|
||||
++failed_lookups;
|
||||
if (packet_feedback.local_net_id == local_net_id_ &&
|
||||
packet_feedback.remote_net_id == remote_net_id_) {
|
||||
packet_feedback_vector.push_back(packet_feedback);
|
||||
}
|
||||
|
||||
++seq_num;
|
||||
}
|
||||
|
||||
if (failed_lookups > 0) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to lookup send time for " << failed_lookups
|
||||
<< " packet" << (failed_lookups > 1 ? "s" : "")
|
||||
<< ". Send time history too small?";
|
||||
}
|
||||
}
|
||||
return packet_feedback_vector;
|
||||
}
|
||||
|
||||
void TransportFeedbackAdapter::OnTransportFeedback(
|
||||
const rtcp::TransportFeedback& feedback) {
|
||||
last_packet_feedback_vector_ = GetPacketFeedbackVector(feedback);
|
||||
{
|
||||
rtc::CritScope cs(&observers_lock_);
|
||||
for (auto* observer : observers_) {
|
||||
observer->OnPacketFeedbackVector(last_packet_feedback_vector_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PacketFeedback>
|
||||
TransportFeedbackAdapter::GetTransportFeedbackVector() const {
|
||||
return last_packet_feedback_vector_;
|
||||
}
|
||||
|
||||
size_t TransportFeedbackAdapter::GetOutstandingBytes() const {
|
||||
rtc::CritScope cs(&lock_);
|
||||
return send_time_history_.GetOutstandingBytes(local_net_id_, remote_net_id_);
|
||||
}
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/congestion_controller/rtp/send_time_history.h"
|
||||
#include "rtc_base/criticalsection.h"
|
||||
#include "rtc_base/thread_annotations.h"
|
||||
#include "rtc_base/thread_checker.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class PacketFeedbackObserver;
|
||||
|
||||
namespace rtcp {
|
||||
class TransportFeedback;
|
||||
} // namespace rtcp
|
||||
|
||||
class TransportFeedbackAdapter {
|
||||
public:
|
||||
explicit TransportFeedbackAdapter(const Clock* clock);
|
||||
virtual ~TransportFeedbackAdapter();
|
||||
|
||||
void RegisterPacketFeedbackObserver(PacketFeedbackObserver* observer);
|
||||
void DeRegisterPacketFeedbackObserver(PacketFeedbackObserver* observer);
|
||||
|
||||
void AddPacket(uint32_t ssrc,
|
||||
uint16_t sequence_number,
|
||||
size_t length,
|
||||
const PacedPacketInfo& pacing_info);
|
||||
void OnSentPacket(uint16_t sequence_number, int64_t send_time_ms);
|
||||
|
||||
// TODO(holmer): This method should return DelayBasedBwe::Result so that we
|
||||
// can get rid of the dependency on BitrateController. Requires changes
|
||||
// to the CongestionController interface.
|
||||
void OnTransportFeedback(const rtcp::TransportFeedback& feedback);
|
||||
std::vector<PacketFeedback> GetTransportFeedbackVector() const;
|
||||
rtc::Optional<PacketFeedback> GetPacket(uint16_t sequence_number) const;
|
||||
|
||||
void SetTransportOverhead(int transport_overhead_bytes_per_packet);
|
||||
|
||||
void SetNetworkIds(uint16_t local_id, uint16_t remote_id);
|
||||
|
||||
size_t GetOutstandingBytes() const;
|
||||
|
||||
private:
|
||||
std::vector<PacketFeedback> GetPacketFeedbackVector(
|
||||
const rtcp::TransportFeedback& feedback);
|
||||
|
||||
rtc::CriticalSection lock_;
|
||||
SendTimeHistory send_time_history_ RTC_GUARDED_BY(&lock_);
|
||||
const Clock* const clock_;
|
||||
int64_t current_offset_ms_;
|
||||
int64_t last_timestamp_us_;
|
||||
std::vector<PacketFeedback> last_packet_feedback_vector_;
|
||||
uint16_t local_net_id_ RTC_GUARDED_BY(&lock_);
|
||||
uint16_t remote_net_id_ RTC_GUARDED_BY(&lock_);
|
||||
|
||||
rtc::CriticalSection observers_lock_;
|
||||
std::vector<PacketFeedbackObserver*> observers_
|
||||
RTC_GUARDED_BY(&observers_lock_);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_
|
||||
@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/bitrate_controller/include/mock/mock_bitrate_controller.h"
|
||||
#include "modules/congestion_controller/rtp/congestion_controller_unittests_helper.h"
|
||||
#include "modules/congestion_controller/rtp/transport_feedback_adapter.h"
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::Invoke;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
const PacedPacketInfo kPacingInfo0(0, 5, 2000);
|
||||
const PacedPacketInfo kPacingInfo1(1, 8, 4000);
|
||||
const PacedPacketInfo kPacingInfo2(2, 14, 7000);
|
||||
const PacedPacketInfo kPacingInfo3(3, 20, 10000);
|
||||
const PacedPacketInfo kPacingInfo4(4, 22, 10000);
|
||||
}
|
||||
|
||||
namespace test {
|
||||
|
||||
class MockPacketFeedbackObserver : public webrtc::PacketFeedbackObserver {
|
||||
public:
|
||||
MOCK_METHOD2(OnPacketAdded, void(uint32_t ssrc, uint16_t seq_num));
|
||||
MOCK_METHOD1(OnPacketFeedbackVector,
|
||||
void(const std::vector<PacketFeedback>& packet_feedback_vector));
|
||||
};
|
||||
|
||||
class TransportFeedbackAdapterTest : public ::testing::Test {
|
||||
public:
|
||||
TransportFeedbackAdapterTest() : clock_(0) {}
|
||||
|
||||
virtual ~TransportFeedbackAdapterTest() {}
|
||||
|
||||
virtual void SetUp() {
|
||||
adapter_.reset(new TransportFeedbackAdapter(&clock_));
|
||||
}
|
||||
|
||||
virtual void TearDown() { adapter_.reset(); }
|
||||
|
||||
protected:
|
||||
void OnReceivedEstimatedBitrate(uint32_t bitrate) {}
|
||||
|
||||
void OnReceivedRtcpReceiverReport(const ReportBlockList& report_blocks,
|
||||
int64_t rtt,
|
||||
int64_t now_ms) {}
|
||||
|
||||
void OnSentPacket(const PacketFeedback& packet_feedback) {
|
||||
adapter_->AddPacket(kSsrc, packet_feedback.sequence_number,
|
||||
packet_feedback.payload_size,
|
||||
packet_feedback.pacing_info);
|
||||
adapter_->OnSentPacket(packet_feedback.sequence_number,
|
||||
packet_feedback.send_time_ms);
|
||||
}
|
||||
|
||||
static constexpr uint32_t kSsrc = 8492;
|
||||
|
||||
SimulatedClock clock_;
|
||||
std::unique_ptr<TransportFeedbackAdapter> adapter_;
|
||||
};
|
||||
|
||||
TEST_F(TransportFeedbackAdapterTest, ObserverSanity) {
|
||||
MockPacketFeedbackObserver mock;
|
||||
adapter_->RegisterPacketFeedbackObserver(&mock);
|
||||
|
||||
const std::vector<PacketFeedback> packets = {
|
||||
PacketFeedback(100, 200, 0, 1000, kPacingInfo0),
|
||||
PacketFeedback(110, 210, 1, 2000, kPacingInfo0),
|
||||
PacketFeedback(120, 220, 2, 3000, kPacingInfo0)
|
||||
};
|
||||
|
||||
rtcp::TransportFeedback feedback;
|
||||
feedback.SetBase(packets[0].sequence_number,
|
||||
packets[0].arrival_time_ms * 1000);
|
||||
|
||||
for (const PacketFeedback& packet : packets) {
|
||||
EXPECT_CALL(mock, OnPacketAdded(kSsrc, packet.sequence_number)).Times(1);
|
||||
OnSentPacket(packet);
|
||||
EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number,
|
||||
packet.arrival_time_ms * 1000));
|
||||
}
|
||||
|
||||
EXPECT_CALL(mock, OnPacketFeedbackVector(_)).Times(1);
|
||||
adapter_->OnTransportFeedback(feedback);
|
||||
|
||||
adapter_->DeRegisterPacketFeedbackObserver(&mock);
|
||||
|
||||
// After deregistration, the observer no longers gets indications.
|
||||
EXPECT_CALL(mock, OnPacketAdded(_, _)).Times(0);
|
||||
const PacketFeedback new_packet(130, 230, 3, 4000, kPacingInfo0);
|
||||
OnSentPacket(new_packet);
|
||||
|
||||
rtcp::TransportFeedback second_feedback;
|
||||
second_feedback.SetBase(new_packet.sequence_number,
|
||||
new_packet.arrival_time_ms * 1000);
|
||||
EXPECT_TRUE(feedback.AddReceivedPacket(new_packet.sequence_number,
|
||||
new_packet.arrival_time_ms * 1000));
|
||||
EXPECT_CALL(mock, OnPacketFeedbackVector(_)).Times(0);
|
||||
adapter_->OnTransportFeedback(second_feedback);
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
TEST_F(TransportFeedbackAdapterTest, ObserverDoubleRegistrationDeathTest) {
|
||||
MockPacketFeedbackObserver mock;
|
||||
adapter_->RegisterPacketFeedbackObserver(&mock);
|
||||
EXPECT_DEATH(adapter_->RegisterPacketFeedbackObserver(&mock), "");
|
||||
adapter_->DeRegisterPacketFeedbackObserver(&mock);
|
||||
}
|
||||
|
||||
TEST_F(TransportFeedbackAdapterTest, ObserverMissingDeRegistrationDeathTest) {
|
||||
MockPacketFeedbackObserver mock;
|
||||
adapter_->RegisterPacketFeedbackObserver(&mock);
|
||||
EXPECT_DEATH(adapter_.reset(), "");
|
||||
adapter_->DeRegisterPacketFeedbackObserver(&mock);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(TransportFeedbackAdapterTest, AdaptsFeedbackAndPopulatesSendTimes) {
|
||||
std::vector<PacketFeedback> packets;
|
||||
packets.push_back(PacketFeedback(100, 200, 0, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(110, 210, 1, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(120, 220, 2, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(130, 230, 3, 1500, kPacingInfo1));
|
||||
packets.push_back(PacketFeedback(140, 240, 4, 1500, kPacingInfo1));
|
||||
|
||||
for (const PacketFeedback& packet : packets)
|
||||
OnSentPacket(packet);
|
||||
|
||||
rtcp::TransportFeedback feedback;
|
||||
feedback.SetBase(packets[0].sequence_number,
|
||||
packets[0].arrival_time_ms * 1000);
|
||||
|
||||
for (const PacketFeedback& packet : packets) {
|
||||
EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number,
|
||||
packet.arrival_time_ms * 1000));
|
||||
}
|
||||
|
||||
feedback.Build();
|
||||
|
||||
adapter_->OnTransportFeedback(feedback);
|
||||
ComparePacketFeedbackVectors(packets, adapter_->GetTransportFeedbackVector());
|
||||
}
|
||||
|
||||
TEST_F(TransportFeedbackAdapterTest, FeedbackVectorReportsUnreceived) {
|
||||
std::vector<PacketFeedback> sent_packets = {
|
||||
PacketFeedback(100, 220, 0, 1500, kPacingInfo0),
|
||||
PacketFeedback(110, 210, 1, 1500, kPacingInfo0),
|
||||
PacketFeedback(120, 220, 2, 1500, kPacingInfo0),
|
||||
PacketFeedback(130, 230, 3, 1500, kPacingInfo0),
|
||||
PacketFeedback(140, 240, 4, 1500, kPacingInfo0),
|
||||
PacketFeedback(150, 250, 5, 1500, kPacingInfo0),
|
||||
PacketFeedback(160, 260, 6, 1500, kPacingInfo0)
|
||||
};
|
||||
|
||||
for (const PacketFeedback& packet : sent_packets)
|
||||
OnSentPacket(packet);
|
||||
|
||||
// Note: Important to include the last packet, as only unreceived packets in
|
||||
// between received packets can be inferred.
|
||||
std::vector<PacketFeedback> received_packets = {
|
||||
sent_packets[0], sent_packets[2], sent_packets[6]
|
||||
};
|
||||
|
||||
rtcp::TransportFeedback feedback;
|
||||
feedback.SetBase(received_packets[0].sequence_number,
|
||||
received_packets[0].arrival_time_ms * 1000);
|
||||
|
||||
for (const PacketFeedback& packet : received_packets) {
|
||||
EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number,
|
||||
packet.arrival_time_ms * 1000));
|
||||
}
|
||||
|
||||
feedback.Build();
|
||||
|
||||
adapter_->OnTransportFeedback(feedback);
|
||||
ComparePacketFeedbackVectors(sent_packets,
|
||||
adapter_->GetTransportFeedbackVector());
|
||||
}
|
||||
|
||||
TEST_F(TransportFeedbackAdapterTest, HandlesDroppedPackets) {
|
||||
std::vector<PacketFeedback> packets;
|
||||
packets.push_back(PacketFeedback(100, 200, 0, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(110, 210, 1, 1500, kPacingInfo1));
|
||||
packets.push_back(PacketFeedback(120, 220, 2, 1500, kPacingInfo2));
|
||||
packets.push_back(PacketFeedback(130, 230, 3, 1500, kPacingInfo3));
|
||||
packets.push_back(PacketFeedback(140, 240, 4, 1500, kPacingInfo4));
|
||||
|
||||
const uint16_t kSendSideDropBefore = 1;
|
||||
const uint16_t kReceiveSideDropAfter = 3;
|
||||
|
||||
for (const PacketFeedback& packet : packets) {
|
||||
if (packet.sequence_number >= kSendSideDropBefore)
|
||||
OnSentPacket(packet);
|
||||
}
|
||||
|
||||
rtcp::TransportFeedback feedback;
|
||||
feedback.SetBase(packets[0].sequence_number,
|
||||
packets[0].arrival_time_ms * 1000);
|
||||
|
||||
for (const PacketFeedback& packet : packets) {
|
||||
if (packet.sequence_number <= kReceiveSideDropAfter) {
|
||||
EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number,
|
||||
packet.arrival_time_ms * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
feedback.Build();
|
||||
|
||||
std::vector<PacketFeedback> expected_packets(
|
||||
packets.begin(), packets.begin() + kReceiveSideDropAfter + 1);
|
||||
// Packets that have timed out on the send-side have lost the
|
||||
// information stored on the send-side.
|
||||
for (size_t i = 0; i < kSendSideDropBefore; ++i) {
|
||||
expected_packets[i].send_time_ms = -1;
|
||||
expected_packets[i].payload_size = 0;
|
||||
expected_packets[i].pacing_info = PacedPacketInfo();
|
||||
}
|
||||
|
||||
adapter_->OnTransportFeedback(feedback);
|
||||
ComparePacketFeedbackVectors(expected_packets,
|
||||
adapter_->GetTransportFeedbackVector());
|
||||
}
|
||||
|
||||
TEST_F(TransportFeedbackAdapterTest, SendTimeWrapsBothWays) {
|
||||
int64_t kHighArrivalTimeMs = rtcp::TransportFeedback::kDeltaScaleFactor *
|
||||
static_cast<int64_t>(1 << 8) *
|
||||
static_cast<int64_t>((1 << 23) - 1) / 1000;
|
||||
std::vector<PacketFeedback> packets;
|
||||
packets.push_back(
|
||||
PacketFeedback(kHighArrivalTimeMs - 64, 200, 0, 1500, PacedPacketInfo()));
|
||||
packets.push_back(
|
||||
PacketFeedback(kHighArrivalTimeMs + 64, 210, 1, 1500, PacedPacketInfo()));
|
||||
packets.push_back(
|
||||
PacketFeedback(kHighArrivalTimeMs, 220, 2, 1500, PacedPacketInfo()));
|
||||
|
||||
for (const PacketFeedback& packet : packets)
|
||||
OnSentPacket(packet);
|
||||
|
||||
for (size_t i = 0; i < packets.size(); ++i) {
|
||||
std::unique_ptr<rtcp::TransportFeedback> feedback(
|
||||
new rtcp::TransportFeedback());
|
||||
feedback->SetBase(packets[i].sequence_number,
|
||||
packets[i].arrival_time_ms * 1000);
|
||||
|
||||
EXPECT_TRUE(feedback->AddReceivedPacket(packets[i].sequence_number,
|
||||
packets[i].arrival_time_ms * 1000));
|
||||
|
||||
rtc::Buffer raw_packet = feedback->Build();
|
||||
feedback = rtcp::TransportFeedback::ParseFrom(raw_packet.data(),
|
||||
raw_packet.size());
|
||||
|
||||
std::vector<PacketFeedback> expected_packets;
|
||||
expected_packets.push_back(packets[i]);
|
||||
|
||||
adapter_->OnTransportFeedback(*feedback.get());
|
||||
ComparePacketFeedbackVectors(expected_packets,
|
||||
adapter_->GetTransportFeedbackVector());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransportFeedbackAdapterTest, HandlesArrivalReordering) {
|
||||
std::vector<PacketFeedback> packets;
|
||||
packets.push_back(PacketFeedback(120, 200, 0, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(110, 210, 1, 1500, kPacingInfo0));
|
||||
packets.push_back(PacketFeedback(100, 220, 2, 1500, kPacingInfo0));
|
||||
|
||||
for (const PacketFeedback& packet : packets)
|
||||
OnSentPacket(packet);
|
||||
|
||||
rtcp::TransportFeedback feedback;
|
||||
feedback.SetBase(packets[0].sequence_number,
|
||||
packets[0].arrival_time_ms * 1000);
|
||||
|
||||
for (const PacketFeedback& packet : packets) {
|
||||
EXPECT_TRUE(feedback.AddReceivedPacket(packet.sequence_number,
|
||||
packet.arrival_time_ms * 1000));
|
||||
}
|
||||
|
||||
feedback.Build();
|
||||
|
||||
// Adapter keeps the packets ordered by sequence number (which is itself
|
||||
// assigned by the order of transmission). Reordering by some other criteria,
|
||||
// eg. arrival time, is up to the observers.
|
||||
adapter_->OnTransportFeedback(feedback);
|
||||
ComparePacketFeedbackVectors(packets, adapter_->GetTransportFeedbackVector());
|
||||
}
|
||||
|
||||
TEST_F(TransportFeedbackAdapterTest, TimestampDeltas) {
|
||||
std::vector<PacketFeedback> sent_packets;
|
||||
const int64_t kSmallDeltaUs =
|
||||
rtcp::TransportFeedback::kDeltaScaleFactor * ((1 << 8) - 1);
|
||||
const int64_t kLargePositiveDeltaUs =
|
||||
rtcp::TransportFeedback::kDeltaScaleFactor *
|
||||
std::numeric_limits<int16_t>::max();
|
||||
const int64_t kLargeNegativeDeltaUs =
|
||||
rtcp::TransportFeedback::kDeltaScaleFactor *
|
||||
std::numeric_limits<int16_t>::min();
|
||||
|
||||
PacketFeedback packet_feedback(100, 200, 0, 1500, true, 0, 0,
|
||||
PacedPacketInfo());
|
||||
sent_packets.push_back(packet_feedback);
|
||||
|
||||
packet_feedback.send_time_ms += kSmallDeltaUs / 1000;
|
||||
packet_feedback.arrival_time_ms += kSmallDeltaUs / 1000;
|
||||
++packet_feedback.sequence_number;
|
||||
sent_packets.push_back(packet_feedback);
|
||||
|
||||
packet_feedback.send_time_ms += kLargePositiveDeltaUs / 1000;
|
||||
packet_feedback.arrival_time_ms += kLargePositiveDeltaUs / 1000;
|
||||
++packet_feedback.sequence_number;
|
||||
sent_packets.push_back(packet_feedback);
|
||||
|
||||
packet_feedback.send_time_ms += kLargeNegativeDeltaUs / 1000;
|
||||
packet_feedback.arrival_time_ms += kLargeNegativeDeltaUs / 1000;
|
||||
++packet_feedback.sequence_number;
|
||||
sent_packets.push_back(packet_feedback);
|
||||
|
||||
// Too large, delta - will need two feedback messages.
|
||||
packet_feedback.send_time_ms += (kLargePositiveDeltaUs + 1000) / 1000;
|
||||
packet_feedback.arrival_time_ms += (kLargePositiveDeltaUs + 1000) / 1000;
|
||||
++packet_feedback.sequence_number;
|
||||
|
||||
// Packets will be added to send history.
|
||||
for (const PacketFeedback& packet : sent_packets)
|
||||
OnSentPacket(packet);
|
||||
OnSentPacket(packet_feedback);
|
||||
|
||||
// Create expected feedback and send into adapter.
|
||||
std::unique_ptr<rtcp::TransportFeedback> feedback(
|
||||
new rtcp::TransportFeedback());
|
||||
feedback->SetBase(sent_packets[0].sequence_number,
|
||||
sent_packets[0].arrival_time_ms * 1000);
|
||||
|
||||
for (const PacketFeedback& packet : sent_packets) {
|
||||
EXPECT_TRUE(feedback->AddReceivedPacket(packet.sequence_number,
|
||||
packet.arrival_time_ms * 1000));
|
||||
}
|
||||
EXPECT_FALSE(feedback->AddReceivedPacket(
|
||||
packet_feedback.sequence_number, packet_feedback.arrival_time_ms * 1000));
|
||||
|
||||
rtc::Buffer raw_packet = feedback->Build();
|
||||
feedback =
|
||||
rtcp::TransportFeedback::ParseFrom(raw_packet.data(), raw_packet.size());
|
||||
|
||||
std::vector<PacketFeedback> received_feedback;
|
||||
|
||||
EXPECT_TRUE(feedback.get() != nullptr);
|
||||
adapter_->OnTransportFeedback(*feedback.get());
|
||||
ComparePacketFeedbackVectors(sent_packets,
|
||||
adapter_->GetTransportFeedbackVector());
|
||||
|
||||
// Create a new feedback message and add the trailing item.
|
||||
feedback.reset(new rtcp::TransportFeedback());
|
||||
feedback->SetBase(packet_feedback.sequence_number,
|
||||
packet_feedback.arrival_time_ms * 1000);
|
||||
EXPECT_TRUE(feedback->AddReceivedPacket(
|
||||
packet_feedback.sequence_number, packet_feedback.arrival_time_ms * 1000));
|
||||
raw_packet = feedback->Build();
|
||||
feedback =
|
||||
rtcp::TransportFeedback::ParseFrom(raw_packet.data(), raw_packet.size());
|
||||
|
||||
EXPECT_TRUE(feedback.get() != nullptr);
|
||||
adapter_->OnTransportFeedback(*feedback.get());
|
||||
{
|
||||
std::vector<PacketFeedback> expected_packets;
|
||||
expected_packets.push_back(packet_feedback);
|
||||
ComparePacketFeedbackVectors(expected_packets,
|
||||
adapter_->GetTransportFeedbackVector());
|
||||
}
|
||||
}
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
183
modules/congestion_controller/rtp/trendline_estimator.cc
Normal file
183
modules/congestion_controller/rtp/trendline_estimator.cc
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/trendline_estimator.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "api/optional.h"
|
||||
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/numerics/safe_minmax.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
rtc::Optional<double> LinearFitSlope(
|
||||
const std::deque<std::pair<double, double>>& points) {
|
||||
RTC_DCHECK(points.size() >= 2);
|
||||
// Compute the "center of mass".
|
||||
double sum_x = 0;
|
||||
double sum_y = 0;
|
||||
for (const auto& point : points) {
|
||||
sum_x += point.first;
|
||||
sum_y += point.second;
|
||||
}
|
||||
double x_avg = sum_x / points.size();
|
||||
double y_avg = sum_y / points.size();
|
||||
// Compute the slope k = \sum (x_i-x_avg)(y_i-y_avg) / \sum (x_i-x_avg)^2
|
||||
double numerator = 0;
|
||||
double denominator = 0;
|
||||
for (const auto& point : points) {
|
||||
numerator += (point.first - x_avg) * (point.second - y_avg);
|
||||
denominator += (point.first - x_avg) * (point.first - x_avg);
|
||||
}
|
||||
if (denominator == 0)
|
||||
return rtc::nullopt;
|
||||
return numerator / denominator;
|
||||
}
|
||||
|
||||
constexpr double kMaxAdaptOffsetMs = 15.0;
|
||||
constexpr double kOverUsingTimeThreshold = 10;
|
||||
constexpr int kMinNumDeltas = 60;
|
||||
|
||||
} // namespace
|
||||
|
||||
enum { kDeltaCounterMax = 1000 };
|
||||
|
||||
TrendlineEstimator::TrendlineEstimator(size_t window_size,
|
||||
double smoothing_coef,
|
||||
double threshold_gain)
|
||||
: window_size_(window_size),
|
||||
smoothing_coef_(smoothing_coef),
|
||||
threshold_gain_(threshold_gain),
|
||||
num_of_deltas_(0),
|
||||
first_arrival_time_ms_(-1),
|
||||
accumulated_delay_(0),
|
||||
smoothed_delay_(0),
|
||||
delay_hist_(),
|
||||
trendline_(0),
|
||||
k_up_(0.0087),
|
||||
k_down_(0.039),
|
||||
overusing_time_threshold_(kOverUsingTimeThreshold),
|
||||
threshold_(12.5),
|
||||
last_update_ms_(-1),
|
||||
prev_offset_(0.0),
|
||||
time_over_using_(-1),
|
||||
overuse_counter_(0),
|
||||
hypothesis_(BandwidthUsage::kBwNormal) {}
|
||||
|
||||
TrendlineEstimator::~TrendlineEstimator() {}
|
||||
|
||||
void TrendlineEstimator::Update(double recv_delta_ms,
|
||||
double send_delta_ms,
|
||||
int64_t arrival_time_ms) {
|
||||
const double delta_ms = recv_delta_ms - send_delta_ms;
|
||||
++num_of_deltas_;
|
||||
if (num_of_deltas_ > kDeltaCounterMax)
|
||||
num_of_deltas_ = kDeltaCounterMax;
|
||||
if (first_arrival_time_ms_ == -1)
|
||||
first_arrival_time_ms_ = arrival_time_ms;
|
||||
|
||||
// Exponential backoff filter.
|
||||
accumulated_delay_ += delta_ms;
|
||||
BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", arrival_time_ms,
|
||||
accumulated_delay_);
|
||||
smoothed_delay_ = smoothing_coef_ * smoothed_delay_ +
|
||||
(1 - smoothing_coef_) * accumulated_delay_;
|
||||
BWE_TEST_LOGGING_PLOT(1, "smoothed_delay_ms", arrival_time_ms,
|
||||
smoothed_delay_);
|
||||
|
||||
// Simple linear regression.
|
||||
delay_hist_.push_back(std::make_pair(
|
||||
static_cast<double>(arrival_time_ms - first_arrival_time_ms_),
|
||||
smoothed_delay_));
|
||||
if (delay_hist_.size() > window_size_)
|
||||
delay_hist_.pop_front();
|
||||
if (delay_hist_.size() == window_size_) {
|
||||
// Only update trendline_ if it is possible to fit a line to the data.
|
||||
trendline_ = LinearFitSlope(delay_hist_).value_or(trendline_);
|
||||
}
|
||||
|
||||
BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trendline_);
|
||||
|
||||
Detect(trendline_slope(), send_delta_ms, num_of_deltas(), arrival_time_ms);
|
||||
}
|
||||
|
||||
BandwidthUsage TrendlineEstimator::State() const {
|
||||
return hypothesis_;
|
||||
}
|
||||
|
||||
void TrendlineEstimator::Detect(double offset,
|
||||
double ts_delta,
|
||||
int num_of_deltas,
|
||||
int64_t now_ms) {
|
||||
if (num_of_deltas < 2) {
|
||||
hypothesis_ = BandwidthUsage::kBwNormal;
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
void TrendlineEstimator::UpdateThreshold(double modified_offset,
|
||||
int64_t now_ms) {
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
92
modules/congestion_controller/rtp/trendline_estimator.h
Normal file
92
modules/congestion_controller/rtp/trendline_estimator.h
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef MODULES_CONGESTION_CONTROLLER_RTP_TRENDLINE_ESTIMATOR_H_
|
||||
#define MODULES_CONGESTION_CONTROLLER_RTP_TRENDLINE_ESTIMATOR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
|
||||
#include "modules/congestion_controller/rtp/delay_increase_detector_interface.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class TrendlineEstimator : public DelayIncreaseDetectorInterface {
|
||||
public:
|
||||
// |window_size| is the number of points required to compute a trend line.
|
||||
// |smoothing_coef| controls how much we smooth out the delay before fitting
|
||||
// the trend line. |threshold_gain| is used to scale the trendline slope for
|
||||
// comparison to the old threshold. Once the old estimator has been removed
|
||||
// (or the thresholds been merged into the estimators), we can just set the
|
||||
// threshold instead of setting a gain.
|
||||
TrendlineEstimator(size_t window_size,
|
||||
double smoothing_coef,
|
||||
double threshold_gain);
|
||||
|
||||
~TrendlineEstimator() override;
|
||||
|
||||
// Update the estimator with a new sample. The deltas should represent deltas
|
||||
// between timestamp groups as defined by the InterArrival class.
|
||||
void Update(double recv_delta_ms,
|
||||
double send_delta_ms,
|
||||
int64_t arrival_time_ms) override;
|
||||
|
||||
BandwidthUsage State() const override;
|
||||
|
||||
// Returns the estimated trend k multiplied by some gain.
|
||||
// 0 < k < 1 -> the delay increases, queues are filling up
|
||||
// k == 0 -> the delay does not change
|
||||
// k < 0 -> the delay decreases, queues are being emptied
|
||||
double trendline_slope() const { return trendline_ * threshold_gain_; }
|
||||
|
||||
// Returns the number of deltas which the current estimator state is based on.
|
||||
unsigned int num_of_deltas() const { return num_of_deltas_; }
|
||||
|
||||
private:
|
||||
void Detect(double offset,
|
||||
double ts_delta,
|
||||
int num_of_deltas,
|
||||
int64_t now_ms);
|
||||
|
||||
void UpdateThreshold(double modified_offset, int64_t now_ms);
|
||||
|
||||
// Parameters.
|
||||
const size_t window_size_;
|
||||
const double smoothing_coef_;
|
||||
const double threshold_gain_;
|
||||
// Used by the existing threshold.
|
||||
unsigned int num_of_deltas_;
|
||||
// Keep the arrival times small by using the change from the first packet.
|
||||
int64_t first_arrival_time_ms_;
|
||||
// Exponential backoff filtering.
|
||||
double accumulated_delay_;
|
||||
double smoothed_delay_;
|
||||
// Linear least squares regression.
|
||||
std::deque<std::pair<double, double>> delay_hist_;
|
||||
double trendline_;
|
||||
|
||||
const double k_up_;
|
||||
const 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(TrendlineEstimator);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_CONGESTION_CONTROLLER_RTP_TRENDLINE_ESTIMATOR_H_
|
||||
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 "modules/congestion_controller/rtp/trendline_estimator.h"
|
||||
#include "rtc_base/random.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
constexpr size_t kWindowSize = 20;
|
||||
constexpr double kSmoothing = 0.0;
|
||||
constexpr double kGain = 1;
|
||||
constexpr int64_t kAvgTimeBetweenPackets = 10;
|
||||
constexpr size_t kPacketCount = 2 * kWindowSize + 1;
|
||||
|
||||
void TestEstimator(double slope, double jitter_stddev, double tolerance) {
|
||||
TrendlineEstimator estimator(kWindowSize, kSmoothing, kGain);
|
||||
Random random(0x1234567);
|
||||
int64_t send_times[kPacketCount];
|
||||
int64_t recv_times[kPacketCount];
|
||||
int64_t send_start_time = random.Rand(1000000);
|
||||
int64_t recv_start_time = random.Rand(1000000);
|
||||
for (size_t i = 0; i < kPacketCount; ++i) {
|
||||
send_times[i] = send_start_time + i * kAvgTimeBetweenPackets;
|
||||
double latency = i * kAvgTimeBetweenPackets / (1 - slope);
|
||||
double jitter = random.Gaussian(0, jitter_stddev);
|
||||
recv_times[i] = recv_start_time + latency + jitter;
|
||||
}
|
||||
for (size_t i = 1; i < kPacketCount; ++i) {
|
||||
double recv_delta = recv_times[i] - recv_times[i - 1];
|
||||
double send_delta = send_times[i] - send_times[i - 1];
|
||||
estimator.Update(recv_delta, send_delta, recv_times[i]);
|
||||
if (i < kWindowSize)
|
||||
EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001);
|
||||
else
|
||||
EXPECT_NEAR(estimator.trendline_slope(), slope, tolerance);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(TrendlineEstimator, PerfectLineSlopeOneHalf) {
|
||||
TestEstimator(0.5, 0, 0.001);
|
||||
}
|
||||
|
||||
TEST(TrendlineEstimator, PerfectLineSlopeMinusOne) {
|
||||
TestEstimator(-1, 0, 0.001);
|
||||
}
|
||||
|
||||
TEST(TrendlineEstimator, PerfectLineSlopeZero) {
|
||||
TestEstimator(0, 0, 0.001);
|
||||
}
|
||||
|
||||
TEST(TrendlineEstimator, JitteryLineSlopeOneHalf) {
|
||||
TestEstimator(0.5, kAvgTimeBetweenPackets / 3.0, 0.01);
|
||||
}
|
||||
|
||||
TEST(TrendlineEstimator, JitteryLineSlopeMinusOne) {
|
||||
TestEstimator(-1, kAvgTimeBetweenPackets / 3.0, 0.075);
|
||||
}
|
||||
|
||||
TEST(TrendlineEstimator, JitteryLineSlopeZero) {
|
||||
TestEstimator(0, kAvgTimeBetweenPackets / 3.0, 0.02);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
Loading…
x
Reference in New Issue
Block a user