Implement periodic bandwidth probing in application-limited region.

Now ProbeController can send periodic bandwidth probes when in
application-limited region. This will allow to maintain correct
bottleneck bandwidth estimate, even not all bandwidth is being used.
The feature is not enabled by default, but can be enabled with a flag.
Interval between probes is currently set to 5 seconds.

BUG=webrtc:6332

Review-Url: https://codereview.webrtc.org/2504023002
Cr-Commit-Position: refs/heads/master@{#15279}
This commit is contained in:
sergeyu 2016-11-28 13:11:13 -08:00 committed by Commit bot
parent bf22be902e
commit 80ed35e21c
15 changed files with 194 additions and 62 deletions

View File

@ -129,6 +129,9 @@ struct MediaConfig {
// VideoReceiveStream, where the value is passed on to the
// IncomingVideoStream constructor.
bool disable_prerenderer_smoothing = false;
// Enables periodic bandwidth probing in application-limited region.
bool periodic_alr_bandwidth_probing = false;
} video;
};

View File

@ -1102,6 +1102,8 @@ bool WebRtcVideoChannel2::AddSendStream(const StreamParams& sp) {
webrtc::VideoSendStream::Config config(this);
config.suspend_below_min_bitrate = video_config_.suspend_below_min_bitrate;
config.periodic_alr_bandwidth_probing =
video_config_.periodic_alr_bandwidth_probing;
WebRtcVideoSendStream* stream = new WebRtcVideoSendStream(
call_, sp, std::move(config), default_send_options_,
external_encoder_factory_, video_config_.enable_cpu_overuse_detection,

View File

@ -275,6 +275,10 @@ RateLimiter* CongestionController::GetRetransmissionRateLimiter() {
return retransmission_rate_limiter_.get();
}
void CongestionController::EnablePeriodicAlrProbing(bool enable) {
probe_controller_->EnablePeriodicAlrProbing(enable);
}
void CongestionController::SetAllocatedSendBitrateLimits(
int min_send_bitrate_bps,
int max_padding_bitrate_bps) {
@ -319,6 +323,7 @@ int64_t CongestionController::TimeUntilNextProcess() {
void CongestionController::Process() {
bitrate_controller_->Process();
remote_bitrate_estimator_->Process();
probe_controller_->Process();
MaybeTriggerOnNetworkChanged();
}

View File

@ -82,6 +82,7 @@ class CongestionController : public CallStatsObserver, public Module {
virtual PacketRouter* packet_router() { return packet_router_.get(); }
virtual TransportFeedbackObserver* GetTransportFeedbackObserver();
RateLimiter* GetRetransmissionRateLimiter();
void EnablePeriodicAlrProbing(bool enable);
// SetAllocatedSendBitrateLimits sets bitrates limits imposed by send codec
// settings.

View File

@ -38,8 +38,14 @@ constexpr int kExponentialProbingDisabled = 0;
constexpr int kDefaultMaxProbingBitrateBps = 100000000;
// This is a limit on how often probing can be done when there is a BW
// drop detected in ALR region.
constexpr int kAlrProbingIntervalLimitMs = 5000;
// drop detected in ALR.
constexpr int64_t kAlrProbingIntervalMinMs = 5000;
// Interval between probes when ALR periodic probing is enabled.
constexpr int64_t kAlrPeriodicProbingIntervalMs = 5000;
// Minimum probe bitrate percentage to probe further for repeated probes.
constexpr int kRepeatedProbeMinPercentage = 125;
} // namespace
@ -53,7 +59,8 @@ ProbeController::ProbeController(PacedSender* pacer, Clock* clock)
estimated_bitrate_bps_(0),
start_bitrate_bps_(0),
max_bitrate_bps_(0),
last_alr_probing_time_(clock_->TimeInMilliseconds()) {}
last_alr_probing_time_(clock_->TimeInMilliseconds()),
enable_periodic_alr_probing_(false) {}
void ProbeController::SetBitrates(int min_bitrate_bps,
int start_bitrate_bps,
@ -83,7 +90,8 @@ void ProbeController::SetBitrates(int min_bitrate_bps,
if (estimated_bitrate_bps_ != 0 &&
estimated_bitrate_bps_ < old_max_bitrate_bps &&
max_bitrate_bps_ > old_max_bitrate_bps) {
InitiateProbing({max_bitrate_bps}, kExponentialProbingDisabled);
InitiateProbing(clock_->TimeInMilliseconds(), {max_bitrate_bps},
kExponentialProbingDisabled);
}
break;
}
@ -103,14 +111,17 @@ void ProbeController::InitiateExponentialProbing() {
// When probing at 1.8 Mbps ( 6x 300), this represents a threshold of
// 1.2 Mbps to continue probing.
InitiateProbing({3 * start_bitrate_bps_, 6 * start_bitrate_bps_},
InitiateProbing(clock_->TimeInMilliseconds(),
{3 * start_bitrate_bps_, 6 * start_bitrate_bps_},
4 * start_bitrate_bps_);
}
void ProbeController::SetEstimatedBitrate(int bitrate_bps) {
rtc::CritScope cs(&critsect_);
int64_t now_ms = clock_->TimeInMilliseconds();
if (state_ == State::kWaitingForProbingResult) {
if ((clock_->TimeInMilliseconds() - time_last_probing_initiated_ms_) >
if ((now_ms - time_last_probing_initiated_ms_) >
kMaxWaitingTimeForProbingResultMs) {
LOG(LS_INFO) << "kWaitingForProbingResult: timeout";
state_ = State::kProbingComplete;
@ -125,38 +136,73 @@ void ProbeController::SetEstimatedBitrate(int bitrate_bps) {
bitrate_bps > min_bitrate_to_probe_further_bps_) {
// Double the probing bitrate and expect a minimum of 25% gain to
// continue probing.
InitiateProbing({2 * bitrate_bps}, 1.25 * bitrate_bps);
}
}
} else {
// A drop in estimated BW when operating in ALR and not already probing.
// The current response is to initiate a single probe session at the
// previous bitrate and immediately use the reported bitrate as the new
// bitrate.
//
// If the probe session fails, the assumption is that this drop was a
// real one from a competing flow or something else on the network and
// it ramps up from bitrate_bps.
if (pacer_->InApplicationLimitedRegion() &&
bitrate_bps < 0.5 * estimated_bitrate_bps_) {
int64_t now_ms = clock_->TimeInMilliseconds();
if ((now_ms - last_alr_probing_time_) > kAlrProbingIntervalLimitMs) {
LOG(LS_INFO) << "Detected big BW drop in ALR, start probe.";
// Track how often we probe in response to BW drop in ALR.
RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.AlrProbingIntervalInS",
(now_ms - last_alr_probing_time_) / 1000);
InitiateProbing({estimated_bitrate_bps_}, kExponentialProbingDisabled);
last_alr_probing_time_ = now_ms;
InitiateProbing(now_ms, {2 * bitrate_bps},
bitrate_bps * kRepeatedProbeMinPercentage / 100);
} else {
// Stop exponential probing.
state_ = State::kProbingComplete;
min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled;
}
}
}
// Detect a drop in estimated BW when operating in ALR and not already
// probing. The current response is to initiate a single probe session at the
// previous bitrate and immediately use the reported bitrate as the new
// bitrate.
//
// If the probe session fails, the assumption is that this drop was a
// real one from a competing flow or something else on the network and
// it ramps up from bitrate_bps.
if (state_ == State::kProbingComplete &&
pacer_->GetApplicationLimitedRegionStartTime() &&
bitrate_bps < estimated_bitrate_bps_ / 2 &&
(now_ms - last_alr_probing_time_) > kAlrProbingIntervalMinMs) {
LOG(LS_INFO) << "Detected big BW drop in ALR, start probe.";
// Track how often we probe in response to BW drop in ALR.
RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.AlrProbingIntervalInS",
(now_ms - last_alr_probing_time_) / 1000);
InitiateProbing(now_ms, {estimated_bitrate_bps_},
kExponentialProbingDisabled);
last_alr_probing_time_ = now_ms;
// TODO(isheriff): May want to track when we did ALR probing in order
// to reset |last_alr_probing_time_| if we validate that it was a
// drop due to exogenous event.
}
estimated_bitrate_bps_ = bitrate_bps;
}
void ProbeController::EnablePeriodicAlrProbing(bool enable) {
rtc::CritScope cs(&critsect_);
enable_periodic_alr_probing_ = enable;
}
void ProbeController::Process() {
rtc::CritScope cs(&critsect_);
if (state_ != State::kProbingComplete || !enable_periodic_alr_probing_)
return;
// Probe bandwidth periodically when in ALR state.
rtc::Optional<int64_t> alr_start_time =
pacer_->GetApplicationLimitedRegionStartTime();
if (alr_start_time) {
int64_t now_ms = clock_->TimeInMilliseconds();
int64_t next_probe_time_ms =
std::max(*alr_start_time, time_last_probing_initiated_ms_) +
kAlrPeriodicProbingIntervalMs;
if (now_ms >= next_probe_time_ms) {
InitiateProbing(
now_ms, {estimated_bitrate_bps_ * 2},
estimated_bitrate_bps_ * kRepeatedProbeMinPercentage / 100);
}
}
}
void ProbeController::InitiateProbing(
int64_t now_ms,
std::initializer_list<int> bitrates_to_probe,
int min_bitrate_to_probe_further_bps) {
bool first_cluster = true;
@ -172,7 +218,7 @@ void ProbeController::InitiateProbing(
}
}
min_bitrate_to_probe_further_bps_ = min_bitrate_to_probe_further_bps;
time_last_probing_initiated_ms_ = clock_->TimeInMilliseconds();
time_last_probing_initiated_ms_ = now_ms;
if (min_bitrate_to_probe_further_bps == kExponentialProbingDisabled)
state_ = State::kProbingComplete;
else

View File

@ -36,6 +36,9 @@ class ProbeController {
void SetEstimatedBitrate(int bitrate_bps);
void EnablePeriodicAlrProbing(bool enable);
void Process();
private:
enum class State {
// Initial state where no probing has been triggered yet.
@ -47,7 +50,8 @@ class ProbeController {
};
void InitiateExponentialProbing() EXCLUSIVE_LOCKS_REQUIRED(critsect_);
void InitiateProbing(std::initializer_list<int> bitrates_to_probe,
void InitiateProbing(int64_t now_ms,
std::initializer_list<int> bitrates_to_probe,
int min_bitrate_to_probe_further_bps)
EXCLUSIVE_LOCKS_REQUIRED(critsect_);
@ -62,6 +66,7 @@ class ProbeController {
int start_bitrate_bps_ GUARDED_BY(critsect_);
int max_bitrate_bps_ GUARDED_BY(critsect_);
int64_t last_alr_probing_time_ GUARDED_BY(critsect_);
bool enable_periodic_alr_probing_ GUARDED_BY(critsect_);
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ProbeController);
};

View File

@ -19,6 +19,7 @@
using testing::_;
using testing::AtLeast;
using testing::NiceMock;
using testing::Return;
namespace webrtc {
namespace test {
@ -31,11 +32,13 @@ constexpr int kMaxBitrateBps = 10000;
constexpr int kExponentialProbingTimeoutMs = 5000;
constexpr int kAlrProbeInterval = 5000;
} // namespace
class ProbeControllerTest : public ::testing::Test {
protected:
ProbeControllerTest() : clock_(0) {
ProbeControllerTest() : clock_(100000000L) {
probe_controller_.reset(new ProbeController(&pacer_, &clock_));
}
~ProbeControllerTest() override {}
@ -79,8 +82,6 @@ TEST_F(ProbeControllerTest, TestExponentialProbing) {
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
kMaxBitrateBps);
EXPECT_CALL(pacer_, CreateProbeCluster(2 * 1800, _));
probe_controller_->SetEstimatedBitrate(1800);
}
@ -95,5 +96,59 @@ TEST_F(ProbeControllerTest, TestExponentialProbingTimeout) {
probe_controller_->SetEstimatedBitrate(1800);
}
TEST_F(ProbeControllerTest, ProbeAfterEstimateDropInAlr) {
EXPECT_CALL(pacer_, CreateProbeCluster(_, _)).Times(2);
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
kMaxBitrateBps);
probe_controller_->SetEstimatedBitrate(500);
testing::Mock::VerifyAndClearExpectations(&pacer_);
// When bandwidth estimate drops the controller should send a probe at the
// previous bitrate.
EXPECT_CALL(pacer_, CreateProbeCluster(500, _)).Times(1);
EXPECT_CALL(pacer_, GetApplicationLimitedRegionStartTime())
.WillRepeatedly(
Return(rtc::Optional<int64_t>(clock_.TimeInMilliseconds())));
clock_.AdvanceTimeMilliseconds(kAlrProbeInterval + 1);
probe_controller_->SetEstimatedBitrate(50);
}
TEST_F(ProbeControllerTest, PeriodicProbing) {
EXPECT_CALL(pacer_, CreateProbeCluster(_, _)).Times(2);
probe_controller_->EnablePeriodicAlrProbing(true);
probe_controller_->SetBitrates(kMinBitrateBps, kStartBitrateBps,
kMaxBitrateBps);
probe_controller_->SetEstimatedBitrate(500);
testing::Mock::VerifyAndClearExpectations(&pacer_);
int64_t start_time = clock_.TimeInMilliseconds();
// Expect the controller to send a new probe after 5s has passed.
EXPECT_CALL(pacer_, CreateProbeCluster(1000, _)).Times(1);
EXPECT_CALL(pacer_, GetApplicationLimitedRegionStartTime())
.WillRepeatedly(Return(rtc::Optional<int64_t>(start_time)));
clock_.AdvanceTimeMilliseconds(5000);
probe_controller_->Process();
probe_controller_->SetEstimatedBitrate(500);
testing::Mock::VerifyAndClearExpectations(&pacer_);
// The following probe should be sent at 10s into ALR.
EXPECT_CALL(pacer_, CreateProbeCluster(_, _)).Times(0);
EXPECT_CALL(pacer_, GetApplicationLimitedRegionStartTime())
.WillRepeatedly(Return(rtc::Optional<int64_t>(start_time)));
clock_.AdvanceTimeMilliseconds(4000);
probe_controller_->Process();
probe_controller_->SetEstimatedBitrate(500);
testing::Mock::VerifyAndClearExpectations(&pacer_);
EXPECT_CALL(pacer_, CreateProbeCluster(_, _)).Times(1);
EXPECT_CALL(pacer_, GetApplicationLimitedRegionStartTime())
.WillRepeatedly(Return(rtc::Optional<int64_t>(start_time)));
clock_.AdvanceTimeMilliseconds(1000);
probe_controller_->Process();
probe_controller_->SetEstimatedBitrate(500);
testing::Mock::VerifyAndClearExpectations(&pacer_);
}
} // namespace test
} // namespace webrtc

View File

@ -44,10 +44,10 @@ void AlrDetector::OnBytesSent(size_t bytes_sent, int64_t now_ms) {
return;
int percentage = static_cast<int>(*rate) * 100 / estimated_bitrate_bps_;
if (percentage < kAlrStartUsagePercent && !application_limited_) {
application_limited_ = true;
} else if (percentage > kAlrEndUsagePercent && application_limited_) {
application_limited_ = false;
if (percentage < kAlrStartUsagePercent && !alr_started_time_ms_) {
alr_started_time_ms_ = rtc::Optional<int64_t>(now_ms);
} else if (percentage > kAlrEndUsagePercent && alr_started_time_ms_) {
alr_started_time_ms_ = rtc::Optional<int64_t>();
}
}
@ -56,8 +56,9 @@ void AlrDetector::SetEstimatedBitrate(int bitrate_bps) {
estimated_bitrate_bps_ = bitrate_bps;
}
bool AlrDetector::InApplicationLimitedRegion() const {
return application_limited_;
rtc::Optional<int64_t> AlrDetector::GetApplicationLimitedRegionStartTime()
const {
return alr_started_time_ms_;
}
} // namespace webrtc

View File

@ -35,13 +35,16 @@ class AlrDetector {
// Set current estimated bandwidth.
void SetEstimatedBitrate(int bitrate_bps);
// Returns true if currently in application-limited region.
bool InApplicationLimitedRegion() const;
// 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;
private:
RateStatistics rate_;
int estimated_bitrate_bps_ = 0;
bool application_limited_ = false;
// Non-empty in ALR state.
rtc::Optional<int64_t> alr_started_time_ms_;
};
} // namespace webrtc

View File

@ -51,61 +51,61 @@ class AlrDetectorTest : public testing::Test {
TEST_F(AlrDetectorTest, AlrDetection) {
// Start in non-ALR state.
EXPECT_FALSE(alr_detector_.InApplicationLimitedRegion());
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
// Stay in non-ALR state when usage is close to 100%.
SimulateOutgoingTraffic(500, 90);
EXPECT_FALSE(alr_detector_.InApplicationLimitedRegion());
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
// Verify that we ALR starts when bitrate drops below 20%.
SimulateOutgoingTraffic(500, 20);
EXPECT_TRUE(alr_detector_.InApplicationLimitedRegion());
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
// Verify that we remain in ALR state while usage is still below 50%.
SimulateOutgoingTraffic(500, 40);
EXPECT_TRUE(alr_detector_.InApplicationLimitedRegion());
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
// Verify that ALR ends when usage is above 50%.
SimulateOutgoingTraffic(500, 60);
EXPECT_FALSE(alr_detector_.InApplicationLimitedRegion());
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
}
TEST_F(AlrDetectorTest, ShortSpike) {
// Start in non-ALR state.
EXPECT_FALSE(alr_detector_.InApplicationLimitedRegion());
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
// Verify that we ALR starts when bitrate drops below 20%.
SimulateOutgoingTraffic(500, 20);
EXPECT_TRUE(alr_detector_.InApplicationLimitedRegion());
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
// Verify that we stay in ALR region even after a short bitrate spike.
SimulateOutgoingTraffic(100, 150);
EXPECT_TRUE(alr_detector_.InApplicationLimitedRegion());
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
SimulateOutgoingTraffic(200, 20);
EXPECT_TRUE(alr_detector_.InApplicationLimitedRegion());
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
// ALR ends when usage is above 50%.
SimulateOutgoingTraffic(500, 60);
EXPECT_FALSE(alr_detector_.InApplicationLimitedRegion());
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
}
TEST_F(AlrDetectorTest, BandwidthEstimateChanges) {
// Start in non-ALR state.
EXPECT_FALSE(alr_detector_.InApplicationLimitedRegion());
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
// ALR starts when bitrate drops below 20%.
SimulateOutgoingTraffic(500, 20);
EXPECT_TRUE(alr_detector_.InApplicationLimitedRegion());
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_.InApplicationLimitedRegion());
EXPECT_TRUE(alr_detector_.GetApplicationLimitedRegionStartTime());
SimulateOutgoingTraffic(10, 20);
EXPECT_FALSE(alr_detector_.InApplicationLimitedRegion());
EXPECT_FALSE(alr_detector_.GetApplicationLimitedRegionStartTime());
}
} // namespace webrtc

View File

@ -33,6 +33,8 @@ class MockPacedSender : public PacedSender {
MOCK_CONST_METHOD0(QueueInMs, int64_t());
MOCK_CONST_METHOD0(QueueInPackets, int());
MOCK_CONST_METHOD0(ExpectedQueueTimeMs, int64_t());
MOCK_CONST_METHOD0(GetApplicationLimitedRegionStartTime,
rtc::Optional<int64_t>());
};
} // namespace webrtc

View File

@ -343,9 +343,10 @@ int64_t PacedSender::ExpectedQueueTimeMs() const {
pacing_bitrate_kbps_);
}
bool PacedSender::InApplicationLimitedRegion() const {
rtc::Optional<int64_t> PacedSender::GetApplicationLimitedRegionStartTime()
const {
CriticalSectionScoped cs(critsect_.get());
return alr_detector_->InApplicationLimitedRegion();
return alr_detector_->GetApplicationLimitedRegionStartTime();
}
size_t PacedSender::QueueSizePackets() const {

View File

@ -15,6 +15,7 @@
#include <memory>
#include <set>
#include "webrtc/base/optional.h"
#include "webrtc/base/thread_annotations.h"
#include "webrtc/modules/include/module.h"
#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
@ -120,12 +121,13 @@ class PacedSender : public Module, public RtpPacketSender {
// packets in the queue, given the current size and bitrate, ignoring prio.
virtual int64_t ExpectedQueueTimeMs() const;
// Application Limited Region refers to operating in a state where the
// Returns time in milliseconds when the current application-limited region
// started or empty result if the sender is currently not application-limited.
//
// Application Limited Region (ALR) refers to operating in a state where the
// traffic on network is limited due to application not having enough
// traffic to meet the current channel capacity.
//
// Returns true if network is currently application-limited.
bool InApplicationLimitedRegion() const;
virtual rtc::Optional<int64_t> GetApplicationLimitedRegionStartTime() const;
// Returns the average time since being enqueued, in milliseconds, for all
// packets currently in the pacer queue, or 0 if queue is empty.

View File

@ -748,6 +748,9 @@ VideoSendStreamImpl::VideoSendStreamImpl(
RTC_DCHECK(congestion_controller_);
RTC_DCHECK(remb_);
congestion_controller_->EnablePeriodicAlrProbing(
config_->periodic_alr_bandwidth_probing);
// RTP/RTCP initialization.
for (RtpRtcp* rtp_rtcp : rtp_rtcp_modules_) {
congestion_controller_->packet_router()->AddRtpModule(rtp_rtcp);

View File

@ -185,6 +185,9 @@ class VideoSendStream {
// stream may send at a rate higher than the estimated available bitrate.
bool suspend_below_min_bitrate = false;
// Enables periodic bandwidth probing in application-limited region.
bool periodic_alr_bandwidth_probing = false;
private:
// Access to the copy constructor is private to force use of the Copy()
// method for those exceptional cases where we do use it.