Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
|
|
@ -0,0 +1,5 @@
|
|||
danilchap@webrtc.org
|
||||
stefan@webrtc.org
|
||||
terelius@webrtc.org
|
||||
mflodman@webrtc.org
|
||||
perkj@webrtc.org
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/remote_bitrate_estimator/aimd_rate_control.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/match.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "modules/remote_bitrate_estimator/overuse_detector.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/safe_minmax.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
constexpr TimeDelta kDefaultRtt = TimeDelta::Millis(200);
|
||||
constexpr double kDefaultBackoffFactor = 0.85;
|
||||
|
||||
constexpr char kBweBackOffFactorExperiment[] = "WebRTC-BweBackOffFactor";
|
||||
|
||||
double ReadBackoffFactor(const FieldTrialsView& key_value_config) {
|
||||
std::string experiment_string =
|
||||
key_value_config.Lookup(kBweBackOffFactorExperiment);
|
||||
double backoff_factor;
|
||||
int parsed_values =
|
||||
sscanf(experiment_string.c_str(), "Enabled-%lf", &backoff_factor);
|
||||
if (parsed_values == 1) {
|
||||
if (backoff_factor >= 1.0) {
|
||||
RTC_LOG(LS_WARNING) << "Back-off factor must be less than 1.";
|
||||
} else if (backoff_factor <= 0.0) {
|
||||
RTC_LOG(LS_WARNING) << "Back-off factor must be greater than 0.";
|
||||
} else {
|
||||
return backoff_factor;
|
||||
}
|
||||
}
|
||||
RTC_LOG(LS_WARNING) << "Failed to parse parameters for AimdRateControl "
|
||||
"experiment from field trial string. Using default.";
|
||||
return kDefaultBackoffFactor;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AimdRateControl::AimdRateControl(const FieldTrialsView& key_value_config)
|
||||
: AimdRateControl(key_value_config, /* send_side =*/false) {}
|
||||
|
||||
AimdRateControl::AimdRateControl(const FieldTrialsView& key_value_config,
|
||||
bool send_side)
|
||||
: min_configured_bitrate_(kCongestionControllerMinBitrate),
|
||||
max_configured_bitrate_(DataRate::KilobitsPerSec(30000)),
|
||||
current_bitrate_(max_configured_bitrate_),
|
||||
latest_estimated_throughput_(current_bitrate_),
|
||||
link_capacity_(),
|
||||
rate_control_state_(RateControlState::kRcHold),
|
||||
time_last_bitrate_change_(Timestamp::MinusInfinity()),
|
||||
time_last_bitrate_decrease_(Timestamp::MinusInfinity()),
|
||||
time_first_throughput_estimate_(Timestamp::MinusInfinity()),
|
||||
bitrate_is_initialized_(false),
|
||||
beta_(key_value_config.IsEnabled(kBweBackOffFactorExperiment)
|
||||
? ReadBackoffFactor(key_value_config)
|
||||
: kDefaultBackoffFactor),
|
||||
in_alr_(false),
|
||||
rtt_(kDefaultRtt),
|
||||
send_side_(send_side),
|
||||
no_bitrate_increase_in_alr_(
|
||||
key_value_config.IsEnabled("WebRTC-DontIncreaseDelayBasedBweInAlr")),
|
||||
subtract_additional_backoff_term_(!key_value_config.IsDisabled(
|
||||
"WebRTC-Bwe-SubtractAdditionalBackoffTerm")) {
|
||||
ParseFieldTrial(
|
||||
{&disable_estimate_bounded_increase_,
|
||||
&use_current_estimate_as_min_upper_bound_},
|
||||
key_value_config.Lookup("WebRTC-Bwe-EstimateBoundedIncrease"));
|
||||
RTC_LOG(LS_INFO) << "Using aimd rate control with back off factor " << beta_;
|
||||
}
|
||||
|
||||
AimdRateControl::~AimdRateControl() {}
|
||||
|
||||
void AimdRateControl::SetStartBitrate(DataRate start_bitrate) {
|
||||
current_bitrate_ = start_bitrate;
|
||||
latest_estimated_throughput_ = current_bitrate_;
|
||||
bitrate_is_initialized_ = true;
|
||||
}
|
||||
|
||||
void AimdRateControl::SetMinBitrate(DataRate min_bitrate) {
|
||||
min_configured_bitrate_ = min_bitrate;
|
||||
current_bitrate_ = std::max(min_bitrate, current_bitrate_);
|
||||
}
|
||||
|
||||
bool AimdRateControl::ValidEstimate() const {
|
||||
return bitrate_is_initialized_;
|
||||
}
|
||||
|
||||
TimeDelta AimdRateControl::GetFeedbackInterval() const {
|
||||
// Estimate how often we can send RTCP if we allocate up to 5% of bandwidth
|
||||
// to feedback.
|
||||
const DataSize kRtcpSize = DataSize::Bytes(80);
|
||||
const DataRate rtcp_bitrate = current_bitrate_ * 0.05;
|
||||
const TimeDelta interval = kRtcpSize / rtcp_bitrate;
|
||||
const TimeDelta kMinFeedbackInterval = TimeDelta::Millis(200);
|
||||
const TimeDelta kMaxFeedbackInterval = TimeDelta::Millis(1000);
|
||||
return interval.Clamped(kMinFeedbackInterval, kMaxFeedbackInterval);
|
||||
}
|
||||
|
||||
bool AimdRateControl::TimeToReduceFurther(Timestamp at_time,
|
||||
DataRate estimated_throughput) const {
|
||||
const TimeDelta bitrate_reduction_interval =
|
||||
rtt_.Clamped(TimeDelta::Millis(10), TimeDelta::Millis(200));
|
||||
if (at_time - time_last_bitrate_change_ >= bitrate_reduction_interval) {
|
||||
return true;
|
||||
}
|
||||
if (ValidEstimate()) {
|
||||
// TODO(terelius/holmer): Investigate consequences of increasing
|
||||
// the threshold to 0.95 * LatestEstimate().
|
||||
const DataRate threshold = 0.5 * LatestEstimate();
|
||||
return estimated_throughput < threshold;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AimdRateControl::InitialTimeToReduceFurther(Timestamp at_time) const {
|
||||
return ValidEstimate() &&
|
||||
TimeToReduceFurther(at_time,
|
||||
LatestEstimate() / 2 - DataRate::BitsPerSec(1));
|
||||
}
|
||||
|
||||
DataRate AimdRateControl::LatestEstimate() const {
|
||||
return current_bitrate_;
|
||||
}
|
||||
|
||||
void AimdRateControl::SetRtt(TimeDelta rtt) {
|
||||
rtt_ = rtt;
|
||||
}
|
||||
|
||||
DataRate AimdRateControl::Update(const RateControlInput& input,
|
||||
Timestamp at_time) {
|
||||
// Set the initial bit rate value to what we're receiving the first half
|
||||
// second.
|
||||
// TODO(bugs.webrtc.org/9379): The comment above doesn't match to the code.
|
||||
if (!bitrate_is_initialized_) {
|
||||
const TimeDelta kInitializationTime = TimeDelta::Seconds(5);
|
||||
RTC_DCHECK_LE(kBitrateWindow, kInitializationTime);
|
||||
if (time_first_throughput_estimate_.IsInfinite()) {
|
||||
if (input.estimated_throughput)
|
||||
time_first_throughput_estimate_ = at_time;
|
||||
} else if (at_time - time_first_throughput_estimate_ >
|
||||
kInitializationTime &&
|
||||
input.estimated_throughput) {
|
||||
current_bitrate_ = *input.estimated_throughput;
|
||||
bitrate_is_initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
ChangeBitrate(input, at_time);
|
||||
return current_bitrate_;
|
||||
}
|
||||
|
||||
void AimdRateControl::SetInApplicationLimitedRegion(bool in_alr) {
|
||||
in_alr_ = in_alr;
|
||||
}
|
||||
|
||||
void AimdRateControl::SetEstimate(DataRate bitrate, Timestamp at_time) {
|
||||
bitrate_is_initialized_ = true;
|
||||
DataRate prev_bitrate = current_bitrate_;
|
||||
current_bitrate_ = ClampBitrate(bitrate);
|
||||
time_last_bitrate_change_ = at_time;
|
||||
if (current_bitrate_ < prev_bitrate) {
|
||||
time_last_bitrate_decrease_ = at_time;
|
||||
}
|
||||
}
|
||||
|
||||
void AimdRateControl::SetNetworkStateEstimate(
|
||||
const absl::optional<NetworkStateEstimate>& estimate) {
|
||||
network_estimate_ = estimate;
|
||||
}
|
||||
|
||||
double AimdRateControl::GetNearMaxIncreaseRateBpsPerSecond() const {
|
||||
RTC_DCHECK(!current_bitrate_.IsZero());
|
||||
const TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 30;
|
||||
DataSize frame_size = current_bitrate_ * kFrameInterval;
|
||||
const DataSize kPacketSize = DataSize::Bytes(1200);
|
||||
double packets_per_frame = std::ceil(frame_size / kPacketSize);
|
||||
DataSize avg_packet_size = frame_size / packets_per_frame;
|
||||
|
||||
// Approximate the over-use estimator delay to 100 ms.
|
||||
TimeDelta response_time = rtt_ + TimeDelta::Millis(100);
|
||||
|
||||
response_time = response_time * 2;
|
||||
double increase_rate_bps_per_second =
|
||||
(avg_packet_size / response_time).bps<double>();
|
||||
double kMinIncreaseRateBpsPerSecond = 4000;
|
||||
return std::max(kMinIncreaseRateBpsPerSecond, increase_rate_bps_per_second);
|
||||
}
|
||||
|
||||
TimeDelta AimdRateControl::GetExpectedBandwidthPeriod() const {
|
||||
const TimeDelta kMinPeriod = TimeDelta::Seconds(2);
|
||||
const TimeDelta kDefaultPeriod = TimeDelta::Seconds(3);
|
||||
const TimeDelta kMaxPeriod = TimeDelta::Seconds(50);
|
||||
|
||||
double increase_rate_bps_per_second = GetNearMaxIncreaseRateBpsPerSecond();
|
||||
if (!last_decrease_)
|
||||
return kDefaultPeriod;
|
||||
double time_to_recover_decrease_seconds =
|
||||
last_decrease_->bps() / increase_rate_bps_per_second;
|
||||
TimeDelta period = TimeDelta::Seconds(time_to_recover_decrease_seconds);
|
||||
return period.Clamped(kMinPeriod, kMaxPeriod);
|
||||
}
|
||||
|
||||
void AimdRateControl::ChangeBitrate(const RateControlInput& input,
|
||||
Timestamp at_time) {
|
||||
absl::optional<DataRate> new_bitrate;
|
||||
DataRate estimated_throughput =
|
||||
input.estimated_throughput.value_or(latest_estimated_throughput_);
|
||||
if (input.estimated_throughput)
|
||||
latest_estimated_throughput_ = *input.estimated_throughput;
|
||||
|
||||
// An over-use should always trigger us to reduce the bitrate, even though
|
||||
// we have not yet established our first estimate. By acting on the over-use,
|
||||
// we will end up with a valid estimate.
|
||||
if (!bitrate_is_initialized_ &&
|
||||
input.bw_state != BandwidthUsage::kBwOverusing)
|
||||
return;
|
||||
|
||||
ChangeState(input, at_time);
|
||||
|
||||
switch (rate_control_state_) {
|
||||
case RateControlState::kRcHold:
|
||||
break;
|
||||
|
||||
case RateControlState::kRcIncrease: {
|
||||
if (estimated_throughput > link_capacity_.UpperBound())
|
||||
link_capacity_.Reset();
|
||||
|
||||
// We limit the new bitrate based on the troughput to avoid unlimited
|
||||
// bitrate increases. We allow a bit more lag at very low rates to not too
|
||||
// easily get stuck if the encoder produces uneven outputs.
|
||||
DataRate increase_limit =
|
||||
1.5 * estimated_throughput + DataRate::KilobitsPerSec(10);
|
||||
if (send_side_ && in_alr_ && no_bitrate_increase_in_alr_) {
|
||||
// Do not increase the delay based estimate in alr since the estimator
|
||||
// will not be able to get transport feedback necessary to detect if
|
||||
// the new estimate is correct.
|
||||
// If we have previously increased above the limit (for instance due to
|
||||
// probing), we don't allow further changes.
|
||||
increase_limit = current_bitrate_;
|
||||
}
|
||||
|
||||
if (current_bitrate_ < increase_limit) {
|
||||
DataRate increased_bitrate = DataRate::MinusInfinity();
|
||||
if (link_capacity_.has_estimate()) {
|
||||
// The link_capacity estimate is reset if the measured throughput
|
||||
// is too far from the estimate. We can therefore assume that our
|
||||
// target rate is reasonably close to link capacity and use additive
|
||||
// increase.
|
||||
DataRate additive_increase =
|
||||
AdditiveRateIncrease(at_time, time_last_bitrate_change_);
|
||||
increased_bitrate = current_bitrate_ + additive_increase;
|
||||
} else {
|
||||
// If we don't have an estimate of the link capacity, use faster ramp
|
||||
// up to discover the capacity.
|
||||
DataRate multiplicative_increase = MultiplicativeRateIncrease(
|
||||
at_time, time_last_bitrate_change_, current_bitrate_);
|
||||
increased_bitrate = current_bitrate_ + multiplicative_increase;
|
||||
}
|
||||
new_bitrate = std::min(increased_bitrate, increase_limit);
|
||||
}
|
||||
time_last_bitrate_change_ = at_time;
|
||||
break;
|
||||
}
|
||||
|
||||
case RateControlState::kRcDecrease: {
|
||||
DataRate decreased_bitrate = DataRate::PlusInfinity();
|
||||
|
||||
// Set bit rate to something slightly lower than the measured throughput
|
||||
// to get rid of any self-induced delay.
|
||||
decreased_bitrate = estimated_throughput * beta_;
|
||||
if (decreased_bitrate > DataRate::KilobitsPerSec(5) &&
|
||||
subtract_additional_backoff_term_) {
|
||||
decreased_bitrate -= DataRate::KilobitsPerSec(5);
|
||||
}
|
||||
|
||||
if (decreased_bitrate > current_bitrate_) {
|
||||
// TODO(terelius): The link_capacity estimate may be based on old
|
||||
// throughput measurements. Relying on them may lead to unnecessary
|
||||
// BWE drops.
|
||||
if (link_capacity_.has_estimate()) {
|
||||
decreased_bitrate = beta_ * link_capacity_.estimate();
|
||||
}
|
||||
}
|
||||
// Avoid increasing the rate when over-using.
|
||||
if (decreased_bitrate < current_bitrate_) {
|
||||
new_bitrate = decreased_bitrate;
|
||||
}
|
||||
|
||||
if (bitrate_is_initialized_ && estimated_throughput < current_bitrate_) {
|
||||
if (!new_bitrate.has_value()) {
|
||||
last_decrease_ = DataRate::Zero();
|
||||
} else {
|
||||
last_decrease_ = current_bitrate_ - *new_bitrate;
|
||||
}
|
||||
}
|
||||
if (estimated_throughput < link_capacity_.LowerBound()) {
|
||||
// The current throughput is far from the estimated link capacity. Clear
|
||||
// the estimate to allow an immediate update in OnOveruseDetected.
|
||||
link_capacity_.Reset();
|
||||
}
|
||||
|
||||
bitrate_is_initialized_ = true;
|
||||
link_capacity_.OnOveruseDetected(estimated_throughput);
|
||||
// Stay on hold until the pipes are cleared.
|
||||
rate_control_state_ = RateControlState::kRcHold;
|
||||
time_last_bitrate_change_ = at_time;
|
||||
time_last_bitrate_decrease_ = at_time;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
}
|
||||
|
||||
current_bitrate_ = ClampBitrate(new_bitrate.value_or(current_bitrate_));
|
||||
}
|
||||
|
||||
DataRate AimdRateControl::ClampBitrate(DataRate new_bitrate) const {
|
||||
if (!disable_estimate_bounded_increase_ && network_estimate_ &&
|
||||
network_estimate_->link_capacity_upper.IsFinite()) {
|
||||
DataRate upper_bound =
|
||||
use_current_estimate_as_min_upper_bound_
|
||||
? std::max(network_estimate_->link_capacity_upper, current_bitrate_)
|
||||
: network_estimate_->link_capacity_upper;
|
||||
new_bitrate = std::min(upper_bound, new_bitrate);
|
||||
}
|
||||
if (network_estimate_ && network_estimate_->link_capacity_lower.IsFinite() &&
|
||||
new_bitrate < current_bitrate_) {
|
||||
new_bitrate = std::min(
|
||||
current_bitrate_,
|
||||
std::max(new_bitrate, network_estimate_->link_capacity_lower * beta_));
|
||||
}
|
||||
new_bitrate = std::max(new_bitrate, min_configured_bitrate_);
|
||||
return new_bitrate;
|
||||
}
|
||||
|
||||
DataRate AimdRateControl::MultiplicativeRateIncrease(
|
||||
Timestamp at_time,
|
||||
Timestamp last_time,
|
||||
DataRate current_bitrate) const {
|
||||
double alpha = 1.08;
|
||||
if (last_time.IsFinite()) {
|
||||
auto time_since_last_update = at_time - last_time;
|
||||
alpha = pow(alpha, std::min(time_since_last_update.seconds<double>(), 1.0));
|
||||
}
|
||||
DataRate multiplicative_increase =
|
||||
std::max(current_bitrate * (alpha - 1.0), DataRate::BitsPerSec(1000));
|
||||
return multiplicative_increase;
|
||||
}
|
||||
|
||||
DataRate AimdRateControl::AdditiveRateIncrease(Timestamp at_time,
|
||||
Timestamp last_time) const {
|
||||
double time_period_seconds = (at_time - last_time).seconds<double>();
|
||||
double data_rate_increase_bps =
|
||||
GetNearMaxIncreaseRateBpsPerSecond() * time_period_seconds;
|
||||
return DataRate::BitsPerSec(data_rate_increase_bps);
|
||||
}
|
||||
|
||||
void AimdRateControl::ChangeState(const RateControlInput& input,
|
||||
Timestamp at_time) {
|
||||
switch (input.bw_state) {
|
||||
case BandwidthUsage::kBwNormal:
|
||||
if (rate_control_state_ == RateControlState::kRcHold) {
|
||||
time_last_bitrate_change_ = at_time;
|
||||
rate_control_state_ = RateControlState::kRcIncrease;
|
||||
}
|
||||
break;
|
||||
case BandwidthUsage::kBwOverusing:
|
||||
if (rate_control_state_ != RateControlState::kRcDecrease) {
|
||||
rate_control_state_ = RateControlState::kRcDecrease;
|
||||
}
|
||||
break;
|
||||
case BandwidthUsage::kBwUnderusing:
|
||||
rate_control_state_ = RateControlState::kRcHold;
|
||||
break;
|
||||
default:
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/transport/network_types.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "modules/congestion_controller/goog_cc/link_capacity_estimator.h"
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
|
||||
namespace webrtc {
|
||||
// A rate control implementation based on additive increases of
|
||||
// bitrate when no over-use is detected and multiplicative decreases when
|
||||
// over-uses are detected. When we think the available bandwidth has changes or
|
||||
// is unknown, we will switch to a "slow-start mode" where we increase
|
||||
// multiplicatively.
|
||||
class AimdRateControl {
|
||||
public:
|
||||
explicit AimdRateControl(const FieldTrialsView& key_value_config);
|
||||
AimdRateControl(const FieldTrialsView& key_value_config, bool send_side);
|
||||
~AimdRateControl();
|
||||
|
||||
// Returns true if the target bitrate has been initialized. This happens
|
||||
// either if it has been explicitly set via SetStartBitrate/SetEstimate, or if
|
||||
// we have measured a throughput.
|
||||
bool ValidEstimate() const;
|
||||
void SetStartBitrate(DataRate start_bitrate);
|
||||
void SetMinBitrate(DataRate min_bitrate);
|
||||
TimeDelta GetFeedbackInterval() const;
|
||||
|
||||
// Returns true if the bitrate estimate hasn't been changed for more than
|
||||
// an RTT, or if the estimated_throughput is less than half of the current
|
||||
// estimate. Should be used to decide if we should reduce the rate further
|
||||
// when over-using.
|
||||
bool TimeToReduceFurther(Timestamp at_time,
|
||||
DataRate estimated_throughput) const;
|
||||
// As above. To be used if overusing before we have measured a throughput.
|
||||
bool InitialTimeToReduceFurther(Timestamp at_time) const;
|
||||
|
||||
DataRate LatestEstimate() const;
|
||||
void SetRtt(TimeDelta rtt);
|
||||
DataRate Update(const RateControlInput& input, Timestamp at_time);
|
||||
void SetInApplicationLimitedRegion(bool in_alr);
|
||||
void SetEstimate(DataRate bitrate, Timestamp at_time);
|
||||
void SetNetworkStateEstimate(
|
||||
const absl::optional<NetworkStateEstimate>& estimate);
|
||||
|
||||
// Returns the increase rate when used bandwidth is near the link capacity.
|
||||
double GetNearMaxIncreaseRateBpsPerSecond() const;
|
||||
// Returns the expected time between overuse signals (assuming steady state).
|
||||
TimeDelta GetExpectedBandwidthPeriod() const;
|
||||
|
||||
private:
|
||||
enum class RateControlState { kRcHold, kRcIncrease, kRcDecrease };
|
||||
|
||||
friend class GoogCcStatePrinter;
|
||||
// Update the target bitrate based on, among other things, the current rate
|
||||
// control state, the current target bitrate and the estimated throughput.
|
||||
// When in the "increase" state the bitrate will be increased either
|
||||
// additively or multiplicatively depending on the rate control region. When
|
||||
// in the "decrease" state the bitrate will be decreased to slightly below the
|
||||
// current throughput. When in the "hold" state the bitrate will be kept
|
||||
// constant to allow built up queues to drain.
|
||||
void ChangeBitrate(const RateControlInput& input, Timestamp at_time);
|
||||
|
||||
DataRate ClampBitrate(DataRate new_bitrate) const;
|
||||
DataRate MultiplicativeRateIncrease(Timestamp at_time,
|
||||
Timestamp last_ms,
|
||||
DataRate current_bitrate) const;
|
||||
DataRate AdditiveRateIncrease(Timestamp at_time, Timestamp last_time) const;
|
||||
void UpdateChangePeriod(Timestamp at_time);
|
||||
void ChangeState(const RateControlInput& input, Timestamp at_time);
|
||||
|
||||
DataRate min_configured_bitrate_;
|
||||
DataRate max_configured_bitrate_;
|
||||
DataRate current_bitrate_;
|
||||
DataRate latest_estimated_throughput_;
|
||||
LinkCapacityEstimator link_capacity_;
|
||||
absl::optional<NetworkStateEstimate> network_estimate_;
|
||||
RateControlState rate_control_state_;
|
||||
Timestamp time_last_bitrate_change_;
|
||||
Timestamp time_last_bitrate_decrease_;
|
||||
Timestamp time_first_throughput_estimate_;
|
||||
bool bitrate_is_initialized_;
|
||||
double beta_;
|
||||
bool in_alr_;
|
||||
TimeDelta rtt_;
|
||||
const bool send_side_;
|
||||
// Allow the delay based estimate to only increase as long as application
|
||||
// limited region (alr) is not detected.
|
||||
const bool no_bitrate_increase_in_alr_;
|
||||
// If true, subtract an additional 5kbps when backing off.
|
||||
const bool subtract_additional_backoff_term_;
|
||||
// If "Disabled", estimated link capacity is not used as upper bound.
|
||||
FieldTrialFlag disable_estimate_bounded_increase_{"Disabled"};
|
||||
FieldTrialParameter<bool> use_current_estimate_as_min_upper_bound_{"c_upper",
|
||||
true};
|
||||
absl::optional<DataRate> last_decrease_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
const char kBweTypeHistogram[] = "WebRTC.BWE.Types";
|
||||
|
||||
RateControlInput::RateControlInput(
|
||||
BandwidthUsage bw_state,
|
||||
const absl::optional<DataRate>& estimated_throughput)
|
||||
: bw_state(bw_state), estimated_throughput(estimated_throughput) {}
|
||||
|
||||
RateControlInput::~RateControlInput() = default;
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/network_state_predictor.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
inline constexpr DataRate kCongestionControllerMinBitrate =
|
||||
DataRate::BitsPerSec(5'000);
|
||||
inline constexpr TimeDelta kBitrateWindow = TimeDelta::Seconds(1);
|
||||
|
||||
extern const char kBweTypeHistogram[];
|
||||
|
||||
enum BweNames {
|
||||
kReceiverNoExtension = 0,
|
||||
kReceiverTOffset = 1,
|
||||
kReceiverAbsSendTime = 2,
|
||||
kSendSideTransportSeqNum = 3,
|
||||
kBweNamesMax = 4
|
||||
};
|
||||
|
||||
struct RateControlInput {
|
||||
RateControlInput(BandwidthUsage bw_state,
|
||||
const absl::optional<DataRate>& estimated_throughput);
|
||||
~RateControlInput();
|
||||
|
||||
BandwidthUsage bw_state;
|
||||
absl::optional<DataRate> estimated_throughput;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
// This class estimates the incoming available bandwidth.
|
||||
|
||||
#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "modules/include/module_common_types.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class Clock;
|
||||
|
||||
// RemoteBitrateObserver is used to signal changes in bitrate estimates for
|
||||
// the incoming streams.
|
||||
class RemoteBitrateObserver {
|
||||
public:
|
||||
// Called when a receive channel group has a new bitrate estimate for the
|
||||
// incoming streams.
|
||||
virtual void OnReceiveBitrateChanged(const std::vector<uint32_t>& ssrcs,
|
||||
uint32_t bitrate) = 0;
|
||||
|
||||
virtual ~RemoteBitrateObserver() {}
|
||||
};
|
||||
|
||||
class RemoteBitrateEstimator : public CallStatsObserver {
|
||||
public:
|
||||
~RemoteBitrateEstimator() override {}
|
||||
|
||||
// Called for each incoming packet. Updates the incoming payload bitrate
|
||||
// estimate and the over-use detector. If an over-use is detected the
|
||||
// remote bitrate estimate will be updated.
|
||||
virtual void IncomingPacket(const RtpPacketReceived& rtp_packet) = 0;
|
||||
|
||||
// Removes all data for `ssrc`.
|
||||
virtual void RemoveStream(uint32_t ssrc) = 0;
|
||||
|
||||
// Returns latest estimate or DataRate::Zero() if estimation is unavailable.
|
||||
virtual DataRate LatestEstimate() const = 0;
|
||||
|
||||
virtual TimeDelta Process() = 0;
|
||||
|
||||
protected:
|
||||
static constexpr TimeDelta kProcessInterval = TimeDelta::Millis(500);
|
||||
static constexpr TimeDelta kStreamTimeOut = TimeDelta::Seconds(2);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/remote_bitrate_estimator/inter_arrival.h"
|
||||
|
||||
#include "modules/include/module_common_types_public.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
static const int kBurstDeltaThresholdMs = 5;
|
||||
static const int kMaxBurstDurationMs = 100;
|
||||
|
||||
InterArrival::InterArrival(uint32_t timestamp_group_length_ticks,
|
||||
double timestamp_to_ms_coeff)
|
||||
: kTimestampGroupLengthTicks(timestamp_group_length_ticks),
|
||||
current_timestamp_group_(),
|
||||
prev_timestamp_group_(),
|
||||
timestamp_to_ms_coeff_(timestamp_to_ms_coeff),
|
||||
num_consecutive_reordered_packets_(0) {}
|
||||
|
||||
bool InterArrival::ComputeDeltas(uint32_t timestamp,
|
||||
int64_t arrival_time_ms,
|
||||
int64_t system_time_ms,
|
||||
size_t packet_size,
|
||||
uint32_t* timestamp_delta,
|
||||
int64_t* arrival_time_delta_ms,
|
||||
int* packet_size_delta) {
|
||||
RTC_DCHECK(timestamp_delta);
|
||||
RTC_DCHECK(arrival_time_delta_ms);
|
||||
RTC_DCHECK(packet_size_delta);
|
||||
bool calculated_deltas = false;
|
||||
if (current_timestamp_group_.IsFirstPacket()) {
|
||||
// We don't have enough data to update the filter, so we store it until we
|
||||
// have two frames of data to process.
|
||||
current_timestamp_group_.timestamp = timestamp;
|
||||
current_timestamp_group_.first_timestamp = timestamp;
|
||||
current_timestamp_group_.first_arrival_ms = arrival_time_ms;
|
||||
} else if (!PacketInOrder(timestamp)) {
|
||||
return false;
|
||||
} else if (NewTimestampGroup(arrival_time_ms, timestamp)) {
|
||||
// First packet of a later frame, the previous frame sample is ready.
|
||||
if (prev_timestamp_group_.complete_time_ms >= 0) {
|
||||
*timestamp_delta =
|
||||
current_timestamp_group_.timestamp - prev_timestamp_group_.timestamp;
|
||||
*arrival_time_delta_ms = current_timestamp_group_.complete_time_ms -
|
||||
prev_timestamp_group_.complete_time_ms;
|
||||
// Check system time differences to see if we have an unproportional jump
|
||||
// in arrival time. In that case reset the inter-arrival computations.
|
||||
int64_t system_time_delta_ms =
|
||||
current_timestamp_group_.last_system_time_ms -
|
||||
prev_timestamp_group_.last_system_time_ms;
|
||||
if (*arrival_time_delta_ms - system_time_delta_ms >=
|
||||
kArrivalTimeOffsetThresholdMs) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "The arrival time clock offset has changed (diff = "
|
||||
<< *arrival_time_delta_ms - system_time_delta_ms
|
||||
<< " ms), resetting.";
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
if (*arrival_time_delta_ms < 0) {
|
||||
// The group of packets has been reordered since receiving its local
|
||||
// arrival timestamp.
|
||||
++num_consecutive_reordered_packets_;
|
||||
if (num_consecutive_reordered_packets_ >= kReorderedResetThreshold) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Packets are being reordered on the path from the "
|
||||
"socket to the bandwidth estimator. Ignoring this "
|
||||
"packet for bandwidth estimation, resetting.";
|
||||
Reset();
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
num_consecutive_reordered_packets_ = 0;
|
||||
}
|
||||
RTC_DCHECK_GE(*arrival_time_delta_ms, 0);
|
||||
*packet_size_delta = static_cast<int>(current_timestamp_group_.size) -
|
||||
static_cast<int>(prev_timestamp_group_.size);
|
||||
calculated_deltas = true;
|
||||
}
|
||||
prev_timestamp_group_ = current_timestamp_group_;
|
||||
// The new timestamp is now the current frame.
|
||||
current_timestamp_group_.first_timestamp = timestamp;
|
||||
current_timestamp_group_.timestamp = timestamp;
|
||||
current_timestamp_group_.first_arrival_ms = arrival_time_ms;
|
||||
current_timestamp_group_.size = 0;
|
||||
} else {
|
||||
current_timestamp_group_.timestamp =
|
||||
LatestTimestamp(current_timestamp_group_.timestamp, timestamp);
|
||||
}
|
||||
// Accumulate the frame size.
|
||||
current_timestamp_group_.size += packet_size;
|
||||
current_timestamp_group_.complete_time_ms = arrival_time_ms;
|
||||
current_timestamp_group_.last_system_time_ms = system_time_ms;
|
||||
|
||||
return calculated_deltas;
|
||||
}
|
||||
|
||||
bool InterArrival::PacketInOrder(uint32_t timestamp) {
|
||||
if (current_timestamp_group_.IsFirstPacket()) {
|
||||
return true;
|
||||
} else {
|
||||
// Assume that a diff which is bigger than half the timestamp interval
|
||||
// (32 bits) must be due to reordering. This code is almost identical to
|
||||
// that in IsNewerTimestamp() in module_common_types.h.
|
||||
uint32_t timestamp_diff =
|
||||
timestamp - current_timestamp_group_.first_timestamp;
|
||||
return timestamp_diff < 0x80000000;
|
||||
}
|
||||
}
|
||||
|
||||
// Assumes that `timestamp` is not reordered compared to
|
||||
// `current_timestamp_group_`.
|
||||
bool InterArrival::NewTimestampGroup(int64_t arrival_time_ms,
|
||||
uint32_t timestamp) const {
|
||||
if (current_timestamp_group_.IsFirstPacket()) {
|
||||
return false;
|
||||
} else if (BelongsToBurst(arrival_time_ms, timestamp)) {
|
||||
return false;
|
||||
} else {
|
||||
uint32_t timestamp_diff =
|
||||
timestamp - current_timestamp_group_.first_timestamp;
|
||||
return timestamp_diff > kTimestampGroupLengthTicks;
|
||||
}
|
||||
}
|
||||
|
||||
bool InterArrival::BelongsToBurst(int64_t arrival_time_ms,
|
||||
uint32_t timestamp) const {
|
||||
RTC_DCHECK_GE(current_timestamp_group_.complete_time_ms, 0);
|
||||
int64_t arrival_time_delta_ms =
|
||||
arrival_time_ms - current_timestamp_group_.complete_time_ms;
|
||||
uint32_t timestamp_diff = timestamp - current_timestamp_group_.timestamp;
|
||||
int64_t ts_delta_ms = timestamp_to_ms_coeff_ * timestamp_diff + 0.5;
|
||||
if (ts_delta_ms == 0)
|
||||
return true;
|
||||
int propagation_delta_ms = arrival_time_delta_ms - ts_delta_ms;
|
||||
if (propagation_delta_ms < 0 &&
|
||||
arrival_time_delta_ms <= kBurstDeltaThresholdMs &&
|
||||
arrival_time_ms - current_timestamp_group_.first_arrival_ms <
|
||||
kMaxBurstDurationMs)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void InterArrival::Reset() {
|
||||
num_consecutive_reordered_packets_ = 0;
|
||||
current_timestamp_group_ = TimestampGroup();
|
||||
prev_timestamp_group_ = TimestampGroup();
|
||||
}
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Helper class to compute the inter-arrival time delta and the size delta
|
||||
// between two timestamp groups. A timestamp is a 32 bit unsigned number with
|
||||
// a client defined rate.
|
||||
class InterArrival {
|
||||
public:
|
||||
// After this many packet groups received out of order InterArrival will
|
||||
// reset, assuming that clocks have made a jump.
|
||||
static constexpr int kReorderedResetThreshold = 3;
|
||||
static constexpr int64_t kArrivalTimeOffsetThresholdMs = 3000;
|
||||
|
||||
// A timestamp group is defined as all packets with a timestamp which are at
|
||||
// most timestamp_group_length_ticks older than the first timestamp in that
|
||||
// group.
|
||||
InterArrival(uint32_t timestamp_group_length_ticks,
|
||||
double timestamp_to_ms_coeff);
|
||||
|
||||
InterArrival() = delete;
|
||||
InterArrival(const InterArrival&) = delete;
|
||||
InterArrival& operator=(const InterArrival&) = delete;
|
||||
|
||||
// This function returns true if a delta was computed, or false if the current
|
||||
// group is still incomplete or if only one group has been completed.
|
||||
// `timestamp` is the timestamp.
|
||||
// `arrival_time_ms` is the local time at which the packet arrived.
|
||||
// `packet_size` is the size of the packet.
|
||||
// `timestamp_delta` (output) is the computed timestamp delta.
|
||||
// `arrival_time_delta_ms` (output) is the computed arrival-time delta.
|
||||
// `packet_size_delta` (output) is the computed size delta.
|
||||
bool ComputeDeltas(uint32_t timestamp,
|
||||
int64_t arrival_time_ms,
|
||||
int64_t system_time_ms,
|
||||
size_t packet_size,
|
||||
uint32_t* timestamp_delta,
|
||||
int64_t* arrival_time_delta_ms,
|
||||
int* packet_size_delta);
|
||||
|
||||
private:
|
||||
struct TimestampGroup {
|
||||
TimestampGroup()
|
||||
: size(0),
|
||||
first_timestamp(0),
|
||||
timestamp(0),
|
||||
first_arrival_ms(-1),
|
||||
complete_time_ms(-1) {}
|
||||
|
||||
bool IsFirstPacket() const { return complete_time_ms == -1; }
|
||||
|
||||
size_t size;
|
||||
uint32_t first_timestamp;
|
||||
uint32_t timestamp;
|
||||
int64_t first_arrival_ms;
|
||||
int64_t complete_time_ms;
|
||||
int64_t last_system_time_ms;
|
||||
};
|
||||
|
||||
// Returns true if the packet with timestamp `timestamp` arrived in order.
|
||||
bool PacketInOrder(uint32_t timestamp);
|
||||
|
||||
// Returns true if the last packet was the end of the current batch and the
|
||||
// packet with `timestamp` is the first of a new batch.
|
||||
bool NewTimestampGroup(int64_t arrival_time_ms, uint32_t timestamp) const;
|
||||
|
||||
bool BelongsToBurst(int64_t arrival_time_ms, uint32_t timestamp) const;
|
||||
|
||||
void Reset();
|
||||
|
||||
const uint32_t kTimestampGroupLengthTicks;
|
||||
TimestampGroup current_timestamp_group_;
|
||||
TimestampGroup prev_timestamp_group_;
|
||||
double timestamp_to_ms_coeff_;
|
||||
int num_consecutive_reordered_packets_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/remote_bitrate_estimator/overuse_detector.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#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 {
|
||||
|
||||
constexpr double kMaxAdaptOffsetMs = 15.0;
|
||||
constexpr double kOverUsingTimeThreshold = 10;
|
||||
constexpr int kMaxNumDeltas = 60;
|
||||
constexpr double kUp = 0.0087;
|
||||
constexpr double kDown = 0.039;
|
||||
|
||||
} // namespace
|
||||
|
||||
OveruseDetector::OveruseDetector() = default;
|
||||
|
||||
BandwidthUsage OveruseDetector::State() const {
|
||||
return hypothesis_;
|
||||
}
|
||||
|
||||
BandwidthUsage OveruseDetector::Detect(double offset,
|
||||
double ts_delta,
|
||||
int num_of_deltas,
|
||||
int64_t now_ms) {
|
||||
if (num_of_deltas < 2) {
|
||||
return BandwidthUsage::kBwNormal;
|
||||
}
|
||||
const double T = std::min(num_of_deltas, kMaxNumDeltas) * 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_ > kOverUsingTimeThreshold && overuse_counter_ > 1) {
|
||||
if (offset >= prev_offset_) {
|
||||
time_over_using_ = 0;
|
||||
overuse_counter_ = 0;
|
||||
hypothesis_ = BandwidthUsage::kBwOverusing;
|
||||
}
|
||||
}
|
||||
} else if (T < -threshold_) {
|
||||
time_over_using_ = -1;
|
||||
overuse_counter_ = 0;
|
||||
hypothesis_ = BandwidthUsage::kBwUnderusing;
|
||||
} else {
|
||||
time_over_using_ = -1;
|
||||
overuse_counter_ = 0;
|
||||
hypothesis_ = BandwidthUsage::kBwNormal;
|
||||
}
|
||||
prev_offset_ = offset;
|
||||
|
||||
UpdateThreshold(T, now_ms);
|
||||
|
||||
return hypothesis_;
|
||||
}
|
||||
|
||||
void OveruseDetector::UpdateThreshold(double modified_offset, int64_t now_ms) {
|
||||
if (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_ ? kDown : kUp;
|
||||
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
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "api/network_state_predictor.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class OveruseDetector {
|
||||
public:
|
||||
OveruseDetector();
|
||||
|
||||
OveruseDetector(const OveruseDetector&) = delete;
|
||||
OveruseDetector& operator=(const OveruseDetector&) = delete;
|
||||
|
||||
~OveruseDetector() = default;
|
||||
|
||||
// Update the detection state based on the estimated inter-arrival time delta
|
||||
// offset. `timestamp_delta` is the delta between the last timestamp which the
|
||||
// estimated offset is based on and the last timestamp on which the last
|
||||
// offset was based on, representing the time between detector updates.
|
||||
// `num_of_deltas` is the number of deltas the offset estimate is based on.
|
||||
// Returns the state after the detection update.
|
||||
BandwidthUsage Detect(double offset,
|
||||
double timestamp_delta,
|
||||
int num_of_deltas,
|
||||
int64_t now_ms);
|
||||
|
||||
// Returns the current detector state.
|
||||
BandwidthUsage State() const;
|
||||
|
||||
private:
|
||||
void UpdateThreshold(double modified_offset, int64_t now_ms);
|
||||
|
||||
double threshold_ = 12.5;
|
||||
int64_t last_update_ms_ = -1;
|
||||
double prev_offset_ = 0.0;
|
||||
double time_over_using_ = -1;
|
||||
int overuse_counter_ = 0;
|
||||
BandwidthUsage hypothesis_ = BandwidthUsage::kBwNormal;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/remote_bitrate_estimator/overuse_estimator.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "api/network_state_predictor.h"
|
||||
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
constexpr int kMinFramePeriodHistoryLength = 60;
|
||||
constexpr int kDeltaCounterMax = 1000;
|
||||
|
||||
} // namespace
|
||||
|
||||
OveruseEstimator::OveruseEstimator() = default;
|
||||
|
||||
void OveruseEstimator::Update(int64_t t_delta,
|
||||
double ts_delta,
|
||||
int size_delta,
|
||||
BandwidthUsage current_hypothesis,
|
||||
int64_t now_ms) {
|
||||
const double min_frame_period = UpdateMinFramePeriod(ts_delta);
|
||||
const double t_ts_delta = t_delta - ts_delta;
|
||||
BWE_TEST_LOGGING_PLOT(1, "dm_ms", now_ms, t_ts_delta);
|
||||
double fs_delta = size_delta;
|
||||
|
||||
++num_of_deltas_;
|
||||
if (num_of_deltas_ > kDeltaCounterMax) {
|
||||
num_of_deltas_ = kDeltaCounterMax;
|
||||
}
|
||||
|
||||
// Update the Kalman filter.
|
||||
E_[0][0] += process_noise_[0];
|
||||
E_[1][1] += process_noise_[1];
|
||||
|
||||
if ((current_hypothesis == BandwidthUsage::kBwOverusing &&
|
||||
offset_ < prev_offset_) ||
|
||||
(current_hypothesis == BandwidthUsage::kBwUnderusing &&
|
||||
offset_ > prev_offset_)) {
|
||||
E_[1][1] += 10 * process_noise_[1];
|
||||
}
|
||||
|
||||
const double h[2] = {fs_delta, 1.0};
|
||||
const double Eh[2] = {E_[0][0] * h[0] + E_[0][1] * h[1],
|
||||
E_[1][0] * h[0] + E_[1][1] * h[1]};
|
||||
|
||||
BWE_TEST_LOGGING_PLOT(1, "d_ms", now_ms, slope_ * h[0] - offset_);
|
||||
|
||||
const double residual = t_ts_delta - slope_ * h[0] - offset_;
|
||||
|
||||
const bool in_stable_state =
|
||||
(current_hypothesis == BandwidthUsage::kBwNormal);
|
||||
const double max_residual = 3.0 * sqrt(var_noise_);
|
||||
// We try to filter out very late frames. For instance periodic key
|
||||
// frames doesn't fit the Gaussian model well.
|
||||
if (fabs(residual) < max_residual) {
|
||||
UpdateNoiseEstimate(residual, min_frame_period, in_stable_state);
|
||||
} else {
|
||||
UpdateNoiseEstimate(residual < 0 ? -max_residual : max_residual,
|
||||
min_frame_period, in_stable_state);
|
||||
}
|
||||
|
||||
const double denom = var_noise_ + h[0] * Eh[0] + h[1] * Eh[1];
|
||||
|
||||
const double K[2] = {Eh[0] / denom, Eh[1] / denom};
|
||||
|
||||
const double IKh[2][2] = {{1.0 - K[0] * h[0], -K[0] * h[1]},
|
||||
{-K[1] * h[0], 1.0 - K[1] * h[1]}};
|
||||
const double e00 = E_[0][0];
|
||||
const double e01 = E_[0][1];
|
||||
|
||||
// Update state.
|
||||
E_[0][0] = e00 * IKh[0][0] + E_[1][0] * IKh[0][1];
|
||||
E_[0][1] = e01 * IKh[0][0] + E_[1][1] * IKh[0][1];
|
||||
E_[1][0] = e00 * IKh[1][0] + E_[1][0] * IKh[1][1];
|
||||
E_[1][1] = e01 * IKh[1][0] + E_[1][1] * IKh[1][1];
|
||||
|
||||
// The covariance matrix must be positive semi-definite.
|
||||
bool positive_semi_definite =
|
||||
E_[0][0] + E_[1][1] >= 0 &&
|
||||
E_[0][0] * E_[1][1] - E_[0][1] * E_[1][0] >= 0 && E_[0][0] >= 0;
|
||||
RTC_DCHECK(positive_semi_definite);
|
||||
if (!positive_semi_definite) {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "The over-use estimator's covariance matrix is no longer "
|
||||
"semi-definite.";
|
||||
}
|
||||
|
||||
slope_ = slope_ + K[0] * residual;
|
||||
prev_offset_ = offset_;
|
||||
offset_ = offset_ + K[1] * residual;
|
||||
|
||||
BWE_TEST_LOGGING_PLOT(1, "kc", now_ms, K[0]);
|
||||
BWE_TEST_LOGGING_PLOT(1, "km", now_ms, K[1]);
|
||||
BWE_TEST_LOGGING_PLOT(1, "slope_1/bps", now_ms, slope_);
|
||||
BWE_TEST_LOGGING_PLOT(1, "var_noise", now_ms, var_noise_);
|
||||
}
|
||||
|
||||
double OveruseEstimator::UpdateMinFramePeriod(double ts_delta) {
|
||||
double min_frame_period = ts_delta;
|
||||
if (ts_delta_hist_.size() >= kMinFramePeriodHistoryLength) {
|
||||
ts_delta_hist_.pop_front();
|
||||
}
|
||||
for (const double old_ts_delta : ts_delta_hist_) {
|
||||
min_frame_period = std::min(old_ts_delta, min_frame_period);
|
||||
}
|
||||
ts_delta_hist_.push_back(ts_delta);
|
||||
return min_frame_period;
|
||||
}
|
||||
|
||||
void OveruseEstimator::UpdateNoiseEstimate(double residual,
|
||||
double ts_delta,
|
||||
bool stable_state) {
|
||||
if (!stable_state) {
|
||||
return;
|
||||
}
|
||||
// Faster filter during startup to faster adapt to the jitter level
|
||||
// of the network. `alpha` is tuned for 30 frames per second, but is scaled
|
||||
// according to `ts_delta`.
|
||||
double alpha = 0.01;
|
||||
if (num_of_deltas_ > 10 * 30) {
|
||||
alpha = 0.002;
|
||||
}
|
||||
// Only update the noise estimate if we're not over-using. `beta` is a
|
||||
// function of alpha and the time delta since the previous update.
|
||||
const double beta = pow(1 - alpha, ts_delta * 30.0 / 1000.0);
|
||||
avg_noise_ = beta * avg_noise_ + (1 - beta) * residual;
|
||||
var_noise_ = beta * var_noise_ +
|
||||
(1 - beta) * (avg_noise_ - residual) * (avg_noise_ - residual);
|
||||
if (var_noise_ < 1) {
|
||||
var_noise_ = 1;
|
||||
}
|
||||
}
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "api/network_state_predictor.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class OveruseEstimator {
|
||||
public:
|
||||
OveruseEstimator();
|
||||
|
||||
OveruseEstimator(const OveruseEstimator&) = delete;
|
||||
OveruseEstimator& operator=(const OveruseEstimator&) = delete;
|
||||
|
||||
~OveruseEstimator() = default;
|
||||
|
||||
// Update the estimator with a new sample. The deltas should represent deltas
|
||||
// between timestamp groups as defined by the InterArrival class.
|
||||
// `current_hypothesis` should be the hypothesis of the over-use detector at
|
||||
// this time.
|
||||
void Update(int64_t t_delta,
|
||||
double ts_delta,
|
||||
int size_delta,
|
||||
BandwidthUsage current_hypothesis,
|
||||
int64_t now_ms);
|
||||
|
||||
// Returns the estimated noise/jitter variance in ms^2.
|
||||
double var_noise() const { return var_noise_; }
|
||||
|
||||
// Returns the estimated inter-arrival time delta offset in ms.
|
||||
double offset() const { return offset_; }
|
||||
|
||||
// Returns the number of deltas which the current over-use estimator state is
|
||||
// based on.
|
||||
int num_of_deltas() const { return num_of_deltas_; }
|
||||
|
||||
private:
|
||||
double UpdateMinFramePeriod(double ts_delta);
|
||||
void UpdateNoiseEstimate(double residual, double ts_delta, bool stable_state);
|
||||
|
||||
int num_of_deltas_ = 0;
|
||||
double slope_ = 8.0 / 512.0;
|
||||
double offset_ = 0;
|
||||
double prev_offset_ = 0;
|
||||
double E_[2][2] = {{100.0, 0.0}, {0.0, 1e-1}};
|
||||
double process_noise_[2] = {1e-13, 1e-3};
|
||||
double avg_noise_ = 0.0;
|
||||
double var_noise_ = 50.0;
|
||||
std::deque<double> ts_delta_hist_;
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can 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/remote_bitrate_estimator/packet_arrival_map.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
void PacketArrivalTimeMap::AddPacket(int64_t sequence_number,
|
||||
Timestamp arrival_time) {
|
||||
RTC_DCHECK_GE(arrival_time, Timestamp::Zero());
|
||||
if (!has_seen_packet()) {
|
||||
// First packet.
|
||||
Reallocate(kMinCapacity);
|
||||
begin_sequence_number_ = sequence_number;
|
||||
end_sequence_number_ = sequence_number + 1;
|
||||
arrival_times_[Index(sequence_number)] = arrival_time;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sequence_number >= begin_sequence_number() &&
|
||||
sequence_number < end_sequence_number()) {
|
||||
// The packet is within the buffer - no need to expand it.
|
||||
arrival_times_[Index(sequence_number)] = arrival_time;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sequence_number < begin_sequence_number()) {
|
||||
// The packet goes before the current buffer. Expand to add packet, but only
|
||||
// if it fits within kMaxNumberOfPackets.
|
||||
int64_t new_size = end_sequence_number() - sequence_number;
|
||||
if (new_size > kMaxNumberOfPackets) {
|
||||
// Don't expand the buffer further, as that would remove newly received
|
||||
// packets.
|
||||
return;
|
||||
}
|
||||
AdjustToSize(new_size);
|
||||
|
||||
arrival_times_[Index(sequence_number)] = arrival_time;
|
||||
SetNotReceived(sequence_number + 1, begin_sequence_number_);
|
||||
begin_sequence_number_ = sequence_number;
|
||||
return;
|
||||
}
|
||||
|
||||
// The packet goes after the buffer.
|
||||
RTC_DCHECK_GE(sequence_number, end_sequence_number_);
|
||||
int64_t new_end_sequence_number = sequence_number + 1;
|
||||
|
||||
if (new_end_sequence_number >= end_sequence_number_ + kMaxNumberOfPackets) {
|
||||
// All old packets have to be removed.
|
||||
begin_sequence_number_ = sequence_number;
|
||||
end_sequence_number_ = new_end_sequence_number;
|
||||
arrival_times_[Index(sequence_number)] = arrival_time;
|
||||
return;
|
||||
}
|
||||
|
||||
if (begin_sequence_number_ < new_end_sequence_number - kMaxNumberOfPackets) {
|
||||
// Remove oldest entries
|
||||
begin_sequence_number_ = new_end_sequence_number - kMaxNumberOfPackets;
|
||||
RTC_DCHECK_GT(end_sequence_number_, begin_sequence_number_);
|
||||
}
|
||||
|
||||
AdjustToSize(new_end_sequence_number - begin_sequence_number_);
|
||||
|
||||
// Packets can be received out-of-order. If this isn't the next expected
|
||||
// packet, add enough placeholders to fill the gap.
|
||||
SetNotReceived(end_sequence_number_, sequence_number);
|
||||
end_sequence_number_ = new_end_sequence_number;
|
||||
arrival_times_[Index(sequence_number)] = arrival_time;
|
||||
}
|
||||
|
||||
void PacketArrivalTimeMap::SetNotReceived(
|
||||
int64_t begin_sequence_number_inclusive,
|
||||
int64_t end_sequence_number_exclusive) {
|
||||
static constexpr Timestamp value = Timestamp::MinusInfinity();
|
||||
|
||||
int begin_index = Index(begin_sequence_number_inclusive);
|
||||
int end_index = Index(end_sequence_number_exclusive);
|
||||
|
||||
if (begin_index <= end_index) {
|
||||
// Entries to clear are in single block:
|
||||
// [......{-----}....]
|
||||
std::fill(arrival_times_.get() + begin_index,
|
||||
arrival_times_.get() + end_index, value);
|
||||
} else {
|
||||
// Entries to clear span across arrival_times_ border:
|
||||
// [--}..........{---]
|
||||
std::fill(arrival_times_.get() + begin_index,
|
||||
arrival_times_.get() + capacity(), value);
|
||||
std::fill(arrival_times_.get(), arrival_times_.get() + end_index, value);
|
||||
}
|
||||
}
|
||||
|
||||
void PacketArrivalTimeMap::RemoveOldPackets(int64_t sequence_number,
|
||||
Timestamp arrival_time_limit) {
|
||||
int64_t check_to = std::min(sequence_number, end_sequence_number_);
|
||||
while (begin_sequence_number_ < check_to &&
|
||||
arrival_times_[Index(begin_sequence_number_)] <= arrival_time_limit) {
|
||||
++begin_sequence_number_;
|
||||
}
|
||||
AdjustToSize(end_sequence_number_ - begin_sequence_number_);
|
||||
}
|
||||
|
||||
void PacketArrivalTimeMap::EraseTo(int64_t sequence_number) {
|
||||
if (sequence_number < begin_sequence_number_) {
|
||||
return;
|
||||
}
|
||||
if (sequence_number >= end_sequence_number_) {
|
||||
// Erase all.
|
||||
begin_sequence_number_ = end_sequence_number_;
|
||||
return;
|
||||
}
|
||||
// Remove some.
|
||||
begin_sequence_number_ = sequence_number;
|
||||
AdjustToSize(end_sequence_number_ - begin_sequence_number_);
|
||||
}
|
||||
|
||||
void PacketArrivalTimeMap::AdjustToSize(int new_size) {
|
||||
if (new_size > capacity()) {
|
||||
int new_capacity = capacity();
|
||||
while (new_capacity < new_size)
|
||||
new_capacity *= 2;
|
||||
Reallocate(new_capacity);
|
||||
}
|
||||
if (capacity() > std::max(kMinCapacity, 4 * new_size)) {
|
||||
int new_capacity = capacity();
|
||||
while (new_capacity > 2 * std::max(new_size, kMinCapacity)) {
|
||||
new_capacity /= 2;
|
||||
}
|
||||
Reallocate(new_capacity);
|
||||
}
|
||||
RTC_DCHECK_LE(new_size, capacity());
|
||||
}
|
||||
|
||||
void PacketArrivalTimeMap::Reallocate(int new_capacity) {
|
||||
int new_capacity_minus_1 = new_capacity - 1;
|
||||
// Check capacity is a power of 2.
|
||||
RTC_DCHECK_EQ(new_capacity & new_capacity_minus_1, 0);
|
||||
// Create uninitialized memory.
|
||||
// All valid entries should be set by `AddPacket` before use.
|
||||
void* raw = operator new[](new_capacity * sizeof(Timestamp));
|
||||
Timestamp* new_buffer = static_cast<Timestamp*>(raw);
|
||||
|
||||
for (int64_t sequence_number = begin_sequence_number_;
|
||||
sequence_number < end_sequence_number_; ++sequence_number) {
|
||||
new_buffer[sequence_number & new_capacity_minus_1] =
|
||||
arrival_times_[sequence_number & capacity_minus_1_];
|
||||
}
|
||||
arrival_times_.reset(new_buffer);
|
||||
capacity_minus_1_ = new_capacity_minus_1;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can 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_REMOTE_BITRATE_ESTIMATOR_PACKET_ARRIVAL_MAP_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_PACKET_ARRIVAL_MAP_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "api/units/timestamp.h"
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// PacketArrivalTimeMap is an optimized map of packet sequence number to arrival
|
||||
// time, limited in size to never exceed `kMaxNumberOfPackets`. It will grow as
|
||||
// needed, and remove old packets, and will expand to allow earlier packets to
|
||||
// be added (out-of-order).
|
||||
//
|
||||
// Not yet received packets have the arrival time zero. The queue will not span
|
||||
// larger than necessary and the last packet should always be received. The
|
||||
// first packet in the queue doesn't have to be received in case of receiving
|
||||
// packets out-of-order.
|
||||
class PacketArrivalTimeMap {
|
||||
public:
|
||||
struct PacketArrivalTime {
|
||||
Timestamp arrival_time;
|
||||
int64_t sequence_number;
|
||||
};
|
||||
// Impossible to request feedback older than what can be represented by 15
|
||||
// bits.
|
||||
static constexpr int kMaxNumberOfPackets = (1 << 15);
|
||||
|
||||
PacketArrivalTimeMap() = default;
|
||||
PacketArrivalTimeMap(const PacketArrivalTimeMap&) = delete;
|
||||
PacketArrivalTimeMap& operator=(const PacketArrivalTimeMap&) = delete;
|
||||
~PacketArrivalTimeMap() = default;
|
||||
|
||||
// Indicates if the packet with `sequence_number` has already been received.
|
||||
bool has_received(int64_t sequence_number) const {
|
||||
return sequence_number >= begin_sequence_number() &&
|
||||
sequence_number < end_sequence_number() &&
|
||||
arrival_times_[Index(sequence_number)] >= Timestamp::Zero();
|
||||
}
|
||||
|
||||
// Returns the sequence number of the first entry in the map, i.e. the
|
||||
// sequence number that a `begin()` iterator would represent.
|
||||
int64_t begin_sequence_number() const { return begin_sequence_number_; }
|
||||
|
||||
// Returns the sequence number of the element just after the map, i.e. the
|
||||
// sequence number that an `end()` iterator would represent.
|
||||
int64_t end_sequence_number() const { return end_sequence_number_; }
|
||||
|
||||
// Returns an element by `sequence_number`, which must be valid, i.e.
|
||||
// between [begin_sequence_number, end_sequence_number).
|
||||
Timestamp get(int64_t sequence_number) {
|
||||
RTC_DCHECK_GE(sequence_number, begin_sequence_number());
|
||||
RTC_DCHECK_LT(sequence_number, end_sequence_number());
|
||||
return arrival_times_[Index(sequence_number)];
|
||||
}
|
||||
|
||||
// Returns timestamp and sequence number of the received packet with sequence
|
||||
// number equal or larger than `sequence_number`. `sequence_number` must be in
|
||||
// range [begin_sequence_number, end_sequence_number).
|
||||
PacketArrivalTime FindNextAtOrAfter(int64_t sequence_number) const {
|
||||
RTC_DCHECK_GE(sequence_number, begin_sequence_number());
|
||||
RTC_DCHECK_LT(sequence_number, end_sequence_number());
|
||||
while (true) {
|
||||
Timestamp t = arrival_times_[Index(sequence_number)];
|
||||
if (t >= Timestamp::Zero()) {
|
||||
return {.arrival_time = t, .sequence_number = sequence_number};
|
||||
}
|
||||
++sequence_number;
|
||||
}
|
||||
}
|
||||
|
||||
// Clamps `sequence_number` between [begin_sequence_number,
|
||||
// end_sequence_number].
|
||||
int64_t clamp(int64_t sequence_number) const {
|
||||
return std::clamp(sequence_number, begin_sequence_number(),
|
||||
end_sequence_number());
|
||||
}
|
||||
|
||||
// Erases all elements from the beginning of the map until `sequence_number`.
|
||||
void EraseTo(int64_t sequence_number);
|
||||
|
||||
// Records the fact that a packet with `sequence_number` arrived at
|
||||
// `arrival_time_ms`.
|
||||
void AddPacket(int64_t sequence_number, Timestamp arrival_time);
|
||||
|
||||
// Removes packets from the beginning of the map as long as they are received
|
||||
// before `sequence_number` and with an age older than `arrival_time_limit`
|
||||
void RemoveOldPackets(int64_t sequence_number, Timestamp arrival_time_limit);
|
||||
|
||||
private:
|
||||
static constexpr int kMinCapacity = 128;
|
||||
|
||||
// Returns index in the `arrival_times_` for value for `sequence_number`.
|
||||
int Index(int64_t sequence_number) const {
|
||||
// Note that sequence_number might be negative, thus taking '%' requires
|
||||
// extra handling and can be slow. Because capacity is a power of two, it
|
||||
// is much faster to use '&' operator.
|
||||
return sequence_number & capacity_minus_1_;
|
||||
}
|
||||
|
||||
void SetNotReceived(int64_t begin_sequence_number_inclusive,
|
||||
int64_t end_sequence_number_exclusive);
|
||||
|
||||
// Adjust capacity to match new_size, may reduce capacity.
|
||||
// On return guarantees capacity >= new_size.
|
||||
void AdjustToSize(int new_size);
|
||||
void Reallocate(int new_capacity);
|
||||
|
||||
int capacity() const { return capacity_minus_1_ + 1; }
|
||||
bool has_seen_packet() const { return arrival_times_ != nullptr; }
|
||||
|
||||
// Circular buffer. Packet with sequence number `sequence_number`
|
||||
// is stored in the slot `sequence_number % capacity_`
|
||||
std::unique_ptr<Timestamp[]> arrival_times_ = nullptr;
|
||||
|
||||
// Allocated size of the `arrival_times_`
|
||||
// capacity_ is a power of 2 in range [kMinCapacity, kMaxNumberOfPackets]
|
||||
// `capacity - 1` is used much more often than `capacity`, thus that value is
|
||||
// stored.
|
||||
int capacity_minus_1_ = -1;
|
||||
|
||||
// The unwrapped sequence number for valid range of sequence numbers.
|
||||
// arrival_times_ entries only valid for sequence numbers in range
|
||||
// `begin_sequence_number_ <= sequence_number < end_sequence_number_`
|
||||
int64_t begin_sequence_number_ = 0;
|
||||
int64_t end_sequence_number_ = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_PACKET_ARRIVAL_MAP_H_
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "api/transport/field_trial_based_config.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
constexpr TimeDelta kMinClusterDelta = TimeDelta::Millis(1);
|
||||
constexpr TimeDelta kInitialProbingInterval = TimeDelta::Seconds(2);
|
||||
constexpr int kTimestampGroupLengthMs = 5;
|
||||
constexpr int kAbsSendTimeInterArrivalUpshift = 8;
|
||||
constexpr int kInterArrivalShift =
|
||||
RTPHeaderExtension::kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift;
|
||||
constexpr int kMinClusterSize = 4;
|
||||
constexpr int kMaxProbePackets = 15;
|
||||
constexpr int kExpectedNumberOfProbes = 3;
|
||||
constexpr double kTimestampToMs =
|
||||
1000.0 / static_cast<double>(1 << kInterArrivalShift);
|
||||
|
||||
template <typename K, typename V>
|
||||
std::vector<K> Keys(const std::map<K, V>& map) {
|
||||
std::vector<K> keys;
|
||||
keys.reserve(map.size());
|
||||
for (const auto& kv_pair : map) {
|
||||
keys.push_back(kv_pair.first);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RemoteBitrateEstimatorAbsSendTime::~RemoteBitrateEstimatorAbsSendTime() =
|
||||
default;
|
||||
|
||||
bool RemoteBitrateEstimatorAbsSendTime::IsWithinClusterBounds(
|
||||
TimeDelta send_delta,
|
||||
const Cluster& cluster_aggregate) {
|
||||
if (cluster_aggregate.count == 0)
|
||||
return true;
|
||||
TimeDelta cluster_mean =
|
||||
cluster_aggregate.send_mean / cluster_aggregate.count;
|
||||
return (send_delta - cluster_mean).Abs() < TimeDelta::Micros(2'500);
|
||||
}
|
||||
|
||||
void RemoteBitrateEstimatorAbsSendTime::MaybeAddCluster(
|
||||
const Cluster& cluster_aggregate,
|
||||
std::list<Cluster>& clusters) {
|
||||
if (cluster_aggregate.count < kMinClusterSize ||
|
||||
cluster_aggregate.send_mean <= TimeDelta::Zero() ||
|
||||
cluster_aggregate.recv_mean <= TimeDelta::Zero()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Cluster cluster;
|
||||
cluster.send_mean = cluster_aggregate.send_mean / cluster_aggregate.count;
|
||||
cluster.recv_mean = cluster_aggregate.recv_mean / cluster_aggregate.count;
|
||||
cluster.mean_size = cluster_aggregate.mean_size / cluster_aggregate.count;
|
||||
cluster.count = cluster_aggregate.count;
|
||||
cluster.num_above_min_delta = cluster_aggregate.num_above_min_delta;
|
||||
clusters.push_back(cluster);
|
||||
}
|
||||
|
||||
RemoteBitrateEstimatorAbsSendTime::RemoteBitrateEstimatorAbsSendTime(
|
||||
RemoteBitrateObserver* observer,
|
||||
Clock* clock)
|
||||
: clock_(clock), observer_(observer), remote_rate_(field_trials_) {
|
||||
RTC_DCHECK(clock_);
|
||||
RTC_DCHECK(observer_);
|
||||
RTC_LOG(LS_INFO) << "RemoteBitrateEstimatorAbsSendTime: Instantiating.";
|
||||
}
|
||||
|
||||
std::list<RemoteBitrateEstimatorAbsSendTime::Cluster>
|
||||
RemoteBitrateEstimatorAbsSendTime::ComputeClusters() const {
|
||||
std::list<Cluster> clusters;
|
||||
Cluster cluster_aggregate;
|
||||
Timestamp prev_send_time = Timestamp::MinusInfinity();
|
||||
Timestamp prev_recv_time = Timestamp::MinusInfinity();
|
||||
for (const Probe& probe : probes_) {
|
||||
if (prev_send_time.IsFinite()) {
|
||||
TimeDelta send_delta = probe.send_time - prev_send_time;
|
||||
TimeDelta recv_delta = probe.recv_time - prev_recv_time;
|
||||
if (send_delta >= kMinClusterDelta && recv_delta >= kMinClusterDelta) {
|
||||
++cluster_aggregate.num_above_min_delta;
|
||||
}
|
||||
if (!IsWithinClusterBounds(send_delta, cluster_aggregate)) {
|
||||
MaybeAddCluster(cluster_aggregate, clusters);
|
||||
cluster_aggregate = Cluster();
|
||||
}
|
||||
cluster_aggregate.send_mean += send_delta;
|
||||
cluster_aggregate.recv_mean += recv_delta;
|
||||
cluster_aggregate.mean_size += probe.payload_size;
|
||||
++cluster_aggregate.count;
|
||||
}
|
||||
prev_send_time = probe.send_time;
|
||||
prev_recv_time = probe.recv_time;
|
||||
}
|
||||
MaybeAddCluster(cluster_aggregate, clusters);
|
||||
return clusters;
|
||||
}
|
||||
|
||||
const RemoteBitrateEstimatorAbsSendTime::Cluster*
|
||||
RemoteBitrateEstimatorAbsSendTime::FindBestProbe(
|
||||
const std::list<Cluster>& clusters) const {
|
||||
DataRate highest_probe_bitrate = DataRate::Zero();
|
||||
const Cluster* best = nullptr;
|
||||
for (const auto& cluster : clusters) {
|
||||
if (cluster.send_mean == TimeDelta::Zero() ||
|
||||
cluster.recv_mean == TimeDelta::Zero()) {
|
||||
continue;
|
||||
}
|
||||
if (cluster.num_above_min_delta > cluster.count / 2 &&
|
||||
(cluster.recv_mean - cluster.send_mean <= TimeDelta::Millis(2) &&
|
||||
cluster.send_mean - cluster.recv_mean <= TimeDelta::Millis(5))) {
|
||||
DataRate probe_bitrate =
|
||||
std::min(cluster.SendBitrate(), cluster.RecvBitrate());
|
||||
if (probe_bitrate > highest_probe_bitrate) {
|
||||
highest_probe_bitrate = probe_bitrate;
|
||||
best = &cluster;
|
||||
}
|
||||
} else {
|
||||
RTC_LOG(LS_INFO) << "Probe failed, sent at "
|
||||
<< cluster.SendBitrate().bps() << " bps, received at "
|
||||
<< cluster.RecvBitrate().bps()
|
||||
<< " bps. Mean send delta: " << cluster.send_mean.ms()
|
||||
<< " ms, mean recv delta: " << cluster.recv_mean.ms()
|
||||
<< " ms, num probes: " << cluster.count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
RemoteBitrateEstimatorAbsSendTime::ProbeResult
|
||||
RemoteBitrateEstimatorAbsSendTime::ProcessClusters(Timestamp now) {
|
||||
std::list<Cluster> clusters = ComputeClusters();
|
||||
if (clusters.empty()) {
|
||||
// If we reach the max number of probe packets and still have no clusters,
|
||||
// we will remove the oldest one.
|
||||
if (probes_.size() >= kMaxProbePackets)
|
||||
probes_.pop_front();
|
||||
return ProbeResult::kNoUpdate;
|
||||
}
|
||||
|
||||
if (const Cluster* best = FindBestProbe(clusters)) {
|
||||
DataRate probe_bitrate = std::min(best->SendBitrate(), best->RecvBitrate());
|
||||
// Make sure that a probe sent on a lower bitrate than our estimate can't
|
||||
// reduce the estimate.
|
||||
if (IsBitrateImproving(probe_bitrate)) {
|
||||
RTC_LOG(LS_INFO) << "Probe successful, sent at "
|
||||
<< best->SendBitrate().bps() << " bps, received at "
|
||||
<< best->RecvBitrate().bps()
|
||||
<< " bps. Mean send delta: " << best->send_mean.ms()
|
||||
<< " ms, mean recv delta: " << best->recv_mean.ms()
|
||||
<< " ms, num probes: " << best->count;
|
||||
remote_rate_.SetEstimate(probe_bitrate, now);
|
||||
return ProbeResult::kBitrateUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
// Not probing and received non-probe packet, or finished with current set
|
||||
// of probes.
|
||||
if (clusters.size() >= kExpectedNumberOfProbes)
|
||||
probes_.clear();
|
||||
return ProbeResult::kNoUpdate;
|
||||
}
|
||||
|
||||
bool RemoteBitrateEstimatorAbsSendTime::IsBitrateImproving(
|
||||
DataRate probe_bitrate) const {
|
||||
bool initial_probe =
|
||||
!remote_rate_.ValidEstimate() && probe_bitrate > DataRate::Zero();
|
||||
bool bitrate_above_estimate = remote_rate_.ValidEstimate() &&
|
||||
probe_bitrate > remote_rate_.LatestEstimate();
|
||||
return initial_probe || bitrate_above_estimate;
|
||||
}
|
||||
|
||||
void RemoteBitrateEstimatorAbsSendTime::IncomingPacket(
|
||||
const RtpPacketReceived& rtp_packet) {
|
||||
uint32_t send_time_24bits;
|
||||
if (!rtp_packet.GetExtension<AbsoluteSendTime>(&send_time_24bits)) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "RemoteBitrateEstimatorAbsSendTimeImpl: Incoming packet "
|
||||
"is missing absolute send time extension!";
|
||||
return;
|
||||
}
|
||||
|
||||
Timestamp arrival_time = rtp_packet.arrival_time();
|
||||
DataSize payload_size =
|
||||
DataSize::Bytes(rtp_packet.payload_size() + rtp_packet.padding_size());
|
||||
|
||||
if (!uma_recorded_) {
|
||||
RTC_HISTOGRAM_ENUMERATION(kBweTypeHistogram, BweNames::kReceiverAbsSendTime,
|
||||
BweNames::kBweNamesMax);
|
||||
uma_recorded_ = true;
|
||||
}
|
||||
// Shift up send time to use the full 32 bits that inter_arrival works with,
|
||||
// so wrapping works properly.
|
||||
uint32_t timestamp = send_time_24bits << kAbsSendTimeInterArrivalUpshift;
|
||||
Timestamp send_time =
|
||||
Timestamp::Millis(static_cast<int64_t>(timestamp) * kTimestampToMs);
|
||||
|
||||
Timestamp now = clock_->CurrentTime();
|
||||
// TODO(holmer): SSRCs are only needed for REMB, should be broken out from
|
||||
// here.
|
||||
|
||||
// Check if incoming bitrate estimate is valid, and if it needs to be reset.
|
||||
absl::optional<DataRate> incoming_bitrate =
|
||||
incoming_bitrate_.Rate(arrival_time);
|
||||
if (incoming_bitrate) {
|
||||
incoming_bitrate_initialized_ = true;
|
||||
} else if (incoming_bitrate_initialized_) {
|
||||
// Incoming bitrate had a previous valid value, but now not enough data
|
||||
// point are left within the current window. Reset incoming bitrate
|
||||
// estimator so that the window size will only contain new data points.
|
||||
incoming_bitrate_.Reset();
|
||||
incoming_bitrate_initialized_ = false;
|
||||
}
|
||||
incoming_bitrate_.Update(payload_size, arrival_time);
|
||||
|
||||
if (first_packet_time_.IsInfinite()) {
|
||||
first_packet_time_ = now;
|
||||
}
|
||||
|
||||
uint32_t ts_delta = 0;
|
||||
int64_t t_delta = 0;
|
||||
int size_delta = 0;
|
||||
bool update_estimate = false;
|
||||
DataRate target_bitrate = DataRate::Zero();
|
||||
|
||||
TimeoutStreams(now);
|
||||
RTC_DCHECK(inter_arrival_);
|
||||
RTC_DCHECK(estimator_);
|
||||
ssrcs_.insert_or_assign(rtp_packet.Ssrc(), now);
|
||||
|
||||
// For now only try to detect probes while we don't have a valid estimate.
|
||||
// We currently assume that only packets larger than 200 bytes are paced by
|
||||
// the sender.
|
||||
static constexpr DataSize kMinProbePacketSize = DataSize::Bytes(200);
|
||||
if (payload_size > kMinProbePacketSize &&
|
||||
(!remote_rate_.ValidEstimate() ||
|
||||
now - first_packet_time_ < kInitialProbingInterval)) {
|
||||
// TODO(holmer): Use a map instead to get correct order?
|
||||
if (total_probes_received_ < kMaxProbePackets) {
|
||||
TimeDelta send_delta = TimeDelta::Millis(-1);
|
||||
TimeDelta recv_delta = TimeDelta::Millis(-1);
|
||||
if (!probes_.empty()) {
|
||||
send_delta = send_time - probes_.back().send_time;
|
||||
recv_delta = arrival_time - probes_.back().recv_time;
|
||||
}
|
||||
RTC_LOG(LS_INFO) << "Probe packet received: send time=" << send_time.ms()
|
||||
<< " ms, recv time=" << arrival_time.ms()
|
||||
<< " ms, send delta=" << send_delta.ms()
|
||||
<< " ms, recv delta=" << recv_delta.ms() << " ms.";
|
||||
}
|
||||
probes_.emplace_back(send_time, arrival_time, payload_size);
|
||||
++total_probes_received_;
|
||||
// Make sure that a probe which updated the bitrate immediately has an
|
||||
// effect by calling the OnReceiveBitrateChanged callback.
|
||||
if (ProcessClusters(now) == ProbeResult::kBitrateUpdated)
|
||||
update_estimate = true;
|
||||
}
|
||||
if (inter_arrival_->ComputeDeltas(timestamp, arrival_time.ms(), now.ms(),
|
||||
payload_size.bytes(), &ts_delta, &t_delta,
|
||||
&size_delta)) {
|
||||
double ts_delta_ms = (1000.0 * ts_delta) / (1 << kInterArrivalShift);
|
||||
estimator_->Update(t_delta, ts_delta_ms, size_delta, detector_.State(),
|
||||
arrival_time.ms());
|
||||
detector_.Detect(estimator_->offset(), ts_delta_ms,
|
||||
estimator_->num_of_deltas(), arrival_time.ms());
|
||||
}
|
||||
|
||||
if (!update_estimate) {
|
||||
// Check if it's time for a periodic update or if we should update because
|
||||
// of an over-use.
|
||||
if (last_update_.IsInfinite() ||
|
||||
now.ms() - last_update_.ms() >
|
||||
remote_rate_.GetFeedbackInterval().ms()) {
|
||||
update_estimate = true;
|
||||
} else if (detector_.State() == BandwidthUsage::kBwOverusing) {
|
||||
absl::optional<DataRate> incoming_rate =
|
||||
incoming_bitrate_.Rate(arrival_time);
|
||||
if (incoming_rate.has_value() &&
|
||||
remote_rate_.TimeToReduceFurther(now, *incoming_rate)) {
|
||||
update_estimate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (update_estimate) {
|
||||
// The first overuse should immediately trigger a new estimate.
|
||||
// We also have to update the estimate immediately if we are overusing
|
||||
// and the target bitrate is too high compared to what we are receiving.
|
||||
const RateControlInput input(detector_.State(),
|
||||
incoming_bitrate_.Rate(arrival_time));
|
||||
target_bitrate = remote_rate_.Update(input, now);
|
||||
update_estimate = remote_rate_.ValidEstimate();
|
||||
}
|
||||
|
||||
if (update_estimate) {
|
||||
last_update_ = now;
|
||||
observer_->OnReceiveBitrateChanged(Keys(ssrcs_),
|
||||
target_bitrate.bps<uint32_t>());
|
||||
}
|
||||
}
|
||||
|
||||
TimeDelta RemoteBitrateEstimatorAbsSendTime::Process() {
|
||||
return TimeDelta::PlusInfinity();
|
||||
}
|
||||
|
||||
void RemoteBitrateEstimatorAbsSendTime::TimeoutStreams(Timestamp now) {
|
||||
for (auto it = ssrcs_.begin(); it != ssrcs_.end();) {
|
||||
if (now - it->second > kStreamTimeOut) {
|
||||
ssrcs_.erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
if (ssrcs_.empty()) {
|
||||
// We can't update the estimate if we don't have any active streams.
|
||||
inter_arrival_ = std::make_unique<InterArrival>(
|
||||
(kTimestampGroupLengthMs << kInterArrivalShift) / 1000, kTimestampToMs);
|
||||
estimator_ = std::make_unique<OveruseEstimator>();
|
||||
// We deliberately don't reset the first_packet_time_ms_ here for now since
|
||||
// we only probe for bandwidth in the beginning of a call right now.
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteBitrateEstimatorAbsSendTime::OnRttUpdate(int64_t avg_rtt_ms,
|
||||
int64_t /*max_rtt_ms*/) {
|
||||
remote_rate_.SetRtt(TimeDelta::Millis(avg_rtt_ms));
|
||||
}
|
||||
|
||||
void RemoteBitrateEstimatorAbsSendTime::RemoveStream(uint32_t ssrc) {
|
||||
ssrcs_.erase(ssrc);
|
||||
}
|
||||
|
||||
DataRate RemoteBitrateEstimatorAbsSendTime::LatestEstimate() const {
|
||||
// Currently accessed only from the worker thread (see Call::GetStats()).
|
||||
if (!remote_rate_.ValidEstimate() || ssrcs_.empty()) {
|
||||
return DataRate::Zero();
|
||||
}
|
||||
return remote_rate_.LatestEstimate();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "api/rtp_headers.h"
|
||||
#include "api/transport/field_trial_based_config.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.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 "modules/remote_bitrate_estimator/overuse_detector.h"
|
||||
#include "modules/remote_bitrate_estimator/overuse_estimator.h"
|
||||
#include "rtc_base/bitrate_tracker.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class RemoteBitrateEstimatorAbsSendTime : public RemoteBitrateEstimator {
|
||||
public:
|
||||
RemoteBitrateEstimatorAbsSendTime(RemoteBitrateObserver* observer,
|
||||
Clock* clock);
|
||||
|
||||
RemoteBitrateEstimatorAbsSendTime() = delete;
|
||||
RemoteBitrateEstimatorAbsSendTime(const RemoteBitrateEstimatorAbsSendTime&) =
|
||||
delete;
|
||||
RemoteBitrateEstimatorAbsSendTime& operator=(
|
||||
const RemoteBitrateEstimatorAbsSendTime&) = delete;
|
||||
|
||||
~RemoteBitrateEstimatorAbsSendTime() override;
|
||||
|
||||
void IncomingPacket(const RtpPacketReceived& rtp_packet) override;
|
||||
TimeDelta Process() override;
|
||||
void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override;
|
||||
void RemoveStream(uint32_t ssrc) override;
|
||||
DataRate LatestEstimate() const override;
|
||||
|
||||
private:
|
||||
struct Probe {
|
||||
Probe(Timestamp send_time, Timestamp recv_time, DataSize payload_size)
|
||||
: send_time(send_time),
|
||||
recv_time(recv_time),
|
||||
payload_size(payload_size) {}
|
||||
|
||||
Timestamp send_time;
|
||||
Timestamp recv_time;
|
||||
DataSize payload_size;
|
||||
};
|
||||
|
||||
struct Cluster {
|
||||
DataRate SendBitrate() const { return mean_size / send_mean; }
|
||||
DataRate RecvBitrate() const { return mean_size / recv_mean; }
|
||||
|
||||
TimeDelta send_mean = TimeDelta::Zero();
|
||||
TimeDelta recv_mean = TimeDelta::Zero();
|
||||
// TODO(holmer): Add some variance metric as well?
|
||||
DataSize mean_size = DataSize::Zero();
|
||||
int count = 0;
|
||||
int num_above_min_delta = 0;
|
||||
};
|
||||
|
||||
enum class ProbeResult { kBitrateUpdated, kNoUpdate };
|
||||
|
||||
static bool IsWithinClusterBounds(TimeDelta send_delta,
|
||||
const Cluster& cluster_aggregate);
|
||||
|
||||
static void MaybeAddCluster(const Cluster& cluster_aggregate,
|
||||
std::list<Cluster>& clusters);
|
||||
|
||||
std::list<Cluster> ComputeClusters() const;
|
||||
|
||||
const Cluster* FindBestProbe(const std::list<Cluster>& clusters) const;
|
||||
|
||||
// Returns true if a probe which changed the estimate was detected.
|
||||
ProbeResult ProcessClusters(Timestamp now);
|
||||
|
||||
bool IsBitrateImproving(DataRate probe_bitrate) const;
|
||||
|
||||
void TimeoutStreams(Timestamp now);
|
||||
|
||||
Clock* const clock_;
|
||||
const FieldTrialBasedConfig field_trials_;
|
||||
RemoteBitrateObserver* const observer_;
|
||||
std::unique_ptr<InterArrival> inter_arrival_;
|
||||
std::unique_ptr<OveruseEstimator> estimator_;
|
||||
OveruseDetector detector_;
|
||||
BitrateTracker incoming_bitrate_{kBitrateWindow};
|
||||
bool incoming_bitrate_initialized_ = false;
|
||||
std::list<Probe> probes_;
|
||||
size_t total_probes_received_ = 0;
|
||||
Timestamp first_packet_time_ = Timestamp::MinusInfinity();
|
||||
Timestamp last_update_ = Timestamp::MinusInfinity();
|
||||
bool uma_recorded_ = false;
|
||||
|
||||
std::map<uint32_t, Timestamp> ssrcs_;
|
||||
AimdRateControl remote_rate_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* 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/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "modules/remote_bitrate_estimator/aimd_rate_control.h"
|
||||
#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
|
||||
#include "modules/remote_bitrate_estimator/inter_arrival.h"
|
||||
#include "modules/remote_bitrate_estimator/overuse_detector.h"
|
||||
#include "modules/remote_bitrate_estimator/overuse_estimator.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
constexpr int kTimestampGroupLengthMs = 5;
|
||||
constexpr double kTimestampToMs = 1.0 / 90.0;
|
||||
|
||||
} // namespace
|
||||
|
||||
RemoteBitrateEstimatorSingleStream::Detector::Detector()
|
||||
: last_packet_time(Timestamp::Zero()),
|
||||
inter_arrival(90 * kTimestampGroupLengthMs, kTimestampToMs) {}
|
||||
|
||||
RemoteBitrateEstimatorSingleStream::RemoteBitrateEstimatorSingleStream(
|
||||
RemoteBitrateObserver* observer,
|
||||
Clock* clock)
|
||||
: clock_(clock),
|
||||
incoming_bitrate_(kBitrateWindow),
|
||||
last_valid_incoming_bitrate_(DataRate::Zero()),
|
||||
remote_rate_(field_trials_),
|
||||
observer_(observer),
|
||||
process_interval_(kProcessInterval),
|
||||
uma_recorded_(false) {
|
||||
RTC_LOG(LS_INFO) << "RemoteBitrateEstimatorSingleStream: Instantiating.";
|
||||
}
|
||||
|
||||
RemoteBitrateEstimatorSingleStream::~RemoteBitrateEstimatorSingleStream() =
|
||||
default;
|
||||
|
||||
void RemoteBitrateEstimatorSingleStream::IncomingPacket(
|
||||
const RtpPacketReceived& rtp_packet) {
|
||||
absl::optional<int32_t> transmission_time_offset =
|
||||
rtp_packet.GetExtension<TransmissionOffset>();
|
||||
if (!uma_recorded_) {
|
||||
BweNames type = transmission_time_offset.has_value()
|
||||
? BweNames::kReceiverTOffset
|
||||
: BweNames::kReceiverNoExtension;
|
||||
RTC_HISTOGRAM_ENUMERATION(kBweTypeHistogram, type, BweNames::kBweNamesMax);
|
||||
uma_recorded_ = true;
|
||||
}
|
||||
uint32_t ssrc = rtp_packet.Ssrc();
|
||||
uint32_t rtp_timestamp =
|
||||
rtp_packet.Timestamp() + transmission_time_offset.value_or(0);
|
||||
Timestamp now = clock_->CurrentTime();
|
||||
Detector& estimator = overuse_detectors_[ssrc];
|
||||
estimator.last_packet_time = now;
|
||||
|
||||
// Check if incoming bitrate estimate is valid, and if it needs to be reset.
|
||||
absl::optional<DataRate> incoming_bitrate = incoming_bitrate_.Rate(now);
|
||||
if (incoming_bitrate) {
|
||||
last_valid_incoming_bitrate_ = *incoming_bitrate;
|
||||
} else if (last_valid_incoming_bitrate_ > DataRate::Zero()) {
|
||||
// Incoming bitrate had a previous valid value, but now not enough data
|
||||
// point are left within the current window. Reset incoming bitrate
|
||||
// estimator so that the window size will only contain new data points.
|
||||
incoming_bitrate_.Reset();
|
||||
last_valid_incoming_bitrate_ = DataRate::Zero();
|
||||
}
|
||||
size_t payload_size = rtp_packet.payload_size() + rtp_packet.padding_size();
|
||||
incoming_bitrate_.Update(payload_size, now);
|
||||
|
||||
const BandwidthUsage prior_state = estimator.detector.State();
|
||||
uint32_t timestamp_delta = 0;
|
||||
int64_t time_delta = 0;
|
||||
int size_delta = 0;
|
||||
int64_t now_ms = now.ms();
|
||||
if (estimator.inter_arrival.ComputeDeltas(
|
||||
rtp_timestamp, rtp_packet.arrival_time().ms(), now_ms, payload_size,
|
||||
×tamp_delta, &time_delta, &size_delta)) {
|
||||
double timestamp_delta_ms = timestamp_delta * kTimestampToMs;
|
||||
estimator.estimator.Update(time_delta, timestamp_delta_ms, size_delta,
|
||||
estimator.detector.State(), now_ms);
|
||||
estimator.detector.Detect(estimator.estimator.offset(), timestamp_delta_ms,
|
||||
estimator.estimator.num_of_deltas(), now_ms);
|
||||
}
|
||||
if (estimator.detector.State() == BandwidthUsage::kBwOverusing) {
|
||||
absl::optional<DataRate> incoming_bitrate = incoming_bitrate_.Rate(now);
|
||||
if (incoming_bitrate.has_value() &&
|
||||
(prior_state != BandwidthUsage::kBwOverusing ||
|
||||
remote_rate_.TimeToReduceFurther(now, *incoming_bitrate))) {
|
||||
// The first overuse should immediately trigger a new estimate.
|
||||
// We also have to update the estimate immediately if we are overusing
|
||||
// and the target bitrate is too high compared to what we are receiving.
|
||||
UpdateEstimate(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeDelta RemoteBitrateEstimatorSingleStream::Process() {
|
||||
Timestamp now = clock_->CurrentTime();
|
||||
Timestamp next_process_time = last_process_time_.has_value()
|
||||
? *last_process_time_ + process_interval_
|
||||
: now;
|
||||
// TODO(bugs.webrtc.org/13756): Removing rounding to milliseconds after
|
||||
// investigating why tests fails without that rounding.
|
||||
if (now.ms() >= next_process_time.ms()) {
|
||||
UpdateEstimate(now);
|
||||
last_process_time_ = now;
|
||||
return process_interval_;
|
||||
}
|
||||
|
||||
return next_process_time - now;
|
||||
}
|
||||
|
||||
void RemoteBitrateEstimatorSingleStream::UpdateEstimate(Timestamp now) {
|
||||
BandwidthUsage bw_state = BandwidthUsage::kBwNormal;
|
||||
auto it = overuse_detectors_.begin();
|
||||
while (it != overuse_detectors_.end()) {
|
||||
if (now - it->second.last_packet_time > kStreamTimeOut) {
|
||||
// This over-use detector hasn't received packets for `kStreamTimeOut`
|
||||
// and is considered stale.
|
||||
overuse_detectors_.erase(it++);
|
||||
} else {
|
||||
// Make sure that we trigger an over-use if any of the over-use detectors
|
||||
// is detecting over-use.
|
||||
if (it->second.detector.State() > bw_state) {
|
||||
bw_state = it->second.detector.State();
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
// We can't update the estimate if we don't have any active streams.
|
||||
if (overuse_detectors_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const RateControlInput input(bw_state, incoming_bitrate_.Rate(now));
|
||||
uint32_t target_bitrate = remote_rate_.Update(input, now).bps<uint32_t>();
|
||||
if (remote_rate_.ValidEstimate()) {
|
||||
process_interval_ = remote_rate_.GetFeedbackInterval();
|
||||
RTC_DCHECK_GT(process_interval_, TimeDelta::Zero());
|
||||
if (observer_)
|
||||
observer_->OnReceiveBitrateChanged(GetSsrcs(), target_bitrate);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteBitrateEstimatorSingleStream::OnRttUpdate(int64_t avg_rtt_ms,
|
||||
int64_t max_rtt_ms) {
|
||||
remote_rate_.SetRtt(TimeDelta::Millis(avg_rtt_ms));
|
||||
}
|
||||
|
||||
void RemoteBitrateEstimatorSingleStream::RemoveStream(uint32_t ssrc) {
|
||||
overuse_detectors_.erase(ssrc);
|
||||
}
|
||||
|
||||
DataRate RemoteBitrateEstimatorSingleStream::LatestEstimate() const {
|
||||
if (!remote_rate_.ValidEstimate() || overuse_detectors_.empty()) {
|
||||
return DataRate::Zero();
|
||||
}
|
||||
return remote_rate_.LatestEstimate();
|
||||
}
|
||||
|
||||
std::vector<uint32_t> RemoteBitrateEstimatorSingleStream::GetSsrcs() const {
|
||||
std::vector<uint32_t> ssrcs;
|
||||
ssrcs.reserve(overuse_detectors_.size());
|
||||
for (const auto& [ssrc, unused] : overuse_detectors_) {
|
||||
ssrcs.push_back(ssrc);
|
||||
}
|
||||
return ssrcs;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_SINGLE_STREAM_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_SINGLE_STREAM_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/transport/field_trial_based_config.h"
|
||||
#include "api/units/data_rate.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.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 "modules/remote_bitrate_estimator/overuse_detector.h"
|
||||
#include "modules/remote_bitrate_estimator/overuse_estimator.h"
|
||||
#include "rtc_base/bitrate_tracker.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class Clock;
|
||||
struct RTPHeader;
|
||||
|
||||
class RemoteBitrateEstimatorSingleStream : public RemoteBitrateEstimator {
|
||||
public:
|
||||
RemoteBitrateEstimatorSingleStream(RemoteBitrateObserver* observer,
|
||||
Clock* clock);
|
||||
|
||||
RemoteBitrateEstimatorSingleStream() = delete;
|
||||
RemoteBitrateEstimatorSingleStream(
|
||||
const RemoteBitrateEstimatorSingleStream&) = delete;
|
||||
RemoteBitrateEstimatorSingleStream& operator=(
|
||||
const RemoteBitrateEstimatorSingleStream&) = delete;
|
||||
|
||||
~RemoteBitrateEstimatorSingleStream() override;
|
||||
|
||||
void IncomingPacket(const RtpPacketReceived& rtp_packet) override;
|
||||
TimeDelta Process() override;
|
||||
void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override;
|
||||
void RemoveStream(uint32_t ssrc) override;
|
||||
DataRate LatestEstimate() const override;
|
||||
|
||||
private:
|
||||
struct Detector {
|
||||
Detector();
|
||||
|
||||
Timestamp last_packet_time;
|
||||
InterArrival inter_arrival;
|
||||
OveruseEstimator estimator;
|
||||
OveruseDetector detector;
|
||||
};
|
||||
|
||||
// Triggers a new estimate calculation.
|
||||
void UpdateEstimate(Timestamp now);
|
||||
|
||||
std::vector<uint32_t> GetSsrcs() const;
|
||||
|
||||
Clock* const clock_;
|
||||
const FieldTrialBasedConfig field_trials_;
|
||||
std::map<uint32_t, Detector> overuse_detectors_;
|
||||
BitrateTracker incoming_bitrate_;
|
||||
DataRate last_valid_incoming_bitrate_;
|
||||
AimdRateControl remote_rate_;
|
||||
RemoteBitrateObserver* const observer_;
|
||||
absl::optional<Timestamp> last_process_time_;
|
||||
TimeDelta process_interval_;
|
||||
bool uma_recorded_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_SINGLE_STREAM_H_
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* 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/remote_bitrate_estimator/remote_estimator_proxy.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h"
|
||||
#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/safe_minmax.h"
|
||||
#include "system_wrappers/include/clock.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
constexpr TimeDelta kBackWindow = TimeDelta::Millis(500);
|
||||
constexpr TimeDelta kMinInterval = TimeDelta::Millis(50);
|
||||
constexpr TimeDelta kMaxInterval = TimeDelta::Millis(250);
|
||||
constexpr TimeDelta kDefaultInterval = TimeDelta::Millis(100);
|
||||
|
||||
TimeDelta GetAbsoluteSendTimeDelta(uint32_t new_sendtime,
|
||||
uint32_t previous_sendtime) {
|
||||
static constexpr uint32_t kWrapAroundPeriod = 0x0100'0000;
|
||||
RTC_DCHECK_LT(new_sendtime, kWrapAroundPeriod);
|
||||
RTC_DCHECK_LT(previous_sendtime, kWrapAroundPeriod);
|
||||
uint32_t delta = (new_sendtime - previous_sendtime) % kWrapAroundPeriod;
|
||||
if (delta >= kWrapAroundPeriod / 2) {
|
||||
// absolute send time wraps around, thus treat deltas larger than half of
|
||||
// the wrap around period as negative.
|
||||
delta = (previous_sendtime - new_sendtime) % kWrapAroundPeriod;
|
||||
return TimeDelta::Micros(int64_t{delta} * -1'000'000 / (1 << 18));
|
||||
}
|
||||
return TimeDelta::Micros(int64_t{delta} * 1'000'000 / (1 << 18));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
RemoteEstimatorProxy::RemoteEstimatorProxy(
|
||||
TransportFeedbackSender feedback_sender,
|
||||
NetworkStateEstimator* network_state_estimator)
|
||||
: feedback_sender_(std::move(feedback_sender)),
|
||||
last_process_time_(Timestamp::MinusInfinity()),
|
||||
network_state_estimator_(network_state_estimator),
|
||||
media_ssrc_(0),
|
||||
feedback_packet_count_(0),
|
||||
packet_overhead_(DataSize::Zero()),
|
||||
send_interval_(kDefaultInterval),
|
||||
send_periodic_feedback_(true),
|
||||
previous_abs_send_time_(0),
|
||||
abs_send_timestamp_(Timestamp::Zero()),
|
||||
last_arrival_time_with_abs_send_time_(Timestamp::MinusInfinity()) {
|
||||
RTC_LOG(LS_INFO)
|
||||
<< "Maximum interval between transport feedback RTCP messages: "
|
||||
<< kMaxInterval;
|
||||
}
|
||||
|
||||
RemoteEstimatorProxy::~RemoteEstimatorProxy() {}
|
||||
|
||||
void RemoteEstimatorProxy::MaybeCullOldPackets(int64_t sequence_number,
|
||||
Timestamp arrival_time) {
|
||||
if (periodic_window_start_seq_ >=
|
||||
packet_arrival_times_.end_sequence_number() &&
|
||||
arrival_time - Timestamp::Zero() >= kBackWindow) {
|
||||
// Start new feedback packet, cull old packets.
|
||||
packet_arrival_times_.RemoveOldPackets(sequence_number,
|
||||
arrival_time - kBackWindow);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteEstimatorProxy::IncomingPacket(const RtpPacketReceived& packet) {
|
||||
if (packet.arrival_time().IsInfinite()) {
|
||||
RTC_LOG(LS_WARNING) << "Arrival time not set.";
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t seqnum = 0;
|
||||
absl::optional<FeedbackRequest> feedback_request;
|
||||
if (!packet.GetExtension<TransportSequenceNumber>(&seqnum) &&
|
||||
!packet.GetExtension<TransportSequenceNumberV2>(&seqnum,
|
||||
&feedback_request)) {
|
||||
// This function expected to be called only for packets that have
|
||||
// TransportSequenceNumber rtp header extension, however malformed RTP
|
||||
// packet may contain unparsable TransportSequenceNumber.
|
||||
RTC_DCHECK(packet.HasExtension<TransportSequenceNumber>() ||
|
||||
packet.HasExtension<TransportSequenceNumberV2>())
|
||||
<< " Expected transport sequence number.";
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLock lock(&lock_);
|
||||
send_periodic_feedback_ = packet.HasExtension<TransportSequenceNumber>();
|
||||
|
||||
media_ssrc_ = packet.Ssrc();
|
||||
int64_t seq = unwrapper_.Unwrap(seqnum);
|
||||
|
||||
if (send_periodic_feedback_) {
|
||||
MaybeCullOldPackets(seq, packet.arrival_time());
|
||||
|
||||
if (!periodic_window_start_seq_ || seq < *periodic_window_start_seq_) {
|
||||
periodic_window_start_seq_ = seq;
|
||||
}
|
||||
}
|
||||
|
||||
// We are only interested in the first time a packet is received.
|
||||
if (packet_arrival_times_.has_received(seq)) {
|
||||
return;
|
||||
}
|
||||
|
||||
packet_arrival_times_.AddPacket(seq, packet.arrival_time());
|
||||
|
||||
// Limit the range of sequence numbers to send feedback for.
|
||||
if (periodic_window_start_seq_ <
|
||||
packet_arrival_times_.begin_sequence_number()) {
|
||||
periodic_window_start_seq_ = packet_arrival_times_.begin_sequence_number();
|
||||
}
|
||||
|
||||
if (feedback_request.has_value()) {
|
||||
// Send feedback packet immediately.
|
||||
SendFeedbackOnRequest(seq, *feedback_request);
|
||||
}
|
||||
|
||||
absl::optional<uint32_t> absolute_send_time_24bits =
|
||||
packet.GetExtension<AbsoluteSendTime>();
|
||||
if (network_state_estimator_ && absolute_send_time_24bits.has_value()) {
|
||||
PacketResult packet_result;
|
||||
packet_result.receive_time = packet.arrival_time();
|
||||
if (packet.arrival_time() - last_arrival_time_with_abs_send_time_ <
|
||||
TimeDelta::Seconds(10)) {
|
||||
abs_send_timestamp_ += GetAbsoluteSendTimeDelta(
|
||||
*absolute_send_time_24bits, previous_abs_send_time_);
|
||||
} else {
|
||||
abs_send_timestamp_ = packet.arrival_time();
|
||||
}
|
||||
last_arrival_time_with_abs_send_time_ = packet.arrival_time();
|
||||
previous_abs_send_time_ = *absolute_send_time_24bits;
|
||||
packet_result.sent_packet.send_time = abs_send_timestamp_;
|
||||
packet_result.sent_packet.size =
|
||||
DataSize::Bytes(packet.size()) + packet_overhead_;
|
||||
packet_result.sent_packet.sequence_number = seq;
|
||||
network_state_estimator_->OnReceivedPacket(packet_result);
|
||||
}
|
||||
}
|
||||
|
||||
TimeDelta RemoteEstimatorProxy::Process(Timestamp now) {
|
||||
MutexLock lock(&lock_);
|
||||
if (!send_periodic_feedback_) {
|
||||
// If TransportSequenceNumberV2 has been received in one packet,
|
||||
// PeriodicFeedback is disabled for the rest of the call.
|
||||
return TimeDelta::PlusInfinity();
|
||||
}
|
||||
Timestamp next_process_time = last_process_time_ + send_interval_;
|
||||
if (now >= next_process_time) {
|
||||
last_process_time_ = now;
|
||||
SendPeriodicFeedbacks();
|
||||
return send_interval_;
|
||||
}
|
||||
|
||||
return next_process_time - now;
|
||||
}
|
||||
|
||||
void RemoteEstimatorProxy::OnBitrateChanged(int bitrate_bps) {
|
||||
// TwccReportSize = Ipv4(20B) + UDP(8B) + SRTP(10B) +
|
||||
// AverageTwccReport(30B)
|
||||
// TwccReport size at 50ms interval is 24 byte.
|
||||
// TwccReport size at 250ms interval is 36 byte.
|
||||
// AverageTwccReport = (TwccReport(50ms) + TwccReport(250ms)) / 2
|
||||
constexpr DataSize kTwccReportSize = DataSize::Bytes(20 + 8 + 10 + 30);
|
||||
constexpr DataRate kMinTwccRate = kTwccReportSize / kMaxInterval;
|
||||
|
||||
// Let TWCC reports occupy 5% of total bandwidth.
|
||||
DataRate twcc_bitrate = DataRate::BitsPerSec(0.05 * bitrate_bps);
|
||||
|
||||
// Check upper send_interval bound by checking bitrate to avoid overflow when
|
||||
// dividing by small bitrate, in particular avoid dividing by zero bitrate.
|
||||
TimeDelta send_interval =
|
||||
twcc_bitrate <= kMinTwccRate
|
||||
? kMaxInterval
|
||||
: std::max(kTwccReportSize / twcc_bitrate, kMinInterval);
|
||||
|
||||
MutexLock lock(&lock_);
|
||||
send_interval_ = send_interval;
|
||||
}
|
||||
|
||||
void RemoteEstimatorProxy::SetTransportOverhead(DataSize overhead_per_packet) {
|
||||
MutexLock lock(&lock_);
|
||||
packet_overhead_ = overhead_per_packet;
|
||||
}
|
||||
|
||||
void RemoteEstimatorProxy::SendPeriodicFeedbacks() {
|
||||
// `periodic_window_start_seq_` is the first sequence number to include in
|
||||
// the current feedback packet. Some older may still be in the map, in case
|
||||
// a reordering happens and we need to retransmit them.
|
||||
if (!periodic_window_start_seq_)
|
||||
return;
|
||||
|
||||
std::unique_ptr<rtcp::RemoteEstimate> remote_estimate;
|
||||
if (network_state_estimator_) {
|
||||
absl::optional<NetworkStateEstimate> state_estimate =
|
||||
network_state_estimator_->GetCurrentEstimate();
|
||||
if (state_estimate) {
|
||||
remote_estimate = std::make_unique<rtcp::RemoteEstimate>();
|
||||
remote_estimate->SetEstimate(state_estimate.value());
|
||||
}
|
||||
}
|
||||
|
||||
int64_t packet_arrival_times_end_seq =
|
||||
packet_arrival_times_.end_sequence_number();
|
||||
while (periodic_window_start_seq_ < packet_arrival_times_end_seq) {
|
||||
auto feedback_packet = MaybeBuildFeedbackPacket(
|
||||
/*include_timestamps=*/true, *periodic_window_start_seq_,
|
||||
packet_arrival_times_end_seq,
|
||||
/*is_periodic_update=*/true);
|
||||
|
||||
if (feedback_packet == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
RTC_DCHECK(feedback_sender_ != nullptr);
|
||||
|
||||
std::vector<std::unique_ptr<rtcp::RtcpPacket>> packets;
|
||||
if (remote_estimate) {
|
||||
packets.push_back(std::move(remote_estimate));
|
||||
}
|
||||
packets.push_back(std::move(feedback_packet));
|
||||
|
||||
feedback_sender_(std::move(packets));
|
||||
// Note: Don't erase items from packet_arrival_times_ after sending, in
|
||||
// case they need to be re-sent after a reordering. Removal will be
|
||||
// handled by OnPacketArrival once packets are too old.
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteEstimatorProxy::SendFeedbackOnRequest(
|
||||
int64_t sequence_number,
|
||||
const FeedbackRequest& feedback_request) {
|
||||
if (feedback_request.sequence_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t first_sequence_number =
|
||||
sequence_number - feedback_request.sequence_count + 1;
|
||||
|
||||
auto feedback_packet = MaybeBuildFeedbackPacket(
|
||||
feedback_request.include_timestamps, first_sequence_number,
|
||||
sequence_number + 1, /*is_periodic_update=*/false);
|
||||
|
||||
// Even though this is called when a packet has just been added,
|
||||
// no feedback may be produced when that new packet is too old.
|
||||
if (feedback_packet == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear up to the first packet that is included in this feedback packet.
|
||||
packet_arrival_times_.EraseTo(first_sequence_number);
|
||||
|
||||
RTC_DCHECK(feedback_sender_ != nullptr);
|
||||
std::vector<std::unique_ptr<rtcp::RtcpPacket>> packets;
|
||||
packets.push_back(std::move(feedback_packet));
|
||||
feedback_sender_(std::move(packets));
|
||||
}
|
||||
|
||||
std::unique_ptr<rtcp::TransportFeedback>
|
||||
RemoteEstimatorProxy::MaybeBuildFeedbackPacket(
|
||||
bool include_timestamps,
|
||||
int64_t begin_sequence_number_inclusive,
|
||||
int64_t end_sequence_number_exclusive,
|
||||
bool is_periodic_update) {
|
||||
RTC_DCHECK_LT(begin_sequence_number_inclusive, end_sequence_number_exclusive);
|
||||
|
||||
int64_t start_seq =
|
||||
packet_arrival_times_.clamp(begin_sequence_number_inclusive);
|
||||
|
||||
int64_t end_seq = packet_arrival_times_.clamp(end_sequence_number_exclusive);
|
||||
|
||||
// Create the packet on demand, as it's not certain that there are packets
|
||||
// in the range that have been received.
|
||||
std::unique_ptr<rtcp::TransportFeedback> feedback_packet;
|
||||
|
||||
int64_t next_sequence_number = begin_sequence_number_inclusive;
|
||||
|
||||
for (int64_t seq = start_seq; seq < end_seq; ++seq) {
|
||||
PacketArrivalTimeMap::PacketArrivalTime packet =
|
||||
packet_arrival_times_.FindNextAtOrAfter(seq);
|
||||
seq = packet.sequence_number;
|
||||
if (seq >= end_seq) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (feedback_packet == nullptr) {
|
||||
feedback_packet =
|
||||
std::make_unique<rtcp::TransportFeedback>(include_timestamps);
|
||||
feedback_packet->SetMediaSsrc(media_ssrc_);
|
||||
|
||||
// It should be possible to add `seq` to this new `feedback_packet`,
|
||||
// If difference between `seq` and `begin_sequence_number_inclusive`,
|
||||
// is too large, discard reporting too old missing packets.
|
||||
static constexpr int kMaxMissingSequenceNumbers = 0x7FFE;
|
||||
int64_t base_sequence_number = std::max(begin_sequence_number_inclusive,
|
||||
seq - kMaxMissingSequenceNumbers);
|
||||
|
||||
// Base sequence number is the expected first sequence number. This is
|
||||
// known, but we might not have actually received it, so the base time
|
||||
// shall be the time of the first received packet in the feedback.
|
||||
feedback_packet->SetBase(static_cast<uint16_t>(base_sequence_number),
|
||||
packet.arrival_time);
|
||||
feedback_packet->SetFeedbackSequenceNumber(feedback_packet_count_++);
|
||||
|
||||
if (!feedback_packet->AddReceivedPacket(static_cast<uint16_t>(seq),
|
||||
packet.arrival_time)) {
|
||||
// Could not add a single received packet to the feedback.
|
||||
RTC_DCHECK_NOTREACHED()
|
||||
<< "Failed to create an RTCP transport feedback with base sequence "
|
||||
"number "
|
||||
<< base_sequence_number << " and 1st received " << seq;
|
||||
periodic_window_start_seq_ = seq;
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
if (!feedback_packet->AddReceivedPacket(static_cast<uint16_t>(seq),
|
||||
packet.arrival_time)) {
|
||||
// Could not add timestamp, feedback packet might be full. Return and
|
||||
// try again with a fresh packet.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
next_sequence_number = seq + 1;
|
||||
}
|
||||
if (is_periodic_update) {
|
||||
periodic_window_start_seq_ = next_sequence_number;
|
||||
}
|
||||
return feedback_packet;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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_REMOTE_BITRATE_ESTIMATOR_REMOTE_ESTIMATOR_PROXY_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_ESTIMATOR_PROXY_H_
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/field_trials_view.h"
|
||||
#include "api/rtp_headers.h"
|
||||
#include "api/transport/network_control.h"
|
||||
#include "api/units/data_size.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "api/units/timestamp.h"
|
||||
#include "modules/remote_bitrate_estimator/packet_arrival_map.h"
|
||||
#include "modules/rtp_rtcp/source/rtcp_packet.h"
|
||||
#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
|
||||
#include "rtc_base/numerics/sequence_number_unwrapper.h"
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Class used when send-side BWE is enabled: This proxy is instantiated on the
|
||||
// receive side. It buffers a number of receive timestamps and then sends
|
||||
// transport feedback messages back too the send side.
|
||||
class RemoteEstimatorProxy {
|
||||
public:
|
||||
// Used for sending transport feedback messages when send side
|
||||
// BWE is used.
|
||||
using TransportFeedbackSender = std::function<void(
|
||||
std::vector<std::unique_ptr<rtcp::RtcpPacket>> packets)>;
|
||||
RemoteEstimatorProxy(TransportFeedbackSender feedback_sender,
|
||||
NetworkStateEstimator* network_state_estimator);
|
||||
~RemoteEstimatorProxy();
|
||||
|
||||
void IncomingPacket(const RtpPacketReceived& packet);
|
||||
|
||||
// Sends periodic feedback if it is time to send it.
|
||||
// Returns time until next call to Process should be made.
|
||||
TimeDelta Process(Timestamp now);
|
||||
|
||||
void OnBitrateChanged(int bitrate);
|
||||
void SetTransportOverhead(DataSize overhead_per_packet);
|
||||
|
||||
private:
|
||||
void MaybeCullOldPackets(int64_t sequence_number, Timestamp arrival_time)
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(&lock_);
|
||||
void SendPeriodicFeedbacks() RTC_EXCLUSIVE_LOCKS_REQUIRED(&lock_);
|
||||
void SendFeedbackOnRequest(int64_t sequence_number,
|
||||
const FeedbackRequest& feedback_request)
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(&lock_);
|
||||
|
||||
// Returns a Transport Feedback packet with information about as many packets
|
||||
// that has been received between [`begin_sequence_number_incl`,
|
||||
// `end_sequence_number_excl`) that can fit in it. If `is_periodic_update`,
|
||||
// this represents sending a periodic feedback message, which will make it
|
||||
// update the `periodic_window_start_seq_` variable with the first packet that
|
||||
// was not included in the feedback packet, so that the next update can
|
||||
// continue from that sequence number.
|
||||
//
|
||||
// If no incoming packets were added, nullptr is returned.
|
||||
//
|
||||
// `include_timestamps` decide if the returned TransportFeedback should
|
||||
// include timestamps.
|
||||
std::unique_ptr<rtcp::TransportFeedback> MaybeBuildFeedbackPacket(
|
||||
bool include_timestamps,
|
||||
int64_t begin_sequence_number_inclusive,
|
||||
int64_t end_sequence_number_exclusive,
|
||||
bool is_periodic_update) RTC_EXCLUSIVE_LOCKS_REQUIRED(&lock_);
|
||||
|
||||
const TransportFeedbackSender feedback_sender_;
|
||||
Timestamp last_process_time_;
|
||||
|
||||
Mutex lock_;
|
||||
// `network_state_estimator_` may be null.
|
||||
NetworkStateEstimator* const network_state_estimator_
|
||||
RTC_PT_GUARDED_BY(&lock_);
|
||||
uint32_t media_ssrc_ RTC_GUARDED_BY(&lock_);
|
||||
uint8_t feedback_packet_count_ RTC_GUARDED_BY(&lock_);
|
||||
SeqNumUnwrapper<uint16_t> unwrapper_ RTC_GUARDED_BY(&lock_);
|
||||
DataSize packet_overhead_ RTC_GUARDED_BY(&lock_);
|
||||
|
||||
// The next sequence number that should be the start sequence number during
|
||||
// periodic reporting. Will be absl::nullopt before the first seen packet.
|
||||
absl::optional<int64_t> periodic_window_start_seq_ RTC_GUARDED_BY(&lock_);
|
||||
|
||||
// Packet arrival times, by sequence number.
|
||||
PacketArrivalTimeMap packet_arrival_times_ RTC_GUARDED_BY(&lock_);
|
||||
|
||||
TimeDelta send_interval_ RTC_GUARDED_BY(&lock_);
|
||||
bool send_periodic_feedback_ RTC_GUARDED_BY(&lock_);
|
||||
|
||||
// Unwraps absolute send times.
|
||||
uint32_t previous_abs_send_time_ RTC_GUARDED_BY(&lock_);
|
||||
Timestamp abs_send_timestamp_ RTC_GUARDED_BY(&lock_);
|
||||
Timestamp last_arrival_time_with_abs_send_time_ RTC_GUARDED_BY(&lock_);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_ESTIMATOR_PROXY_H_
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
|
||||
|
||||
#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/platform_thread.h"
|
||||
#include "rtc_base/strings/string_builder.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace testing {
|
||||
namespace bwe {
|
||||
|
||||
static std::string ToString(uint32_t v) {
|
||||
rtc::StringBuilder ss;
|
||||
ss << v;
|
||||
return ss.Release();
|
||||
}
|
||||
|
||||
Logging::ThreadState::ThreadState() = default;
|
||||
Logging::ThreadState::~ThreadState() = default;
|
||||
|
||||
Logging::Context::Context(uint32_t name, int64_t timestamp_ms, bool enabled) {
|
||||
Logging::GetInstance()->PushState(ToString(name), timestamp_ms, enabled);
|
||||
}
|
||||
|
||||
Logging::Context::Context(const std::string& name,
|
||||
int64_t timestamp_ms,
|
||||
bool enabled) {
|
||||
Logging::GetInstance()->PushState(name, timestamp_ms, enabled);
|
||||
}
|
||||
|
||||
Logging::Context::Context(const char* name,
|
||||
int64_t timestamp_ms,
|
||||
bool enabled) {
|
||||
Logging::GetInstance()->PushState(name, timestamp_ms, enabled);
|
||||
}
|
||||
|
||||
Logging::Context::~Context() {
|
||||
Logging::GetInstance()->PopState();
|
||||
}
|
||||
|
||||
Logging* Logging::GetInstance() {
|
||||
static Logging* logging = new Logging();
|
||||
return logging;
|
||||
}
|
||||
|
||||
void Logging::SetGlobalContext(uint32_t name) {
|
||||
MutexLock lock(&mutex_);
|
||||
thread_map_[rtc::CurrentThreadId()].global_state.tag = ToString(name);
|
||||
}
|
||||
|
||||
void Logging::SetGlobalContext(const std::string& name) {
|
||||
MutexLock lock(&mutex_);
|
||||
thread_map_[rtc::CurrentThreadId()].global_state.tag = name;
|
||||
}
|
||||
|
||||
void Logging::SetGlobalContext(const char* name) {
|
||||
MutexLock lock(&mutex_);
|
||||
thread_map_[rtc::CurrentThreadId()].global_state.tag = name;
|
||||
}
|
||||
|
||||
void Logging::SetGlobalEnable(bool enabled) {
|
||||
MutexLock lock(&mutex_);
|
||||
thread_map_[rtc::CurrentThreadId()].global_state.enabled = enabled;
|
||||
}
|
||||
|
||||
void Logging::Log(const char format[], ...) {
|
||||
MutexLock lock(&mutex_);
|
||||
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
|
||||
RTC_DCHECK(it != thread_map_.end());
|
||||
const State& state = it->second.stack.top();
|
||||
if (state.enabled) {
|
||||
printf("%s\t", state.tag.c_str());
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vprintf(format, args);
|
||||
va_end(args);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Logging::Plot(int figure, const std::string& name, double value) {
|
||||
Plot(figure, name, value, 0, "-");
|
||||
}
|
||||
|
||||
void Logging::Plot(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
uint32_t ssrc) {
|
||||
Plot(figure, name, value, ssrc, "-");
|
||||
}
|
||||
|
||||
void Logging::Plot(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
const std::string& alg_name) {
|
||||
Plot(figure, name, value, 0, alg_name);
|
||||
}
|
||||
|
||||
void Logging::Plot(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
uint32_t ssrc,
|
||||
const std::string& alg_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
|
||||
RTC_DCHECK(it != thread_map_.end());
|
||||
const State& state = it->second.stack.top();
|
||||
if (state.enabled) {
|
||||
printf("PLOT\t%d\t%s:%" PRIu32 "@%s\t%f\t%f\n", figure, name.c_str(), ssrc,
|
||||
alg_name.c_str(), state.timestamp_ms * 0.001, value);
|
||||
}
|
||||
}
|
||||
|
||||
void Logging::PlotBar(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
int flow_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
|
||||
RTC_DCHECK(it != thread_map_.end());
|
||||
const State& state = it->second.stack.top();
|
||||
if (state.enabled) {
|
||||
printf("BAR\t%d\t%s_%d\t%f\n", figure, name.c_str(), flow_id, value);
|
||||
}
|
||||
}
|
||||
|
||||
void Logging::PlotBaselineBar(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
int flow_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
|
||||
RTC_DCHECK(it != thread_map_.end());
|
||||
const State& state = it->second.stack.top();
|
||||
if (state.enabled) {
|
||||
printf("BASELINE\t%d\t%s_%d\t%f\n", figure, name.c_str(), flow_id, value);
|
||||
}
|
||||
}
|
||||
|
||||
void Logging::PlotErrorBar(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
double ylow,
|
||||
double yhigh,
|
||||
const std::string& error_title,
|
||||
int flow_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
|
||||
RTC_DCHECK(it != thread_map_.end());
|
||||
const State& state = it->second.stack.top();
|
||||
if (state.enabled) {
|
||||
printf("ERRORBAR\t%d\t%s_%d\t%f\t%f\t%f\t%s\n", figure, name.c_str(),
|
||||
flow_id, value, ylow, yhigh, error_title.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Logging::PlotLimitErrorBar(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
double ylow,
|
||||
double yhigh,
|
||||
const std::string& error_title,
|
||||
double ymax,
|
||||
const std::string& limit_title,
|
||||
int flow_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
|
||||
RTC_DCHECK(it != thread_map_.end());
|
||||
const State& state = it->second.stack.top();
|
||||
if (state.enabled) {
|
||||
printf("LIMITERRORBAR\t%d\t%s_%d\t%f\t%f\t%f\t%s\t%f\t%s\n", figure,
|
||||
name.c_str(), flow_id, value, ylow, yhigh, error_title.c_str(), ymax,
|
||||
limit_title.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Logging::PlotLabel(int figure,
|
||||
const std::string& title,
|
||||
const std::string& y_label,
|
||||
int num_flows) {
|
||||
MutexLock lock(&mutex_);
|
||||
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
|
||||
RTC_DCHECK(it != thread_map_.end());
|
||||
const State& state = it->second.stack.top();
|
||||
if (state.enabled) {
|
||||
printf("LABEL\t%d\t%s\t%s\t%d\n", figure, title.c_str(), y_label.c_str(),
|
||||
num_flows);
|
||||
}
|
||||
}
|
||||
|
||||
Logging::Logging() : thread_map_() {}
|
||||
|
||||
Logging::~Logging() = default;
|
||||
|
||||
Logging::State::State() : tag(""), timestamp_ms(0), enabled(true) {}
|
||||
|
||||
Logging::State::State(const std::string& tag,
|
||||
int64_t timestamp_ms,
|
||||
bool enabled)
|
||||
: tag(tag), timestamp_ms(timestamp_ms), enabled(enabled) {}
|
||||
|
||||
void Logging::State::MergePrevious(const State& previous) {
|
||||
if (tag.empty()) {
|
||||
tag = previous.tag;
|
||||
} else if (!previous.tag.empty()) {
|
||||
tag = previous.tag + "_" + tag;
|
||||
}
|
||||
timestamp_ms = std::max(previous.timestamp_ms, timestamp_ms);
|
||||
enabled = previous.enabled && enabled;
|
||||
}
|
||||
|
||||
void Logging::PushState(const std::string& append_to_tag,
|
||||
int64_t timestamp_ms,
|
||||
bool enabled) {
|
||||
MutexLock lock(&mutex_);
|
||||
State new_state(append_to_tag, timestamp_ms, enabled);
|
||||
ThreadState* thread_state = &thread_map_[rtc::CurrentThreadId()];
|
||||
std::stack<State>* stack = &thread_state->stack;
|
||||
if (stack->empty()) {
|
||||
new_state.MergePrevious(thread_state->global_state);
|
||||
} else {
|
||||
new_state.MergePrevious(stack->top());
|
||||
}
|
||||
stack->push(new_state);
|
||||
}
|
||||
|
||||
void Logging::PopState() {
|
||||
MutexLock lock(&mutex_);
|
||||
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
|
||||
RTC_DCHECK(it != thread_map_.end());
|
||||
std::stack<State>* stack = &it->second.stack;
|
||||
int64_t newest_timestamp_ms = stack->top().timestamp_ms;
|
||||
stack->pop();
|
||||
if (!stack->empty()) {
|
||||
State* state = &stack->top();
|
||||
// Update time so that next log/plot will use the latest time seen so far
|
||||
// in this call tree.
|
||||
state->timestamp_ms = std::max(state->timestamp_ms, newest_timestamp_ms);
|
||||
}
|
||||
}
|
||||
} // namespace bwe
|
||||
} // namespace testing
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
|
||||
|
|
@ -0,0 +1,360 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
|
||||
|
||||
// To enable BWE logging, run this command from trunk/ :
|
||||
// build/gyp_chromium --depth=. webrtc/modules/modules.gyp
|
||||
// -Denable_bwe_test_logging=1
|
||||
#ifndef BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
|
||||
#define BWE_TEST_LOGGING_COMPILE_TIME_ENABLE 0
|
||||
#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
|
||||
|
||||
// BWE logging allows you to insert dynamically named log/plot points in the
|
||||
// call tree. E.g. the function:
|
||||
// void f1() {
|
||||
// BWE_TEST_LOGGING_TIME(clock_->TimeInMilliseconds());
|
||||
// BWE_TEST_LOGGING_CONTEXT("stream");
|
||||
// for (uint32_t i=0; i<4; ++i) {
|
||||
// BWE_TEST_LOGGING_ENABLE(i & 1);
|
||||
// BWE_TEST_LOGGING_CONTEXT(i);
|
||||
// BWE_TEST_LOGGING_LOG1("weight", "%f tonnes", weights_[i]);
|
||||
// for (float j=0.0f; j<1.0; j+=0.4f) {
|
||||
// BWE_TEST_LOGGING_PLOT(0, "bps", -1, j);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Might produce the output:
|
||||
// stream_00000001_weight 13.000000 tonnes
|
||||
// PLOT stream_00000001_bps 1.000000 0.000000
|
||||
// PLOT stream_00000001_bps 1.000000 0.400000
|
||||
// PLOT stream_00000001_bps 1.000000 0.800000
|
||||
// stream_00000003_weight 39.000000 tonnes
|
||||
// PLOT stream_00000003_bps 1.000000 0.000000
|
||||
// PLOT stream_00000003_bps 1.000000 0.400000
|
||||
// PLOT stream_00000003_bps 1.000000 0.800000
|
||||
//
|
||||
// Log *contexts* are names concatenated with '_' between them, with the name
|
||||
// of the logged/plotted string/value last. Plot *time* is inherited down the
|
||||
// tree. A branch is enabled by default but can be *disabled* to reduce output.
|
||||
// The difference between the RTC_LOG and PLOT macros is that PLOT prefixes the
|
||||
// line so it can be easily filtered, plus it outputs the current time.
|
||||
|
||||
#if !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
|
||||
|
||||
// Set a thread-global base logging context. This name will be prepended to all
|
||||
// hierarchical contexts.
|
||||
// `name` is a char*, std::string or uint32_t to name the context.
|
||||
#define BWE_TEST_LOGGING_GLOBAL_CONTEXT(name)
|
||||
|
||||
// Thread-globally allow/disallow logging.
|
||||
// `enable` is expected to be a bool.
|
||||
#define BWE_TEST_LOGGING_GLOBAL_ENABLE(enabled)
|
||||
|
||||
// Insert a (hierarchical) logging context.
|
||||
// `name` is a char*, std::string or uint32_t to name the context.
|
||||
#define BWE_TEST_LOGGING_CONTEXT(name)
|
||||
|
||||
// Allow/disallow logging down the call tree from this point. Logging must be
|
||||
// enabled all the way to the root of the call tree to take place.
|
||||
// `enable` is expected to be a bool.
|
||||
#define BWE_TEST_LOGGING_ENABLE(enabled)
|
||||
|
||||
// Set current time (only affects PLOT output). Down the call tree, the latest
|
||||
// time set always takes precedence.
|
||||
// `time` is an int64_t time in ms, or -1 to inherit time from previous context.
|
||||
#define BWE_TEST_LOGGING_TIME(time)
|
||||
|
||||
// Print to stdout, e.g.:
|
||||
// Context1_Context2_Name printf-formated-string
|
||||
// `name` is a char*, std::string or uint32_t to name the log line.
|
||||
// `format` is a printf format string.
|
||||
// |_1...| are arguments for printf.
|
||||
#define BWE_TEST_LOGGING_LOG1(name, format, _1)
|
||||
#define BWE_TEST_LOGGING_LOG2(name, format, _1, _2)
|
||||
#define BWE_TEST_LOGGING_LOG3(name, format, _1, _2, _3)
|
||||
#define BWE_TEST_LOGGING_LOG4(name, format, _1, _2, _3, _4)
|
||||
#define BWE_TEST_LOGGING_LOG5(name, format, _1, _2, _3, _4, _5)
|
||||
|
||||
// Print to stdout in tab-separated format suitable for plotting, e.g.:
|
||||
// PLOT figure Context1_Context2_Name time value
|
||||
// `figure` is a figure id. Different figures are plotted in different windows.
|
||||
// `name` is a char*, std::string or uint32_t to name the plotted value.
|
||||
// `time` is an int64_t time in ms, or -1 to inherit time from previous context.
|
||||
// `value` is a double precision float to be plotted.
|
||||
// `ssrc` identifies the source of a stream
|
||||
// `alg_name` is an optional argument, a string
|
||||
#define BWE_TEST_LOGGING_PLOT(figure, name, time, value)
|
||||
#define BWE_TEST_LOGGING_PLOT_WITH_NAME(figure, name, time, value, alg_name)
|
||||
#define BWE_TEST_LOGGING_PLOT_WITH_SSRC(figure, name, time, value, ssrc)
|
||||
#define BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(figure, name, time, value, \
|
||||
ssrc, alg_name)
|
||||
|
||||
// Print to stdout in tab-separated format suitable for plotting, e.g.:
|
||||
// BAR figure Context1_Context2_Name x_left width value
|
||||
// `figure` is a figure id. Different figures are plotted in different windows.
|
||||
// `name` is a char*, std::string or uint32_t to name the plotted value.
|
||||
// `value` is a double precision float to be plotted.
|
||||
// `ylow` and `yhigh` are double precision float for the error line.
|
||||
// `title` is a string and refers to the error label.
|
||||
// `ymax` is a double precision float for the limit horizontal line.
|
||||
// `limit_title` is a string and refers to the limit label.
|
||||
#define BWE_TEST_LOGGING_BAR(figure, name, value, flow_id)
|
||||
#define BWE_TEST_LOGGING_ERRORBAR(figure, name, value, ylow, yhigh, \
|
||||
error_title, flow_id)
|
||||
#define BWE_TEST_LOGGING_LIMITERRORBAR( \
|
||||
figure, name, value, ylow, yhigh, error_title, ymax, limit_title, flow_id)
|
||||
|
||||
#define BWE_TEST_LOGGING_BASELINEBAR(figure, name, value, flow_id)
|
||||
|
||||
// `num_flows` is an integer refering to the number of RMCAT flows in the
|
||||
// scenario.
|
||||
// Define `x_label` and `y_label` for plots.
|
||||
#define BWE_TEST_LOGGING_LABEL(figure, x_label, y_label, num_flows)
|
||||
|
||||
#else // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
|
||||
#define BWE_TEST_LOGGING_GLOBAL_CONTEXT(name) \
|
||||
do { \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->SetGlobalContext(name); \
|
||||
} while (0)
|
||||
|
||||
#define BWE_TEST_LOGGING_GLOBAL_ENABLE(enabled) \
|
||||
do { \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->SetGlobalEnable(enabled); \
|
||||
} while (0)
|
||||
|
||||
#define __BWE_TEST_LOGGING_CONTEXT_NAME(ctx, line) ctx##line
|
||||
#define __BWE_TEST_LOGGING_CONTEXT_DECLARE(ctx, line, name, time, enabled) \
|
||||
webrtc::testing::bwe::Logging::Context __BWE_TEST_LOGGING_CONTEXT_NAME( \
|
||||
ctx, line)(name, time, enabled)
|
||||
|
||||
#define BWE_TEST_LOGGING_CONTEXT(name) \
|
||||
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, name, -1, true)
|
||||
#define BWE_TEST_LOGGING_ENABLE(enabled) \
|
||||
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, "", -1, \
|
||||
static_cast<bool>(enabled))
|
||||
#define BWE_TEST_LOGGING_TIME(time) \
|
||||
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, "", \
|
||||
static_cast<int64_t>(time), true)
|
||||
|
||||
#define BWE_TEST_LOGGING_LOG1(name, format, _1) \
|
||||
do { \
|
||||
BWE_TEST_LOGGING_CONTEXT(name); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1); \
|
||||
} while (0)
|
||||
#define BWE_TEST_LOGGING_LOG2(name, format, _1, _2) \
|
||||
do { \
|
||||
BWE_TEST_LOGGING_CONTEXT(name); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2); \
|
||||
} while (0)
|
||||
#define BWE_TEST_LOGGING_LOG3(name, format, _1, _2, _3) \
|
||||
do { \
|
||||
BWE_TEST_LOGGING_CONTEXT(name); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3); \
|
||||
} while (0)
|
||||
#define BWE_TEST_LOGGING_LOG4(name, format, _1, _2, _3, _4) \
|
||||
do { \
|
||||
BWE_TEST_LOGGING_CONTEXT(name); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3, _4); \
|
||||
} while (0)
|
||||
#define BWE_TEST_LOGGING_LOG5(name, format, _1, _2, _3, _4, _5) \
|
||||
do { \
|
||||
BWE_TEST_LOGGING_CONTEXT(name); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3, _4, \
|
||||
_5); \
|
||||
} while (0)
|
||||
|
||||
#define BWE_TEST_LOGGING_PLOT(figure, name, time, value) \
|
||||
do { \
|
||||
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
|
||||
static_cast<int64_t>(time), true); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value); \
|
||||
} while (0)
|
||||
|
||||
#define BWE_TEST_LOGGING_PLOT_WITH_NAME(figure, name, time, value, alg_name) \
|
||||
do { \
|
||||
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
|
||||
static_cast<int64_t>(time), true); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value, \
|
||||
alg_name); \
|
||||
} while (0)
|
||||
|
||||
#define BWE_TEST_LOGGING_PLOT_WITH_SSRC(figure, name, time, value, ssrc) \
|
||||
do { \
|
||||
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
|
||||
static_cast<int64_t>(time), true); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value, \
|
||||
ssrc); \
|
||||
} while (0)
|
||||
|
||||
#define BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(figure, name, time, value, \
|
||||
ssrc, alg_name) \
|
||||
do { \
|
||||
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
|
||||
static_cast<int64_t>(time), true); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value, \
|
||||
ssrc, alg_name); \
|
||||
} while (0)
|
||||
|
||||
#define BWE_TEST_LOGGING_BAR(figure, name, value, flow_id) \
|
||||
do { \
|
||||
BWE_TEST_LOGGING_CONTEXT(name); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->PlotBar(figure, name, value, \
|
||||
flow_id); \
|
||||
} while (0)
|
||||
|
||||
#define BWE_TEST_LOGGING_BASELINEBAR(figure, name, value, flow_id) \
|
||||
do { \
|
||||
BWE_TEST_LOGGING_CONTEXT(name); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->PlotBaselineBar( \
|
||||
figure, name, value, flow_id); \
|
||||
} while (0)
|
||||
|
||||
#define BWE_TEST_LOGGING_ERRORBAR(figure, name, value, ylow, yhigh, title, \
|
||||
flow_id) \
|
||||
do { \
|
||||
BWE_TEST_LOGGING_CONTEXT(name); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->PlotErrorBar( \
|
||||
figure, name, value, ylow, yhigh, title, flow_id); \
|
||||
} while (0)
|
||||
|
||||
#define BWE_TEST_LOGGING_LIMITERRORBAR( \
|
||||
figure, name, value, ylow, yhigh, error_title, ymax, limit_title, flow_id) \
|
||||
do { \
|
||||
BWE_TEST_LOGGING_CONTEXT(name); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->PlotLimitErrorBar( \
|
||||
figure, name, value, ylow, yhigh, error_title, ymax, limit_title, \
|
||||
flow_id); \
|
||||
} while (0)
|
||||
|
||||
#define BWE_TEST_LOGGING_LABEL(figure, title, y_label, num_flows) \
|
||||
do { \
|
||||
BWE_TEST_LOGGING_CONTEXT(title); \
|
||||
webrtc::testing::bwe::Logging::GetInstance()->PlotLabel( \
|
||||
figure, title, y_label, num_flows); \
|
||||
} while (0)
|
||||
|
||||
namespace webrtc {
|
||||
namespace testing {
|
||||
namespace bwe {
|
||||
|
||||
class Logging {
|
||||
public:
|
||||
class Context {
|
||||
public:
|
||||
Context(uint32_t name, int64_t timestamp_ms, bool enabled);
|
||||
Context(const std::string& name, int64_t timestamp_ms, bool enabled);
|
||||
Context(const char* name, int64_t timestamp_ms, bool enabled);
|
||||
|
||||
Context() = delete;
|
||||
Context(const Context&) = delete;
|
||||
Context& operator=(const Context&) = delete;
|
||||
~Context();
|
||||
};
|
||||
|
||||
static Logging* GetInstance();
|
||||
|
||||
void SetGlobalContext(uint32_t name);
|
||||
void SetGlobalContext(const std::string& name);
|
||||
void SetGlobalContext(const char* name);
|
||||
void SetGlobalEnable(bool enabled);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
// Note: Implicit `this` argument counts as the first argument.
|
||||
__attribute__((__format__(__printf__, 2, 3)))
|
||||
#endif
|
||||
void
|
||||
Log(const char format[], ...);
|
||||
void Plot(int figure, const std::string& name, double value);
|
||||
void Plot(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
const std::string& alg_name);
|
||||
void Plot(int figure, const std::string& name, double value, uint32_t ssrc);
|
||||
void Plot(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
uint32_t ssrc,
|
||||
const std::string& alg_name);
|
||||
void PlotBar(int figure, const std::string& name, double value, int flow_id);
|
||||
void PlotBaselineBar(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
int flow_id);
|
||||
void PlotErrorBar(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
double ylow,
|
||||
double yhigh,
|
||||
const std::string& error_title,
|
||||
int flow_id);
|
||||
|
||||
void PlotLimitErrorBar(int figure,
|
||||
const std::string& name,
|
||||
double value,
|
||||
double ylow,
|
||||
double yhigh,
|
||||
const std::string& error_title,
|
||||
double ymax,
|
||||
const std::string& limit_title,
|
||||
int flow_id);
|
||||
void PlotLabel(int figure,
|
||||
const std::string& title,
|
||||
const std::string& y_label,
|
||||
int num_flows);
|
||||
|
||||
private:
|
||||
struct State {
|
||||
State();
|
||||
State(const std::string& new_tag, int64_t timestamp_ms, bool enabled);
|
||||
void MergePrevious(const State& previous);
|
||||
|
||||
std::string tag;
|
||||
int64_t timestamp_ms;
|
||||
bool enabled;
|
||||
};
|
||||
struct ThreadState {
|
||||
ThreadState();
|
||||
~ThreadState();
|
||||
State global_state;
|
||||
std::stack<State> stack;
|
||||
};
|
||||
typedef std::map<uint32_t, ThreadState> ThreadMap;
|
||||
|
||||
Logging();
|
||||
~Logging();
|
||||
|
||||
Logging(const Logging&) = delete;
|
||||
Logging& operator=(const Logging&) = delete;
|
||||
|
||||
void PushState(const std::string& append_to_tag,
|
||||
int64_t timestamp_ms,
|
||||
bool enabled);
|
||||
void PopState();
|
||||
|
||||
Mutex mutex_;
|
||||
ThreadMap thread_map_;
|
||||
};
|
||||
} // namespace bwe
|
||||
} // namespace testing
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/remote_bitrate_estimator/tools/bwe_rtp.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/parse.h"
|
||||
#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
|
||||
#include "test/rtp_file_reader.h"
|
||||
|
||||
ABSL_FLAG(std::string,
|
||||
extension_type,
|
||||
"abs",
|
||||
"Extension type, either abs for absolute send time or tsoffset "
|
||||
"for timestamp offset.");
|
||||
std::string ExtensionType() {
|
||||
return absl::GetFlag(FLAGS_extension_type);
|
||||
}
|
||||
|
||||
ABSL_FLAG(int, extension_id, 3, "Extension id.");
|
||||
int ExtensionId() {
|
||||
return absl::GetFlag(FLAGS_extension_id);
|
||||
}
|
||||
|
||||
ABSL_FLAG(std::string, input_file, "", "Input file.");
|
||||
std::string InputFile() {
|
||||
return absl::GetFlag(FLAGS_input_file);
|
||||
}
|
||||
|
||||
ABSL_FLAG(std::string,
|
||||
ssrc_filter,
|
||||
"",
|
||||
"Comma-separated list of SSRCs in hexadecimal which are to be "
|
||||
"used as input to the BWE (only applicable to pcap files).");
|
||||
std::set<uint32_t> SsrcFilter() {
|
||||
std::string ssrc_filter_string = absl::GetFlag(FLAGS_ssrc_filter);
|
||||
if (ssrc_filter_string.empty())
|
||||
return std::set<uint32_t>();
|
||||
std::stringstream ss;
|
||||
std::string ssrc_filter = ssrc_filter_string;
|
||||
std::set<uint32_t> ssrcs;
|
||||
|
||||
// Parse the ssrcs in hexadecimal format.
|
||||
ss << std::hex << ssrc_filter;
|
||||
uint32_t ssrc;
|
||||
while (ss >> ssrc) {
|
||||
ssrcs.insert(ssrc);
|
||||
ss.ignore(1, ',');
|
||||
}
|
||||
return ssrcs;
|
||||
}
|
||||
|
||||
bool ParseArgsAndSetupRtpReader(
|
||||
int argc,
|
||||
char** argv,
|
||||
std::unique_ptr<webrtc::test::RtpFileReader>& rtp_reader,
|
||||
webrtc::RtpHeaderExtensionMap& rtp_header_extensions) {
|
||||
absl::ParseCommandLine(argc, argv);
|
||||
std::string filename = InputFile();
|
||||
|
||||
std::set<uint32_t> ssrc_filter = SsrcFilter();
|
||||
fprintf(stderr, "Filter on SSRC: ");
|
||||
for (auto& s : ssrc_filter) {
|
||||
fprintf(stderr, "0x%08x, ", s);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
if (filename.substr(filename.find_last_of('.')) == ".pcap") {
|
||||
fprintf(stderr, "Opening as pcap\n");
|
||||
rtp_reader.reset(webrtc::test::RtpFileReader::Create(
|
||||
webrtc::test::RtpFileReader::kPcap, filename.c_str(), SsrcFilter()));
|
||||
} else {
|
||||
fprintf(stderr, "Opening as rtp\n");
|
||||
rtp_reader.reset(webrtc::test::RtpFileReader::Create(
|
||||
webrtc::test::RtpFileReader::kRtpDump, filename.c_str()));
|
||||
}
|
||||
if (!rtp_reader) {
|
||||
fprintf(stderr, "Cannot open input file %s\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
fprintf(stderr, "Input file: %s\n\n", filename.c_str());
|
||||
|
||||
webrtc::RTPExtensionType extension = webrtc::kRtpExtensionAbsoluteSendTime;
|
||||
if (ExtensionType() == "tsoffset") {
|
||||
extension = webrtc::kRtpExtensionTransmissionTimeOffset;
|
||||
fprintf(stderr, "Extension: toffset\n");
|
||||
} else if (ExtensionType() == "abs") {
|
||||
fprintf(stderr, "Extension: abs\n");
|
||||
} else {
|
||||
fprintf(stderr, "Unknown extension type\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
rtp_header_extensions.RegisterByType(ExtensionId(), extension);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_TOOLS_BWE_RTP_H_
|
||||
#define MODULES_REMOTE_BITRATE_ESTIMATOR_TOOLS_BWE_RTP_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
|
||||
#include "test/rtp_file_reader.h"
|
||||
|
||||
bool ParseArgsAndSetupRtpReader(
|
||||
int argc,
|
||||
char** argv,
|
||||
std::unique_ptr<webrtc::test::RtpFileReader>& rtp_reader,
|
||||
webrtc::RtpHeaderExtensionMap& rtp_header_extensions);
|
||||
|
||||
#endif // MODULES_REMOTE_BITRATE_ESTIMATOR_TOOLS_BWE_RTP_H_
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/remote_bitrate_estimator/tools/bwe_rtp.h"
|
||||
#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_packet.h"
|
||||
#include "rtc_base/strings/string_builder.h"
|
||||
#include "test/rtp_file_reader.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::unique_ptr<webrtc::test::RtpFileReader> reader;
|
||||
webrtc::RtpHeaderExtensionMap rtp_header_extensions;
|
||||
if (!ParseArgsAndSetupRtpReader(argc, argv, reader, rtp_header_extensions)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool arrival_time_only = (argc >= 5 && strncmp(argv[4], "-t", 2) == 0);
|
||||
|
||||
fprintf(stdout,
|
||||
"seqnum timestamp ts_offset abs_sendtime recvtime "
|
||||
"markerbit ssrc size original_size\n");
|
||||
int packet_counter = 0;
|
||||
int non_zero_abs_send_time = 0;
|
||||
int non_zero_ts_offsets = 0;
|
||||
webrtc::test::RtpPacket packet;
|
||||
while (reader->NextPacket(&packet)) {
|
||||
webrtc::RtpPacket header(&rtp_header_extensions);
|
||||
header.Parse(packet.data, packet.length);
|
||||
uint32_t abs_send_time = 0;
|
||||
if (header.GetExtension<webrtc::AbsoluteSendTime>(&abs_send_time) &&
|
||||
abs_send_time != 0)
|
||||
++non_zero_abs_send_time;
|
||||
int32_t toffset = 0;
|
||||
if (header.GetExtension<webrtc::TransmissionOffset>(&toffset) &&
|
||||
toffset != 0)
|
||||
++non_zero_ts_offsets;
|
||||
if (arrival_time_only) {
|
||||
rtc::StringBuilder ss;
|
||||
ss << static_cast<int64_t>(packet.time_ms) * 1000000;
|
||||
fprintf(stdout, "%s\n", ss.str().c_str());
|
||||
} else {
|
||||
fprintf(stdout, "%u %u %d %u %u %d %u %zu %zu\n", header.SequenceNumber(),
|
||||
header.Timestamp(), toffset, abs_send_time, packet.time_ms,
|
||||
header.Marker(), header.Ssrc(), packet.length,
|
||||
packet.original_length);
|
||||
}
|
||||
++packet_counter;
|
||||
}
|
||||
fprintf(stderr, "Parsed %d packets\n", packet_counter);
|
||||
fprintf(stderr, "Packets with non-zero absolute send time: %d\n",
|
||||
non_zero_abs_send_time);
|
||||
fprintf(stderr, "Packets with non-zero timestamp offset: %d\n",
|
||||
non_zero_ts_offsets);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue