summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/pacing
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/modules/pacing')
-rw-r--r--third_party/libwebrtc/modules/pacing/BUILD.gn117
-rw-r--r--third_party/libwebrtc/modules/pacing/DEPS6
-rw-r--r--third_party/libwebrtc/modules/pacing/OWNERS5
-rw-r--r--third_party/libwebrtc/modules/pacing/bitrate_prober.cc185
-rw-r--r--third_party/libwebrtc/modules/pacing/bitrate_prober.h126
-rw-r--r--third_party/libwebrtc/modules/pacing/bitrate_prober_unittest.cc371
-rw-r--r--third_party/libwebrtc/modules/pacing/g3doc/index.md164
-rw-r--r--third_party/libwebrtc/modules/pacing/interval_budget.cc68
-rw-r--r--third_party/libwebrtc/modules/pacing/interval_budget.h44
-rw-r--r--third_party/libwebrtc/modules/pacing/interval_budget_gn/moz.build221
-rw-r--r--third_party/libwebrtc/modules/pacing/interval_budget_unittest.cc123
-rw-r--r--third_party/libwebrtc/modules/pacing/pacing_controller.cc710
-rw-r--r--third_party/libwebrtc/modules/pacing/pacing_controller.h251
-rw-r--r--third_party/libwebrtc/modules/pacing/pacing_controller_unittest.cc2175
-rw-r--r--third_party/libwebrtc/modules/pacing/pacing_gn/moz.build239
-rw-r--r--third_party/libwebrtc/modules/pacing/packet_router.cc356
-rw-r--r--third_party/libwebrtc/modules/pacing/packet_router.h118
-rw-r--r--third_party/libwebrtc/modules/pacing/packet_router_unittest.cc736
-rw-r--r--third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc343
-rw-r--r--third_party/libwebrtc/modules/pacing/prioritized_packet_queue.h171
-rw-r--r--third_party/libwebrtc/modules/pacing/prioritized_packet_queue_unittest.cc363
-rw-r--r--third_party/libwebrtc/modules/pacing/rtp_packet_pacer.h74
-rw-r--r--third_party/libwebrtc/modules/pacing/task_queue_paced_sender.cc372
-rw-r--r--third_party/libwebrtc/modules/pacing/task_queue_paced_sender.h209
-rw-r--r--third_party/libwebrtc/modules/pacing/task_queue_paced_sender_unittest.cc913
25 files changed, 8460 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/pacing/BUILD.gn b/third_party/libwebrtc/modules/pacing/BUILD.gn
new file mode 100644
index 0000000000..3fdac71a0c
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/BUILD.gn
@@ -0,0 +1,117 @@
+# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS. All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+
+import("../../webrtc.gni")
+
+rtc_library("pacing") {
+ # Client code SHOULD NOT USE THIS TARGET, but for now it needs to be public
+ # because there exists client code that uses it.
+ # TODO(bugs.webrtc.org/9808): Move to private visibility as soon as that
+ # client code gets updated.
+ visibility = [ "*" ]
+ sources = [
+ "bitrate_prober.cc",
+ "bitrate_prober.h",
+ "pacing_controller.cc",
+ "pacing_controller.h",
+ "packet_router.cc",
+ "packet_router.h",
+ "prioritized_packet_queue.cc",
+ "prioritized_packet_queue.h",
+ "rtp_packet_pacer.h",
+ "task_queue_paced_sender.cc",
+ "task_queue_paced_sender.h",
+ ]
+
+ deps = [
+ ":interval_budget",
+ "../../api:field_trials_view",
+ "../../api:field_trials_view",
+ "../../api:function_view",
+ "../../api:sequence_checker",
+ "../../api/rtc_event_log",
+ "../../api/task_queue:pending_task_safety_flag",
+ "../../api/task_queue:task_queue",
+ "../../api/transport:field_trial_based_config",
+ "../../api/transport:network_control",
+ "../../api/units:data_rate",
+ "../../api/units:data_size",
+ "../../api/units:time_delta",
+ "../../api/units:timestamp",
+ "../../logging:rtc_event_bwe",
+ "../../logging:rtc_event_pacing",
+ "../../rtc_base:checks",
+ "../../rtc_base:event_tracer",
+ "../../rtc_base:logging",
+ "../../rtc_base:macromagic",
+ "../../rtc_base:rtc_numerics",
+ "../../rtc_base:rtc_task_queue",
+ "../../rtc_base:timeutils",
+ "../../rtc_base/experiments:field_trial_parser",
+ "../../rtc_base/synchronization:mutex",
+ "../../rtc_base/system:unused",
+ "../../system_wrappers",
+ "../../system_wrappers:metrics",
+ "../rtp_rtcp",
+ "../rtp_rtcp:rtp_rtcp_format",
+ "../utility:utility",
+ ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/memory",
+ "//third_party/abseil-cpp/absl/strings",
+ "//third_party/abseil-cpp/absl/types:optional",
+ ]
+}
+
+rtc_library("interval_budget") {
+ sources = [
+ "interval_budget.cc",
+ "interval_budget.h",
+ ]
+
+ deps = [
+ "../../rtc_base:checks",
+ "../../rtc_base:safe_conversions",
+ ]
+}
+
+if (rtc_include_tests) {
+ rtc_library("pacing_unittests") {
+ testonly = true
+
+ sources = [
+ "bitrate_prober_unittest.cc",
+ "interval_budget_unittest.cc",
+ "pacing_controller_unittest.cc",
+ "packet_router_unittest.cc",
+ "prioritized_packet_queue_unittest.cc",
+ "task_queue_paced_sender_unittest.cc",
+ ]
+ deps = [
+ ":interval_budget",
+ ":pacing",
+ "../../api/task_queue:task_queue",
+ "../../api/transport:network_control",
+ "../../api/units:data_rate",
+ "../../api/units:time_delta",
+ "../../api/units:timestamp",
+ "../../rtc_base:checks",
+ "../../rtc_base:rtc_base_tests_utils",
+ "../../rtc_base/experiments:alr_experiment",
+ "../../system_wrappers",
+ "../../test:explicit_key_value_config",
+ "../../test:scoped_key_value_config",
+ "../../test:test_support",
+ "../../test/time_controller:time_controller",
+ "../rtp_rtcp",
+ "../rtp_rtcp:mock_rtp_rtcp",
+ "../rtp_rtcp:rtp_rtcp_format",
+ ]
+ absl_deps = [ "//third_party/abseil-cpp/absl/functional:any_invocable" ]
+ }
+}
diff --git a/third_party/libwebrtc/modules/pacing/DEPS b/third_party/libwebrtc/modules/pacing/DEPS
new file mode 100644
index 0000000000..42f3dfcb14
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+system_wrappers",
+ # Avoid directly using field_trial. Instead use FieldTrialsView.
+ "-system_wrappers/include/field_trial.h",
+ "+logging/rtc_event_log"
+]
diff --git a/third_party/libwebrtc/modules/pacing/OWNERS b/third_party/libwebrtc/modules/pacing/OWNERS
new file mode 100644
index 0000000000..7266156891
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/OWNERS
@@ -0,0 +1,5 @@
+stefan@webrtc.org
+mflodman@webrtc.org
+philipel@webrtc.org
+srte@webrtc.org
+sprang@webrtc.org
diff --git a/third_party/libwebrtc/modules/pacing/bitrate_prober.cc b/third_party/libwebrtc/modules/pacing/bitrate_prober.cc
new file mode 100644
index 0000000000..e8ebf54f32
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/bitrate_prober.cc
@@ -0,0 +1,185 @@
+/*
+ * 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/pacing/bitrate_prober.h"
+
+#include <algorithm>
+
+#include "absl/memory/memory.h"
+#include "api/rtc_event_log/rtc_event.h"
+#include "api/rtc_event_log/rtc_event_log.h"
+#include "logging/rtc_event_log/events/rtc_event_probe_cluster_created.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+namespace {
+constexpr TimeDelta kProbeClusterTimeout = TimeDelta::Seconds(5);
+constexpr size_t kMaxPendingProbeClusters = 5;
+
+} // namespace
+
+BitrateProberConfig::BitrateProberConfig(
+ const FieldTrialsView* key_value_config)
+ : min_probe_delta("min_probe_delta", TimeDelta::Millis(2)),
+ max_probe_delay("max_probe_delay", TimeDelta::Millis(10)),
+ min_packet_size("min_packet_size", DataSize::Bytes(200)) {
+ ParseFieldTrial({&min_probe_delta, &max_probe_delay, &min_packet_size},
+ key_value_config->Lookup("WebRTC-Bwe-ProbingBehavior"));
+}
+
+BitrateProber::BitrateProber(const FieldTrialsView& field_trials)
+ : probing_state_(ProbingState::kDisabled),
+ next_probe_time_(Timestamp::PlusInfinity()),
+ config_(&field_trials) {
+ SetEnabled(true);
+}
+
+void BitrateProber::SetEnabled(bool enable) {
+ if (enable) {
+ if (probing_state_ == ProbingState::kDisabled) {
+ probing_state_ = ProbingState::kInactive;
+ RTC_LOG(LS_INFO) << "Bandwidth probing enabled, set to inactive";
+ }
+ } else {
+ probing_state_ = ProbingState::kDisabled;
+ RTC_LOG(LS_INFO) << "Bandwidth probing disabled";
+ }
+}
+
+void BitrateProber::OnIncomingPacket(DataSize packet_size) {
+ // Don't initialize probing unless we have something large enough to start
+ // probing.
+ // Note that the pacer can send several packets at once when sending a probe,
+ // and thus, packets can be smaller than needed for a probe.
+ if (probing_state_ == ProbingState::kInactive && !clusters_.empty() &&
+ packet_size >=
+ std::min(RecommendedMinProbeSize(), config_.min_packet_size.Get())) {
+ // Send next probe right away.
+ next_probe_time_ = Timestamp::MinusInfinity();
+ probing_state_ = ProbingState::kActive;
+ }
+}
+
+void BitrateProber::CreateProbeCluster(
+ const ProbeClusterConfig& cluster_config) {
+ RTC_DCHECK(probing_state_ != ProbingState::kDisabled);
+
+ while (!clusters_.empty() &&
+ (cluster_config.at_time - clusters_.front().requested_at >
+ kProbeClusterTimeout ||
+ clusters_.size() > kMaxPendingProbeClusters)) {
+ clusters_.pop();
+ }
+
+ ProbeCluster cluster;
+ cluster.requested_at = cluster_config.at_time;
+ cluster.pace_info.probe_cluster_min_probes =
+ cluster_config.target_probe_count;
+ cluster.pace_info.probe_cluster_min_bytes =
+ (cluster_config.target_data_rate * cluster_config.target_duration)
+ .bytes();
+ RTC_DCHECK_GE(cluster.pace_info.probe_cluster_min_bytes, 0);
+ cluster.pace_info.send_bitrate_bps = cluster_config.target_data_rate.bps();
+ cluster.pace_info.probe_cluster_id = cluster_config.id;
+ clusters_.push(cluster);
+
+ RTC_LOG(LS_INFO) << "Probe cluster (bitrate:min bytes:min packets): ("
+ << cluster.pace_info.send_bitrate_bps << ":"
+ << cluster.pace_info.probe_cluster_min_bytes << ":"
+ << cluster.pace_info.probe_cluster_min_probes << ")";
+
+ // If we are already probing, continue to do so. Otherwise set it to
+ // kInactive and wait for OnIncomingPacket to start the probing.
+ if (probing_state_ != ProbingState::kActive)
+ probing_state_ = ProbingState::kInactive;
+}
+
+Timestamp BitrateProber::NextProbeTime(Timestamp now) const {
+ // Probing is not active or probing is already complete.
+ if (probing_state_ != ProbingState::kActive || clusters_.empty()) {
+ return Timestamp::PlusInfinity();
+ }
+
+ return next_probe_time_;
+}
+
+absl::optional<PacedPacketInfo> BitrateProber::CurrentCluster(Timestamp now) {
+ if (clusters_.empty() || probing_state_ != ProbingState::kActive) {
+ return absl::nullopt;
+ }
+
+ if (next_probe_time_.IsFinite() &&
+ now - next_probe_time_ > config_.max_probe_delay.Get()) {
+ RTC_DLOG(LS_WARNING) << "Probe delay too high"
+ " (next_ms:"
+ << next_probe_time_.ms() << ", now_ms: " << now.ms()
+ << "), discarding probe cluster.";
+ clusters_.pop();
+ if (clusters_.empty()) {
+ probing_state_ = ProbingState::kSuspended;
+ return absl::nullopt;
+ }
+ }
+
+ PacedPacketInfo info = clusters_.front().pace_info;
+ info.probe_cluster_bytes_sent = clusters_.front().sent_bytes;
+ return info;
+}
+
+DataSize BitrateProber::RecommendedMinProbeSize() const {
+ if (clusters_.empty()) {
+ return DataSize::Zero();
+ }
+ DataRate send_rate =
+ DataRate::BitsPerSec(clusters_.front().pace_info.send_bitrate_bps);
+ return send_rate * config_.min_probe_delta;
+}
+
+void BitrateProber::ProbeSent(Timestamp now, DataSize size) {
+ RTC_DCHECK(probing_state_ == ProbingState::kActive);
+ RTC_DCHECK(!size.IsZero());
+
+ if (!clusters_.empty()) {
+ ProbeCluster* cluster = &clusters_.front();
+ if (cluster->sent_probes == 0) {
+ RTC_DCHECK(cluster->started_at.IsInfinite());
+ cluster->started_at = now;
+ }
+ cluster->sent_bytes += size.bytes<int>();
+ cluster->sent_probes += 1;
+ next_probe_time_ = CalculateNextProbeTime(*cluster);
+ if (cluster->sent_bytes >= cluster->pace_info.probe_cluster_min_bytes &&
+ cluster->sent_probes >= cluster->pace_info.probe_cluster_min_probes) {
+ clusters_.pop();
+ }
+ if (clusters_.empty()) {
+ probing_state_ = ProbingState::kSuspended;
+ }
+ }
+}
+
+Timestamp BitrateProber::CalculateNextProbeTime(
+ const ProbeCluster& cluster) const {
+ RTC_CHECK_GT(cluster.pace_info.send_bitrate_bps, 0);
+ RTC_CHECK(cluster.started_at.IsFinite());
+
+ // Compute the time delta from the cluster start to ensure probe bitrate stays
+ // close to the target bitrate. Result is in milliseconds.
+ DataSize sent_bytes = DataSize::Bytes(cluster.sent_bytes);
+ DataRate send_bitrate =
+ DataRate::BitsPerSec(cluster.pace_info.send_bitrate_bps);
+
+ TimeDelta delta = sent_bytes / send_bitrate;
+ return cluster.started_at + delta;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/bitrate_prober.h b/third_party/libwebrtc/modules/pacing/bitrate_prober.h
new file mode 100644
index 0000000000..4d8ec68c4f
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/bitrate_prober.h
@@ -0,0 +1,126 @@
+/*
+ * 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_PACING_BITRATE_PROBER_H_
+#define MODULES_PACING_BITRATE_PROBER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <queue>
+
+#include "api/transport/field_trial_based_config.h"
+#include "api/transport/network_types.h"
+#include "rtc_base/experiments/field_trial_parser.h"
+
+namespace webrtc {
+class RtcEventLog;
+
+struct BitrateProberConfig {
+ explicit BitrateProberConfig(const FieldTrialsView* key_value_config);
+ BitrateProberConfig(const BitrateProberConfig&) = default;
+ BitrateProberConfig& operator=(const BitrateProberConfig&) = default;
+ ~BitrateProberConfig() = default;
+
+ // A minimum interval between probes to allow scheduling to be feasible.
+ FieldTrialParameter<TimeDelta> min_probe_delta;
+ // Maximum amount of time each probe can be delayed.
+ FieldTrialParameter<TimeDelta> max_probe_delay;
+ // This is used to start sending a probe after a large enough packet.
+ // The min packet size is scaled with the bitrate we're probing at.
+ // This defines the max min packet size, meaning that on high bitrates
+ // a packet of at least this size is needed to trigger sending a probe.
+ FieldTrialParameter<DataSize> min_packet_size;
+};
+
+// Note that this class isn't thread-safe by itself and therefore relies
+// on being protected by the caller.
+class BitrateProber {
+ public:
+ explicit BitrateProber(const FieldTrialsView& field_trials);
+ ~BitrateProber() = default;
+
+ void SetEnabled(bool enable);
+
+ // Returns true if the prober is in a probing session, i.e., it currently
+ // wants packets to be sent out according to the time returned by
+ // TimeUntilNextProbe().
+ bool is_probing() const { return probing_state_ == ProbingState::kActive; }
+
+ // Initializes a new probing session if the prober is allowed to probe. Does
+ // not initialize the prober unless the packet size is large enough to probe
+ // with.
+ void OnIncomingPacket(DataSize packet_size);
+
+ // Create a cluster used to probe.
+ void CreateProbeCluster(const ProbeClusterConfig& cluster_config);
+ // Returns the time at which the next probe should be sent to get accurate
+ // probing. If probing is not desired at this time, Timestamp::PlusInfinity()
+ // will be returned.
+ // TODO(bugs.webrtc.org/11780): Remove `now` argument when old mode is gone.
+ Timestamp NextProbeTime(Timestamp now) const;
+
+ // Information about the current probing cluster.
+ absl::optional<PacedPacketInfo> CurrentCluster(Timestamp now);
+
+ // Returns the minimum number of bytes that the prober recommends for
+ // the next probe, or zero if not probing. A probe can consist of multiple
+ // packets that are sent back to back.
+ DataSize RecommendedMinProbeSize() const;
+
+ // Called to report to the prober that a probe has been sent. In case of
+ // multiple packets per probe, this call would be made at the end of sending
+ // the last packet in probe. `size` is the total size of all packets in probe.
+ void ProbeSent(Timestamp now, DataSize size);
+
+ private:
+ enum class ProbingState {
+ // Probing will not be triggered in this state at all times.
+ kDisabled,
+ // Probing is enabled and ready to trigger on the first packet arrival.
+ kInactive,
+ // Probe cluster is filled with the set of data rates to be probed and
+ // probes are being sent.
+ kActive,
+ // Probing is enabled, but currently suspended until an explicit trigger
+ // to start probing again.
+ kSuspended,
+ };
+
+ // A probe cluster consists of a set of probes. Each probe in turn can be
+ // divided into a number of packets to accommodate the MTU on the network.
+ struct ProbeCluster {
+ PacedPacketInfo pace_info;
+
+ int sent_probes = 0;
+ int sent_bytes = 0;
+ Timestamp requested_at = Timestamp::MinusInfinity();
+ Timestamp started_at = Timestamp::MinusInfinity();
+ int retries = 0;
+ };
+
+ Timestamp CalculateNextProbeTime(const ProbeCluster& cluster) const;
+
+ ProbingState probing_state_;
+
+ // Probe bitrate per packet. These are used to compute the delta relative to
+ // the previous probe packet based on the size and time when that packet was
+ // sent.
+ std::queue<ProbeCluster> clusters_;
+
+ // Time the next probe should be sent when in kActive state.
+ Timestamp next_probe_time_;
+
+ BitrateProberConfig config_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_PACING_BITRATE_PROBER_H_
diff --git a/third_party/libwebrtc/modules/pacing/bitrate_prober_unittest.cc b/third_party/libwebrtc/modules/pacing/bitrate_prober_unittest.cc
new file mode 100644
index 0000000000..3be7d2d99e
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/bitrate_prober_unittest.cc
@@ -0,0 +1,371 @@
+/*
+ * 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/pacing/bitrate_prober.h"
+
+#include <algorithm>
+
+#include "api/units/data_rate.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "test/explicit_key_value_config.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+TEST(BitrateProberTest, VerifyStatesAndTimeBetweenProbes) {
+ const FieldTrialBasedConfig config;
+ BitrateProber prober(config);
+ EXPECT_FALSE(prober.is_probing());
+
+ Timestamp now = Timestamp::Zero();
+ const Timestamp start_time = now;
+ EXPECT_EQ(prober.NextProbeTime(now), Timestamp::PlusInfinity());
+
+ const DataRate kTestBitrate1 = DataRate::KilobitsPerSec(900);
+ const DataRate kTestBitrate2 = DataRate::KilobitsPerSec(1800);
+ const int kClusterSize = 5;
+ const DataSize kProbeSize = DataSize::Bytes(1000);
+ const TimeDelta kMinProbeDuration = TimeDelta::Millis(15);
+
+ prober.CreateProbeCluster({.at_time = now,
+ .target_data_rate = kTestBitrate1,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+ prober.CreateProbeCluster({.at_time = now,
+ .target_data_rate = kTestBitrate2,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 1});
+ EXPECT_FALSE(prober.is_probing());
+
+ prober.OnIncomingPacket(kProbeSize);
+ EXPECT_TRUE(prober.is_probing());
+ EXPECT_EQ(0, prober.CurrentCluster(now)->probe_cluster_id);
+
+ // First packet should probe as soon as possible.
+ EXPECT_EQ(Timestamp::MinusInfinity(), prober.NextProbeTime(now));
+
+ for (int i = 0; i < kClusterSize; ++i) {
+ now = std::max(now, prober.NextProbeTime(now));
+ EXPECT_EQ(now, std::max(now, prober.NextProbeTime(now)));
+ EXPECT_EQ(0, prober.CurrentCluster(now)->probe_cluster_id);
+ prober.ProbeSent(now, kProbeSize);
+ }
+
+ EXPECT_GE(now - start_time, kMinProbeDuration);
+ // Verify that the actual bitrate is withing 10% of the target.
+ DataRate bitrate = kProbeSize * (kClusterSize - 1) / (now - start_time);
+ EXPECT_GT(bitrate, kTestBitrate1 * 0.9);
+ EXPECT_LT(bitrate, kTestBitrate1 * 1.1);
+
+ now = std::max(now, prober.NextProbeTime(now));
+ Timestamp probe2_started = now;
+
+ for (int i = 0; i < kClusterSize; ++i) {
+ now = std::max(now, prober.NextProbeTime(now));
+ EXPECT_EQ(now, std::max(now, prober.NextProbeTime(now)));
+ EXPECT_EQ(1, prober.CurrentCluster(now)->probe_cluster_id);
+ prober.ProbeSent(now, kProbeSize);
+ }
+
+ // Verify that the actual bitrate is withing 10% of the target.
+ TimeDelta duration = now - probe2_started;
+ EXPECT_GE(duration, kMinProbeDuration);
+ bitrate = (kProbeSize * (kClusterSize - 1)) / duration;
+ EXPECT_GT(bitrate, kTestBitrate2 * 0.9);
+ EXPECT_LT(bitrate, kTestBitrate2 * 1.1);
+
+ EXPECT_EQ(prober.NextProbeTime(now), Timestamp::PlusInfinity());
+ EXPECT_FALSE(prober.is_probing());
+}
+
+TEST(BitrateProberTest, DoesntProbeWithoutRecentPackets) {
+ const FieldTrialBasedConfig config;
+ BitrateProber prober(config);
+ const DataSize kProbeSize = DataSize::Bytes(1000);
+
+ Timestamp now = Timestamp::Zero();
+ EXPECT_EQ(prober.NextProbeTime(now), Timestamp::PlusInfinity());
+
+ prober.CreateProbeCluster({.at_time = now,
+ .target_data_rate = DataRate::KilobitsPerSec(900),
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+ EXPECT_FALSE(prober.is_probing());
+
+ prober.OnIncomingPacket(kProbeSize);
+ EXPECT_TRUE(prober.is_probing());
+ EXPECT_EQ(now, std::max(now, prober.NextProbeTime(now)));
+ prober.ProbeSent(now, kProbeSize);
+}
+
+TEST(BitrateProberTest, DiscardsDelayedProbes) {
+ const TimeDelta kMaxProbeDelay = TimeDelta::Millis(3);
+ const test::ExplicitKeyValueConfig trials(
+ "WebRTC-Bwe-ProbingBehavior/"
+ "abort_delayed_probes:1,"
+ "max_probe_delay:3ms/");
+ BitrateProber prober(trials);
+ const DataSize kProbeSize = DataSize::Bytes(1000);
+
+ Timestamp now = Timestamp::Zero();
+
+ // Add two probe clusters.
+ prober.CreateProbeCluster({.at_time = now,
+ .target_data_rate = DataRate::KilobitsPerSec(900),
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+
+ prober.OnIncomingPacket(kProbeSize);
+ EXPECT_TRUE(prober.is_probing());
+ EXPECT_EQ(prober.CurrentCluster(now)->probe_cluster_id, 0);
+ // Advance to first probe time and indicate sent probe.
+ now = std::max(now, prober.NextProbeTime(now));
+ prober.ProbeSent(now, kProbeSize);
+
+ // Advance time 1ms past timeout for the next probe.
+ Timestamp next_probe_time = prober.NextProbeTime(now);
+ EXPECT_GT(next_probe_time, now);
+ now += next_probe_time - now + kMaxProbeDelay + TimeDelta::Millis(1);
+
+ // Still indicates the time we wanted to probe at.
+ EXPECT_EQ(prober.NextProbeTime(now), next_probe_time);
+ // First and only cluster removed due to timeout.
+ EXPECT_FALSE(prober.CurrentCluster(now).has_value());
+}
+
+TEST(BitrateProberTest, LimitsNumberOfPendingProbeClusters) {
+ const FieldTrialBasedConfig config;
+ BitrateProber prober(config);
+ const DataSize kProbeSize = DataSize::Bytes(1000);
+ Timestamp now = Timestamp::Zero();
+ prober.CreateProbeCluster({.at_time = now,
+ .target_data_rate = DataRate::KilobitsPerSec(900),
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+ prober.OnIncomingPacket(kProbeSize);
+ ASSERT_TRUE(prober.is_probing());
+ ASSERT_EQ(prober.CurrentCluster(now)->probe_cluster_id, 0);
+
+ for (int i = 1; i < 11; ++i) {
+ prober.CreateProbeCluster(
+ {.at_time = now,
+ .target_data_rate = DataRate::KilobitsPerSec(900),
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = i});
+ prober.OnIncomingPacket(kProbeSize);
+ }
+ // Expect some clusters has been dropped.
+ EXPECT_TRUE(prober.is_probing());
+ EXPECT_GE(prober.CurrentCluster(now)->probe_cluster_id, 5);
+
+ Timestamp max_expected_probe_time = now + TimeDelta::Seconds(1);
+ while (prober.is_probing() && now < max_expected_probe_time) {
+ now = std::max(now, prober.NextProbeTime(now));
+ prober.ProbeSent(now, kProbeSize);
+ }
+ EXPECT_FALSE(prober.is_probing());
+}
+
+TEST(BitrateProberTest, DoesntInitializeProbingForSmallPackets) {
+ const FieldTrialBasedConfig config;
+ BitrateProber prober(config);
+ prober.SetEnabled(true);
+ ASSERT_FALSE(prober.is_probing());
+
+ prober.CreateProbeCluster({.at_time = Timestamp::Zero(),
+ .target_data_rate = DataRate::KilobitsPerSec(1000),
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+ prober.OnIncomingPacket(DataSize::Bytes(100));
+
+ EXPECT_FALSE(prober.is_probing());
+}
+
+TEST(BitrateProberTest, DoesInitializeProbingForSmallPacketsIfConfigured) {
+ const test::ExplicitKeyValueConfig config(
+ "WebRTC-Bwe-ProbingBehavior/"
+ "min_packet_size:0bytes/");
+ BitrateProber prober(config);
+ prober.SetEnabled(true);
+ ASSERT_FALSE(prober.is_probing());
+
+ prober.CreateProbeCluster({.at_time = Timestamp::Zero(),
+ .target_data_rate = DataRate::KilobitsPerSec(1000),
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+ prober.OnIncomingPacket(DataSize::Bytes(10));
+
+ EXPECT_TRUE(prober.is_probing());
+}
+
+TEST(BitrateProberTest, VerifyProbeSizeOnHighBitrate) {
+ const FieldTrialBasedConfig config;
+ BitrateProber prober(config);
+
+ const DataRate kHighBitrate = DataRate::KilobitsPerSec(10000); // 10 Mbps
+
+ prober.CreateProbeCluster({.at_time = Timestamp::Zero(),
+ .target_data_rate = kHighBitrate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+ // Probe size should ensure a minimum of 1 ms interval.
+ EXPECT_GT(prober.RecommendedMinProbeSize(),
+ kHighBitrate * TimeDelta::Millis(1));
+}
+
+TEST(BitrateProberTest, ProbeSizeCanBeSetWithFieldTrial) {
+ const test::ExplicitKeyValueConfig trials(
+ "WebRTC-Bwe-ProbingBehavior/min_probe_delta:20ms/");
+ BitrateProber prober(trials);
+ prober.SetEnabled(true);
+
+ const DataRate kHighBitrate = DataRate::KilobitsPerSec(10000); // 10 Mbps
+
+ prober.CreateProbeCluster({.at_time = Timestamp::Zero(),
+ .target_data_rate = kHighBitrate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+ EXPECT_EQ(prober.RecommendedMinProbeSize(),
+ kHighBitrate * TimeDelta::Millis(20));
+
+ prober.OnIncomingPacket(DataSize::Bytes(1000));
+ // Next time to send probe should be "min_probe_delta" if the recommended
+ // number of bytes has been sent.
+ prober.ProbeSent(Timestamp::Zero(), prober.RecommendedMinProbeSize());
+ EXPECT_EQ(prober.NextProbeTime(Timestamp::Zero()),
+ Timestamp::Zero() + TimeDelta::Millis(20));
+}
+
+TEST(BitrateProberTest, MinumumNumberOfProbingPackets) {
+ const FieldTrialBasedConfig config;
+ BitrateProber prober(config);
+ // Even when probing at a low bitrate we expect a minimum number
+ // of packets to be sent.
+ const DataRate kBitrate = DataRate::KilobitsPerSec(100);
+ const DataSize kPacketSize = DataSize::Bytes(1000);
+
+ Timestamp now = Timestamp::Zero();
+ prober.CreateProbeCluster({.at_time = Timestamp::Zero(),
+ .target_data_rate = kBitrate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+
+ prober.OnIncomingPacket(kPacketSize);
+ for (int i = 0; i < 5; ++i) {
+ EXPECT_TRUE(prober.is_probing());
+ prober.ProbeSent(now, kPacketSize);
+ }
+
+ EXPECT_FALSE(prober.is_probing());
+}
+
+TEST(BitrateProberTest, ScaleBytesUsedForProbing) {
+ const FieldTrialBasedConfig config;
+ BitrateProber prober(config);
+ const DataRate kBitrate = DataRate::KilobitsPerSec(10000); // 10 Mbps.
+ const DataSize kPacketSize = DataSize::Bytes(1000);
+ const DataSize kExpectedDataSent = kBitrate * TimeDelta::Millis(15);
+
+ Timestamp now = Timestamp::Zero();
+ prober.CreateProbeCluster({.at_time = Timestamp::Zero(),
+ .target_data_rate = kBitrate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+ prober.OnIncomingPacket(kPacketSize);
+ DataSize data_sent = DataSize::Zero();
+ while (data_sent < kExpectedDataSent) {
+ ASSERT_TRUE(prober.is_probing());
+ prober.ProbeSent(now, kPacketSize);
+ data_sent += kPacketSize;
+ }
+
+ EXPECT_FALSE(prober.is_probing());
+}
+
+TEST(BitrateProberTest, HighBitrateProbing) {
+ const FieldTrialBasedConfig config;
+ BitrateProber prober(config);
+ const DataRate kBitrate = DataRate::KilobitsPerSec(1000000); // 1 Gbps.
+ const DataSize kPacketSize = DataSize::Bytes(1000);
+ const DataSize kExpectedDataSent = kBitrate * TimeDelta::Millis(15);
+
+ Timestamp now = Timestamp::Zero();
+ prober.CreateProbeCluster({.at_time = Timestamp::Zero(),
+ .target_data_rate = kBitrate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+ prober.OnIncomingPacket(kPacketSize);
+ DataSize data_sent = DataSize::Zero();
+ while (data_sent < kExpectedDataSent) {
+ ASSERT_TRUE(prober.is_probing());
+ prober.ProbeSent(now, kPacketSize);
+ data_sent += kPacketSize;
+ }
+
+ EXPECT_FALSE(prober.is_probing());
+}
+
+TEST(BitrateProberTest, ProbeClusterTimeout) {
+ const FieldTrialBasedConfig config;
+ BitrateProber prober(config);
+ const DataRate kBitrate = DataRate::KilobitsPerSec(300);
+ const DataSize kSmallPacketSize = DataSize::Bytes(20);
+ // Expecting two probe clusters of 5 packets each.
+ const DataSize kExpectedDataSent = kSmallPacketSize * 2 * 5;
+ const TimeDelta kTimeout = TimeDelta::Millis(5000);
+
+ Timestamp now = Timestamp::Zero();
+ prober.CreateProbeCluster({.at_time = now,
+ .target_data_rate = kBitrate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0});
+ prober.OnIncomingPacket(kSmallPacketSize);
+ EXPECT_FALSE(prober.is_probing());
+ now += kTimeout;
+ prober.CreateProbeCluster({.at_time = now,
+ .target_data_rate = kBitrate / 10,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 1});
+ prober.OnIncomingPacket(kSmallPacketSize);
+ EXPECT_FALSE(prober.is_probing());
+ now += TimeDelta::Millis(1);
+ prober.CreateProbeCluster({.at_time = now,
+ .target_data_rate = kBitrate / 10,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 2});
+ prober.OnIncomingPacket(kSmallPacketSize);
+ EXPECT_TRUE(prober.is_probing());
+ DataSize data_sent = DataSize::Zero();
+ while (data_sent < kExpectedDataSent) {
+ ASSERT_TRUE(prober.is_probing());
+ prober.ProbeSent(now, kSmallPacketSize);
+ data_sent += kSmallPacketSize;
+ }
+
+ EXPECT_FALSE(prober.is_probing());
+}
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/g3doc/index.md b/third_party/libwebrtc/modules/pacing/g3doc/index.md
new file mode 100644
index 0000000000..69f1e69513
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/g3doc/index.md
@@ -0,0 +1,164 @@
+<!-- go/cmark -->
+<!--* freshness: {owner: 'sprang' reviewed: '2021-04-12'} *-->
+
+# Paced Sending
+
+The paced sender, often referred to as just the "pacer", is a part of the WebRTC
+RTP stack used primarily to smooth the flow of packets sent onto the network.
+
+## Background
+
+Consider a video stream at 5Mbps and 30fps. This would in an ideal world result
+in each frame being ~21kB large and packetized into 18 RTP packets. While the
+average bitrate over say a one second sliding window would be a correct 5Mbps,
+on a shorter time scale it can be seen as a burst of 167Mbps every 33ms, each
+followed by a 32ms silent period. Further, it is quite common that video
+encoders overshoot the target frame size in case of sudden movement especially
+dealing with screensharing. Frames being 10x or even 100x larger than the ideal
+size is an all too real scenario. These packet bursts can cause several issues,
+such as congesting networks and causing buffer bloat or even packet loss. Most
+sessions have more than one media stream, e.g. a video and an audio track. If
+you put a frame on the wire in one go, and those packets take 100ms to reach the
+other side - that means you have now blocked any audio packets from reaching the
+remote end in time as well.
+
+The paced sender solves this by having a buffer in which media is queued, and
+then using a _leaky bucket_ algorithm to pace them onto the network. The buffer
+contains separate fifo streams for all media tracks so that e.g. audio can be
+prioritized over video - and equal prio streams can be sent in a round-robin
+fashion to avoid any one stream blocking others.
+
+Since the pacer is in control of the bitrate sent on the wire, it is also used
+to generate padding in cases where a minimum send rate is required - and to
+generate packet trains if bitrate probing is used.
+
+## Life of a Packet
+
+The typical path for media packets when using the paced sender looks something
+like this:
+
+1. `RTPSenderVideo` or `RTPSenderAudio` packetizes media into RTP packets.
+2. The packets are sent to the [RTPSender] class for transmission.
+3. The pacer is called via [RtpPacketSender] interface to enqueue the packet
+ batch.
+4. The packets are put into a queue within the pacer awaiting opportune moments
+ to send them.
+5. At a calculated time, the pacer calls the `PacingController::PacketSender()`
+ callback method, normally implemented by the [PacketRouter] class.
+6. The router forwards the packet to the correct RTP module based on the
+ packet's SSRC, and in which the `RTPSenderEgress` class makes final time
+ stamping, potentially records it for retransmissions etc.
+7. The packet is sent to the low-level `Transport` interface, after which it is
+ now out of scope.
+
+Asynchronously to this, the estimated available send bandwidth is determined -
+and the target send rate is set on the `RtpPacketPacer` via the `void
+SetPacingRates(DataRate pacing_rate, DataRate padding_rate)` method.
+
+## Packet Prioritization
+
+The pacer prioritized packets based on two criteria:
+
+* Packet type, with most to least prioritized:
+ 1. Audio
+ 2. Retransmissions
+ 3. Video and FEC
+ 4. Padding
+* Enqueue order
+
+The enqueue order is enforced on a per stream (SSRC) basis. Given equal
+priority, the [RoundRobinPacketQueue] alternates between media streams to ensure
+no stream needlessly blocks others.
+
+## Implementations
+
+The main class to use is called [TaskQueuePacedSender]. It uses a task queue to
+manage thread safety and schedule delayed tasks, but delegates most of the actual
+work to the `PacingController` class.
+This way, it's possible to develop a custom pacer with different scheduling
+mechanism - but ratain the same pacing logic.
+
+## The Packet Router
+
+An adjacent component called [PacketRouter] is used to route packets coming out
+of the pacer and into the correct RTP module. It has the following functions:
+
+* The `SendPacket` method looks up an RTP module with an SSRC corresponding to
+ the packet for further routing to the network.
+* If send-side bandwidth estimation is used, it populates the transport-wide
+ sequence number extension.
+* Generate padding. Modules supporting payload-based padding are prioritized,
+ with the last module to have sent media always being the first choice.
+* Returns any generated FEC after having sent media.
+* Forwards REMB and/or TransportFeedback messages to suitable RTP modules.
+
+At present the FEC is generated on a per SSRC basis, so is always returned from
+an RTP module after sending media. Hopefully one day we will support covering
+multiple streams with a single FlexFEC stream - and the packet router is the
+likely place for that FEC generator to live. It may even be used for FEC padding
+as an alternative to RTX.
+
+## The API
+
+The section outlines the classes and methods relevant to a few different use
+cases of the pacer.
+
+### Packet sending
+
+For sending packets, use
+`RtpPacketSender::EnqueuePackets(std::vector<std::unique_ptr<RtpPacketToSend>>
+packets)` The pacer takes a `PacingController::PacketSender` as constructor
+argument, this callback is used when it's time to actually send packets.
+
+### Send rates
+
+To control the send rate, use `void SetPacingRates(DataRate pacing_rate,
+DataRate padding_rate)` If the packet queue becomes empty and the send rate
+drops below `padding_rate`, the pacer will request padding packets from the
+`PacketRouter`.
+
+In order to completely suspend/resume sending data (e.g. due to network
+availability), use the `Pause()` and `Resume()` methods.
+
+The specified pacing rate may be overriden in some cases, e.g. due to extreme
+encoder overshoot. Use `void SetQueueTimeLimit(TimeDelta limit)` to specify the
+longest time you want packets to spend waiting in the pacer queue (pausing
+excluded). The actual send rate may then be increased past the pacing_rate to
+try to make the _average_ queue time less than that requested limit. The
+rationale for this is that if the send queue is say longer than three seconds,
+it's better to risk packet loss and then try to recover using a key-frame rather
+than cause severe delays.
+
+### Bandwidth estimation
+
+If the bandwidth estimator supports bandwidth probing, it may request a cluster
+of packets to be sent at a specified rate in order to gauge if this causes
+increased delay/loss on the network. Use the `void CreateProbeCluster(...)`
+method - packets sent via this `PacketRouter` will be marked with the
+corresponding cluster_id in the attached `PacedPacketInfo` struct.
+
+If congestion window pushback is used, the state can be updated using
+`SetCongestionWindow()` and `UpdateOutstandingData()`.
+
+A few more methods control how we pace: * `SetAccountForAudioPackets()`
+determines if audio packets count into bandwidth consumed. *
+`SetIncludeOverhead()` determines if the entire RTP packet size counts into
+bandwidth used (otherwise just media payload). * `SetTransportOverhead()` sets
+an additional data size consumed per packet, representing e.g. UDP/IP headers.
+
+### Stats
+
+Several methods are used to gather statistics in pacer state:
+
+* `OldestPacketWaitTime()` time since the oldest packet in the queue was
+ added.
+* `QueueSizeData()` total bytes currently in the queue.
+* `FirstSentPacketTime()` absolute time the first packet was sent.
+* `ExpectedQueueTime()` total bytes in the queue divided by the send rate.
+
+[RTPSender]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/modules/rtp_rtcp/source/rtp_sender.h;drc=77ee8542dd35d5143b5788ddf47fb7cdb96eb08e
+[RtpPacketSender]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/modules/rtp_rtcp/include/rtp_packet_sender.h;drc=ea55b0872f14faab23a4e5dbcb6956369c8ed5dc
+[RtpPacketPacer]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/modules/pacing/rtp_packet_pacer.h;drc=e7bc3a347760023dd4840cf6ebdd1e6c8592f4d7
+[PacketRouter]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/modules/pacing/packet_router.h;drc=3d2210876e31d0bb5c7de88b27fd02ceb1f4e03e
+[TaskQueuePacedSender]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/modules/pacing/task_queue_paced_sender.h;drc=5051693ada61bc7b78855c6fb3fa87a0394fa813
+[RoundRobinPacketQueue]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/modules/pacing/round_robin_packet_queue.h;drc=b571ff48f8fe07678da5a854cd6c3f5dde02855f
diff --git a/third_party/libwebrtc/modules/pacing/interval_budget.cc b/third_party/libwebrtc/modules/pacing/interval_budget.cc
new file mode 100644
index 0000000000..321ca46be4
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/interval_budget.cc
@@ -0,0 +1,68 @@
+/*
+ * 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/pacing/interval_budget.h"
+
+#include <algorithm>
+
+#include "rtc_base/numerics/safe_conversions.h"
+
+namespace webrtc {
+namespace {
+constexpr int64_t kWindowMs = 500;
+}
+
+IntervalBudget::IntervalBudget(int initial_target_rate_kbps)
+ : IntervalBudget(initial_target_rate_kbps, false) {}
+
+IntervalBudget::IntervalBudget(int initial_target_rate_kbps,
+ bool can_build_up_underuse)
+ : bytes_remaining_(0), can_build_up_underuse_(can_build_up_underuse) {
+ set_target_rate_kbps(initial_target_rate_kbps);
+}
+
+void IntervalBudget::set_target_rate_kbps(int target_rate_kbps) {
+ target_rate_kbps_ = target_rate_kbps;
+ max_bytes_in_budget_ = (kWindowMs * target_rate_kbps_) / 8;
+ bytes_remaining_ = std::min(std::max(-max_bytes_in_budget_, bytes_remaining_),
+ max_bytes_in_budget_);
+}
+
+void IntervalBudget::IncreaseBudget(int64_t delta_time_ms) {
+ int64_t bytes = target_rate_kbps_ * delta_time_ms / 8;
+ if (bytes_remaining_ < 0 || can_build_up_underuse_) {
+ // We overused last interval, compensate this interval.
+ bytes_remaining_ = std::min(bytes_remaining_ + bytes, max_bytes_in_budget_);
+ } else {
+ // If we underused last interval we can't use it this interval.
+ bytes_remaining_ = std::min(bytes, max_bytes_in_budget_);
+ }
+}
+
+void IntervalBudget::UseBudget(size_t bytes) {
+ bytes_remaining_ = std::max(bytes_remaining_ - static_cast<int>(bytes),
+ -max_bytes_in_budget_);
+}
+
+size_t IntervalBudget::bytes_remaining() const {
+ return rtc::saturated_cast<size_t>(std::max<int64_t>(0, bytes_remaining_));
+}
+
+double IntervalBudget::budget_ratio() const {
+ if (max_bytes_in_budget_ == 0)
+ return 0.0;
+ return static_cast<double>(bytes_remaining_) / max_bytes_in_budget_;
+}
+
+int IntervalBudget::target_rate_kbps() const {
+ return target_rate_kbps_;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/interval_budget.h b/third_party/libwebrtc/modules/pacing/interval_budget.h
new file mode 100644
index 0000000000..faeb1d8fc4
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/interval_budget.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_PACING_INTERVAL_BUDGET_H_
+#define MODULES_PACING_INTERVAL_BUDGET_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace webrtc {
+
+// TODO(tschumim): Reflector IntervalBudget so that we can set a under- and
+// over-use budget in ms.
+class IntervalBudget {
+ public:
+ explicit IntervalBudget(int initial_target_rate_kbps);
+ IntervalBudget(int initial_target_rate_kbps, bool can_build_up_underuse);
+ void set_target_rate_kbps(int target_rate_kbps);
+
+ // TODO(tschumim): Unify IncreaseBudget and UseBudget to one function.
+ void IncreaseBudget(int64_t delta_time_ms);
+ void UseBudget(size_t bytes);
+
+ size_t bytes_remaining() const;
+ double budget_ratio() const;
+ int target_rate_kbps() const;
+
+ private:
+ int target_rate_kbps_;
+ int64_t max_bytes_in_budget_;
+ int64_t bytes_remaining_;
+ bool can_build_up_underuse_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_PACING_INTERVAL_BUDGET_H_
diff --git a/third_party/libwebrtc/modules/pacing/interval_budget_gn/moz.build b/third_party/libwebrtc/modules/pacing/interval_budget_gn/moz.build
new file mode 100644
index 0000000000..17d4371d72
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/interval_budget_gn/moz.build
@@ -0,0 +1,221 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+ ### This moz.build was AUTOMATICALLY GENERATED from a GN config, ###
+ ### DO NOT edit it by hand. ###
+
+COMPILE_FLAGS["OS_INCLUDES"] = []
+AllowCompilerWarnings()
+
+DEFINES["ABSL_ALLOCATOR_NOTHROW"] = "1"
+DEFINES["RTC_DAV1D_IN_INTERNAL_DECODER_FACTORY"] = True
+DEFINES["RTC_ENABLE_VP9"] = True
+DEFINES["WEBRTC_ENABLE_PROTOBUF"] = "0"
+DEFINES["WEBRTC_LIBRARY_IMPL"] = True
+DEFINES["WEBRTC_MOZILLA_BUILD"] = True
+DEFINES["WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS"] = "0"
+DEFINES["WEBRTC_STRICT_FIELD_TRIALS"] = "0"
+
+FINAL_LIBRARY = "webrtc"
+
+
+LOCAL_INCLUDES += [
+ "!/ipc/ipdl/_ipdlheaders",
+ "!/third_party/libwebrtc/gen",
+ "/ipc/chromium/src",
+ "/third_party/libwebrtc/",
+ "/third_party/libwebrtc/third_party/abseil-cpp/",
+ "/tools/profiler/public"
+]
+
+UNIFIED_SOURCES += [
+ "/third_party/libwebrtc/modules/pacing/interval_budget.cc"
+]
+
+if not CONFIG["MOZ_DEBUG"]:
+
+ DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "0"
+ DEFINES["NDEBUG"] = True
+ DEFINES["NVALGRIND"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1":
+
+ DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "1"
+
+if CONFIG["OS_TARGET"] == "Android":
+
+ DEFINES["ANDROID"] = True
+ DEFINES["ANDROID_NDK_VERSION_ROLL"] = "r22_1"
+ DEFINES["HAVE_SYS_UIO_H"] = True
+ DEFINES["WEBRTC_ANDROID"] = True
+ DEFINES["WEBRTC_ANDROID_OPENSLES"] = True
+ DEFINES["WEBRTC_LINUX"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_GNU_SOURCE"] = True
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+ OS_LIBS += [
+ "log"
+ ]
+
+if CONFIG["OS_TARGET"] == "Darwin":
+
+ DEFINES["WEBRTC_MAC"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_LIBCPP_HAS_NO_ALIGNED_ALLOCATION"] = True
+ DEFINES["__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES"] = "0"
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+if CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["USE_AURA"] = "1"
+ DEFINES["USE_GLIB"] = "1"
+ DEFINES["USE_NSS_CERTS"] = "1"
+ DEFINES["USE_OZONE"] = "1"
+ DEFINES["USE_UDEV"] = True
+ DEFINES["WEBRTC_LINUX"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_FILE_OFFSET_BITS"] = "64"
+ DEFINES["_LARGEFILE64_SOURCE"] = True
+ DEFINES["_LARGEFILE_SOURCE"] = True
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+if CONFIG["OS_TARGET"] == "OpenBSD":
+
+ DEFINES["USE_GLIB"] = "1"
+ DEFINES["USE_OZONE"] = "1"
+ DEFINES["USE_X11"] = "1"
+ DEFINES["WEBRTC_BSD"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_FILE_OFFSET_BITS"] = "64"
+ DEFINES["_LARGEFILE64_SOURCE"] = True
+ DEFINES["_LARGEFILE_SOURCE"] = True
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+if CONFIG["OS_TARGET"] == "WINNT":
+
+ DEFINES["CERT_CHAIN_PARA_HAS_EXTRA_FIELDS"] = True
+ DEFINES["NOMINMAX"] = True
+ DEFINES["NTDDI_VERSION"] = "0x0A000000"
+ DEFINES["PSAPI_VERSION"] = "2"
+ DEFINES["UNICODE"] = True
+ DEFINES["USE_AURA"] = "1"
+ DEFINES["WEBRTC_WIN"] = True
+ DEFINES["WIN32"] = True
+ DEFINES["WIN32_LEAN_AND_MEAN"] = True
+ DEFINES["WINAPI_FAMILY"] = "WINAPI_FAMILY_DESKTOP_APP"
+ DEFINES["WINVER"] = "0x0A00"
+ DEFINES["_ATL_NO_OPENGL"] = True
+ DEFINES["_CRT_RAND_S"] = True
+ DEFINES["_CRT_SECURE_NO_DEPRECATE"] = True
+ DEFINES["_ENABLE_EXTENDED_ALIGNED_STORAGE"] = True
+ DEFINES["_HAS_EXCEPTIONS"] = "0"
+ DEFINES["_HAS_NODISCARD"] = True
+ DEFINES["_SCL_SECURE_NO_DEPRECATE"] = True
+ DEFINES["_SECURE_ATL"] = True
+ DEFINES["_UNICODE"] = True
+ DEFINES["_WIN32_WINNT"] = "0x0A00"
+ DEFINES["_WINDOWS"] = True
+ DEFINES["__STD_C"] = True
+
+if CONFIG["CPU_ARCH"] == "aarch64":
+
+ DEFINES["WEBRTC_ARCH_ARM64"] = True
+ DEFINES["WEBRTC_HAS_NEON"] = True
+
+if CONFIG["CPU_ARCH"] == "arm":
+
+ CXXFLAGS += [
+ "-mfpu=neon"
+ ]
+
+ DEFINES["WEBRTC_ARCH_ARM"] = True
+ DEFINES["WEBRTC_ARCH_ARM_V7"] = True
+ DEFINES["WEBRTC_HAS_NEON"] = True
+
+if CONFIG["CPU_ARCH"] == "mips32":
+
+ DEFINES["MIPS32_LE"] = True
+ DEFINES["MIPS_FPU_LE"] = True
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["CPU_ARCH"] == "mips64":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["CPU_ARCH"] == "x86":
+
+ DEFINES["WEBRTC_ENABLE_AVX2"] = True
+
+if CONFIG["CPU_ARCH"] == "x86_64":
+
+ DEFINES["WEBRTC_ENABLE_AVX2"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Android":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Darwin":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "OpenBSD":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "WINNT":
+
+ DEFINES["_HAS_ITERATOR_DEBUGGING"] = "0"
+
+if CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["USE_X11"] = "1"
+
+if CONFIG["CPU_ARCH"] == "arm" and CONFIG["OS_TARGET"] == "Android":
+
+ OS_LIBS += [
+ "android_support",
+ "unwind"
+ ]
+
+if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Android":
+
+ CXXFLAGS += [
+ "-msse2"
+ ]
+
+ OS_LIBS += [
+ "android_support"
+ ]
+
+if CONFIG["CPU_ARCH"] == "aarch64" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["CPU_ARCH"] == "arm" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Linux":
+
+ CXXFLAGS += [
+ "-msse2"
+ ]
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+Library("interval_budget_gn")
diff --git a/third_party/libwebrtc/modules/pacing/interval_budget_unittest.cc b/third_party/libwebrtc/modules/pacing/interval_budget_unittest.cc
new file mode 100644
index 0000000000..e182d35510
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/interval_budget_unittest.cc
@@ -0,0 +1,123 @@
+/*
+ * 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/pacing/interval_budget.h"
+
+#include "test/gtest.h"
+
+namespace webrtc {
+
+namespace {
+constexpr int kWindowMs = 500;
+constexpr int kBitrateKbps = 100;
+constexpr bool kCanBuildUpUnderuse = true;
+constexpr bool kCanNotBuildUpUnderuse = false;
+size_t TimeToBytes(int bitrate_kbps, int time_ms) {
+ return static_cast<size_t>(bitrate_kbps * time_ms / 8);
+}
+} // namespace
+
+TEST(IntervalBudgetTest, InitailState) {
+ IntervalBudget interval_budget(kBitrateKbps);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(), 0.0);
+ EXPECT_EQ(interval_budget.bytes_remaining(), 0u);
+}
+
+TEST(IntervalBudgetTest, Underuse) {
+ IntervalBudget interval_budget(kBitrateKbps);
+ int delta_time_ms = 50;
+ interval_budget.IncreaseBudget(delta_time_ms);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(),
+ kWindowMs / static_cast<double>(100 * delta_time_ms));
+ EXPECT_EQ(interval_budget.bytes_remaining(),
+ TimeToBytes(kBitrateKbps, delta_time_ms));
+}
+
+TEST(IntervalBudgetTest, DontUnderuseMoreThanMaxWindow) {
+ IntervalBudget interval_budget(kBitrateKbps);
+ int delta_time_ms = 1000;
+ interval_budget.IncreaseBudget(delta_time_ms);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(), 1.0);
+ EXPECT_EQ(interval_budget.bytes_remaining(),
+ TimeToBytes(kBitrateKbps, kWindowMs));
+}
+
+TEST(IntervalBudgetTest, DontUnderuseMoreThanMaxWindowWhenChangeBitrate) {
+ IntervalBudget interval_budget(kBitrateKbps);
+ int delta_time_ms = kWindowMs / 2;
+ interval_budget.IncreaseBudget(delta_time_ms);
+ interval_budget.set_target_rate_kbps(kBitrateKbps / 10);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(), 1.0);
+ EXPECT_EQ(interval_budget.bytes_remaining(),
+ TimeToBytes(kBitrateKbps / 10, kWindowMs));
+}
+
+TEST(IntervalBudgetTest, BalanceChangeOnBitrateChange) {
+ IntervalBudget interval_budget(kBitrateKbps);
+ int delta_time_ms = kWindowMs;
+ interval_budget.IncreaseBudget(delta_time_ms);
+ interval_budget.set_target_rate_kbps(kBitrateKbps * 2);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(), 0.5);
+ EXPECT_EQ(interval_budget.bytes_remaining(),
+ TimeToBytes(kBitrateKbps, kWindowMs));
+}
+
+TEST(IntervalBudgetTest, Overuse) {
+ IntervalBudget interval_budget(kBitrateKbps);
+ int overuse_time_ms = 50;
+ int used_bytes = TimeToBytes(kBitrateKbps, overuse_time_ms);
+ interval_budget.UseBudget(used_bytes);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(),
+ -kWindowMs / static_cast<double>(100 * overuse_time_ms));
+ EXPECT_EQ(interval_budget.bytes_remaining(), 0u);
+}
+
+TEST(IntervalBudgetTest, DontOveruseMoreThanMaxWindow) {
+ IntervalBudget interval_budget(kBitrateKbps);
+ int overuse_time_ms = 1000;
+ int used_bytes = TimeToBytes(kBitrateKbps, overuse_time_ms);
+ interval_budget.UseBudget(used_bytes);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(), -1.0);
+ EXPECT_EQ(interval_budget.bytes_remaining(), 0u);
+}
+
+TEST(IntervalBudgetTest, CanBuildUpUnderuseWhenConfigured) {
+ IntervalBudget interval_budget(kBitrateKbps, kCanBuildUpUnderuse);
+ int delta_time_ms = 50;
+ interval_budget.IncreaseBudget(delta_time_ms);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(),
+ kWindowMs / static_cast<double>(100 * delta_time_ms));
+ EXPECT_EQ(interval_budget.bytes_remaining(),
+ TimeToBytes(kBitrateKbps, delta_time_ms));
+
+ interval_budget.IncreaseBudget(delta_time_ms);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(),
+ 2 * kWindowMs / static_cast<double>(100 * delta_time_ms));
+ EXPECT_EQ(interval_budget.bytes_remaining(),
+ TimeToBytes(kBitrateKbps, 2 * delta_time_ms));
+}
+
+TEST(IntervalBudgetTest, CanNotBuildUpUnderuseWhenConfigured) {
+ IntervalBudget interval_budget(kBitrateKbps, kCanNotBuildUpUnderuse);
+ int delta_time_ms = 50;
+ interval_budget.IncreaseBudget(delta_time_ms);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(),
+ kWindowMs / static_cast<double>(100 * delta_time_ms));
+ EXPECT_EQ(interval_budget.bytes_remaining(),
+ TimeToBytes(kBitrateKbps, delta_time_ms));
+
+ interval_budget.IncreaseBudget(delta_time_ms);
+ EXPECT_DOUBLE_EQ(interval_budget.budget_ratio(),
+ kWindowMs / static_cast<double>(100 * delta_time_ms));
+ EXPECT_EQ(interval_budget.bytes_remaining(),
+ TimeToBytes(kBitrateKbps, delta_time_ms));
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/pacing_controller.cc b/third_party/libwebrtc/modules/pacing/pacing_controller.cc
new file mode 100644
index 0000000000..3ba7598cc2
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/pacing_controller.cc
@@ -0,0 +1,710 @@
+/*
+ * Copyright (c) 2019 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/pacing/pacing_controller.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/match.h"
+#include "modules/pacing/bitrate_prober.h"
+#include "modules/pacing/interval_budget.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/experiments/field_trial_parser.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/time_utils.h"
+#include "system_wrappers/include/clock.h"
+
+namespace webrtc {
+namespace {
+// Time limit in milliseconds between packet bursts.
+constexpr TimeDelta kDefaultMinPacketLimit = TimeDelta::Millis(5);
+constexpr TimeDelta kCongestedPacketInterval = TimeDelta::Millis(500);
+// TODO(sprang): Consider dropping this limit.
+// The maximum debt level, in terms of time, capped when sending packets.
+constexpr TimeDelta kMaxDebtInTime = TimeDelta::Millis(500);
+constexpr TimeDelta kMaxElapsedTime = TimeDelta::Seconds(2);
+
+bool IsDisabled(const FieldTrialsView& field_trials, absl::string_view key) {
+ return absl::StartsWith(field_trials.Lookup(key), "Disabled");
+}
+
+bool IsEnabled(const FieldTrialsView& field_trials, absl::string_view key) {
+ return absl::StartsWith(field_trials.Lookup(key), "Enabled");
+}
+
+} // namespace
+
+const TimeDelta PacingController::kMaxExpectedQueueLength =
+ TimeDelta::Millis(2000);
+const TimeDelta PacingController::kPausedProcessInterval =
+ kCongestedPacketInterval;
+const TimeDelta PacingController::kMinSleepTime = TimeDelta::Millis(1);
+const TimeDelta PacingController::kTargetPaddingDuration = TimeDelta::Millis(5);
+const TimeDelta PacingController::kMaxPaddingReplayDuration =
+ TimeDelta::Millis(50);
+const TimeDelta PacingController::kMaxEarlyProbeProcessing =
+ TimeDelta::Millis(1);
+
+PacingController::PacingController(Clock* clock,
+ PacketSender* packet_sender,
+ const FieldTrialsView& field_trials)
+ : clock_(clock),
+ packet_sender_(packet_sender),
+ field_trials_(field_trials),
+ drain_large_queues_(
+ !IsDisabled(field_trials_, "WebRTC-Pacer-DrainQueue")),
+ send_padding_if_silent_(
+ IsEnabled(field_trials_, "WebRTC-Pacer-PadInSilence")),
+ pace_audio_(IsEnabled(field_trials_, "WebRTC-Pacer-BlockAudio")),
+ ignore_transport_overhead_(
+ IsEnabled(field_trials_, "WebRTC-Pacer-IgnoreTransportOverhead")),
+ fast_retransmissions_(
+ IsEnabled(field_trials_, "WebRTC-Pacer-FastRetransmissions")),
+ min_packet_limit_(kDefaultMinPacketLimit),
+ transport_overhead_per_packet_(DataSize::Zero()),
+ send_burst_interval_(TimeDelta::Zero()),
+ last_timestamp_(clock_->CurrentTime()),
+ paused_(false),
+ media_debt_(DataSize::Zero()),
+ padding_debt_(DataSize::Zero()),
+ pacing_rate_(DataRate::Zero()),
+ adjusted_media_rate_(DataRate::Zero()),
+ padding_rate_(DataRate::Zero()),
+ prober_(field_trials_),
+ probing_send_failure_(false),
+ last_process_time_(clock->CurrentTime()),
+ last_send_time_(last_process_time_),
+ seen_first_packet_(false),
+ packet_queue_(/*creation_time=*/last_process_time_),
+ congested_(false),
+ queue_time_limit_(kMaxExpectedQueueLength),
+ account_for_audio_(false),
+ include_overhead_(false),
+ circuit_breaker_threshold_(1 << 16) {
+ if (!drain_large_queues_) {
+ RTC_LOG(LS_WARNING) << "Pacer queues will not be drained,"
+ "pushback experiment must be enabled.";
+ }
+ FieldTrialParameter<int> min_packet_limit_ms("", min_packet_limit_.ms());
+ ParseFieldTrial({&min_packet_limit_ms},
+ field_trials_.Lookup("WebRTC-Pacer-MinPacketLimitMs"));
+ min_packet_limit_ = TimeDelta::Millis(min_packet_limit_ms.Get());
+ UpdateBudgetWithElapsedTime(min_packet_limit_);
+}
+
+PacingController::~PacingController() = default;
+
+void PacingController::CreateProbeClusters(
+ rtc::ArrayView<const ProbeClusterConfig> probe_cluster_configs) {
+ for (const ProbeClusterConfig probe_cluster_config : probe_cluster_configs) {
+ prober_.CreateProbeCluster(probe_cluster_config);
+ }
+}
+
+void PacingController::Pause() {
+ if (!paused_)
+ RTC_LOG(LS_INFO) << "PacedSender paused.";
+ paused_ = true;
+ packet_queue_.SetPauseState(true, CurrentTime());
+}
+
+void PacingController::Resume() {
+ if (paused_)
+ RTC_LOG(LS_INFO) << "PacedSender resumed.";
+ paused_ = false;
+ packet_queue_.SetPauseState(false, CurrentTime());
+}
+
+bool PacingController::IsPaused() const {
+ return paused_;
+}
+
+void PacingController::SetCongested(bool congested) {
+ if (congested_ && !congested) {
+ UpdateBudgetWithElapsedTime(UpdateTimeAndGetElapsed(CurrentTime()));
+ }
+ congested_ = congested;
+}
+
+void PacingController::SetCircuitBreakerThreshold(int num_iterations) {
+ circuit_breaker_threshold_ = num_iterations;
+}
+
+void PacingController::RemovePacketsForSsrc(uint32_t ssrc) {
+ packet_queue_.RemovePacketsForSsrc(ssrc);
+}
+
+bool PacingController::IsProbing() const {
+ return prober_.is_probing();
+}
+
+Timestamp PacingController::CurrentTime() const {
+ Timestamp time = clock_->CurrentTime();
+ if (time < last_timestamp_) {
+ RTC_LOG(LS_WARNING)
+ << "Non-monotonic clock behavior observed. Previous timestamp: "
+ << last_timestamp_.ms() << ", new timestamp: " << time.ms();
+ RTC_DCHECK_GE(time, last_timestamp_);
+ time = last_timestamp_;
+ }
+ last_timestamp_ = time;
+ return time;
+}
+
+void PacingController::SetProbingEnabled(bool enabled) {
+ RTC_CHECK(!seen_first_packet_);
+ prober_.SetEnabled(enabled);
+}
+
+void PacingController::SetPacingRates(DataRate pacing_rate,
+ DataRate padding_rate) {
+ static constexpr DataRate kMaxRate = DataRate::KilobitsPerSec(100'000);
+ RTC_CHECK_GT(pacing_rate, DataRate::Zero());
+ RTC_CHECK_GE(padding_rate, DataRate::Zero());
+ if (padding_rate > pacing_rate) {
+ RTC_LOG(LS_WARNING) << "Padding rate " << padding_rate.kbps()
+ << "kbps is higher than the pacing rate "
+ << pacing_rate.kbps() << "kbps, capping.";
+ padding_rate = pacing_rate;
+ }
+
+ if (pacing_rate > kMaxRate || padding_rate > kMaxRate) {
+ RTC_LOG(LS_WARNING) << "Very high pacing rates ( > " << kMaxRate.kbps()
+ << " kbps) configured: pacing = " << pacing_rate.kbps()
+ << " kbps, padding = " << padding_rate.kbps()
+ << " kbps.";
+ }
+ pacing_rate_ = pacing_rate;
+ padding_rate_ = padding_rate;
+ MaybeUpdateMediaRateDueToLongQueue(CurrentTime());
+
+ RTC_LOG(LS_VERBOSE) << "bwe:pacer_updated pacing_kbps=" << pacing_rate_.kbps()
+ << " padding_budget_kbps=" << padding_rate.kbps();
+}
+
+void PacingController::EnqueuePacket(std::unique_ptr<RtpPacketToSend> packet) {
+ RTC_DCHECK(pacing_rate_ > DataRate::Zero())
+ << "SetPacingRate must be called before InsertPacket.";
+ RTC_CHECK(packet->packet_type());
+
+ prober_.OnIncomingPacket(DataSize::Bytes(packet->payload_size()));
+
+ const Timestamp now = CurrentTime();
+ if (packet_queue_.Empty()) {
+ // If queue is empty, we need to "fast-forward" the last process time,
+ // so that we don't use passed time as budget for sending the first new
+ // packet.
+ Timestamp target_process_time = now;
+ Timestamp next_send_time = NextSendTime();
+ if (next_send_time.IsFinite()) {
+ // There was already a valid planned send time, such as a keep-alive.
+ // Use that as last process time only if it's prior to now.
+ target_process_time = std::min(now, next_send_time);
+ }
+ UpdateBudgetWithElapsedTime(UpdateTimeAndGetElapsed(target_process_time));
+ }
+ packet_queue_.Push(now, std::move(packet));
+ seen_first_packet_ = true;
+
+ // Queue length has increased, check if we need to change the pacing rate.
+ MaybeUpdateMediaRateDueToLongQueue(now);
+}
+
+void PacingController::SetAccountForAudioPackets(bool account_for_audio) {
+ account_for_audio_ = account_for_audio;
+}
+
+void PacingController::SetIncludeOverhead() {
+ include_overhead_ = true;
+}
+
+void PacingController::SetTransportOverhead(DataSize overhead_per_packet) {
+ if (ignore_transport_overhead_)
+ return;
+ transport_overhead_per_packet_ = overhead_per_packet;
+}
+
+void PacingController::SetSendBurstInterval(TimeDelta burst_interval) {
+ send_burst_interval_ = burst_interval;
+}
+
+TimeDelta PacingController::ExpectedQueueTime() const {
+ RTC_DCHECK_GT(adjusted_media_rate_, DataRate::Zero());
+ return QueueSizeData() / adjusted_media_rate_;
+}
+
+size_t PacingController::QueueSizePackets() const {
+ return rtc::checked_cast<size_t>(packet_queue_.SizeInPackets());
+}
+
+const std::array<int, kNumMediaTypes>&
+PacingController::SizeInPacketsPerRtpPacketMediaType() const {
+ return packet_queue_.SizeInPacketsPerRtpPacketMediaType();
+}
+
+DataSize PacingController::QueueSizeData() const {
+ DataSize size = packet_queue_.SizeInPayloadBytes();
+ if (include_overhead_) {
+ size += static_cast<int64_t>(packet_queue_.SizeInPackets()) *
+ transport_overhead_per_packet_;
+ }
+ return size;
+}
+
+DataSize PacingController::CurrentBufferLevel() const {
+ return std::max(media_debt_, padding_debt_);
+}
+
+absl::optional<Timestamp> PacingController::FirstSentPacketTime() const {
+ return first_sent_packet_time_;
+}
+
+Timestamp PacingController::OldestPacketEnqueueTime() const {
+ return packet_queue_.OldestEnqueueTime();
+}
+
+TimeDelta PacingController::UpdateTimeAndGetElapsed(Timestamp now) {
+ // If no previous processing, or last process was "in the future" because of
+ // early probe processing, then there is no elapsed time to add budget for.
+ if (last_process_time_.IsMinusInfinity() || now < last_process_time_) {
+ return TimeDelta::Zero();
+ }
+ TimeDelta elapsed_time = now - last_process_time_;
+ last_process_time_ = now;
+ if (elapsed_time > kMaxElapsedTime) {
+ RTC_LOG(LS_WARNING) << "Elapsed time (" << elapsed_time.ms()
+ << " ms) longer than expected, limiting to "
+ << kMaxElapsedTime.ms();
+ elapsed_time = kMaxElapsedTime;
+ }
+ return elapsed_time;
+}
+
+bool PacingController::ShouldSendKeepalive(Timestamp now) const {
+ if (send_padding_if_silent_ || paused_ || congested_ || !seen_first_packet_) {
+ // We send a padding packet every 500 ms to ensure we won't get stuck in
+ // congested state due to no feedback being received.
+ if (now - last_send_time_ >= kCongestedPacketInterval) {
+ return true;
+ }
+ }
+ return false;
+}
+
+Timestamp PacingController::NextSendTime() const {
+ const Timestamp now = CurrentTime();
+ Timestamp next_send_time = Timestamp::PlusInfinity();
+
+ if (paused_) {
+ return last_send_time_ + kPausedProcessInterval;
+ }
+
+ // If probing is active, that always takes priority.
+ if (prober_.is_probing() && !probing_send_failure_) {
+ Timestamp probe_time = prober_.NextProbeTime(now);
+ if (!probe_time.IsPlusInfinity()) {
+ return probe_time.IsMinusInfinity() ? now : probe_time;
+ }
+ }
+
+ // If queue contains a packet which should not be paced, its target send time
+ // is the time at which it was enqueued.
+ Timestamp unpaced_send_time = NextUnpacedSendTime();
+ if (unpaced_send_time.IsFinite()) {
+ return unpaced_send_time;
+ }
+
+ if (congested_ || !seen_first_packet_) {
+ // We need to at least send keep-alive packets with some interval.
+ return last_send_time_ + kCongestedPacketInterval;
+ }
+
+ if (adjusted_media_rate_ > DataRate::Zero() && !packet_queue_.Empty()) {
+ // If packets are allowed to be sent in a burst, the
+ // debt is allowed to grow up to one packet more than what can be sent
+ // during 'send_burst_period_'.
+ TimeDelta drain_time = media_debt_ / adjusted_media_rate_;
+ next_send_time =
+ last_process_time_ +
+ ((send_burst_interval_ > drain_time) ? TimeDelta::Zero() : drain_time);
+ } else if (padding_rate_ > DataRate::Zero() && packet_queue_.Empty()) {
+ // If we _don't_ have pending packets, check how long until we have
+ // bandwidth for padding packets. Both media and padding debts must
+ // have been drained to do this.
+ RTC_DCHECK_GT(adjusted_media_rate_, DataRate::Zero());
+ TimeDelta drain_time = std::max(media_debt_ / adjusted_media_rate_,
+ padding_debt_ / padding_rate_);
+
+ if (drain_time.IsZero() &&
+ (!media_debt_.IsZero() || !padding_debt_.IsZero())) {
+ // We have a non-zero debt, but drain time is smaller than tick size of
+ // TimeDelta, round it up to the smallest possible non-zero delta.
+ drain_time = TimeDelta::Micros(1);
+ }
+ next_send_time = last_process_time_ + drain_time;
+ } else {
+ // Nothing to do.
+ next_send_time = last_process_time_ + kPausedProcessInterval;
+ }
+
+ if (send_padding_if_silent_) {
+ next_send_time =
+ std::min(next_send_time, last_send_time_ + kPausedProcessInterval);
+ }
+
+ return next_send_time;
+}
+
+void PacingController::ProcessPackets() {
+ const Timestamp now = CurrentTime();
+ Timestamp target_send_time = now;
+
+ if (ShouldSendKeepalive(now)) {
+ DataSize keepalive_data_sent = DataSize::Zero();
+ // We can not send padding unless a normal packet has first been sent. If
+ // we do, timestamps get messed up.
+ if (seen_first_packet_) {
+ std::vector<std::unique_ptr<RtpPacketToSend>> keepalive_packets =
+ packet_sender_->GeneratePadding(DataSize::Bytes(1));
+ for (auto& packet : keepalive_packets) {
+ keepalive_data_sent +=
+ DataSize::Bytes(packet->payload_size() + packet->padding_size());
+ packet_sender_->SendPacket(std::move(packet), PacedPacketInfo());
+ for (auto& packet : packet_sender_->FetchFec()) {
+ EnqueuePacket(std::move(packet));
+ }
+ }
+ }
+ OnPacketSent(RtpPacketMediaType::kPadding, keepalive_data_sent, now);
+ }
+
+ if (paused_) {
+ return;
+ }
+
+ TimeDelta early_execute_margin =
+ prober_.is_probing() ? kMaxEarlyProbeProcessing : TimeDelta::Zero();
+
+ target_send_time = NextSendTime();
+ if (now + early_execute_margin < target_send_time) {
+ // We are too early, but if queue is empty still allow draining some debt.
+ // Probing is allowed to be sent up to kMinSleepTime early.
+ UpdateBudgetWithElapsedTime(UpdateTimeAndGetElapsed(now));
+ return;
+ }
+
+ TimeDelta elapsed_time = UpdateTimeAndGetElapsed(target_send_time);
+
+ if (elapsed_time > TimeDelta::Zero()) {
+ UpdateBudgetWithElapsedTime(elapsed_time);
+ }
+
+ PacedPacketInfo pacing_info;
+ DataSize recommended_probe_size = DataSize::Zero();
+ bool is_probing = prober_.is_probing();
+ if (is_probing) {
+ // Probe timing is sensitive, and handled explicitly by BitrateProber, so
+ // use actual send time rather than target.
+ pacing_info = prober_.CurrentCluster(now).value_or(PacedPacketInfo());
+ if (pacing_info.probe_cluster_id != PacedPacketInfo::kNotAProbe) {
+ recommended_probe_size = prober_.RecommendedMinProbeSize();
+ RTC_DCHECK_GT(recommended_probe_size, DataSize::Zero());
+ } else {
+ // No valid probe cluster returned, probe might have timed out.
+ is_probing = false;
+ }
+ }
+
+ DataSize data_sent = DataSize::Zero();
+ int iteration = 0;
+ int packets_sent = 0;
+ int padding_packets_generated = 0;
+ for (; iteration < circuit_breaker_threshold_; ++iteration) {
+ // Fetch packet, so long as queue is not empty or budget is not
+ // exhausted.
+ std::unique_ptr<RtpPacketToSend> rtp_packet =
+ GetPendingPacket(pacing_info, target_send_time, now);
+ if (rtp_packet == nullptr) {
+ // No packet available to send, check if we should send padding.
+ if (now - target_send_time > kMaxPaddingReplayDuration) {
+ // The target send time is more than `kMaxPaddingReplayDuration` behind
+ // the real-time clock. This can happen if the clock is adjusted forward
+ // without `ProcessPackets()` having been called at the expected times.
+ target_send_time = now - kMaxPaddingReplayDuration;
+ last_process_time_ = std::max(last_process_time_, target_send_time);
+ }
+
+ DataSize padding_to_add = PaddingToAdd(recommended_probe_size, data_sent);
+ if (padding_to_add > DataSize::Zero()) {
+ std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets =
+ packet_sender_->GeneratePadding(padding_to_add);
+ if (!padding_packets.empty()) {
+ padding_packets_generated += padding_packets.size();
+ for (auto& packet : padding_packets) {
+ EnqueuePacket(std::move(packet));
+ }
+ // Continue loop to send the padding that was just added.
+ continue;
+ } else {
+ // Can't generate padding, still update padding budget for next send
+ // time.
+ UpdatePaddingBudgetWithSentData(padding_to_add);
+ }
+ }
+ // Can't fetch new packet and no padding to send, exit send loop.
+ break;
+ } else {
+ RTC_DCHECK(rtp_packet);
+ RTC_DCHECK(rtp_packet->packet_type().has_value());
+ const RtpPacketMediaType packet_type = *rtp_packet->packet_type();
+ DataSize packet_size = DataSize::Bytes(rtp_packet->payload_size() +
+ rtp_packet->padding_size());
+
+ if (include_overhead_) {
+ packet_size += DataSize::Bytes(rtp_packet->headers_size()) +
+ transport_overhead_per_packet_;
+ }
+
+ packet_sender_->SendPacket(std::move(rtp_packet), pacing_info);
+ for (auto& packet : packet_sender_->FetchFec()) {
+ EnqueuePacket(std::move(packet));
+ }
+ data_sent += packet_size;
+ ++packets_sent;
+
+ // Send done, update send time.
+ OnPacketSent(packet_type, packet_size, now);
+
+ if (is_probing) {
+ pacing_info.probe_cluster_bytes_sent += packet_size.bytes();
+ // If we are currently probing, we need to stop the send loop when we
+ // have reached the send target.
+ if (data_sent >= recommended_probe_size) {
+ break;
+ }
+ }
+
+ // Update target send time in case that are more packets that we are late
+ // in processing.
+ target_send_time = NextSendTime();
+ if (target_send_time > now) {
+ // Exit loop if not probing.
+ if (!is_probing) {
+ break;
+ }
+ target_send_time = now;
+ }
+ UpdateBudgetWithElapsedTime(UpdateTimeAndGetElapsed(target_send_time));
+ }
+ }
+
+ if (iteration >= circuit_breaker_threshold_) {
+ // Circuit break activated. Log warning, adjust send time and return.
+ // TODO(sprang): Consider completely clearing state.
+ RTC_LOG(LS_ERROR)
+ << "PacingController exceeded max iterations in "
+ "send-loop. Debug info: "
+ << " packets sent = " << packets_sent
+ << ", padding packets generated = " << padding_packets_generated
+ << ", bytes sent = " << data_sent.bytes()
+ << ", probing = " << (is_probing ? "true" : "false")
+ << ", recommended_probe_size = " << recommended_probe_size.bytes()
+ << ", now = " << now.us()
+ << ", target_send_time = " << target_send_time.us()
+ << ", last_process_time = " << last_process_time_.us()
+ << ", last_send_time = " << last_send_time_.us()
+ << ", paused = " << (paused_ ? "true" : "false")
+ << ", media_debt = " << media_debt_.bytes()
+ << ", padding_debt = " << padding_debt_.bytes()
+ << ", pacing_rate = " << pacing_rate_.bps()
+ << ", adjusted_media_rate = " << adjusted_media_rate_.bps()
+ << ", padding_rate = " << padding_rate_.bps()
+ << ", queue size (packets) = " << packet_queue_.SizeInPackets()
+ << ", queue size (payload bytes) = "
+ << packet_queue_.SizeInPayloadBytes();
+ last_send_time_ = now;
+ last_process_time_ = now;
+ return;
+ }
+
+ if (is_probing) {
+ probing_send_failure_ = data_sent == DataSize::Zero();
+ if (!probing_send_failure_) {
+ prober_.ProbeSent(CurrentTime(), data_sent);
+ }
+ }
+
+ // Queue length has probably decreased, check if pacing rate needs to updated.
+ // Poll the time again, since we might have enqueued new fec/padding packets
+ // with a later timestamp than `now`.
+ MaybeUpdateMediaRateDueToLongQueue(CurrentTime());
+}
+
+DataSize PacingController::PaddingToAdd(DataSize recommended_probe_size,
+ DataSize data_sent) const {
+ if (!packet_queue_.Empty()) {
+ // Actual payload available, no need to add padding.
+ return DataSize::Zero();
+ }
+
+ if (congested_) {
+ // Don't add padding if congested, even if requested for probing.
+ return DataSize::Zero();
+ }
+
+ if (!seen_first_packet_) {
+ // We can not send padding unless a normal packet has first been sent. If
+ // we do, timestamps get messed up.
+ return DataSize::Zero();
+ }
+
+ if (!recommended_probe_size.IsZero()) {
+ if (recommended_probe_size > data_sent) {
+ return recommended_probe_size - data_sent;
+ }
+ return DataSize::Zero();
+ }
+
+ if (padding_rate_ > DataRate::Zero() && padding_debt_ == DataSize::Zero()) {
+ return kTargetPaddingDuration * padding_rate_;
+ }
+ return DataSize::Zero();
+}
+
+std::unique_ptr<RtpPacketToSend> PacingController::GetPendingPacket(
+ const PacedPacketInfo& pacing_info,
+ Timestamp target_send_time,
+ Timestamp now) {
+ const bool is_probe =
+ pacing_info.probe_cluster_id != PacedPacketInfo::kNotAProbe;
+ // If first packet in probe, insert a small padding packet so we have a
+ // more reliable start window for the rate estimation.
+ if (is_probe && pacing_info.probe_cluster_bytes_sent == 0) {
+ auto padding = packet_sender_->GeneratePadding(DataSize::Bytes(1));
+ // If no RTP modules sending media are registered, we may not get a
+ // padding packet back.
+ if (!padding.empty()) {
+ // We should never get more than one padding packets with a requested
+ // size of 1 byte.
+ RTC_DCHECK_EQ(padding.size(), 1u);
+ return std::move(padding[0]);
+ }
+ }
+
+ if (packet_queue_.Empty()) {
+ return nullptr;
+ }
+
+ // First, check if there is any reason _not_ to send the next queued packet.
+ // Unpaced packets and probes are exempted from send checks.
+ if (NextUnpacedSendTime().IsInfinite() && !is_probe) {
+ if (congested_) {
+ // Don't send anything if congested.
+ return nullptr;
+ }
+
+ if (now <= target_send_time && send_burst_interval_.IsZero()) {
+ // We allow sending slightly early if we think that we would actually
+ // had been able to, had we been right on time - i.e. the current debt
+ // is not more than would be reduced to zero at the target sent time.
+ // If we allow packets to be sent in a burst, packet are allowed to be
+ // sent early.
+ TimeDelta flush_time = media_debt_ / adjusted_media_rate_;
+ if (now + flush_time > target_send_time) {
+ return nullptr;
+ }
+ }
+ }
+
+ return packet_queue_.Pop();
+}
+
+void PacingController::OnPacketSent(RtpPacketMediaType packet_type,
+ DataSize packet_size,
+ Timestamp send_time) {
+ if (!first_sent_packet_time_ && packet_type != RtpPacketMediaType::kPadding) {
+ first_sent_packet_time_ = send_time;
+ }
+
+ bool audio_packet = packet_type == RtpPacketMediaType::kAudio;
+ if ((!audio_packet || account_for_audio_) && packet_size > DataSize::Zero()) {
+ UpdateBudgetWithSentData(packet_size);
+ }
+
+ last_send_time_ = send_time;
+}
+
+void PacingController::UpdateBudgetWithElapsedTime(TimeDelta delta) {
+ media_debt_ -= std::min(media_debt_, adjusted_media_rate_ * delta);
+ padding_debt_ -= std::min(padding_debt_, padding_rate_ * delta);
+}
+
+void PacingController::UpdateBudgetWithSentData(DataSize size) {
+ media_debt_ += size;
+ media_debt_ = std::min(media_debt_, adjusted_media_rate_ * kMaxDebtInTime);
+ UpdatePaddingBudgetWithSentData(size);
+}
+
+void PacingController::UpdatePaddingBudgetWithSentData(DataSize size) {
+ padding_debt_ += size;
+ padding_debt_ = std::min(padding_debt_, padding_rate_ * kMaxDebtInTime);
+}
+
+void PacingController::SetQueueTimeLimit(TimeDelta limit) {
+ queue_time_limit_ = limit;
+}
+
+void PacingController::MaybeUpdateMediaRateDueToLongQueue(Timestamp now) {
+ adjusted_media_rate_ = pacing_rate_;
+ if (!drain_large_queues_) {
+ return;
+ }
+
+ DataSize queue_size_data = QueueSizeData();
+ if (queue_size_data > DataSize::Zero()) {
+ // Assuming equal size packets and input/output rate, the average packet
+ // has avg_time_left_ms left to get queue_size_bytes out of the queue, if
+ // time constraint shall be met. Determine bitrate needed for that.
+ packet_queue_.UpdateAverageQueueTime(now);
+ TimeDelta avg_time_left =
+ std::max(TimeDelta::Millis(1),
+ queue_time_limit_ - packet_queue_.AverageQueueTime());
+ DataRate min_rate_needed = queue_size_data / avg_time_left;
+ if (min_rate_needed > pacing_rate_) {
+ adjusted_media_rate_ = min_rate_needed;
+ RTC_LOG(LS_VERBOSE) << "bwe:large_pacing_queue pacing_rate_kbps="
+ << pacing_rate_.kbps();
+ }
+ }
+}
+
+Timestamp PacingController::NextUnpacedSendTime() const {
+ if (!pace_audio_) {
+ Timestamp leading_audio_send_time =
+ packet_queue_.LeadingPacketEnqueueTime(RtpPacketMediaType::kAudio);
+ if (leading_audio_send_time.IsFinite()) {
+ return leading_audio_send_time;
+ }
+ }
+ if (fast_retransmissions_) {
+ Timestamp leading_retransmission_send_time =
+ packet_queue_.LeadingPacketEnqueueTime(
+ RtpPacketMediaType::kRetransmission);
+ if (leading_retransmission_send_time.IsFinite()) {
+ return leading_retransmission_send_time;
+ }
+ }
+ return Timestamp::MinusInfinity();
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/pacing_controller.h b/third_party/libwebrtc/modules/pacing/pacing_controller.h
new file mode 100644
index 0000000000..47dba179f5
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/pacing_controller.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2019 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_PACING_PACING_CONTROLLER_H_
+#define MODULES_PACING_PACING_CONTROLLER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <array>
+#include <atomic>
+#include <memory>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "api/field_trials_view.h"
+#include "api/function_view.h"
+#include "api/transport/field_trial_based_config.h"
+#include "api/transport/network_types.h"
+#include "modules/pacing/bitrate_prober.h"
+#include "modules/pacing/interval_budget.h"
+#include "modules/pacing/prioritized_packet_queue.h"
+#include "modules/pacing/rtp_packet_pacer.h"
+#include "modules/rtp_rtcp/include/rtp_packet_sender.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
+#include "rtc_base/experiments/field_trial_parser.h"
+#include "rtc_base/thread_annotations.h"
+
+namespace webrtc {
+
+// This class implements a leaky-bucket packet pacing algorithm. It handles the
+// logic of determining which packets to send when, but the actual timing of
+// the processing is done externally (e.g. RtpPacketPacer). Furthermore, the
+// forwarding of packets when they are ready to be sent is also handled
+// externally, via the PacingController::PacketSender interface.
+class PacingController {
+ public:
+ class PacketSender {
+ public:
+ virtual ~PacketSender() = default;
+ virtual void SendPacket(std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) = 0;
+ // Should be called after each call to SendPacket().
+ virtual std::vector<std::unique_ptr<RtpPacketToSend>> FetchFec() = 0;
+ virtual std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding(
+ DataSize size) = 0;
+
+ // TODO(bugs.webrtc.org/11340): Make pure virtual once downstream projects
+ // have been updated.
+ virtual void OnAbortedRetransmissions(
+ uint32_t ssrc,
+ rtc::ArrayView<const uint16_t> sequence_numbers) {}
+ virtual absl::optional<uint32_t> GetRtxSsrcForMedia(uint32_t ssrc) const {
+ return absl::nullopt;
+ }
+ };
+
+ // Expected max pacer delay. If ExpectedQueueTime() is higher than
+ // this value, the packet producers should wait (eg drop frames rather than
+ // encoding them). Bitrate sent may temporarily exceed target set by
+ // UpdateBitrate() so that this limit will be upheld.
+ static const TimeDelta kMaxExpectedQueueLength;
+ // If no media or paused, wake up at least every `kPausedProcessIntervalMs` in
+ // order to send a keep-alive packet so we don't get stuck in a bad state due
+ // to lack of feedback.
+ static const TimeDelta kPausedProcessInterval;
+ // The default minimum time that should elapse calls to `ProcessPackets()`.
+ static const TimeDelta kMinSleepTime;
+ // When padding should be generated, add packets to the buffer with a size
+ // corresponding to this duration times the current padding rate.
+ static const TimeDelta kTargetPaddingDuration;
+ // The maximum time that the pacer can use when "replaying" passed time where
+ // padding should have been generated.
+ static const TimeDelta kMaxPaddingReplayDuration;
+ // Allow probes to be processed slightly ahead of inteded send time. Currently
+ // set to 1ms as this is intended to allow times be rounded down to the
+ // nearest millisecond.
+ static const TimeDelta kMaxEarlyProbeProcessing;
+
+ PacingController(Clock* clock,
+ PacketSender* packet_sender,
+ const FieldTrialsView& field_trials);
+
+ ~PacingController();
+
+ // Adds the packet to the queue and calls PacketRouter::SendPacket() when
+ // it's time to send.
+ void EnqueuePacket(std::unique_ptr<RtpPacketToSend> packet);
+
+ void CreateProbeClusters(
+ rtc::ArrayView<const ProbeClusterConfig> probe_cluster_configs);
+
+ void Pause(); // Temporarily pause all sending.
+ void Resume(); // Resume sending packets.
+ bool IsPaused() const;
+
+ void SetCongested(bool congested);
+
+ // Sets the pacing rates. Must be called once before packets can be sent.
+ void SetPacingRates(DataRate pacing_rate, DataRate padding_rate);
+ DataRate pacing_rate() const { return adjusted_media_rate_; }
+
+ // Currently audio traffic is not accounted by pacer and passed through.
+ // With the introduction of audio BWE audio traffic will be accounted for
+ // the pacer budget calculation. The audio traffic still will be injected
+ // at high priority.
+ void SetAccountForAudioPackets(bool account_for_audio);
+ void SetIncludeOverhead();
+
+ void SetTransportOverhead(DataSize overhead_per_packet);
+ // The pacer is allowed to send enqued packets in bursts and can build up a
+ // packet "debt" that correspond to approximately the send rate during
+ // 'burst_interval'.
+ void SetSendBurstInterval(TimeDelta burst_interval);
+
+ // Returns the time when the oldest packet was queued.
+ Timestamp OldestPacketEnqueueTime() const;
+
+ // Number of packets in the pacer queue.
+ size_t QueueSizePackets() const;
+ // Number of packets in the pacer queue per media type (RtpPacketMediaType
+ // values are used as lookup index).
+ const std::array<int, kNumMediaTypes>& SizeInPacketsPerRtpPacketMediaType()
+ const;
+ // Totals size of packets in the pacer queue.
+ DataSize QueueSizeData() const;
+
+ // Current buffer level, i.e. max of media and padding debt.
+ DataSize CurrentBufferLevel() const;
+
+ // Returns the time when the first packet was sent.
+ absl::optional<Timestamp> FirstSentPacketTime() const;
+
+ // Returns the number of milliseconds it will take to send the current
+ // packets in the queue, given the current size and bitrate, ignoring prio.
+ TimeDelta ExpectedQueueTime() const;
+
+ void SetQueueTimeLimit(TimeDelta limit);
+
+ // Enable bitrate probing. Enabled by default, mostly here to simplify
+ // testing. Must be called before any packets are being sent to have an
+ // effect.
+ void SetProbingEnabled(bool enabled);
+
+ // Returns the next time we expect ProcessPackets() to be called.
+ Timestamp NextSendTime() const;
+
+ // Check queue of pending packets and send them or padding packets, if budget
+ // is available.
+ void ProcessPackets();
+
+ bool IsProbing() const;
+
+ // Note: Intended for debugging purposes only, will be removed.
+ // Sets the number of iterations of the main loop in `ProcessPackets()` that
+ // is considered erroneous to exceed.
+ void SetCircuitBreakerThreshold(int num_iterations);
+
+ // Remove any pending packets matching this SSRC from the packet queue.
+ void RemovePacketsForSsrc(uint32_t ssrc);
+
+ private:
+ TimeDelta UpdateTimeAndGetElapsed(Timestamp now);
+ bool ShouldSendKeepalive(Timestamp now) const;
+
+ // Updates the number of bytes that can be sent for the next time interval.
+ void UpdateBudgetWithElapsedTime(TimeDelta delta);
+ void UpdateBudgetWithSentData(DataSize size);
+ void UpdatePaddingBudgetWithSentData(DataSize size);
+
+ DataSize PaddingToAdd(DataSize recommended_probe_size,
+ DataSize data_sent) const;
+
+ std::unique_ptr<RtpPacketToSend> GetPendingPacket(
+ const PacedPacketInfo& pacing_info,
+ Timestamp target_send_time,
+ Timestamp now);
+ void OnPacketSent(RtpPacketMediaType packet_type,
+ DataSize packet_size,
+ Timestamp send_time);
+ void MaybeUpdateMediaRateDueToLongQueue(Timestamp now);
+
+ Timestamp CurrentTime() const;
+
+ // Helper methods for packet that may not be paced. Returns a finite Timestamp
+ // if a packet type is configured to not be paced and the packet queue has at
+ // least one packet of that type. Otherwise returns
+ // Timestamp::MinusInfinity().
+ Timestamp NextUnpacedSendTime() const;
+
+ Clock* const clock_;
+ PacketSender* const packet_sender_;
+ const FieldTrialsView& field_trials_;
+
+ const bool drain_large_queues_;
+ const bool send_padding_if_silent_;
+ const bool pace_audio_;
+ const bool ignore_transport_overhead_;
+ const bool fast_retransmissions_;
+
+ TimeDelta min_packet_limit_;
+ DataSize transport_overhead_per_packet_;
+ TimeDelta send_burst_interval_;
+
+ // TODO(webrtc:9716): Remove this when we are certain clocks are monotonic.
+ // The last millisecond timestamp returned by `clock_`.
+ mutable Timestamp last_timestamp_;
+ bool paused_;
+
+ // Amount of outstanding data for media and padding.
+ DataSize media_debt_;
+ DataSize padding_debt_;
+
+ // The target pacing rate, signaled via SetPacingRates().
+ DataRate pacing_rate_;
+ // The media send rate, which might adjusted from pacing_rate_, e.g. if the
+ // pacing queue is growing too long.
+ DataRate adjusted_media_rate_;
+ // The padding target rate. We aim to fill up to this rate with padding what
+ // is not already used by media.
+ DataRate padding_rate_;
+
+ BitrateProber prober_;
+ bool probing_send_failure_;
+
+ Timestamp last_process_time_;
+ Timestamp last_send_time_;
+ absl::optional<Timestamp> first_sent_packet_time_;
+ bool seen_first_packet_;
+
+ PrioritizedPacketQueue packet_queue_;
+
+ bool congested_;
+
+ TimeDelta queue_time_limit_;
+ bool account_for_audio_;
+ bool include_overhead_;
+
+ int circuit_breaker_threshold_;
+};
+} // namespace webrtc
+
+#endif // MODULES_PACING_PACING_CONTROLLER_H_
diff --git a/third_party/libwebrtc/modules/pacing/pacing_controller_unittest.cc b/third_party/libwebrtc/modules/pacing/pacing_controller_unittest.cc
new file mode 100644
index 0000000000..3b3c3eb761
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/pacing_controller_unittest.cc
@@ -0,0 +1,2175 @@
+/*
+ * Copyright (c) 2019 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/pacing/pacing_controller.h"
+
+#include <algorithm>
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "api/transport/network_types.h"
+#include "api/units/data_rate.h"
+#include "api/units/time_delta.h"
+#include "modules/pacing/packet_router.h"
+#include "system_wrappers/include/clock.h"
+#include "test/explicit_key_value_config.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Field;
+using ::testing::Pointee;
+using ::testing::Property;
+using ::testing::Return;
+using ::testing::WithoutArgs;
+
+using ::webrtc::test::ExplicitKeyValueConfig;
+
+namespace webrtc {
+namespace {
+constexpr DataRate kFirstClusterRate = DataRate::KilobitsPerSec(900);
+constexpr DataRate kSecondClusterRate = DataRate::KilobitsPerSec(1800);
+
+// The error stems from truncating the time interval of probe packets to integer
+// values. This results in probing slightly higher than the target bitrate.
+// For 1.8 Mbps, this comes to be about 120 kbps with 1200 probe packets.
+constexpr DataRate kProbingErrorMargin = DataRate::KilobitsPerSec(150);
+
+const float kPaceMultiplier = 2.5f;
+
+constexpr uint32_t kAudioSsrc = 12345;
+constexpr uint32_t kVideoSsrc = 234565;
+
+constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(800);
+
+std::unique_ptr<RtpPacketToSend> BuildPacket(RtpPacketMediaType type,
+ uint32_t ssrc,
+ uint16_t sequence_number,
+ int64_t capture_time_ms,
+ size_t size) {
+ auto packet = std::make_unique<RtpPacketToSend>(nullptr);
+ packet->set_packet_type(type);
+ packet->SetSsrc(ssrc);
+ packet->SetSequenceNumber(sequence_number);
+ packet->set_capture_time(Timestamp::Millis(capture_time_ms));
+ packet->SetPayloadSize(size);
+ return packet;
+}
+
+class MediaStream {
+ public:
+ MediaStream(SimulatedClock& clock,
+ RtpPacketMediaType type,
+ uint32_t ssrc,
+ size_t packet_size)
+ : clock_(clock), type_(type), ssrc_(ssrc), packet_size_(packet_size) {}
+
+ std::unique_ptr<RtpPacketToSend> BuildNextPacket() {
+ return BuildPacket(type_, ssrc_, seq_num_++, clock_.TimeInMilliseconds(),
+ packet_size_);
+ }
+ std::unique_ptr<RtpPacketToSend> BuildNextPacket(size_t size) {
+ return BuildPacket(type_, ssrc_, seq_num_++, clock_.TimeInMilliseconds(),
+ size);
+ }
+
+ private:
+ SimulatedClock& clock_;
+ const RtpPacketMediaType type_;
+ const uint32_t ssrc_;
+ const size_t packet_size_;
+ uint16_t seq_num_ = 1000;
+};
+
+// Mock callback proxy, where both new and old api redirects to common mock
+// methods that focus on core aspects.
+class MockPacingControllerCallback : public PacingController::PacketSender {
+ public:
+ void SendPacket(std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) override {
+ SendPacket(packet->Ssrc(), packet->SequenceNumber(),
+ packet->capture_time().ms(),
+ packet->packet_type() == RtpPacketMediaType::kRetransmission,
+ packet->packet_type() == RtpPacketMediaType::kPadding);
+ }
+
+ std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding(
+ DataSize target_size) override {
+ std::vector<std::unique_ptr<RtpPacketToSend>> ret;
+ size_t padding_size = SendPadding(target_size.bytes());
+ if (padding_size > 0) {
+ auto packet = std::make_unique<RtpPacketToSend>(nullptr);
+ packet->SetPayloadSize(padding_size);
+ packet->set_packet_type(RtpPacketMediaType::kPadding);
+ ret.emplace_back(std::move(packet));
+ }
+ return ret;
+ }
+
+ MOCK_METHOD(void,
+ SendPacket,
+ (uint32_t ssrc,
+ uint16_t sequence_number,
+ int64_t capture_timestamp,
+ bool retransmission,
+ bool padding));
+ MOCK_METHOD(std::vector<std::unique_ptr<RtpPacketToSend>>,
+ FetchFec,
+ (),
+ (override));
+ MOCK_METHOD(size_t, SendPadding, (size_t target_size));
+ MOCK_METHOD(void,
+ OnAbortedRetransmissions,
+ (uint32_t, rtc::ArrayView<const uint16_t>),
+ (override));
+ MOCK_METHOD(absl::optional<uint32_t>,
+ GetRtxSsrcForMedia,
+ (uint32_t),
+ (const, override));
+};
+
+// Mock callback implementing the raw api.
+class MockPacketSender : public PacingController::PacketSender {
+ public:
+ MOCK_METHOD(void,
+ SendPacket,
+ (std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info),
+ (override));
+ MOCK_METHOD(std::vector<std::unique_ptr<RtpPacketToSend>>,
+ FetchFec,
+ (),
+ (override));
+
+ MOCK_METHOD(std::vector<std::unique_ptr<RtpPacketToSend>>,
+ GeneratePadding,
+ (DataSize target_size),
+ (override));
+ MOCK_METHOD(void,
+ OnAbortedRetransmissions,
+ (uint32_t, rtc::ArrayView<const uint16_t>),
+ (override));
+ MOCK_METHOD(absl::optional<uint32_t>,
+ GetRtxSsrcForMedia,
+ (uint32_t),
+ (const, override));
+};
+
+class PacingControllerPadding : public PacingController::PacketSender {
+ public:
+ static const size_t kPaddingPacketSize = 224;
+
+ PacingControllerPadding() : padding_sent_(0), total_bytes_sent_(0) {}
+
+ void SendPacket(std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& pacing_info) override {
+ total_bytes_sent_ += packet->payload_size();
+ }
+
+ std::vector<std::unique_ptr<RtpPacketToSend>> FetchFec() override {
+ return {};
+ }
+
+ std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding(
+ DataSize target_size) override {
+ size_t num_packets =
+ (target_size.bytes() + kPaddingPacketSize - 1) / kPaddingPacketSize;
+ std::vector<std::unique_ptr<RtpPacketToSend>> packets;
+ for (size_t i = 0; i < num_packets; ++i) {
+ packets.emplace_back(std::make_unique<RtpPacketToSend>(nullptr));
+ packets.back()->SetPadding(kPaddingPacketSize);
+ packets.back()->set_packet_type(RtpPacketMediaType::kPadding);
+ padding_sent_ += kPaddingPacketSize;
+ }
+ return packets;
+ }
+
+ void OnAbortedRetransmissions(uint32_t,
+ rtc::ArrayView<const uint16_t>) override {}
+ absl::optional<uint32_t> GetRtxSsrcForMedia(uint32_t) const override {
+ return absl::nullopt;
+ }
+
+ size_t padding_sent() { return padding_sent_; }
+ size_t total_bytes_sent() { return total_bytes_sent_; }
+
+ private:
+ size_t padding_sent_;
+ size_t total_bytes_sent_;
+};
+
+class PacingControllerProbing : public PacingController::PacketSender {
+ public:
+ PacingControllerProbing() : packets_sent_(0), padding_sent_(0) {}
+
+ void SendPacket(std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& pacing_info) override {
+ if (packet->packet_type() != RtpPacketMediaType::kPadding) {
+ ++packets_sent_;
+ }
+ last_pacing_info_ = pacing_info;
+ }
+
+ std::vector<std::unique_ptr<RtpPacketToSend>> FetchFec() override {
+ return {};
+ }
+
+ std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding(
+ DataSize target_size) override {
+ // From RTPSender:
+ // Max in the RFC 3550 is 255 bytes, we limit it to be modulus 32 for SRTP.
+ const DataSize kMaxPadding = DataSize::Bytes(224);
+
+ std::vector<std::unique_ptr<RtpPacketToSend>> packets;
+ while (target_size > DataSize::Zero()) {
+ DataSize padding_size = std::min(kMaxPadding, target_size);
+ packets.emplace_back(std::make_unique<RtpPacketToSend>(nullptr));
+ packets.back()->SetPadding(padding_size.bytes());
+ packets.back()->set_packet_type(RtpPacketMediaType::kPadding);
+ padding_sent_ += padding_size.bytes();
+ target_size -= padding_size;
+ }
+ return packets;
+ }
+
+ void OnAbortedRetransmissions(uint32_t,
+ rtc::ArrayView<const uint16_t>) override {}
+ absl::optional<uint32_t> GetRtxSsrcForMedia(uint32_t) const override {
+ return absl::nullopt;
+ }
+
+ int packets_sent() const { return packets_sent_; }
+ int padding_sent() const { return padding_sent_; }
+ int total_packets_sent() const { return packets_sent_ + padding_sent_; }
+ PacedPacketInfo last_pacing_info() const { return last_pacing_info_; }
+
+ private:
+ int packets_sent_;
+ int padding_sent_;
+ PacedPacketInfo last_pacing_info_;
+};
+
+class PacingControllerTest : public ::testing::Test {
+ protected:
+ PacingControllerTest() : clock_(123456), trials_("") {}
+
+ void SendAndExpectPacket(PacingController* pacer,
+ RtpPacketMediaType type,
+ uint32_t ssrc,
+ uint16_t sequence_number,
+ int64_t capture_time_ms,
+ size_t size) {
+ pacer->EnqueuePacket(
+ BuildPacket(type, ssrc, sequence_number, capture_time_ms, size));
+
+ EXPECT_CALL(callback_,
+ SendPacket(ssrc, sequence_number, capture_time_ms,
+ type == RtpPacketMediaType::kRetransmission, false));
+ }
+
+ void AdvanceTimeUntil(webrtc::Timestamp time) {
+ Timestamp now = clock_.CurrentTime();
+ clock_.AdvanceTime(std::max(TimeDelta::Zero(), time - now));
+ }
+
+ void ConsumeInitialBudget(PacingController* pacer) {
+ const uint32_t kSsrc = 54321;
+ uint16_t sequence_number = 1234;
+ int64_t capture_time_ms = clock_.TimeInMilliseconds();
+ const size_t kPacketSize = 250;
+
+ EXPECT_TRUE(pacer->OldestPacketEnqueueTime().IsInfinite());
+
+ // Due to the multiplicative factor we can send 5 packets during a send
+ // interval. (network capacity * multiplier / (8 bits per byte *
+ // (packet size * #send intervals per second)
+ const size_t packets_to_send_per_interval =
+ kTargetRate.bps() * kPaceMultiplier / (8 * kPacketSize * 200);
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ SendAndExpectPacket(pacer, RtpPacketMediaType::kVideo, kSsrc,
+ sequence_number++, capture_time_ms, kPacketSize);
+ }
+
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+ }
+
+ SimulatedClock clock_;
+
+ MediaStream audio_ = MediaStream(clock_,
+ /*type*/ RtpPacketMediaType::kAudio,
+ /*ssrc*/ kAudioSsrc,
+ /*packet_size*/ 100);
+ MediaStream video_ = MediaStream(clock_,
+ /*type*/ RtpPacketMediaType::kVideo,
+ /*ssrc*/ kVideoSsrc,
+ /*packet_size*/ 1000);
+
+ ::testing::NiceMock<MockPacingControllerCallback> callback_;
+ ExplicitKeyValueConfig trials_;
+};
+
+TEST_F(PacingControllerTest, DefaultNoPaddingInSilence) {
+ const test::ExplicitKeyValueConfig trials("");
+ PacingController pacer(&clock_, &callback_, trials);
+ pacer.SetPacingRates(kTargetRate, DataRate::Zero());
+ // Video packet to reset last send time and provide padding data.
+ pacer.EnqueuePacket(video_.BuildNextPacket());
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ clock_.AdvanceTimeMilliseconds(5);
+ pacer.ProcessPackets();
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ // Waiting 500 ms should not trigger sending of padding.
+ clock_.AdvanceTimeMilliseconds(500);
+ pacer.ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, PaddingInSilenceWithTrial) {
+ const test::ExplicitKeyValueConfig trials(
+ "WebRTC-Pacer-PadInSilence/Enabled/");
+ PacingController pacer(&clock_, &callback_, trials);
+ pacer.SetPacingRates(kTargetRate, DataRate::Zero());
+ // Video packet to reset last send time and provide padding data.
+ pacer.EnqueuePacket(video_.BuildNextPacket());
+ EXPECT_CALL(callback_, SendPacket).Times(2);
+ clock_.AdvanceTimeMilliseconds(5);
+ pacer.ProcessPackets();
+ EXPECT_CALL(callback_, SendPadding).WillOnce(Return(1000));
+ // Waiting 500 ms should trigger sending of padding.
+ clock_.AdvanceTimeMilliseconds(500);
+ pacer.ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, CongestionWindowAffectsAudioInTrial) {
+ const test::ExplicitKeyValueConfig trials("WebRTC-Pacer-BlockAudio/Enabled/");
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ PacingController pacer(&clock_, &callback_, trials);
+ pacer.SetPacingRates(DataRate::KilobitsPerSec(10000), DataRate::Zero());
+ // Video packet fills congestion window.
+ pacer.EnqueuePacket(video_.BuildNextPacket());
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+ pacer.SetCongested(true);
+ // Audio packet blocked due to congestion.
+ pacer.EnqueuePacket(audio_.BuildNextPacket());
+ EXPECT_CALL(callback_, SendPacket).Times(0);
+ // Forward time to where we send keep-alive.
+ EXPECT_CALL(callback_, SendPadding(1)).Times(2);
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+ // Audio packet unblocked when congestion window clear.
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+ pacer.SetCongested(false);
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, DefaultCongestionWindowDoesNotAffectAudio) {
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ const test::ExplicitKeyValueConfig trials("");
+ PacingController pacer(&clock_, &callback_, trials);
+ pacer.SetPacingRates(DataRate::BitsPerSec(10000000), DataRate::Zero());
+ // Video packet fills congestion window.
+ pacer.EnqueuePacket(video_.BuildNextPacket());
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+ pacer.SetCongested(true);
+ // Audio not blocked due to congestion.
+ pacer.EnqueuePacket(audio_.BuildNextPacket());
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, BudgetAffectsAudioInTrial) {
+ ExplicitKeyValueConfig trials("WebRTC-Pacer-BlockAudio/Enabled/");
+ PacingController pacer(&clock_, &callback_, trials);
+ const size_t kPacketSize = 1000;
+ const int kProcessIntervalsPerSecond = 1000 / 5;
+ DataRate pacing_rate =
+ DataRate::BitsPerSec(kPacketSize / 3 * 8 * kProcessIntervalsPerSecond);
+ pacer.SetPacingRates(pacing_rate, DataRate::Zero());
+ // Video fills budget for following process periods.
+ pacer.EnqueuePacket(video_.BuildNextPacket(kPacketSize));
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+ // Audio packet blocked due to budget limit.
+ pacer.EnqueuePacket(audio_.BuildNextPacket());
+ Timestamp wait_start_time = clock_.CurrentTime();
+ Timestamp wait_end_time = Timestamp::MinusInfinity();
+ EXPECT_CALL(callback_, SendPacket).WillOnce(WithoutArgs([&]() {
+ wait_end_time = clock_.CurrentTime();
+ }));
+ while (!wait_end_time.IsFinite()) {
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+ }
+ const TimeDelta expected_wait_time =
+ DataSize::Bytes(kPacketSize) / pacing_rate;
+ // Verify delay is near expectation, within timing margin.
+ EXPECT_LT(((wait_end_time - wait_start_time) - expected_wait_time).Abs(),
+ PacingController::kMinSleepTime);
+}
+
+TEST_F(PacingControllerTest, DefaultBudgetDoesNotAffectAudio) {
+ const size_t kPacketSize = 1000;
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ const test::ExplicitKeyValueConfig trials("");
+ PacingController pacer(&clock_, &callback_, trials);
+ const int kProcessIntervalsPerSecond = 1000 / 5;
+ pacer.SetPacingRates(
+ DataRate::BitsPerSec(kPacketSize / 3 * 8 * kProcessIntervalsPerSecond),
+ DataRate::Zero());
+ // Video fills budget for following process periods.
+ pacer.EnqueuePacket(video_.BuildNextPacket(kPacketSize));
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+ // Audio packet not blocked due to budget limit.
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ pacer.EnqueuePacket(audio_.BuildNextPacket());
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, FirstSentPacketTimeIsSet) {
+ const Timestamp kStartTime = clock_.CurrentTime();
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ // No packet sent.
+ EXPECT_FALSE(pacer->FirstSentPacketTime().has_value());
+ pacer->EnqueuePacket(video_.BuildNextPacket());
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ EXPECT_EQ(kStartTime, pacer->FirstSentPacketTime());
+}
+
+TEST_F(PacingControllerTest, QueueAndPacePackets) {
+ const uint32_t kSsrc = 12345;
+ uint16_t sequence_number = 1234;
+ const DataSize kPackeSize = DataSize::Bytes(250);
+ const TimeDelta kSendInterval = TimeDelta::Millis(5);
+
+ // Due to the multiplicative factor we can send 5 packets during a 5ms send
+ // interval. (send interval * network capacity * multiplier / packet size)
+ const size_t kPacketsToSend = (kSendInterval * kTargetRate).bytes() *
+ kPaceMultiplier / kPackeSize.bytes();
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ for (size_t i = 0; i < kPacketsToSend; ++i) {
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, kSsrc,
+ sequence_number++, clock_.TimeInMilliseconds(),
+ kPackeSize.bytes());
+ }
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+
+ // Enqueue one extra packet.
+ int64_t queued_packet_timestamp = clock_.TimeInMilliseconds();
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, kSsrc,
+ sequence_number, queued_packet_timestamp,
+ kPackeSize.bytes()));
+ EXPECT_EQ(kPacketsToSend + 1, pacer->QueueSizePackets());
+
+ // Send packets until the initial kPacketsToSend packets are done.
+ Timestamp start_time = clock_.CurrentTime();
+ while (pacer->QueueSizePackets() > 1) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+ EXPECT_LT(clock_.CurrentTime() - start_time, kSendInterval);
+
+ // Proceed till last packet can be sent.
+ EXPECT_CALL(callback_, SendPacket(kSsrc, sequence_number,
+ queued_packet_timestamp, false, false))
+ .Times(1);
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ EXPECT_GE(clock_.CurrentTime() - start_time, kSendInterval);
+ EXPECT_EQ(pacer->QueueSizePackets(), 0u);
+}
+
+TEST_F(PacingControllerTest, PaceQueuedPackets) {
+ uint32_t ssrc = 12345;
+ uint16_t sequence_number = 1234;
+ const size_t kPacketSize = 250;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ // Due to the multiplicative factor we can send 5 packets during a send
+ // interval. (network capacity * multiplier / (8 bits per byte *
+ // (packet size * #send intervals per second)
+ const size_t packets_to_send_per_interval =
+ kTargetRate.bps() * kPaceMultiplier / (8 * kPacketSize * 200);
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++, clock_.TimeInMilliseconds(),
+ kPacketSize);
+ }
+
+ for (size_t j = 0; j < packets_to_send_per_interval * 10; ++j) {
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize));
+ }
+ EXPECT_EQ(packets_to_send_per_interval + packets_to_send_per_interval * 10,
+ pacer->QueueSizePackets());
+
+ while (pacer->QueueSizePackets() > packets_to_send_per_interval * 10) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+ EXPECT_EQ(pacer->QueueSizePackets(), packets_to_send_per_interval * 10);
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+
+ EXPECT_CALL(callback_, SendPacket(ssrc, _, _, false, false))
+ .Times(pacer->QueueSizePackets());
+ const TimeDelta expected_pace_time =
+ DataSize::Bytes(pacer->QueueSizePackets() * kPacketSize) /
+ (kPaceMultiplier * kTargetRate);
+ Timestamp start_time = clock_.CurrentTime();
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+ const TimeDelta actual_pace_time = clock_.CurrentTime() - start_time;
+ EXPECT_LT((actual_pace_time - expected_pace_time).Abs(),
+ PacingController::kMinSleepTime);
+
+ EXPECT_EQ(0u, pacer->QueueSizePackets());
+ AdvanceTimeUntil(pacer->NextSendTime());
+ EXPECT_EQ(0u, pacer->QueueSizePackets());
+ pacer->ProcessPackets();
+
+ // Send some more packet, just show that we can..?
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++, clock_.TimeInMilliseconds(), 250);
+ }
+ EXPECT_EQ(packets_to_send_per_interval, pacer->QueueSizePackets());
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+ EXPECT_EQ(0u, pacer->QueueSizePackets());
+}
+
+TEST_F(PacingControllerTest, RepeatedRetransmissionsAllowed) {
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ // Send one packet, then two retransmissions of that packet.
+ for (size_t i = 0; i < 3; i++) {
+ constexpr uint32_t ssrc = 333;
+ constexpr uint16_t sequence_number = 444;
+ constexpr size_t bytes = 250;
+ bool is_retransmission = (i != 0); // Original followed by retransmissions.
+ SendAndExpectPacket(pacer.get(),
+ is_retransmission ? RtpPacketMediaType::kRetransmission
+ : RtpPacketMediaType::kVideo,
+ ssrc, sequence_number, clock_.TimeInMilliseconds(),
+ bytes);
+ clock_.AdvanceTimeMilliseconds(5);
+ }
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+}
+
+TEST_F(PacingControllerTest,
+ CanQueuePacketsWithSameSequenceNumberOnDifferentSsrcs) {
+ uint32_t ssrc = 12345;
+ uint16_t sequence_number = 1234;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number, clock_.TimeInMilliseconds(), 250);
+
+ // Expect packet on second ssrc to be queued and sent as well.
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc + 1,
+ sequence_number, clock_.TimeInMilliseconds(), 250);
+
+ clock_.AdvanceTimeMilliseconds(1000);
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+}
+
+TEST_F(PacingControllerTest, Padding) {
+ uint32_t ssrc = 12345;
+ uint16_t sequence_number = 1234;
+ const size_t kPacketSize = 250;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, kTargetRate);
+
+ const size_t kPacketsToSend = 20;
+ for (size_t i = 0; i < kPacketsToSend; ++i) {
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++, clock_.TimeInMilliseconds(),
+ kPacketSize);
+ }
+ const TimeDelta expected_pace_time =
+ DataSize::Bytes(pacer->QueueSizePackets() * kPacketSize) /
+ (kPaceMultiplier * kTargetRate);
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ // Only the media packets should be sent.
+ Timestamp start_time = clock_.CurrentTime();
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+ const TimeDelta actual_pace_time = clock_.CurrentTime() - start_time;
+ EXPECT_LE((actual_pace_time - expected_pace_time).Abs(),
+ PacingController::kMinSleepTime);
+
+ // Pacing media happens at 2.5x, but padding was configured with 1.0x
+ // factor. We have to wait until the padding debt is gone before we start
+ // sending padding.
+ const TimeDelta time_to_padding_debt_free =
+ (expected_pace_time * kPaceMultiplier) - actual_pace_time;
+ clock_.AdvanceTime(time_to_padding_debt_free -
+ PacingController::kMinSleepTime);
+ pacer->ProcessPackets();
+
+ // Send 10 padding packets.
+ const size_t kPaddingPacketsToSend = 10;
+ DataSize padding_sent = DataSize::Zero();
+ size_t packets_sent = 0;
+ Timestamp first_send_time = Timestamp::MinusInfinity();
+ Timestamp last_send_time = Timestamp::MinusInfinity();
+
+ EXPECT_CALL(callback_, SendPadding)
+ .Times(kPaddingPacketsToSend)
+ .WillRepeatedly([&](size_t target_size) {
+ ++packets_sent;
+ if (packets_sent < kPaddingPacketsToSend) {
+ // Don't count bytes of last packet, instead just
+ // use this as the time the last packet finished
+ // sending.
+ padding_sent += DataSize::Bytes(target_size);
+ }
+ if (first_send_time.IsInfinite()) {
+ first_send_time = clock_.CurrentTime();
+ } else {
+ last_send_time = clock_.CurrentTime();
+ }
+ return target_size;
+ });
+ EXPECT_CALL(callback_, SendPacket(_, _, _, false, true))
+ .Times(kPaddingPacketsToSend);
+
+ while (packets_sent < kPaddingPacketsToSend) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+
+ // Verify rate of sent padding.
+ TimeDelta padding_duration = last_send_time - first_send_time;
+ DataRate padding_rate = padding_sent / padding_duration;
+ EXPECT_EQ(padding_rate, kTargetRate);
+}
+
+TEST_F(PacingControllerTest, NoPaddingBeforeNormalPacket) {
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, kTargetRate);
+
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+
+ pacer->ProcessPackets();
+ AdvanceTimeUntil(pacer->NextSendTime());
+
+ pacer->ProcessPackets();
+ AdvanceTimeUntil(pacer->NextSendTime());
+
+ uint32_t ssrc = 12345;
+ uint16_t sequence_number = 1234;
+ int64_t capture_time_ms = 56789;
+
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++, capture_time_ms, 250);
+ bool padding_sent = false;
+ EXPECT_CALL(callback_, SendPadding).WillOnce([&](size_t padding) {
+ padding_sent = true;
+ return padding;
+ });
+ EXPECT_CALL(callback_, SendPacket(_, _, _, _, true)).Times(1);
+ while (!padding_sent) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+}
+
+TEST_F(PacingControllerTest, VerifyAverageBitrateVaryingMediaPayload) {
+ uint32_t ssrc = 12345;
+ uint16_t sequence_number = 1234;
+ int64_t capture_time_ms = 56789;
+ const TimeDelta kAveragingWindowLength = TimeDelta::Seconds(10);
+ PacingControllerPadding callback;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback, trials_);
+ pacer->SetProbingEnabled(false);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, kTargetRate);
+
+ Timestamp start_time = clock_.CurrentTime();
+ size_t media_bytes = 0;
+ while (clock_.CurrentTime() - start_time < kAveragingWindowLength) {
+ // Maybe add some new media packets corresponding to expected send rate.
+ int rand_value = rand(); // NOLINT (rand_r instead of rand)
+ while (
+ media_bytes <
+ (kTargetRate * (clock_.CurrentTime() - start_time)).bytes<size_t>()) {
+ size_t media_payload = rand_value % 400 + 800; // [400, 1200] bytes.
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++, capture_time_ms,
+ media_payload));
+ media_bytes += media_payload;
+ }
+
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+
+ EXPECT_NEAR(
+ kTargetRate.bps(),
+ (DataSize::Bytes(callback.total_bytes_sent()) / kAveragingWindowLength)
+ .bps(),
+ (kTargetRate * 0.01 /* 1% error marging */).bps());
+}
+
+TEST_F(PacingControllerTest, Priority) {
+ uint32_t ssrc_low_priority = 12345;
+ uint32_t ssrc = 12346;
+ uint16_t sequence_number = 1234;
+ int64_t capture_time_ms = 56789;
+ int64_t capture_time_ms_low_priority = 1234567;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ ConsumeInitialBudget(pacer.get());
+
+ // Expect normal and low priority to be queued and high to pass through.
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo,
+ ssrc_low_priority, sequence_number++,
+ capture_time_ms_low_priority, 250));
+
+ const size_t packets_to_send_per_interval =
+ kTargetRate.bps() * kPaceMultiplier / (8 * 250 * 200);
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kRetransmission, ssrc,
+ sequence_number++, capture_time_ms, 250));
+ }
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kAudio, ssrc,
+ sequence_number++, capture_time_ms, 250));
+
+ // Expect all high and normal priority to be sent out first.
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ EXPECT_CALL(callback_, SendPacket(ssrc, _, capture_time_ms, _, _))
+ .Times(packets_to_send_per_interval + 1);
+
+ while (pacer->QueueSizePackets() > 1) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+
+ EXPECT_EQ(1u, pacer->QueueSizePackets());
+
+ EXPECT_CALL(callback_, SendPacket(ssrc_low_priority, _,
+ capture_time_ms_low_priority, _, _));
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, RetransmissionPriority) {
+ uint32_t ssrc = 12345;
+ uint16_t sequence_number = 1234;
+ int64_t capture_time_ms = 45678;
+ int64_t capture_time_ms_retransmission = 56789;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ // Due to the multiplicative factor we can send 5 packets during a send
+ // interval. (network capacity * multiplier / (8 bits per byte *
+ // (packet size * #send intervals per second)
+ const size_t packets_to_send_per_interval =
+ kTargetRate.bps() * kPaceMultiplier / (8 * 250 * 200);
+ pacer->ProcessPackets();
+ EXPECT_EQ(0u, pacer->QueueSizePackets());
+
+ // Alternate retransmissions and normal packets.
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++, capture_time_ms, 250));
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kRetransmission, ssrc,
+ sequence_number++,
+ capture_time_ms_retransmission, 250));
+ }
+ EXPECT_EQ(2 * packets_to_send_per_interval, pacer->QueueSizePackets());
+
+ // Expect all retransmissions to be sent out first despite having a later
+ // capture time.
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ EXPECT_CALL(callback_, SendPacket(_, _, _, false, _)).Times(0);
+ EXPECT_CALL(callback_,
+ SendPacket(ssrc, _, capture_time_ms_retransmission, true, _))
+ .Times(packets_to_send_per_interval);
+
+ while (pacer->QueueSizePackets() > packets_to_send_per_interval) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+ EXPECT_EQ(packets_to_send_per_interval, pacer->QueueSizePackets());
+
+ // Expect the remaining (non-retransmission) packets to be sent.
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ EXPECT_CALL(callback_, SendPacket(_, _, _, true, _)).Times(0);
+ EXPECT_CALL(callback_, SendPacket(ssrc, _, capture_time_ms, false, _))
+ .Times(packets_to_send_per_interval);
+
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+ EXPECT_EQ(0u, pacer->QueueSizePackets());
+}
+
+TEST_F(PacingControllerTest, HighPrioDoesntAffectBudget) {
+ const size_t kPacketSize = 250;
+ uint32_t ssrc = 12346;
+ uint16_t sequence_number = 1234;
+ int64_t capture_time_ms = 56789;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ // As high prio packets doesn't affect the budget, we should be able to send
+ // a high number of them at once.
+ const size_t kNumAudioPackets = 25;
+ for (size_t i = 0; i < kNumAudioPackets; ++i) {
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kAudio, ssrc,
+ sequence_number++, capture_time_ms, kPacketSize);
+ }
+ pacer->ProcessPackets();
+ // Low prio packets does affect the budget.
+ // Due to the multiplicative factor we can send 5 packets during a send
+ // interval. (network capacity * multiplier / (8 bits per byte *
+ // (packet size * #send intervals per second)
+ const size_t kPacketsToSendPerInterval =
+ kTargetRate.bps() * kPaceMultiplier / (8 * kPacketSize * 200);
+ for (size_t i = 0; i < kPacketsToSendPerInterval; ++i) {
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++, clock_.TimeInMilliseconds(),
+ kPacketSize);
+ }
+
+ // Send all packets and measure pace time.
+ Timestamp start_time = clock_.CurrentTime();
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+
+ // Measure pacing time. Expect only low-prio packets to affect this.
+ TimeDelta pacing_time = clock_.CurrentTime() - start_time;
+ TimeDelta expected_pacing_time =
+ DataSize::Bytes(kPacketsToSendPerInterval * kPacketSize) /
+ (kTargetRate * kPaceMultiplier);
+ EXPECT_NEAR(pacing_time.us<double>(), expected_pacing_time.us<double>(),
+ PacingController::kMinSleepTime.us<double>());
+}
+
+TEST_F(PacingControllerTest, SendsOnlyPaddingWhenCongested) {
+ uint32_t ssrc = 202020;
+ uint16_t sequence_number = 1000;
+ int kPacketSize = 250;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ // Send an initial packet so we have a last send time.
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++, clock_.TimeInMilliseconds(),
+ kPacketSize);
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+
+ // Set congested state, we should not send anything until the 500ms since
+ // last send time limit for keep-alives is triggered.
+ EXPECT_CALL(callback_, SendPacket).Times(0);
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ pacer->SetCongested(true);
+ size_t blocked_packets = 0;
+ int64_t expected_time_until_padding = 500;
+ while (expected_time_until_padding > 5) {
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize));
+ blocked_packets++;
+ clock_.AdvanceTimeMilliseconds(5);
+ pacer->ProcessPackets();
+ expected_time_until_padding -= 5;
+ }
+
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+ EXPECT_CALL(callback_, SendPadding(1)).WillOnce(Return(1));
+ EXPECT_CALL(callback_, SendPacket(_, _, _, _, true)).Times(1);
+ clock_.AdvanceTimeMilliseconds(5);
+ pacer->ProcessPackets();
+ EXPECT_EQ(blocked_packets, pacer->QueueSizePackets());
+}
+
+TEST_F(PacingControllerTest, DoesNotAllowOveruseAfterCongestion) {
+ uint32_t ssrc = 202020;
+ uint16_t seq_num = 1000;
+ int size = 1000;
+ auto now_ms = [this] { return clock_.TimeInMilliseconds(); };
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ // The pacing rate is low enough that the budget should not allow two packets
+ // to be sent in a row.
+ pacer->SetPacingRates(DataRate::BitsPerSec(400 * 8 * 1000 / 5),
+ DataRate::Zero());
+ // Not yet budget limited or congested, packet is sent.
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kVideo, ssrc, seq_num++, now_ms(), size));
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ clock_.AdvanceTimeMilliseconds(5);
+ pacer->ProcessPackets();
+ // Packet blocked due to congestion.
+ pacer->SetCongested(true);
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kVideo, ssrc, seq_num++, now_ms(), size));
+ EXPECT_CALL(callback_, SendPacket).Times(0);
+ clock_.AdvanceTimeMilliseconds(5);
+ pacer->ProcessPackets();
+ // Packet blocked due to congestion.
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kVideo, ssrc, seq_num++, now_ms(), size));
+ EXPECT_CALL(callback_, SendPacket).Times(0);
+ clock_.AdvanceTimeMilliseconds(5);
+ pacer->ProcessPackets();
+ // Congestion removed and budget has recovered, packet is sent.
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kVideo, ssrc, seq_num++, now_ms(), size));
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ clock_.AdvanceTimeMilliseconds(5);
+ pacer->SetCongested(false);
+ pacer->ProcessPackets();
+ // Should be blocked due to budget limitation as congestion has be removed.
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kVideo, ssrc, seq_num++, now_ms(), size));
+ EXPECT_CALL(callback_, SendPacket).Times(0);
+ clock_.AdvanceTimeMilliseconds(5);
+ pacer->ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, Pause) {
+ uint32_t ssrc_low_priority = 12345;
+ uint32_t ssrc = 12346;
+ uint32_t ssrc_high_priority = 12347;
+ uint16_t sequence_number = 1234;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ EXPECT_TRUE(pacer->OldestPacketEnqueueTime().IsInfinite());
+
+ ConsumeInitialBudget(pacer.get());
+
+ pacer->Pause();
+
+ int64_t capture_time_ms = clock_.TimeInMilliseconds();
+ const size_t packets_to_send_per_interval =
+ kTargetRate.bps() * kPaceMultiplier / (8 * 250 * 200);
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo,
+ ssrc_low_priority, sequence_number++,
+ capture_time_ms, 250));
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kRetransmission, ssrc,
+ sequence_number++, capture_time_ms, 250));
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kAudio,
+ ssrc_high_priority, sequence_number++,
+ capture_time_ms, 250));
+ }
+ clock_.AdvanceTimeMilliseconds(10000);
+ int64_t second_capture_time_ms = clock_.TimeInMilliseconds();
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo,
+ ssrc_low_priority, sequence_number++,
+ second_capture_time_ms, 250));
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kRetransmission, ssrc,
+ sequence_number++, second_capture_time_ms,
+ 250));
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kAudio,
+ ssrc_high_priority, sequence_number++,
+ second_capture_time_ms, 250));
+ }
+
+ // Expect everything to be queued.
+ EXPECT_EQ(capture_time_ms, pacer->OldestPacketEnqueueTime().ms());
+
+ // Process triggers keep-alive packet.
+ EXPECT_CALL(callback_, SendPadding).WillOnce([](size_t padding) {
+ return padding;
+ });
+ EXPECT_CALL(callback_, SendPacket(_, _, _, _, true)).Times(1);
+ pacer->ProcessPackets();
+
+ // Verify no packets sent for the rest of the paused process interval.
+ const TimeDelta kProcessInterval = TimeDelta::Millis(5);
+ TimeDelta expected_time_until_send = PacingController::kPausedProcessInterval;
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ while (expected_time_until_send >= kProcessInterval) {
+ pacer->ProcessPackets();
+ clock_.AdvanceTime(kProcessInterval);
+ expected_time_until_send -= kProcessInterval;
+ }
+
+ // New keep-alive packet.
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+ EXPECT_CALL(callback_, SendPadding).WillOnce([](size_t padding) {
+ return padding;
+ });
+ EXPECT_CALL(callback_, SendPacket(_, _, _, _, true)).Times(1);
+ clock_.AdvanceTime(kProcessInterval);
+ pacer->ProcessPackets();
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+
+ // Expect high prio packets to come out first followed by normal
+ // prio packets and low prio packets (all in capture order).
+ {
+ ::testing::InSequence sequence;
+ EXPECT_CALL(callback_,
+ SendPacket(ssrc_high_priority, _, capture_time_ms, _, _))
+ .Times(packets_to_send_per_interval);
+ EXPECT_CALL(callback_,
+ SendPacket(ssrc_high_priority, _, second_capture_time_ms, _, _))
+ .Times(packets_to_send_per_interval);
+
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ EXPECT_CALL(callback_, SendPacket(ssrc, _, capture_time_ms, _, _))
+ .Times(1);
+ }
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ EXPECT_CALL(callback_, SendPacket(ssrc, _, second_capture_time_ms, _, _))
+ .Times(1);
+ }
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ EXPECT_CALL(callback_,
+ SendPacket(ssrc_low_priority, _, capture_time_ms, _, _))
+ .Times(1);
+ }
+ for (size_t i = 0; i < packets_to_send_per_interval; ++i) {
+ EXPECT_CALL(callback_, SendPacket(ssrc_low_priority, _,
+ second_capture_time_ms, _, _))
+ .Times(1);
+ }
+ }
+ pacer->Resume();
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+
+ EXPECT_TRUE(pacer->OldestPacketEnqueueTime().IsInfinite());
+}
+
+TEST_F(PacingControllerTest, InactiveFromStart) {
+ // Recreate the pacer without the inital time forwarding.
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetProbingEnabled(false);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, kTargetRate);
+
+ // No packets sent, there should be no keep-alives sent either.
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ EXPECT_CALL(callback_, SendPacket).Times(0);
+ pacer->ProcessPackets();
+
+ const Timestamp start_time = clock_.CurrentTime();
+
+ // Determine the margin need so we can advance to the last possible moment
+ // that will not cause a process event.
+ const TimeDelta time_margin =
+ PacingController::kMinSleepTime + TimeDelta::Micros(1);
+
+ EXPECT_EQ(pacer->NextSendTime() - start_time,
+ PacingController::kPausedProcessInterval);
+ clock_.AdvanceTime(PacingController::kPausedProcessInterval - time_margin);
+ pacer->ProcessPackets();
+ EXPECT_EQ(pacer->NextSendTime() - start_time,
+ PacingController::kPausedProcessInterval);
+
+ clock_.AdvanceTime(time_margin);
+ pacer->ProcessPackets();
+ EXPECT_EQ(pacer->NextSendTime() - start_time,
+ 2 * PacingController::kPausedProcessInterval);
+}
+
+TEST_F(PacingControllerTest, QueueTimeGrowsOverTime) {
+ uint32_t ssrc = 12346;
+ uint16_t sequence_number = 1234;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+ EXPECT_TRUE(pacer->OldestPacketEnqueueTime().IsInfinite());
+
+ pacer->SetPacingRates(DataRate::BitsPerSec(30000 * kPaceMultiplier),
+ DataRate::Zero());
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number, clock_.TimeInMilliseconds(), 1200);
+
+ clock_.AdvanceTimeMilliseconds(500);
+ EXPECT_EQ(clock_.TimeInMilliseconds() - 500,
+ pacer->OldestPacketEnqueueTime().ms());
+ pacer->ProcessPackets();
+ EXPECT_TRUE(pacer->OldestPacketEnqueueTime().IsInfinite());
+}
+
+TEST_F(PacingControllerTest, ProbingWithInsertedPackets) {
+ const size_t kPacketSize = 1200;
+ const int kInitialBitrateBps = 300000;
+ uint32_t ssrc = 12346;
+ uint16_t sequence_number = 1234;
+
+ PacingControllerProbing packet_sender;
+ auto pacer =
+ std::make_unique<PacingController>(&clock_, &packet_sender, trials_);
+ std::vector<ProbeClusterConfig> probe_clusters = {
+ {.at_time = clock_.CurrentTime(),
+ .target_data_rate = kFirstClusterRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0},
+ {.at_time = clock_.CurrentTime(),
+ .target_data_rate = kSecondClusterRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 1}};
+ pacer->CreateProbeClusters(probe_clusters);
+ pacer->SetPacingRates(
+ DataRate::BitsPerSec(kInitialBitrateBps * kPaceMultiplier),
+ DataRate::Zero());
+
+ for (int i = 0; i < 10; ++i) {
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize));
+ }
+
+ int64_t start = clock_.TimeInMilliseconds();
+ while (packet_sender.packets_sent() < 5) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+ int packets_sent = packet_sender.packets_sent();
+ // Validate first cluster bitrate. Note that we have to account for number
+ // of intervals and hence (packets_sent - 1) on the first cluster.
+ EXPECT_NEAR((packets_sent - 1) * kPacketSize * 8000 /
+ (clock_.TimeInMilliseconds() - start),
+ kFirstClusterRate.bps(), kProbingErrorMargin.bps());
+ // Probing always starts with a small padding packet.
+ EXPECT_EQ(1, packet_sender.padding_sent());
+
+ AdvanceTimeUntil(pacer->NextSendTime());
+ start = clock_.TimeInMilliseconds();
+ while (packet_sender.packets_sent() < 10) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+ packets_sent = packet_sender.packets_sent() - packets_sent;
+ // Validate second cluster bitrate.
+ EXPECT_NEAR((packets_sent - 1) * kPacketSize * 8000 /
+ (clock_.TimeInMilliseconds() - start),
+ kSecondClusterRate.bps(), kProbingErrorMargin.bps());
+}
+
+TEST_F(PacingControllerTest, SkipsProbesWhenProcessIntervalTooLarge) {
+ const size_t kPacketSize = 1200;
+ const int kInitialBitrateBps = 300000;
+ const uint32_t ssrc = 12346;
+ const int kProbeClusterId = 3;
+
+ uint16_t sequence_number = 1234;
+
+ PacingControllerProbing packet_sender;
+
+ const test::ExplicitKeyValueConfig trials(
+ "WebRTC-Bwe-ProbingBehavior/max_probe_delay:2ms/");
+ auto pacer =
+ std::make_unique<PacingController>(&clock_, &packet_sender, trials);
+ pacer->SetPacingRates(
+ DataRate::BitsPerSec(kInitialBitrateBps * kPaceMultiplier),
+ DataRate::BitsPerSec(kInitialBitrateBps));
+
+ for (int i = 0; i < 10; ++i) {
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize));
+ }
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+
+ // Probe at a very high rate.
+ std::vector<ProbeClusterConfig> probe_clusters = {
+ {.at_time = clock_.CurrentTime(),
+ .target_data_rate = DataRate::KilobitsPerSec(10000), // 10 Mbps,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = kProbeClusterId}};
+ pacer->CreateProbeClusters(probe_clusters);
+
+ // We need one packet to start the probe.
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize));
+ const int packets_sent_before_probe = packet_sender.packets_sent();
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ EXPECT_EQ(packet_sender.packets_sent(), packets_sent_before_probe + 1);
+
+ // Figure out how long between probe packets.
+ Timestamp start_time = clock_.CurrentTime();
+ AdvanceTimeUntil(pacer->NextSendTime());
+ TimeDelta time_between_probes = clock_.CurrentTime() - start_time;
+ // Advance that distance again + 1ms.
+ clock_.AdvanceTime(time_between_probes);
+
+ // Send second probe packet.
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize));
+ pacer->ProcessPackets();
+ EXPECT_EQ(packet_sender.packets_sent(), packets_sent_before_probe + 2);
+ PacedPacketInfo last_pacing_info = packet_sender.last_pacing_info();
+ EXPECT_EQ(last_pacing_info.probe_cluster_id, kProbeClusterId);
+
+ // We're exactly where we should be for the next probe.
+ const Timestamp probe_time = clock_.CurrentTime();
+ EXPECT_EQ(pacer->NextSendTime(), clock_.CurrentTime());
+
+ BitrateProberConfig probing_config(&trials);
+ EXPECT_GT(probing_config.max_probe_delay.Get(), TimeDelta::Zero());
+ // Advance to within max probe delay, should still return same target.
+ clock_.AdvanceTime(probing_config.max_probe_delay.Get());
+ EXPECT_EQ(pacer->NextSendTime(), probe_time);
+
+ // Too high probe delay, drop it!
+ clock_.AdvanceTime(TimeDelta::Micros(1));
+
+ int packets_sent_before_timeout = packet_sender.total_packets_sent();
+ // Expected next process time is unchanged, but calling should not
+ // generate new packets.
+ EXPECT_EQ(pacer->NextSendTime(), probe_time);
+ pacer->ProcessPackets();
+ EXPECT_EQ(packet_sender.total_packets_sent(), packets_sent_before_timeout);
+
+ // Next packet sent is not part of probe.
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ const int expected_probe_id = PacedPacketInfo::kNotAProbe;
+ EXPECT_EQ(packet_sender.last_pacing_info().probe_cluster_id,
+ expected_probe_id);
+}
+
+TEST_F(PacingControllerTest, ProbingWithPaddingSupport) {
+ const size_t kPacketSize = 1200;
+ const int kInitialBitrateBps = 300000;
+ uint32_t ssrc = 12346;
+ uint16_t sequence_number = 1234;
+
+ PacingControllerProbing packet_sender;
+ auto pacer =
+ std::make_unique<PacingController>(&clock_, &packet_sender, trials_);
+ std::vector<ProbeClusterConfig> probe_clusters = {
+ {.at_time = clock_.CurrentTime(),
+ .target_data_rate = kFirstClusterRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0}};
+ pacer->CreateProbeClusters(probe_clusters);
+
+ pacer->SetPacingRates(
+ DataRate::BitsPerSec(kInitialBitrateBps * kPaceMultiplier),
+ DataRate::Zero());
+
+ for (int i = 0; i < 3; ++i) {
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize));
+ }
+
+ int64_t start = clock_.TimeInMilliseconds();
+ int process_count = 0;
+ while (process_count < 5) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ ++process_count;
+ }
+ int packets_sent = packet_sender.packets_sent();
+ int padding_sent = packet_sender.padding_sent();
+ EXPECT_GT(packets_sent, 0);
+ EXPECT_GT(padding_sent, 0);
+ // Note that the number of intervals here for kPacketSize is
+ // packets_sent due to padding in the same cluster.
+ EXPECT_NEAR((packets_sent * kPacketSize * 8000 + padding_sent) /
+ (clock_.TimeInMilliseconds() - start),
+ kFirstClusterRate.bps(), kProbingErrorMargin.bps());
+}
+
+TEST_F(PacingControllerTest, PaddingOveruse) {
+ uint32_t ssrc = 12346;
+ uint16_t sequence_number = 1234;
+ const size_t kPacketSize = 1200;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ // Initially no padding rate.
+ pacer->ProcessPackets();
+ pacer->SetPacingRates(DataRate::BitsPerSec(60000 * kPaceMultiplier),
+ DataRate::Zero());
+
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++, clock_.TimeInMilliseconds(),
+ kPacketSize);
+ pacer->ProcessPackets();
+
+ // Add 30kbit padding. When increasing budget, media budget will increase from
+ // negative (overuse) while padding budget will increase from 0.
+ clock_.AdvanceTimeMilliseconds(5);
+ pacer->SetPacingRates(DataRate::BitsPerSec(60000 * kPaceMultiplier),
+ DataRate::BitsPerSec(30000));
+
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++, clock_.TimeInMilliseconds(),
+ kPacketSize);
+ EXPECT_LT(TimeDelta::Millis(5), pacer->ExpectedQueueTime());
+ // Don't send padding if queue is non-empty, even if padding budget > 0.
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, ProbeClusterId) {
+ MockPacketSender callback;
+ uint32_t ssrc = 12346;
+ uint16_t sequence_number = 1234;
+ const size_t kPacketSize = 1200;
+
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback, trials_);
+ pacer->CreateProbeClusters(std::vector<ProbeClusterConfig>(
+ {{.at_time = clock_.CurrentTime(),
+ .target_data_rate = kFirstClusterRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0},
+ {.at_time = clock_.CurrentTime(),
+ .target_data_rate = kSecondClusterRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 1}}));
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, kTargetRate);
+ pacer->SetProbingEnabled(true);
+ for (int i = 0; i < 10; ++i) {
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, ssrc,
+ sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize));
+ }
+
+ // First probing cluster.
+ EXPECT_CALL(callback,
+ SendPacket(_, Field(&PacedPacketInfo::probe_cluster_id, 0)))
+ .Times(5);
+
+ for (int i = 0; i < 5; ++i) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+
+ // Second probing cluster.
+ EXPECT_CALL(callback,
+ SendPacket(_, Field(&PacedPacketInfo::probe_cluster_id, 1)))
+ .Times(5);
+
+ for (int i = 0; i < 5; ++i) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+
+ // Needed for the Field comparer below.
+ const int kNotAProbe = PacedPacketInfo::kNotAProbe;
+ // No more probing packets.
+ EXPECT_CALL(callback, GeneratePadding).WillOnce([&](DataSize padding_size) {
+ std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets;
+ padding_packets.emplace_back(
+ BuildPacket(RtpPacketMediaType::kPadding, ssrc, sequence_number++,
+ clock_.TimeInMilliseconds(), padding_size.bytes()));
+ return padding_packets;
+ });
+ bool non_probe_packet_seen = false;
+ EXPECT_CALL(callback, SendPacket)
+ .WillOnce([&](std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) {
+ EXPECT_EQ(cluster_info.probe_cluster_id, kNotAProbe);
+ non_probe_packet_seen = true;
+ });
+ while (!non_probe_packet_seen) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+}
+
+TEST_F(PacingControllerTest, OwnedPacketPrioritizedOnType) {
+ MockPacketSender callback;
+ uint32_t ssrc = 123;
+
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback, trials_);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ // Insert a packet of each type, from low to high priority. Since priority
+ // is weighted higher than insert order, these should come out of the pacer
+ // in backwards order with the exception of FEC and Video.
+
+ for (RtpPacketMediaType type :
+ {RtpPacketMediaType::kPadding,
+ RtpPacketMediaType::kForwardErrorCorrection, RtpPacketMediaType::kVideo,
+ RtpPacketMediaType::kRetransmission, RtpPacketMediaType::kAudio}) {
+ pacer->EnqueuePacket(BuildPacket(type, ++ssrc, /*sequence_number=*/123,
+ clock_.TimeInMilliseconds(),
+ /*size=*/150));
+ }
+
+ ::testing::InSequence seq;
+ EXPECT_CALL(callback,
+ SendPacket(Pointee(Property(&RtpPacketToSend::packet_type,
+ RtpPacketMediaType::kAudio)),
+ _));
+ EXPECT_CALL(callback,
+ SendPacket(Pointee(Property(&RtpPacketToSend::packet_type,
+ RtpPacketMediaType::kRetransmission)),
+ _));
+
+ // FEC and video actually have the same priority, so will come out in
+ // insertion order.
+ EXPECT_CALL(
+ callback,
+ SendPacket(Pointee(Property(&RtpPacketToSend::packet_type,
+ RtpPacketMediaType::kForwardErrorCorrection)),
+ _));
+ EXPECT_CALL(callback,
+ SendPacket(Pointee(Property(&RtpPacketToSend::packet_type,
+ RtpPacketMediaType::kVideo)),
+ _));
+
+ EXPECT_CALL(callback,
+ SendPacket(Pointee(Property(&RtpPacketToSend::packet_type,
+ RtpPacketMediaType::kPadding)),
+ _));
+
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+}
+
+TEST_F(PacingControllerTest, SmallFirstProbePacket) {
+ MockPacketSender callback;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback, trials_);
+ std::vector<ProbeClusterConfig> probe_clusters = {
+ {.at_time = clock_.CurrentTime(),
+ .target_data_rate = kFirstClusterRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0}};
+ pacer->CreateProbeClusters(probe_clusters);
+
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+
+ // Add high prio media.
+ pacer->EnqueuePacket(audio_.BuildNextPacket(234));
+
+ // Expect small padding packet to be requested.
+ EXPECT_CALL(callback, GeneratePadding(DataSize::Bytes(1)))
+ .WillOnce([&](DataSize padding_size) {
+ std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets;
+ padding_packets.emplace_back(
+ BuildPacket(RtpPacketMediaType::kPadding, kAudioSsrc, 1,
+ clock_.TimeInMilliseconds(), 1));
+ return padding_packets;
+ });
+
+ size_t packets_sent = 0;
+ bool media_seen = false;
+ EXPECT_CALL(callback, SendPacket)
+ .Times(AnyNumber())
+ .WillRepeatedly([&](std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) {
+ if (packets_sent == 0) {
+ EXPECT_EQ(packet->packet_type(), RtpPacketMediaType::kPadding);
+ } else {
+ if (packet->packet_type() == RtpPacketMediaType::kAudio) {
+ media_seen = true;
+ }
+ }
+ packets_sent++;
+ });
+ while (!media_seen) {
+ pacer->ProcessPackets();
+ clock_.AdvanceTimeMilliseconds(5);
+ }
+}
+
+TEST_F(PacingControllerTest, TaskLate) {
+ // Set a low send rate to more easily test timing issues.
+ DataRate kSendRate = DataRate::KilobitsPerSec(30);
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetPacingRates(kSendRate, DataRate::Zero());
+
+ // Add four packets of equal size and priority.
+ pacer->EnqueuePacket(video_.BuildNextPacket(1000));
+ pacer->EnqueuePacket(video_.BuildNextPacket(1000));
+ pacer->EnqueuePacket(video_.BuildNextPacket(1000));
+ pacer->EnqueuePacket(video_.BuildNextPacket(1000));
+
+ // Process packets, only first should be sent.
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+ pacer->ProcessPackets();
+
+ Timestamp next_send_time = pacer->NextSendTime();
+ // Determine time between packets (ca 62ms)
+ const TimeDelta time_between_packets = next_send_time - clock_.CurrentTime();
+
+ // Simulate a late process call, executed just before we allow sending the
+ // fourth packet.
+ const TimeDelta kOffset = TimeDelta::Millis(1);
+ clock_.AdvanceTime((time_between_packets * 3) - kOffset);
+
+ EXPECT_CALL(callback_, SendPacket).Times(2);
+ pacer->ProcessPackets();
+
+ // Check that next scheduled send time is in ca 1ms.
+ next_send_time = pacer->NextSendTime();
+ const TimeDelta time_left = next_send_time - clock_.CurrentTime();
+ EXPECT_EQ(time_left.RoundTo(TimeDelta::Millis(1)), kOffset);
+
+ clock_.AdvanceTime(time_left);
+ EXPECT_CALL(callback_, SendPacket);
+ pacer->ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, NoProbingWhilePaused) {
+ uint32_t ssrc = 12345;
+ uint16_t sequence_number = 1234;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ pacer->SetProbingEnabled(true);
+ pacer->SetPacingRates(kTargetRate * kPaceMultiplier, DataRate::Zero());
+ pacer->CreateProbeClusters(std::vector<ProbeClusterConfig>(
+ {{.at_time = clock_.CurrentTime(),
+ .target_data_rate = kFirstClusterRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 0},
+ {.at_time = clock_.CurrentTime(),
+ .target_data_rate = kSecondClusterRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 1}}));
+
+ // Send at least one packet so probing can initate.
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, ssrc,
+ sequence_number, clock_.TimeInMilliseconds(), 250);
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+
+ // Trigger probing.
+ std::vector<ProbeClusterConfig> probe_clusters = {
+ {.at_time = clock_.CurrentTime(),
+ .target_data_rate = DataRate::KilobitsPerSec(10000), // 10 Mbps.
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 3}};
+ pacer->CreateProbeClusters(probe_clusters);
+
+ // Time to next send time should be small.
+ EXPECT_LT(pacer->NextSendTime() - clock_.CurrentTime(),
+ PacingController::kPausedProcessInterval);
+
+ // Pause pacer, time to next send time should now be the pause process
+ // interval.
+ pacer->Pause();
+
+ EXPECT_EQ(pacer->NextSendTime() - clock_.CurrentTime(),
+ PacingController::kPausedProcessInterval);
+}
+
+TEST_F(PacingControllerTest, AudioNotPacedEvenWhenAccountedFor) {
+ const uint32_t kSsrc = 12345;
+ uint16_t sequence_number = 1234;
+ const size_t kPacketSize = 123;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+
+ // Account for audio - so that audio packets can cause pushback on other
+ // types such as video. Audio packet should still be immediated passed
+ // through though ("WebRTC-Pacer-BlockAudio" needs to be enabled in order
+ // to pace audio packets).
+ pacer->SetAccountForAudioPackets(true);
+
+ // Set pacing rate to 1 packet/s, no padding.
+ pacer->SetPacingRates(DataSize::Bytes(kPacketSize) / TimeDelta::Seconds(1),
+ DataRate::Zero());
+
+ // Add and send an audio packet.
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kAudio, kSsrc,
+ sequence_number++, clock_.TimeInMilliseconds(),
+ kPacketSize);
+ pacer->ProcessPackets();
+
+ // Advance time, add another audio packet and process. It should be sent
+ // immediately.
+ clock_.AdvanceTimeMilliseconds(5);
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kAudio, kSsrc,
+ sequence_number++, clock_.TimeInMilliseconds(),
+ kPacketSize);
+ pacer->ProcessPackets();
+}
+
+TEST_F(PacingControllerTest,
+ PaddingResumesAfterSaturationEvenWithConcurrentAudio) {
+ const uint32_t kSsrc = 12345;
+ const DataRate kPacingDataRate = DataRate::KilobitsPerSec(125);
+ const DataRate kPaddingDataRate = DataRate::KilobitsPerSec(100);
+ const TimeDelta kMaxBufferInTime = TimeDelta::Millis(500);
+ const DataSize kPacketSize = DataSize::Bytes(130);
+ const TimeDelta kAudioPacketInterval = TimeDelta::Millis(20);
+
+ // In this test, we fist send a burst of video in order to saturate the
+ // padding debt level.
+ // We then proceed to send audio at a bitrate that is slightly lower than
+ // the padding rate, meaning there will be a period with audio but no
+ // padding sent while the debt is draining, then audio and padding will
+ // be interlieved.
+
+ // Verify both with and without accounting for audio.
+ for (bool account_for_audio : {false, true}) {
+ uint16_t sequence_number = 1234;
+ MockPacketSender callback;
+ EXPECT_CALL(callback, SendPacket).Times(AnyNumber());
+ auto pacer =
+ std::make_unique<PacingController>(&clock_, &callback, trials_);
+ pacer->SetAccountForAudioPackets(account_for_audio);
+
+ // First, saturate the padding budget.
+ pacer->SetPacingRates(kPacingDataRate, kPaddingDataRate);
+
+ const TimeDelta kPaddingSaturationTime =
+ kMaxBufferInTime * kPaddingDataRate /
+ (kPacingDataRate - kPaddingDataRate);
+ const DataSize kVideoToSend = kPaddingSaturationTime * kPacingDataRate;
+ const DataSize kVideoPacketSize = DataSize::Bytes(1200);
+ DataSize video_sent = DataSize::Zero();
+ while (video_sent < kVideoToSend) {
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kVideo, kSsrc, sequence_number++,
+ clock_.TimeInMilliseconds(), kVideoPacketSize.bytes()));
+ video_sent += kVideoPacketSize;
+ }
+ while (pacer->QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ }
+
+ // Add a stream of audio packets at a rate slightly lower than the padding
+ // rate, once the padding debt is paid off we expect padding to be
+ // generated.
+ pacer->SetPacingRates(kPacingDataRate, kPaddingDataRate);
+ bool padding_seen = false;
+ EXPECT_CALL(callback, GeneratePadding).WillOnce([&](DataSize padding_size) {
+ padding_seen = true;
+ std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets;
+ padding_packets.emplace_back(
+ BuildPacket(RtpPacketMediaType::kPadding, kSsrc, sequence_number++,
+ clock_.TimeInMilliseconds(), padding_size.bytes()));
+ return padding_packets;
+ });
+
+ Timestamp start_time = clock_.CurrentTime();
+ Timestamp last_audio_time = start_time;
+ while (!padding_seen) {
+ Timestamp now = clock_.CurrentTime();
+ Timestamp next_send_time = pacer->NextSendTime();
+ TimeDelta sleep_time =
+ std::min(next_send_time, last_audio_time + kAudioPacketInterval) -
+ now;
+ clock_.AdvanceTime(sleep_time);
+ while (clock_.CurrentTime() >= last_audio_time + kAudioPacketInterval) {
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kAudio, kSsrc, sequence_number++,
+ clock_.TimeInMilliseconds(), kPacketSize.bytes()));
+ last_audio_time += kAudioPacketInterval;
+ }
+ pacer->ProcessPackets();
+ }
+
+ // Verify how long it took to drain the padding debt. Allow 2% error margin.
+ const DataRate kAudioDataRate = kPacketSize / kAudioPacketInterval;
+ const TimeDelta expected_drain_time =
+ account_for_audio ? (kMaxBufferInTime * kPaddingDataRate /
+ (kPaddingDataRate - kAudioDataRate))
+ : kMaxBufferInTime;
+ const TimeDelta actual_drain_time = clock_.CurrentTime() - start_time;
+ EXPECT_NEAR(actual_drain_time.ms(), expected_drain_time.ms(),
+ expected_drain_time.ms() * 0.02)
+ << " where account_for_audio = "
+ << (account_for_audio ? "true" : "false");
+ }
+}
+
+TEST_F(PacingControllerTest, AccountsForAudioEnqueueTime) {
+ const uint32_t kSsrc = 12345;
+ const DataRate kPacingDataRate = DataRate::KilobitsPerSec(125);
+ const DataRate kPaddingDataRate = DataRate::Zero();
+ const DataSize kPacketSize = DataSize::Bytes(130);
+ const TimeDelta kPacketPacingTime = kPacketSize / kPacingDataRate;
+ uint32_t sequnce_number = 1;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+ // Audio not paced, but still accounted for in budget.
+ pacer->SetAccountForAudioPackets(true);
+ pacer->SetPacingRates(kPacingDataRate, kPaddingDataRate);
+
+ // Enqueue two audio packets, advance clock to where one packet
+ // should have drained the buffer already, has they been sent
+ // immediately.
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kAudio, kSsrc,
+ sequnce_number++, clock_.TimeInMilliseconds(),
+ kPacketSize.bytes());
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kAudio, kSsrc,
+ sequnce_number++, clock_.TimeInMilliseconds(),
+ kPacketSize.bytes());
+ clock_.AdvanceTime(kPacketPacingTime);
+ // Now process and make sure both packets were sent.
+ pacer->ProcessPackets();
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+
+ // Add a video packet. I can't be sent until debt from audio
+ // packets have been drained.
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kVideo, kSsrc + 1, sequnce_number++,
+ clock_.TimeInMilliseconds(), kPacketSize.bytes()));
+ EXPECT_EQ(pacer->NextSendTime() - clock_.CurrentTime(), kPacketPacingTime);
+}
+
+TEST_F(PacingControllerTest, NextSendTimeAccountsForPadding) {
+ const uint32_t kSsrc = 12345;
+ const DataRate kPacingDataRate = DataRate::KilobitsPerSec(125);
+ const DataSize kPacketSize = DataSize::Bytes(130);
+ const TimeDelta kPacketPacingTime = kPacketSize / kPacingDataRate;
+ uint32_t sequnce_number = 1;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+
+ // Start with no padding.
+ pacer->SetPacingRates(kPacingDataRate, DataRate::Zero());
+
+ // Send a single packet.
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, kSsrc,
+ sequnce_number++, clock_.TimeInMilliseconds(),
+ kPacketSize.bytes());
+ pacer->ProcessPackets();
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+
+ // With current conditions, no need to wake until next keep-alive.
+ EXPECT_EQ(pacer->NextSendTime() - clock_.CurrentTime(),
+ PacingController::kPausedProcessInterval);
+
+ // Enqueue a new packet, that can't be sent until previous buffer has
+ // drained.
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, kSsrc,
+ sequnce_number++, clock_.TimeInMilliseconds(),
+ kPacketSize.bytes());
+ EXPECT_EQ(pacer->NextSendTime() - clock_.CurrentTime(), kPacketPacingTime);
+ clock_.AdvanceTime(kPacketPacingTime);
+ pacer->ProcessPackets();
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+
+ // With current conditions, again no need to wake until next keep-alive.
+ EXPECT_EQ(pacer->NextSendTime() - clock_.CurrentTime(),
+ PacingController::kPausedProcessInterval);
+
+ // Set a non-zero padding rate. Padding also can't be sent until
+ // previous debt has cleared. Since padding was disabled before, there
+ // currently is no padding debt.
+ pacer->SetPacingRates(kPacingDataRate, kPacingDataRate / 2);
+ EXPECT_EQ(pacer->NextSendTime() - clock_.CurrentTime(), kPacketPacingTime);
+
+ // Advance time, expect padding.
+ EXPECT_CALL(callback_, SendPadding).WillOnce(Return(kPacketSize.bytes()));
+ clock_.AdvanceTime(kPacketPacingTime);
+ pacer->ProcessPackets();
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+
+ // Since padding rate is half of pacing rate, next time we can send
+ // padding is double the packet pacing time.
+ EXPECT_EQ(pacer->NextSendTime() - clock_.CurrentTime(),
+ kPacketPacingTime * 2);
+
+ // Insert a packet to be sent, this take precedence again.
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kVideo, kSsrc, sequnce_number++,
+ clock_.TimeInMilliseconds(), kPacketSize.bytes()));
+ EXPECT_EQ(pacer->NextSendTime() - clock_.CurrentTime(), kPacketPacingTime);
+}
+
+TEST_F(PacingControllerTest, PaddingTargetAccountsForPaddingRate) {
+ // Target size for a padding packet is 5ms * padding rate.
+ const TimeDelta kPaddingTarget = TimeDelta::Millis(5);
+ srand(0);
+ // Need to initialize PacingController after we initialize clock.
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+
+ const uint32_t kSsrc = 12345;
+ const DataRate kPacingDataRate = DataRate::KilobitsPerSec(125);
+ const DataSize kPacketSize = DataSize::Bytes(130);
+
+ uint32_t sequnce_number = 1;
+
+ // Start with pacing and padding rate equal.
+ pacer->SetPacingRates(kPacingDataRate, kPacingDataRate);
+
+ // Send a single packet.
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, kSsrc,
+ sequnce_number++, clock_.TimeInMilliseconds(),
+ kPacketSize.bytes());
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+
+ size_t expected_padding_target_bytes =
+ (kPaddingTarget * kPacingDataRate).bytes();
+ EXPECT_CALL(callback_, SendPadding(expected_padding_target_bytes))
+ .WillOnce(Return(expected_padding_target_bytes));
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+
+ // Half the padding rate - expect half the padding target.
+ pacer->SetPacingRates(kPacingDataRate, kPacingDataRate / 2);
+ EXPECT_CALL(callback_, SendPadding(expected_padding_target_bytes / 2))
+ .WillOnce(Return(expected_padding_target_bytes / 2));
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, SendsFecPackets) {
+ const uint32_t kSsrc = 12345;
+ const uint32_t kFlexSsrc = 54321;
+ uint16_t sequence_number = 1234;
+ uint16_t flexfec_sequence_number = 4321;
+ const size_t kPacketSize = 123;
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+
+ // Set pacing rate to 1000 packet/s, no padding.
+ pacer->SetPacingRates(
+ DataSize::Bytes(1000 * kPacketSize) / TimeDelta::Seconds(1),
+ DataRate::Zero());
+
+ int64_t now = clock_.TimeInMilliseconds();
+ pacer->EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, kSsrc,
+ sequence_number, now, kPacketSize));
+ EXPECT_CALL(callback_, SendPacket(kSsrc, sequence_number, now, false, false));
+ EXPECT_CALL(callback_, FetchFec).WillOnce([&]() {
+ EXPECT_CALL(callback_, SendPacket(kFlexSsrc, flexfec_sequence_number, now,
+ false, false));
+ EXPECT_CALL(callback_, FetchFec);
+ std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets;
+ fec_packets.push_back(
+ BuildPacket(RtpPacketMediaType::kForwardErrorCorrection, kFlexSsrc,
+ flexfec_sequence_number, now, kPacketSize));
+ return fec_packets;
+ });
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+ AdvanceTimeUntil(pacer->NextSendTime());
+ pacer->ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, GapInPacingDoesntAccumulateBudget) {
+ const uint32_t kSsrc = 12345;
+ uint16_t sequence_number = 1234;
+ const DataSize kPackeSize = DataSize::Bytes(250);
+ const TimeDelta kPacketSendTime = TimeDelta::Millis(15);
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+
+ pacer->SetPacingRates(kPackeSize / kPacketSendTime,
+ /*padding_rate=*/DataRate::Zero());
+
+ // Send an initial packet.
+ SendAndExpectPacket(pacer.get(), RtpPacketMediaType::kVideo, kSsrc,
+ sequence_number++, clock_.TimeInMilliseconds(),
+ kPackeSize.bytes());
+ pacer->ProcessPackets();
+ ::testing::Mock::VerifyAndClearExpectations(&callback_);
+
+ // Advance time kPacketSendTime past where the media debt should be 0.
+ clock_.AdvanceTime(2 * kPacketSendTime);
+
+ // Enqueue two new packets. Expect only one to be sent one ProcessPackets().
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kVideo, kSsrc, sequence_number + 1,
+ clock_.TimeInMilliseconds(), kPackeSize.bytes()));
+ pacer->EnqueuePacket(
+ BuildPacket(RtpPacketMediaType::kVideo, kSsrc, sequence_number + 2,
+ clock_.TimeInMilliseconds(), kPackeSize.bytes()));
+ EXPECT_CALL(callback_, SendPacket(kSsrc, sequence_number + 1,
+ clock_.TimeInMilliseconds(), false, false));
+ pacer->ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, HandlesSubMicrosecondSendIntervals) {
+ static constexpr DataSize kPacketSize = DataSize::Bytes(1);
+ static constexpr TimeDelta kPacketSendTime = TimeDelta::Micros(1);
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+
+ // Set pacing rate such that a packet is sent in 0.5us.
+ pacer->SetPacingRates(/*pacing_rate=*/2 * kPacketSize / kPacketSendTime,
+ /*padding_rate=*/DataRate::Zero());
+
+ // Enqueue three packets, the first two should be sent immediately - the third
+ // should cause a non-zero delta to the next process time.
+ EXPECT_CALL(callback_, SendPacket).Times(2);
+ for (int i = 0; i < 3; ++i) {
+ pacer->EnqueuePacket(BuildPacket(
+ RtpPacketMediaType::kVideo, /*ssrc=*/12345, /*sequence_number=*/i,
+ clock_.TimeInMilliseconds(), kPacketSize.bytes()));
+ }
+ pacer->ProcessPackets();
+
+ EXPECT_GT(pacer->NextSendTime(), clock_.CurrentTime());
+}
+
+TEST_F(PacingControllerTest, HandlesSubMicrosecondPaddingInterval) {
+ static constexpr DataSize kPacketSize = DataSize::Bytes(1);
+ static constexpr TimeDelta kPacketSendTime = TimeDelta::Micros(1);
+ auto pacer = std::make_unique<PacingController>(&clock_, &callback_, trials_);
+
+ // Set both pacing and padding rates to 1 byte per 0.5us.
+ pacer->SetPacingRates(/*pacing_rate=*/2 * kPacketSize / kPacketSendTime,
+ /*padding_rate=*/2 * kPacketSize / kPacketSendTime);
+
+ // Enqueue and send one packet.
+ EXPECT_CALL(callback_, SendPacket);
+ pacer->EnqueuePacket(BuildPacket(
+ RtpPacketMediaType::kVideo, /*ssrc=*/12345, /*sequence_number=*/1234,
+ clock_.TimeInMilliseconds(), kPacketSize.bytes()));
+ pacer->ProcessPackets();
+
+ // The padding debt is now 1 byte, and the pacing time for that is lower than
+ // the precision of a TimeStamp tick. Make sure the pacer still indicates a
+ // non-zero sleep time is needed until the next process.
+ EXPECT_GT(pacer->NextSendTime(), clock_.CurrentTime());
+}
+
+TEST_F(PacingControllerTest, SendsPacketsInBurstImmediately) {
+ constexpr TimeDelta kMaxDelay = TimeDelta::Millis(20);
+ PacingController pacer(&clock_, &callback_, trials_);
+ pacer.SetSendBurstInterval(kMaxDelay);
+ pacer.SetPacingRates(DataRate::BytesPerSec(10000), DataRate::Zero());
+
+ // Max allowed send burst size is 100000*20/1000) = 200byte
+ pacer.EnqueuePacket(video_.BuildNextPacket(100));
+ pacer.EnqueuePacket(video_.BuildNextPacket(100));
+ pacer.EnqueuePacket(video_.BuildNextPacket(100));
+ pacer.ProcessPackets();
+ EXPECT_EQ(pacer.QueueSizePackets(), 1u);
+ EXPECT_EQ(pacer.NextSendTime(), clock_.CurrentTime() + kMaxDelay);
+
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+ EXPECT_EQ(pacer.QueueSizePackets(), 0u);
+}
+
+TEST_F(PacingControllerTest, SendsPacketsInBurstEvenIfNotEnqueedAtSameTime) {
+ constexpr TimeDelta kMaxDelay = TimeDelta::Millis(20);
+ PacingController pacer(&clock_, &callback_, trials_);
+ pacer.SetSendBurstInterval(kMaxDelay);
+ pacer.SetPacingRates(DataRate::BytesPerSec(10000), DataRate::Zero());
+ pacer.EnqueuePacket(video_.BuildNextPacket(200));
+ EXPECT_EQ(pacer.NextSendTime(), clock_.CurrentTime());
+ pacer.ProcessPackets();
+ clock_.AdvanceTime(TimeDelta::Millis(1));
+ pacer.EnqueuePacket(video_.BuildNextPacket(200));
+ EXPECT_EQ(pacer.NextSendTime(), clock_.CurrentTime());
+ pacer.ProcessPackets();
+ EXPECT_EQ(pacer.QueueSizePackets(), 0u);
+}
+
+TEST_F(PacingControllerTest, RespectsTargetRateWhenSendingPacketsInBursts) {
+ PacingController pacer(&clock_, &callback_, trials_);
+ pacer.SetSendBurstInterval(TimeDelta::Millis(20));
+ pacer.SetAccountForAudioPackets(true);
+ pacer.SetPacingRates(DataRate::KilobitsPerSec(1000), DataRate::Zero());
+ Timestamp start_time = clock_.CurrentTime();
+ // Inject 100 packets, with size 1000bytes over 100ms.
+ // Expect only 1Mbps / (8*1000) / 10 = 12 packets to be sent.
+ // Packets are sent in burst. Each burst is then 3 packets * 1000bytes at
+ // 1Mbits = 24ms long. Thus, expect 4 bursts.
+ EXPECT_CALL(callback_, SendPacket).Times(12);
+ int number_of_bursts = 0;
+ while (clock_.CurrentTime() < start_time + TimeDelta::Millis(100)) {
+ pacer.EnqueuePacket(video_.BuildNextPacket(1000));
+ pacer.EnqueuePacket(video_.BuildNextPacket(1000));
+ pacer.EnqueuePacket(video_.BuildNextPacket(1000));
+ pacer.EnqueuePacket(video_.BuildNextPacket(1000));
+ pacer.EnqueuePacket(video_.BuildNextPacket(1000));
+ if (pacer.NextSendTime() <= clock_.CurrentTime()) {
+ pacer.ProcessPackets();
+ ++number_of_bursts;
+ }
+ clock_.AdvanceTime(TimeDelta::Millis(5));
+ }
+ EXPECT_EQ(pacer.QueueSizePackets(), 88u);
+ EXPECT_EQ(number_of_bursts, 4);
+}
+
+TEST_F(PacingControllerTest, RespectsQueueTimeLimit) {
+ static constexpr DataSize kPacketSize = DataSize::Bytes(100);
+ static constexpr DataRate kNominalPacingRate = DataRate::KilobitsPerSec(200);
+ static constexpr TimeDelta kPacketPacingTime =
+ kPacketSize / kNominalPacingRate;
+ static constexpr TimeDelta kQueueTimeLimit = TimeDelta::Millis(1000);
+
+ PacingController pacer(&clock_, &callback_, trials_);
+ pacer.SetPacingRates(kNominalPacingRate, /*padding_rate=*/DataRate::Zero());
+ pacer.SetQueueTimeLimit(kQueueTimeLimit);
+
+ // Fill pacer up to queue time limit.
+ static constexpr int kNumPackets = kQueueTimeLimit / kPacketPacingTime;
+ for (int i = 0; i < kNumPackets; ++i) {
+ pacer.EnqueuePacket(video_.BuildNextPacket(kPacketSize.bytes()));
+ }
+ EXPECT_EQ(pacer.ExpectedQueueTime(), kQueueTimeLimit);
+ EXPECT_EQ(pacer.pacing_rate(), kNominalPacingRate);
+
+ // Double the amount of packets in the queue, the queue time limit should
+ // effectively double the pacing rate in response.
+ for (int i = 0; i < kNumPackets; ++i) {
+ pacer.EnqueuePacket(video_.BuildNextPacket(kPacketSize.bytes()));
+ }
+ EXPECT_EQ(pacer.ExpectedQueueTime(), kQueueTimeLimit);
+ EXPECT_EQ(pacer.pacing_rate(), 2 * kNominalPacingRate);
+
+ // Send all the packets, should take as long as the queue time limit.
+ Timestamp start_time = clock_.CurrentTime();
+ while (pacer.QueueSizePackets() > 0) {
+ AdvanceTimeUntil(pacer.NextSendTime());
+ pacer.ProcessPackets();
+ }
+ EXPECT_EQ(clock_.CurrentTime() - start_time, kQueueTimeLimit);
+
+ // We're back in a normal state - pacing rate should be back to previous
+ // levels.
+ EXPECT_EQ(pacer.pacing_rate(), kNominalPacingRate);
+}
+
+TEST_F(PacingControllerTest, BudgetDoesNotAffectRetransmissionInsTrial) {
+ const DataSize kPacketSize = DataSize::Bytes(1000);
+
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ const test::ExplicitKeyValueConfig trials(
+ "WebRTC-Pacer-FastRetransmissions/Enabled/");
+ PacingController pacer(&clock_, &callback_, trials);
+ pacer.SetPacingRates(kTargetRate, /*padding_rate=*/DataRate::Zero());
+
+ // Send a video packet so that we have a bit debt.
+ pacer.EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, kVideoSsrc,
+ /*sequence_number=*/1,
+ /*capture_time=*/1, kPacketSize.bytes()));
+ EXPECT_CALL(callback_, SendPacket);
+ pacer.ProcessPackets();
+ EXPECT_GT(pacer.NextSendTime(), clock_.CurrentTime());
+
+ // A retransmission packet should still be immediately processed.
+ EXPECT_CALL(callback_, SendPacket);
+ pacer.EnqueuePacket(BuildPacket(RtpPacketMediaType::kRetransmission,
+ kVideoSsrc,
+ /*sequence_number=*/1,
+ /*capture_time=*/1, kPacketSize.bytes()));
+ pacer.ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, AbortsAfterReachingCircuitBreakLimit) {
+ const DataSize kPacketSize = DataSize::Bytes(1000);
+
+ EXPECT_CALL(callback_, SendPadding).Times(0);
+ PacingController pacer(&clock_, &callback_, trials_);
+ pacer.SetPacingRates(kTargetRate, /*padding_rate=*/DataRate::Zero());
+
+ // Set the circuit breaker to abort after one iteration of the main
+ // sending loop.
+ pacer.SetCircuitBreakerThreshold(1);
+ EXPECT_CALL(callback_, SendPacket).Times(1);
+
+ // Send two packets.
+ pacer.EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, kVideoSsrc,
+ /*sequence_number=*/1,
+ /*capture_time=*/1, kPacketSize.bytes()));
+ pacer.EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, kVideoSsrc,
+ /*sequence_number=*/2,
+ /*capture_time=*/2, kPacketSize.bytes()));
+
+ // Advance time to way past where both should be eligible for sending.
+ clock_.AdvanceTime(TimeDelta::Seconds(1));
+
+ pacer.ProcessPackets();
+}
+
+TEST_F(PacingControllerTest, DoesNotPadIfProcessThreadIsBorked) {
+ PacingControllerPadding callback;
+ PacingController pacer(&clock_, &callback, trials_);
+
+ // Set both pacing and padding rate to be non-zero.
+ pacer.SetPacingRates(kTargetRate, /*padding_rate=*/kTargetRate);
+
+ // Add one packet to the queue, but do not send it yet.
+ pacer.EnqueuePacket(BuildPacket(RtpPacketMediaType::kVideo, kVideoSsrc,
+ /*sequence_number=*/1,
+ /*capture_time=*/1,
+ /*size=*/1000));
+
+ // Advance time to waaay after the packet should have been sent.
+ clock_.AdvanceTime(TimeDelta::Seconds(42));
+
+ // `ProcessPackets()` should send the delayed packet, followed by a small
+ // amount of missed padding.
+ pacer.ProcessPackets();
+
+ // The max padding window is the max replay duration + the target padding
+ // duration.
+ const DataSize kMaxPadding = (PacingController::kMaxPaddingReplayDuration +
+ PacingController::kTargetPaddingDuration) *
+ kTargetRate;
+
+ EXPECT_LE(callback.padding_sent(), kMaxPadding.bytes<size_t>());
+}
+
+} // namespace
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/pacing_gn/moz.build b/third_party/libwebrtc/modules/pacing/pacing_gn/moz.build
new file mode 100644
index 0000000000..61ece82430
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/pacing_gn/moz.build
@@ -0,0 +1,239 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+ ### This moz.build was AUTOMATICALLY GENERATED from a GN config, ###
+ ### DO NOT edit it by hand. ###
+
+COMPILE_FLAGS["OS_INCLUDES"] = []
+AllowCompilerWarnings()
+
+DEFINES["ABSL_ALLOCATOR_NOTHROW"] = "1"
+DEFINES["RTC_DAV1D_IN_INTERNAL_DECODER_FACTORY"] = True
+DEFINES["RTC_ENABLE_VP9"] = True
+DEFINES["WEBRTC_ENABLE_PROTOBUF"] = "0"
+DEFINES["WEBRTC_LIBRARY_IMPL"] = True
+DEFINES["WEBRTC_MOZILLA_BUILD"] = True
+DEFINES["WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS"] = "0"
+DEFINES["WEBRTC_STRICT_FIELD_TRIALS"] = "0"
+
+FINAL_LIBRARY = "webrtc"
+
+
+LOCAL_INCLUDES += [
+ "!/ipc/ipdl/_ipdlheaders",
+ "!/third_party/libwebrtc/gen",
+ "/ipc/chromium/src",
+ "/third_party/libwebrtc/",
+ "/third_party/libwebrtc/third_party/abseil-cpp/",
+ "/tools/profiler/public"
+]
+
+SOURCES += [
+ "/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc"
+]
+
+UNIFIED_SOURCES += [
+ "/third_party/libwebrtc/modules/pacing/bitrate_prober.cc",
+ "/third_party/libwebrtc/modules/pacing/pacing_controller.cc",
+ "/third_party/libwebrtc/modules/pacing/packet_router.cc",
+ "/third_party/libwebrtc/modules/pacing/task_queue_paced_sender.cc"
+]
+
+if not CONFIG["MOZ_DEBUG"]:
+
+ DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "0"
+ DEFINES["NDEBUG"] = True
+ DEFINES["NVALGRIND"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1":
+
+ DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "1"
+
+if CONFIG["OS_TARGET"] == "Android":
+
+ DEFINES["ANDROID"] = True
+ DEFINES["ANDROID_NDK_VERSION_ROLL"] = "r22_1"
+ DEFINES["HAVE_SYS_UIO_H"] = True
+ DEFINES["WEBRTC_ANDROID"] = True
+ DEFINES["WEBRTC_ANDROID_OPENSLES"] = True
+ DEFINES["WEBRTC_LINUX"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_GNU_SOURCE"] = True
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+ OS_LIBS += [
+ "log"
+ ]
+
+if CONFIG["OS_TARGET"] == "Darwin":
+
+ DEFINES["WEBRTC_MAC"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_LIBCPP_HAS_NO_ALIGNED_ALLOCATION"] = True
+ DEFINES["__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES"] = "0"
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+if CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["USE_AURA"] = "1"
+ DEFINES["USE_GLIB"] = "1"
+ DEFINES["USE_NSS_CERTS"] = "1"
+ DEFINES["USE_OZONE"] = "1"
+ DEFINES["USE_UDEV"] = True
+ DEFINES["WEBRTC_LINUX"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_FILE_OFFSET_BITS"] = "64"
+ DEFINES["_LARGEFILE64_SOURCE"] = True
+ DEFINES["_LARGEFILE_SOURCE"] = True
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+ OS_LIBS += [
+ "rt"
+ ]
+
+if CONFIG["OS_TARGET"] == "OpenBSD":
+
+ DEFINES["USE_GLIB"] = "1"
+ DEFINES["USE_OZONE"] = "1"
+ DEFINES["USE_X11"] = "1"
+ DEFINES["WEBRTC_BSD"] = True
+ DEFINES["WEBRTC_POSIX"] = True
+ DEFINES["_FILE_OFFSET_BITS"] = "64"
+ DEFINES["_LARGEFILE64_SOURCE"] = True
+ DEFINES["_LARGEFILE_SOURCE"] = True
+ DEFINES["__STDC_CONSTANT_MACROS"] = True
+ DEFINES["__STDC_FORMAT_MACROS"] = True
+
+if CONFIG["OS_TARGET"] == "WINNT":
+
+ DEFINES["CERT_CHAIN_PARA_HAS_EXTRA_FIELDS"] = True
+ DEFINES["NOMINMAX"] = True
+ DEFINES["NTDDI_VERSION"] = "0x0A000000"
+ DEFINES["PSAPI_VERSION"] = "2"
+ DEFINES["UNICODE"] = True
+ DEFINES["USE_AURA"] = "1"
+ DEFINES["WEBRTC_WIN"] = True
+ DEFINES["WIN32"] = True
+ DEFINES["WIN32_LEAN_AND_MEAN"] = True
+ DEFINES["WINAPI_FAMILY"] = "WINAPI_FAMILY_DESKTOP_APP"
+ DEFINES["WINVER"] = "0x0A00"
+ DEFINES["_ATL_NO_OPENGL"] = True
+ DEFINES["_CRT_RAND_S"] = True
+ DEFINES["_CRT_SECURE_NO_DEPRECATE"] = True
+ DEFINES["_ENABLE_EXTENDED_ALIGNED_STORAGE"] = True
+ DEFINES["_HAS_EXCEPTIONS"] = "0"
+ DEFINES["_HAS_NODISCARD"] = True
+ DEFINES["_SCL_SECURE_NO_DEPRECATE"] = True
+ DEFINES["_SECURE_ATL"] = True
+ DEFINES["_UNICODE"] = True
+ DEFINES["_WIN32_WINNT"] = "0x0A00"
+ DEFINES["_WINDOWS"] = True
+ DEFINES["__STD_C"] = True
+
+ OS_LIBS += [
+ "crypt32",
+ "iphlpapi",
+ "secur32",
+ "winmm"
+ ]
+
+if CONFIG["CPU_ARCH"] == "aarch64":
+
+ DEFINES["WEBRTC_ARCH_ARM64"] = True
+ DEFINES["WEBRTC_HAS_NEON"] = True
+
+if CONFIG["CPU_ARCH"] == "arm":
+
+ CXXFLAGS += [
+ "-mfpu=neon"
+ ]
+
+ DEFINES["WEBRTC_ARCH_ARM"] = True
+ DEFINES["WEBRTC_ARCH_ARM_V7"] = True
+ DEFINES["WEBRTC_HAS_NEON"] = True
+
+if CONFIG["CPU_ARCH"] == "mips32":
+
+ DEFINES["MIPS32_LE"] = True
+ DEFINES["MIPS_FPU_LE"] = True
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["CPU_ARCH"] == "mips64":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["CPU_ARCH"] == "x86":
+
+ DEFINES["WEBRTC_ENABLE_AVX2"] = True
+
+if CONFIG["CPU_ARCH"] == "x86_64":
+
+ DEFINES["WEBRTC_ENABLE_AVX2"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Android":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Darwin":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "OpenBSD":
+
+ DEFINES["_DEBUG"] = True
+
+if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "WINNT":
+
+ DEFINES["_HAS_ITERATOR_DEBUGGING"] = "0"
+
+if CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["USE_X11"] = "1"
+
+if CONFIG["CPU_ARCH"] == "arm" and CONFIG["OS_TARGET"] == "Android":
+
+ OS_LIBS += [
+ "android_support",
+ "unwind"
+ ]
+
+if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Android":
+
+ CXXFLAGS += [
+ "-msse2"
+ ]
+
+ OS_LIBS += [
+ "android_support"
+ ]
+
+if CONFIG["CPU_ARCH"] == "aarch64" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["CPU_ARCH"] == "arm" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Linux":
+
+ CXXFLAGS += [
+ "-msse2"
+ ]
+
+ DEFINES["_GNU_SOURCE"] = True
+
+if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["OS_TARGET"] == "Linux":
+
+ DEFINES["_GNU_SOURCE"] = True
+
+Library("pacing_gn")
diff --git a/third_party/libwebrtc/modules/pacing/packet_router.cc b/third_party/libwebrtc/modules/pacing/packet_router.cc
new file mode 100644
index 0000000000..b28d9776dc
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/packet_router.cc
@@ -0,0 +1,356 @@
+/*
+ * 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/pacing/packet_router.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "absl/types/optional.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.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_rtcp_interface.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/system/unused.h"
+#include "rtc_base/time_utils.h"
+#include "rtc_base/trace_event.h"
+
+namespace webrtc {
+
+PacketRouter::PacketRouter() : PacketRouter(0) {}
+
+PacketRouter::PacketRouter(uint16_t start_transport_seq)
+ : last_send_module_(nullptr),
+ active_remb_module_(nullptr),
+ transport_seq_(start_transport_seq) {}
+
+PacketRouter::~PacketRouter() {
+ RTC_DCHECK(send_modules_map_.empty());
+ RTC_DCHECK(send_modules_list_.empty());
+ RTC_DCHECK(rtcp_feedback_senders_.empty());
+ RTC_DCHECK(sender_remb_candidates_.empty());
+ RTC_DCHECK(receiver_remb_candidates_.empty());
+ RTC_DCHECK(active_remb_module_ == nullptr);
+}
+
+void PacketRouter::AddSendRtpModule(RtpRtcpInterface* rtp_module,
+ bool remb_candidate) {
+ MutexLock lock(&modules_mutex_);
+
+ AddSendRtpModuleToMap(rtp_module, rtp_module->SSRC());
+ if (absl::optional<uint32_t> rtx_ssrc = rtp_module->RtxSsrc()) {
+ AddSendRtpModuleToMap(rtp_module, *rtx_ssrc);
+ }
+ if (absl::optional<uint32_t> flexfec_ssrc = rtp_module->FlexfecSsrc()) {
+ AddSendRtpModuleToMap(rtp_module, *flexfec_ssrc);
+ }
+
+ if (rtp_module->SupportsRtxPayloadPadding()) {
+ last_send_module_ = rtp_module;
+ }
+
+ if (remb_candidate) {
+ AddRembModuleCandidate(rtp_module, /* media_sender = */ true);
+ }
+}
+
+void PacketRouter::AddSendRtpModuleToMap(RtpRtcpInterface* rtp_module,
+ uint32_t ssrc) {
+ RTC_DCHECK(send_modules_map_.find(ssrc) == send_modules_map_.end());
+
+ // Signal to module that the pacer thread is attached and can send packets.
+ rtp_module->OnPacketSendingThreadSwitched();
+
+ // Always keep the audio modules at the back of the list, so that when we
+ // iterate over the modules in order to find one that can send padding we
+ // will prioritize video. This is important to make sure they are counted
+ // into the bandwidth estimate properly.
+ if (rtp_module->IsAudioConfigured()) {
+ send_modules_list_.push_back(rtp_module);
+ } else {
+ send_modules_list_.push_front(rtp_module);
+ }
+ send_modules_map_[ssrc] = rtp_module;
+}
+
+void PacketRouter::RemoveSendRtpModuleFromMap(uint32_t ssrc) {
+ auto it = send_modules_map_.find(ssrc);
+ RTC_DCHECK(it != send_modules_map_.end());
+ send_modules_list_.remove(it->second);
+ send_modules_map_.erase(it);
+}
+
+void PacketRouter::RemoveSendRtpModule(RtpRtcpInterface* rtp_module) {
+ MutexLock lock(&modules_mutex_);
+ MaybeRemoveRembModuleCandidate(rtp_module, /* media_sender = */ true);
+
+ RemoveSendRtpModuleFromMap(rtp_module->SSRC());
+ if (absl::optional<uint32_t> rtx_ssrc = rtp_module->RtxSsrc()) {
+ RemoveSendRtpModuleFromMap(*rtx_ssrc);
+ }
+ if (absl::optional<uint32_t> flexfec_ssrc = rtp_module->FlexfecSsrc()) {
+ RemoveSendRtpModuleFromMap(*flexfec_ssrc);
+ }
+
+ if (last_send_module_ == rtp_module) {
+ last_send_module_ = nullptr;
+ }
+ rtp_module->OnPacketSendingThreadSwitched();
+}
+
+void PacketRouter::AddReceiveRtpModule(RtcpFeedbackSenderInterface* rtcp_sender,
+ bool remb_candidate) {
+ MutexLock lock(&modules_mutex_);
+ RTC_DCHECK(std::find(rtcp_feedback_senders_.begin(),
+ rtcp_feedback_senders_.end(),
+ rtcp_sender) == rtcp_feedback_senders_.end());
+
+ rtcp_feedback_senders_.push_back(rtcp_sender);
+
+ if (remb_candidate) {
+ AddRembModuleCandidate(rtcp_sender, /* media_sender = */ false);
+ }
+}
+
+void PacketRouter::RemoveReceiveRtpModule(
+ RtcpFeedbackSenderInterface* rtcp_sender) {
+ MutexLock lock(&modules_mutex_);
+ MaybeRemoveRembModuleCandidate(rtcp_sender, /* media_sender = */ false);
+ auto it = std::find(rtcp_feedback_senders_.begin(),
+ rtcp_feedback_senders_.end(), rtcp_sender);
+ RTC_DCHECK(it != rtcp_feedback_senders_.end());
+ rtcp_feedback_senders_.erase(it);
+}
+
+void PacketRouter::SendPacket(std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) {
+ TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("webrtc"), "PacketRouter::SendPacket",
+ "sequence_number", packet->SequenceNumber(), "rtp_timestamp",
+ packet->Timestamp());
+
+ MutexLock lock(&modules_mutex_);
+ // With the new pacer code path, transport sequence numbers are only set here,
+ // on the pacer thread. Therefore we don't need atomics/synchronization.
+ bool assign_transport_sequence_number =
+ packet->HasExtension<TransportSequenceNumber>();
+ if (assign_transport_sequence_number) {
+ packet->SetExtension<TransportSequenceNumber>((transport_seq_ + 1) &
+ 0xFFFF);
+ }
+
+ uint32_t ssrc = packet->Ssrc();
+ auto it = send_modules_map_.find(ssrc);
+ if (it == send_modules_map_.end()) {
+ RTC_LOG(LS_WARNING)
+ << "Failed to send packet, matching RTP module not found "
+ "or transport error. SSRC = "
+ << packet->Ssrc() << ", sequence number " << packet->SequenceNumber();
+ return;
+ }
+
+ RtpRtcpInterface* rtp_module = it->second;
+ if (!rtp_module->TrySendPacket(packet.get(), cluster_info)) {
+ RTC_LOG(LS_WARNING) << "Failed to send packet, rejected by RTP module.";
+ return;
+ }
+
+ // Sending succeeded.
+
+ if (assign_transport_sequence_number) {
+ ++transport_seq_;
+ }
+
+ if (rtp_module->SupportsRtxPayloadPadding()) {
+ // This is now the last module to send media, and has the desired
+ // properties needed for payload based padding. Cache it for later use.
+ last_send_module_ = rtp_module;
+ }
+
+ for (auto& packet : rtp_module->FetchFecPackets()) {
+ pending_fec_packets_.push_back(std::move(packet));
+ }
+}
+
+std::vector<std::unique_ptr<RtpPacketToSend>> PacketRouter::FetchFec() {
+ MutexLock lock(&modules_mutex_);
+ std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets =
+ std::move(pending_fec_packets_);
+ pending_fec_packets_.clear();
+ return fec_packets;
+}
+
+std::vector<std::unique_ptr<RtpPacketToSend>> PacketRouter::GeneratePadding(
+ DataSize size) {
+ TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("webrtc"),
+ "PacketRouter::GeneratePadding", "bytes", size.bytes());
+
+ MutexLock lock(&modules_mutex_);
+ // First try on the last rtp module to have sent media. This increases the
+ // the chance that any payload based padding will be useful as it will be
+ // somewhat distributed over modules according the packet rate, even if it
+ // will be more skewed towards the highest bitrate stream. At the very least
+ // this prevents sending payload padding on a disabled stream where it's
+ // guaranteed not to be useful.
+ std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets;
+ if (last_send_module_ != nullptr &&
+ last_send_module_->SupportsRtxPayloadPadding()) {
+ padding_packets = last_send_module_->GeneratePadding(size.bytes());
+ }
+
+ if (padding_packets.empty()) {
+ // Iterate over all modules send module. Video modules will be at the front
+ // and so will be prioritized. This is important since audio packets may not
+ // be taken into account by the bandwidth estimator, e.g. in FF.
+ for (RtpRtcpInterface* rtp_module : send_modules_list_) {
+ if (rtp_module->SupportsPadding()) {
+ padding_packets = rtp_module->GeneratePadding(size.bytes());
+ if (!padding_packets.empty()) {
+ last_send_module_ = rtp_module;
+ break;
+ }
+ }
+ }
+ }
+
+ for (auto& packet : padding_packets) {
+ RTC_UNUSED(packet);
+ TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("webrtc"),
+ "PacketRouter::GeneratePadding::Loop", "sequence_number",
+ packet->SequenceNumber(), "rtp_timestamp",
+ packet->Timestamp());
+ }
+
+ return padding_packets;
+}
+
+void PacketRouter::OnAbortedRetransmissions(
+ uint32_t ssrc,
+ rtc::ArrayView<const uint16_t> sequence_numbers) {
+ MutexLock lock(&modules_mutex_);
+ auto it = send_modules_map_.find(ssrc);
+ if (it != send_modules_map_.end()) {
+ it->second->OnAbortedRetransmissions(sequence_numbers);
+ }
+}
+
+absl::optional<uint32_t> PacketRouter::GetRtxSsrcForMedia(uint32_t ssrc) const {
+ MutexLock lock(&modules_mutex_);
+ auto it = send_modules_map_.find(ssrc);
+ if (it != send_modules_map_.end() && it->second->SSRC() == ssrc) {
+ // A module is registered with the given SSRC, and that SSRC is the main
+ // media SSRC for that RTP module.
+ return it->second->RtxSsrc();
+ }
+ return absl::nullopt;
+}
+
+uint16_t PacketRouter::CurrentTransportSequenceNumber() const {
+ MutexLock lock(&modules_mutex_);
+ return transport_seq_ & 0xFFFF;
+}
+
+void PacketRouter::SendRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs) {
+ MutexLock lock(&modules_mutex_);
+
+ if (!active_remb_module_) {
+ return;
+ }
+
+ // The Add* and Remove* methods above ensure that REMB is disabled on all
+ // other modules, because otherwise, they will send REMB with stale info.
+ active_remb_module_->SetRemb(bitrate_bps, std::move(ssrcs));
+}
+
+void PacketRouter::SendCombinedRtcpPacket(
+ std::vector<std::unique_ptr<rtcp::RtcpPacket>> packets) {
+ MutexLock lock(&modules_mutex_);
+
+ // Prefer send modules.
+ for (RtpRtcpInterface* rtp_module : send_modules_list_) {
+ if (rtp_module->RTCP() == RtcpMode::kOff) {
+ continue;
+ }
+ rtp_module->SendCombinedRtcpPacket(std::move(packets));
+ return;
+ }
+
+ if (rtcp_feedback_senders_.empty()) {
+ return;
+ }
+ auto* rtcp_sender = rtcp_feedback_senders_[0];
+ rtcp_sender->SendCombinedRtcpPacket(std::move(packets));
+}
+
+void PacketRouter::AddRembModuleCandidate(
+ RtcpFeedbackSenderInterface* candidate_module,
+ bool media_sender) {
+ RTC_DCHECK(candidate_module);
+ std::vector<RtcpFeedbackSenderInterface*>& candidates =
+ media_sender ? sender_remb_candidates_ : receiver_remb_candidates_;
+ RTC_DCHECK(std::find(candidates.cbegin(), candidates.cend(),
+ candidate_module) == candidates.cend());
+ candidates.push_back(candidate_module);
+ DetermineActiveRembModule();
+}
+
+void PacketRouter::MaybeRemoveRembModuleCandidate(
+ RtcpFeedbackSenderInterface* candidate_module,
+ bool media_sender) {
+ RTC_DCHECK(candidate_module);
+ std::vector<RtcpFeedbackSenderInterface*>& candidates =
+ media_sender ? sender_remb_candidates_ : receiver_remb_candidates_;
+ auto it = std::find(candidates.begin(), candidates.end(), candidate_module);
+
+ if (it == candidates.end()) {
+ return; // Function called due to removal of non-REMB-candidate module.
+ }
+
+ if (*it == active_remb_module_) {
+ UnsetActiveRembModule();
+ }
+ candidates.erase(it);
+ DetermineActiveRembModule();
+}
+
+void PacketRouter::UnsetActiveRembModule() {
+ RTC_CHECK(active_remb_module_);
+ active_remb_module_->UnsetRemb();
+ active_remb_module_ = nullptr;
+}
+
+void PacketRouter::DetermineActiveRembModule() {
+ // Sender modules take precedence over receiver modules, because SRs (sender
+ // reports) are sent more frequently than RR (receiver reports).
+ // When adding the first sender module, we should change the active REMB
+ // module to be that. Otherwise, we remain with the current active module.
+
+ RtcpFeedbackSenderInterface* new_active_remb_module;
+
+ if (!sender_remb_candidates_.empty()) {
+ new_active_remb_module = sender_remb_candidates_.front();
+ } else if (!receiver_remb_candidates_.empty()) {
+ new_active_remb_module = receiver_remb_candidates_.front();
+ } else {
+ new_active_remb_module = nullptr;
+ }
+
+ if (new_active_remb_module != active_remb_module_ && active_remb_module_) {
+ UnsetActiveRembModule();
+ }
+
+ active_remb_module_ = new_active_remb_module;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/packet_router.h b/third_party/libwebrtc/modules/pacing/packet_router.h
new file mode 100644
index 0000000000..68b82c6bd4
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/packet_router.h
@@ -0,0 +1,118 @@
+/*
+ * 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_PACING_PACKET_ROUTER_H_
+#define MODULES_PACING_PACKET_ROUTER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <list>
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "api/transport/network_types.h"
+#include "modules/pacing/pacing_controller.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/rtp_rtcp/source/rtcp_packet.h"
+#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
+#include "rtc_base/synchronization/mutex.h"
+#include "rtc_base/thread_annotations.h"
+
+namespace webrtc {
+
+class RtpRtcpInterface;
+
+// PacketRouter keeps track of rtp send modules to support the pacer.
+// In addition, it handles feedback messages, which are sent on a send
+// module if possible (sender report), otherwise on receive module
+// (receiver report). For the latter case, we also keep track of the
+// receive modules.
+class PacketRouter : public PacingController::PacketSender {
+ public:
+ PacketRouter();
+ explicit PacketRouter(uint16_t start_transport_seq);
+ ~PacketRouter() override;
+
+ PacketRouter(const PacketRouter&) = delete;
+ PacketRouter& operator=(const PacketRouter&) = delete;
+
+ void AddSendRtpModule(RtpRtcpInterface* rtp_module, bool remb_candidate);
+ void RemoveSendRtpModule(RtpRtcpInterface* rtp_module);
+
+ void AddReceiveRtpModule(RtcpFeedbackSenderInterface* rtcp_sender,
+ bool remb_candidate);
+ void RemoveReceiveRtpModule(RtcpFeedbackSenderInterface* rtcp_sender);
+
+ void SendPacket(std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) override;
+ std::vector<std::unique_ptr<RtpPacketToSend>> FetchFec() override;
+ std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding(
+ DataSize size) override;
+ void OnAbortedRetransmissions(
+ uint32_t ssrc,
+ rtc::ArrayView<const uint16_t> sequence_numbers) override;
+ absl::optional<uint32_t> GetRtxSsrcForMedia(uint32_t ssrc) const override;
+
+ uint16_t CurrentTransportSequenceNumber() const;
+
+ // Send REMB feedback.
+ void SendRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs);
+
+ // Sends `packets` in one or more IP packets.
+ void SendCombinedRtcpPacket(
+ std::vector<std::unique_ptr<rtcp::RtcpPacket>> packets);
+
+ private:
+ void AddRembModuleCandidate(RtcpFeedbackSenderInterface* candidate_module,
+ bool media_sender)
+ RTC_EXCLUSIVE_LOCKS_REQUIRED(modules_mutex_);
+ void MaybeRemoveRembModuleCandidate(
+ RtcpFeedbackSenderInterface* candidate_module,
+ bool media_sender) RTC_EXCLUSIVE_LOCKS_REQUIRED(modules_mutex_);
+ void UnsetActiveRembModule() RTC_EXCLUSIVE_LOCKS_REQUIRED(modules_mutex_);
+ void DetermineActiveRembModule() RTC_EXCLUSIVE_LOCKS_REQUIRED(modules_mutex_);
+ void AddSendRtpModuleToMap(RtpRtcpInterface* rtp_module, uint32_t ssrc)
+ RTC_EXCLUSIVE_LOCKS_REQUIRED(modules_mutex_);
+ void RemoveSendRtpModuleFromMap(uint32_t ssrc)
+ RTC_EXCLUSIVE_LOCKS_REQUIRED(modules_mutex_);
+
+ mutable Mutex modules_mutex_;
+ // Ssrc to RtpRtcpInterface module;
+ std::unordered_map<uint32_t, RtpRtcpInterface*> send_modules_map_
+ RTC_GUARDED_BY(modules_mutex_);
+ std::list<RtpRtcpInterface*> send_modules_list_
+ RTC_GUARDED_BY(modules_mutex_);
+ // The last module used to send media.
+ RtpRtcpInterface* last_send_module_ RTC_GUARDED_BY(modules_mutex_);
+ // Rtcp modules of the rtp receivers.
+ std::vector<RtcpFeedbackSenderInterface*> rtcp_feedback_senders_
+ RTC_GUARDED_BY(modules_mutex_);
+
+ // Candidates for the REMB module can be RTP sender/receiver modules, with
+ // the sender modules taking precedence.
+ std::vector<RtcpFeedbackSenderInterface*> sender_remb_candidates_
+ RTC_GUARDED_BY(modules_mutex_);
+ std::vector<RtcpFeedbackSenderInterface*> receiver_remb_candidates_
+ RTC_GUARDED_BY(modules_mutex_);
+ RtcpFeedbackSenderInterface* active_remb_module_
+ RTC_GUARDED_BY(modules_mutex_);
+
+ uint64_t transport_seq_ RTC_GUARDED_BY(modules_mutex_);
+
+ // TODO(bugs.webrtc.org/10809): Replace lock with a sequence checker once the
+ // process thread is gone.
+ std::vector<std::unique_ptr<RtpPacketToSend>> pending_fec_packets_
+ RTC_GUARDED_BY(modules_mutex_);
+};
+} // namespace webrtc
+#endif // MODULES_PACING_PACKET_ROUTER_H_
diff --git a/third_party/libwebrtc/modules/pacing/packet_router_unittest.cc b/third_party/libwebrtc/modules/pacing/packet_router_unittest.cc
new file mode 100644
index 0000000000..65b2ad24d6
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/packet_router_unittest.cc
@@ -0,0 +1,736 @@
+/*
+ * 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/pacing/packet_router.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+#include "api/units/time_delta.h"
+#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
+#include "modules/rtp_rtcp/mocks/mock_rtp_rtcp.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/fake_clock.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+// TODO(eladalon): Restructure and/or replace the existing monolithic tests
+// (only some of the test are monolithic) according to the new
+// guidelines - small tests for one thing at a time.
+// (I'm not removing any tests during CL, so as to demonstrate no regressions.)
+
+namespace {
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::ElementsAreArray;
+using ::testing::Field;
+using ::testing::Gt;
+using ::testing::Le;
+using ::testing::NiceMock;
+using ::testing::Property;
+using ::testing::Return;
+using ::testing::SaveArg;
+
+constexpr int kProbeMinProbes = 5;
+constexpr int kProbeMinBytes = 1000;
+
+} // namespace
+
+class PacketRouterTest : public ::testing::Test {
+ public:
+ PacketRouterTest() {
+ extension_manager.Register<TransportSequenceNumber>(/*id=*/1);
+ }
+
+ protected:
+ std::unique_ptr<RtpPacketToSend> BuildRtpPacket(uint32_t ssrc) {
+ std::unique_ptr<RtpPacketToSend> packet =
+ std::make_unique<RtpPacketToSend>(&extension_manager);
+ packet->SetSsrc(ssrc);
+ return packet;
+ }
+
+ PacketRouter packet_router_;
+ RtpHeaderExtensionMap extension_manager;
+};
+
+TEST_F(PacketRouterTest, Sanity_NoModuleRegistered_GeneratePadding) {
+ constexpr DataSize bytes = DataSize::Bytes(300);
+ const PacedPacketInfo paced_info(1, kProbeMinProbes, kProbeMinBytes);
+
+ EXPECT_TRUE(packet_router_.GeneratePadding(bytes).empty());
+}
+
+
+TEST_F(PacketRouterTest, Sanity_NoModuleRegistered_SendRemb) {
+ const std::vector<uint32_t> ssrcs = {1, 2, 3};
+ constexpr uint32_t bitrate_bps = 10000;
+ // Expect not to crash
+ packet_router_.SendRemb(bitrate_bps, ssrcs);
+}
+
+TEST_F(PacketRouterTest, Sanity_NoModuleRegistered_SendTransportFeedback) {
+ std::vector<std::unique_ptr<rtcp::RtcpPacket>> feedback;
+ feedback.push_back(std::make_unique<rtcp::TransportFeedback>());
+ // Expect not to crash
+ packet_router_.SendCombinedRtcpPacket(std::move(feedback));
+}
+
+TEST_F(PacketRouterTest, GeneratePaddingPrioritizesRtx) {
+ // Two RTP modules. The first (prioritized due to rtx) isn't sending media so
+ // should not be called.
+ const uint16_t kSsrc1 = 1234;
+ const uint16_t kSsrc2 = 4567;
+
+ NiceMock<MockRtpRtcpInterface> rtp_1;
+ ON_CALL(rtp_1, RtxSendStatus()).WillByDefault(Return(kRtxRedundantPayloads));
+ ON_CALL(rtp_1, SSRC()).WillByDefault(Return(kSsrc1));
+ ON_CALL(rtp_1, SupportsPadding).WillByDefault(Return(false));
+
+ NiceMock<MockRtpRtcpInterface> rtp_2;
+ ON_CALL(rtp_2, RtxSendStatus()).WillByDefault(Return(kRtxOff));
+ ON_CALL(rtp_2, SSRC()).WillByDefault(Return(kSsrc2));
+ ON_CALL(rtp_2, SupportsPadding).WillByDefault(Return(true));
+
+ packet_router_.AddSendRtpModule(&rtp_1, false);
+ packet_router_.AddSendRtpModule(&rtp_2, false);
+
+ const size_t kPaddingSize = 123;
+ const size_t kExpectedPaddingPackets = 1;
+ EXPECT_CALL(rtp_1, GeneratePadding(_)).Times(0);
+ EXPECT_CALL(rtp_2, GeneratePadding(kPaddingSize))
+ .WillOnce([&](size_t padding_size) {
+ return std::vector<std::unique_ptr<RtpPacketToSend>>(
+ kExpectedPaddingPackets);
+ });
+ auto generated_padding =
+ packet_router_.GeneratePadding(DataSize::Bytes(kPaddingSize));
+ EXPECT_EQ(generated_padding.size(), kExpectedPaddingPackets);
+
+ packet_router_.RemoveSendRtpModule(&rtp_1);
+ packet_router_.RemoveSendRtpModule(&rtp_2);
+}
+
+TEST_F(PacketRouterTest, GeneratePaddingPrioritizesVideo) {
+ // Two RTP modules. Neither support RTX, both support padding,
+ // but the first one is for audio and second for video.
+ const uint16_t kSsrc1 = 1234;
+ const uint16_t kSsrc2 = 4567;
+ const size_t kPaddingSize = 123;
+ const size_t kExpectedPaddingPackets = 1;
+
+ auto generate_padding = [&](size_t padding_size) {
+ return std::vector<std::unique_ptr<RtpPacketToSend>>(
+ kExpectedPaddingPackets);
+ };
+
+ NiceMock<MockRtpRtcpInterface> audio_module;
+ ON_CALL(audio_module, RtxSendStatus()).WillByDefault(Return(kRtxOff));
+ ON_CALL(audio_module, SSRC()).WillByDefault(Return(kSsrc1));
+ ON_CALL(audio_module, SupportsPadding).WillByDefault(Return(true));
+ ON_CALL(audio_module, IsAudioConfigured).WillByDefault(Return(true));
+
+ NiceMock<MockRtpRtcpInterface> video_module;
+ ON_CALL(video_module, RtxSendStatus()).WillByDefault(Return(kRtxOff));
+ ON_CALL(video_module, SSRC()).WillByDefault(Return(kSsrc2));
+ ON_CALL(video_module, SupportsPadding).WillByDefault(Return(true));
+ ON_CALL(video_module, IsAudioConfigured).WillByDefault(Return(false));
+
+ // First add only the audio module. Since this is the only choice we have,
+ // padding should be sent on the audio ssrc.
+ packet_router_.AddSendRtpModule(&audio_module, false);
+ EXPECT_CALL(audio_module, GeneratePadding(kPaddingSize))
+ .WillOnce(generate_padding);
+ packet_router_.GeneratePadding(DataSize::Bytes(kPaddingSize));
+
+ // Add the video module, this should now be prioritized since we cannot
+ // guarantee that audio packets will be included in the BWE.
+ packet_router_.AddSendRtpModule(&video_module, false);
+ EXPECT_CALL(audio_module, GeneratePadding).Times(0);
+ EXPECT_CALL(video_module, GeneratePadding(kPaddingSize))
+ .WillOnce(generate_padding);
+ packet_router_.GeneratePadding(DataSize::Bytes(kPaddingSize));
+
+ // Remove and the add audio module again. Module order shouldn't matter;
+ // video should still be prioritized.
+ packet_router_.RemoveSendRtpModule(&audio_module);
+ packet_router_.AddSendRtpModule(&audio_module, false);
+ EXPECT_CALL(audio_module, GeneratePadding).Times(0);
+ EXPECT_CALL(video_module, GeneratePadding(kPaddingSize))
+ .WillOnce(generate_padding);
+ packet_router_.GeneratePadding(DataSize::Bytes(kPaddingSize));
+
+ // Remove and the video module, we should fall back to padding on the
+ // audio module again.
+ packet_router_.RemoveSendRtpModule(&video_module);
+ EXPECT_CALL(audio_module, GeneratePadding(kPaddingSize))
+ .WillOnce(generate_padding);
+ packet_router_.GeneratePadding(DataSize::Bytes(kPaddingSize));
+
+ packet_router_.RemoveSendRtpModule(&audio_module);
+}
+
+TEST_F(PacketRouterTest, PadsOnLastActiveMediaStream) {
+ const uint16_t kSsrc1 = 1234;
+ const uint16_t kSsrc2 = 4567;
+ const uint16_t kSsrc3 = 8901;
+
+ // First two rtp modules send media and have rtx.
+ NiceMock<MockRtpRtcpInterface> rtp_1;
+ EXPECT_CALL(rtp_1, SSRC()).WillRepeatedly(Return(kSsrc1));
+ EXPECT_CALL(rtp_1, SupportsPadding).WillRepeatedly(Return(true));
+ EXPECT_CALL(rtp_1, SupportsRtxPayloadPadding).WillRepeatedly(Return(true));
+ EXPECT_CALL(rtp_1, TrySendPacket).WillRepeatedly(Return(false));
+ EXPECT_CALL(
+ rtp_1,
+ TrySendPacket(
+ ::testing::Pointee(Property(&RtpPacketToSend::Ssrc, kSsrc1)), _))
+ .WillRepeatedly(Return(true));
+
+ NiceMock<MockRtpRtcpInterface> rtp_2;
+ EXPECT_CALL(rtp_2, SSRC()).WillRepeatedly(Return(kSsrc2));
+ EXPECT_CALL(rtp_2, SupportsPadding).WillRepeatedly(Return(true));
+ EXPECT_CALL(rtp_2, SupportsRtxPayloadPadding).WillRepeatedly(Return(true));
+ EXPECT_CALL(rtp_2, TrySendPacket).WillRepeatedly(Return(false));
+ EXPECT_CALL(
+ rtp_2,
+ TrySendPacket(
+ ::testing::Pointee(Property(&RtpPacketToSend::Ssrc, kSsrc2)), _))
+ .WillRepeatedly(Return(true));
+
+ // Third module is sending media, but does not support rtx.
+ NiceMock<MockRtpRtcpInterface> rtp_3;
+ EXPECT_CALL(rtp_3, SSRC()).WillRepeatedly(Return(kSsrc3));
+ EXPECT_CALL(rtp_3, SupportsPadding).WillRepeatedly(Return(true));
+ EXPECT_CALL(rtp_3, SupportsRtxPayloadPadding).WillRepeatedly(Return(false));
+ EXPECT_CALL(rtp_3, TrySendPacket).WillRepeatedly(Return(false));
+ EXPECT_CALL(
+ rtp_3,
+ TrySendPacket(
+ ::testing::Pointee(Property(&RtpPacketToSend::Ssrc, kSsrc3)), _))
+ .WillRepeatedly(Return(true));
+
+ packet_router_.AddSendRtpModule(&rtp_1, false);
+ packet_router_.AddSendRtpModule(&rtp_2, false);
+ packet_router_.AddSendRtpModule(&rtp_3, false);
+
+ const size_t kPaddingBytes = 100;
+
+ // Initially, padding will be sent on last added rtp module that sends media
+ // and supports rtx.
+ EXPECT_CALL(rtp_2, GeneratePadding(kPaddingBytes))
+ .Times(1)
+ .WillOnce([&](size_t target_size_bytes) {
+ std::vector<std::unique_ptr<RtpPacketToSend>> packets;
+ packets.push_back(BuildRtpPacket(kSsrc2));
+ return packets;
+ });
+ packet_router_.GeneratePadding(DataSize::Bytes(kPaddingBytes));
+
+ // Send media on first module. Padding should be sent on that module.
+ packet_router_.SendPacket(BuildRtpPacket(kSsrc1), PacedPacketInfo());
+
+ EXPECT_CALL(rtp_1, GeneratePadding(kPaddingBytes))
+ .Times(1)
+ .WillOnce([&](size_t target_size_bytes) {
+ std::vector<std::unique_ptr<RtpPacketToSend>> packets;
+ packets.push_back(BuildRtpPacket(kSsrc1));
+ return packets;
+ });
+ packet_router_.GeneratePadding(DataSize::Bytes(kPaddingBytes));
+
+ // Send media on second module. Padding should be sent there.
+ packet_router_.SendPacket(BuildRtpPacket(kSsrc2), PacedPacketInfo());
+
+ // If the last active module is removed, and no module sends media before
+ // the next padding request, and arbitrary module will be selected.
+ packet_router_.RemoveSendRtpModule(&rtp_2);
+
+ // Send on and then remove all remaining modules.
+ RtpRtcpInterface* last_send_module;
+ EXPECT_CALL(rtp_1, GeneratePadding(kPaddingBytes))
+ .Times(1)
+ .WillOnce([&](size_t target_size_bytes) {
+ last_send_module = &rtp_1;
+ std::vector<std::unique_ptr<RtpPacketToSend>> packets;
+ packets.push_back(BuildRtpPacket(kSsrc1));
+ return packets;
+ });
+ EXPECT_CALL(rtp_3, GeneratePadding(kPaddingBytes))
+ .Times(1)
+ .WillOnce([&](size_t target_size_bytes) {
+ last_send_module = &rtp_3;
+ std::vector<std::unique_ptr<RtpPacketToSend>> packets;
+ packets.push_back(BuildRtpPacket(kSsrc3));
+ return packets;
+ });
+
+ for (int i = 0; i < 2; ++i) {
+ last_send_module = nullptr;
+ packet_router_.GeneratePadding(DataSize::Bytes(kPaddingBytes));
+ EXPECT_NE(last_send_module, nullptr);
+ packet_router_.RemoveSendRtpModule(last_send_module);
+ }
+}
+
+TEST_F(PacketRouterTest, AllocatesTransportSequenceNumbers) {
+ const uint16_t kStartSeq = 0xFFF0;
+ const size_t kNumPackets = 32;
+ const uint16_t kSsrc1 = 1234;
+
+ PacketRouter packet_router(kStartSeq - 1);
+ NiceMock<MockRtpRtcpInterface> rtp_1;
+ EXPECT_CALL(rtp_1, SSRC()).WillRepeatedly(Return(kSsrc1));
+ EXPECT_CALL(rtp_1, TrySendPacket).WillRepeatedly(Return(true));
+ packet_router.AddSendRtpModule(&rtp_1, false);
+
+ for (size_t i = 0; i < kNumPackets; ++i) {
+ auto packet = BuildRtpPacket(kSsrc1);
+ EXPECT_TRUE(packet->ReserveExtension<TransportSequenceNumber>());
+ packet_router.SendPacket(std::move(packet), PacedPacketInfo());
+ uint32_t expected_unwrapped_seq = static_cast<uint32_t>(kStartSeq) + i;
+ EXPECT_EQ(static_cast<uint16_t>(expected_unwrapped_seq & 0xFFFF),
+ packet_router.CurrentTransportSequenceNumber());
+ }
+
+ packet_router.RemoveSendRtpModule(&rtp_1);
+}
+
+TEST_F(PacketRouterTest, SendTransportFeedback) {
+ NiceMock<MockRtpRtcpInterface> rtp_1;
+ NiceMock<MockRtpRtcpInterface> rtp_2;
+
+ ON_CALL(rtp_1, RTCP()).WillByDefault(Return(RtcpMode::kCompound));
+ ON_CALL(rtp_2, RTCP()).WillByDefault(Return(RtcpMode::kCompound));
+
+ packet_router_.AddSendRtpModule(&rtp_1, false);
+ packet_router_.AddReceiveRtpModule(&rtp_2, false);
+
+ std::vector<std::unique_ptr<rtcp::RtcpPacket>> feedback;
+ feedback.push_back(std::make_unique<rtcp::TransportFeedback>());
+ EXPECT_CALL(rtp_1, SendCombinedRtcpPacket);
+ packet_router_.SendCombinedRtcpPacket(std::move(feedback));
+ packet_router_.RemoveSendRtpModule(&rtp_1);
+ EXPECT_CALL(rtp_2, SendCombinedRtcpPacket);
+ std::vector<std::unique_ptr<rtcp::RtcpPacket>> new_feedback;
+ new_feedback.push_back(std::make_unique<rtcp::TransportFeedback>());
+ packet_router_.SendCombinedRtcpPacket(std::move(new_feedback));
+ packet_router_.RemoveReceiveRtpModule(&rtp_2);
+}
+
+TEST_F(PacketRouterTest, SendPacketWithoutTransportSequenceNumbers) {
+ const uint16_t kSsrc1 = 1234;
+ NiceMock<MockRtpRtcpInterface> rtp_1;
+ ON_CALL(rtp_1, SendingMedia).WillByDefault(Return(true));
+ ON_CALL(rtp_1, SSRC).WillByDefault(Return(kSsrc1));
+ packet_router_.AddSendRtpModule(&rtp_1, false);
+
+ // Send a packet without TransportSequenceNumber extension registered,
+ // packets sent should not have the extension set.
+ RtpHeaderExtensionMap extension_manager;
+ auto packet = std::make_unique<RtpPacketToSend>(&extension_manager);
+ packet->SetSsrc(kSsrc1);
+ EXPECT_CALL(
+ rtp_1,
+ TrySendPacket(
+ Property(&RtpPacketToSend::HasExtension<TransportSequenceNumber>,
+ false),
+ _))
+ .WillOnce(Return(true));
+ packet_router_.SendPacket(std::move(packet), PacedPacketInfo());
+
+ packet_router_.RemoveSendRtpModule(&rtp_1);
+}
+
+TEST_F(PacketRouterTest, SendPacketAssignsTransportSequenceNumbers) {
+ NiceMock<MockRtpRtcpInterface> rtp_1;
+ NiceMock<MockRtpRtcpInterface> rtp_2;
+
+ const uint16_t kSsrc1 = 1234;
+ const uint16_t kSsrc2 = 2345;
+
+ ON_CALL(rtp_1, SSRC).WillByDefault(Return(kSsrc1));
+ ON_CALL(rtp_2, SSRC).WillByDefault(Return(kSsrc2));
+
+ packet_router_.AddSendRtpModule(&rtp_1, false);
+ packet_router_.AddSendRtpModule(&rtp_2, false);
+
+ // Transport sequence numbers start at 1, for historical reasons.
+ uint16_t transport_sequence_number = 1;
+
+ auto packet = BuildRtpPacket(kSsrc1);
+ EXPECT_TRUE(packet->ReserveExtension<TransportSequenceNumber>());
+ EXPECT_CALL(
+ rtp_1,
+ TrySendPacket(
+ Property(&RtpPacketToSend::GetExtension<TransportSequenceNumber>,
+ transport_sequence_number),
+ _))
+ .WillOnce(Return(true));
+ packet_router_.SendPacket(std::move(packet), PacedPacketInfo());
+
+ ++transport_sequence_number;
+ packet = BuildRtpPacket(kSsrc2);
+ EXPECT_TRUE(packet->ReserveExtension<TransportSequenceNumber>());
+
+ EXPECT_CALL(
+ rtp_2,
+ TrySendPacket(
+ Property(&RtpPacketToSend::GetExtension<TransportSequenceNumber>,
+ transport_sequence_number),
+ _))
+ .WillOnce(Return(true));
+ packet_router_.SendPacket(std::move(packet), PacedPacketInfo());
+
+ packet_router_.RemoveSendRtpModule(&rtp_1);
+ packet_router_.RemoveSendRtpModule(&rtp_2);
+}
+
+TEST_F(PacketRouterTest, DoesNotIncrementTransportSequenceNumberOnSendFailure) {
+ NiceMock<MockRtpRtcpInterface> rtp;
+ constexpr uint32_t kSsrc = 1234;
+ ON_CALL(rtp, SSRC).WillByDefault(Return(kSsrc));
+ packet_router_.AddSendRtpModule(&rtp, false);
+
+ // Transport sequence numbers start at 1, for historical reasons.
+ const uint16_t kStartTransportSequenceNumber = 1;
+
+ // Build and send a packet - it should be assigned the start sequence number.
+ // Return failure status code to make sure sequence number is not incremented.
+ auto packet = BuildRtpPacket(kSsrc);
+ EXPECT_TRUE(packet->ReserveExtension<TransportSequenceNumber>());
+ EXPECT_CALL(
+ rtp, TrySendPacket(
+ Property(&RtpPacketToSend::GetExtension<TransportSequenceNumber>,
+ kStartTransportSequenceNumber),
+ _))
+ .WillOnce(Return(false));
+ packet_router_.SendPacket(std::move(packet), PacedPacketInfo());
+
+ // Send another packet, verify transport sequence number is still at the
+ // start state.
+ packet = BuildRtpPacket(kSsrc);
+ EXPECT_TRUE(packet->ReserveExtension<TransportSequenceNumber>());
+
+ EXPECT_CALL(
+ rtp, TrySendPacket(
+ Property(&RtpPacketToSend::GetExtension<TransportSequenceNumber>,
+ kStartTransportSequenceNumber),
+ _))
+ .WillOnce(Return(true));
+ packet_router_.SendPacket(std::move(packet), PacedPacketInfo());
+
+ packet_router_.RemoveSendRtpModule(&rtp);
+}
+
+TEST_F(PacketRouterTest, ForwardsAbortedRetransmissions) {
+ NiceMock<MockRtpRtcpInterface> rtp_1;
+ NiceMock<MockRtpRtcpInterface> rtp_2;
+
+ const uint32_t kSsrc1 = 1234;
+ const uint32_t kSsrc2 = 2345;
+ const uint32_t kInvalidSsrc = 3456;
+
+ ON_CALL(rtp_1, SSRC).WillByDefault(Return(kSsrc1));
+ ON_CALL(rtp_2, SSRC).WillByDefault(Return(kSsrc2));
+
+ packet_router_.AddSendRtpModule(&rtp_1, false);
+ packet_router_.AddSendRtpModule(&rtp_2, false);
+
+ // Sets of retransmission sequence numbers we wish to abort, per ssrc.
+ const uint16_t kAbortedRetransmissionsOnSsrc1[] = {17, 42};
+ const uint16_t kAbortedRetransmissionsOnSsrc2[] = {1337, 4711};
+ const uint16_t kAbortedRetransmissionsOnSsrc3[] = {123};
+
+ EXPECT_CALL(rtp_1, OnAbortedRetransmissions(
+ ElementsAreArray(kAbortedRetransmissionsOnSsrc1)));
+ EXPECT_CALL(rtp_2, OnAbortedRetransmissions(
+ ElementsAreArray(kAbortedRetransmissionsOnSsrc2)));
+
+ packet_router_.OnAbortedRetransmissions(kSsrc1,
+ kAbortedRetransmissionsOnSsrc1);
+ packet_router_.OnAbortedRetransmissions(kSsrc2,
+ kAbortedRetransmissionsOnSsrc2);
+
+ // Should be noop and not cause any issues.
+ packet_router_.OnAbortedRetransmissions(kInvalidSsrc,
+ kAbortedRetransmissionsOnSsrc3);
+
+ packet_router_.RemoveSendRtpModule(&rtp_1);
+ packet_router_.RemoveSendRtpModule(&rtp_2);
+}
+
+TEST_F(PacketRouterTest, ReportsRtxSsrc) {
+ NiceMock<MockRtpRtcpInterface> rtp_1;
+ NiceMock<MockRtpRtcpInterface> rtp_2;
+
+ const uint32_t kSsrc1 = 1234;
+ const uint32_t kRtxSsrc1 = 1235;
+ const uint32_t kSsrc2 = 2345;
+ const uint32_t kInvalidSsrc = 3456;
+
+ ON_CALL(rtp_1, SSRC).WillByDefault(Return(kSsrc1));
+ ON_CALL(rtp_1, RtxSsrc).WillByDefault(Return(kRtxSsrc1));
+ ON_CALL(rtp_2, SSRC).WillByDefault(Return(kSsrc2));
+
+ packet_router_.AddSendRtpModule(&rtp_1, false);
+ packet_router_.AddSendRtpModule(&rtp_2, false);
+
+ EXPECT_EQ(packet_router_.GetRtxSsrcForMedia(kSsrc1), kRtxSsrc1);
+ EXPECT_EQ(packet_router_.GetRtxSsrcForMedia(kRtxSsrc1), absl::nullopt);
+ EXPECT_EQ(packet_router_.GetRtxSsrcForMedia(kSsrc2), absl::nullopt);
+ EXPECT_EQ(packet_router_.GetRtxSsrcForMedia(kInvalidSsrc), absl::nullopt);
+
+ packet_router_.RemoveSendRtpModule(&rtp_1);
+ packet_router_.RemoveSendRtpModule(&rtp_2);
+}
+
+#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
+using PacketRouterDeathTest = PacketRouterTest;
+TEST_F(PacketRouterDeathTest, DoubleRegistrationOfSendModuleDisallowed) {
+ NiceMock<MockRtpRtcpInterface> module;
+
+ constexpr bool remb_candidate = false; // Value irrelevant.
+ packet_router_.AddSendRtpModule(&module, remb_candidate);
+ EXPECT_DEATH(packet_router_.AddSendRtpModule(&module, remb_candidate), "");
+
+ // Test tear-down
+ packet_router_.RemoveSendRtpModule(&module);
+}
+
+TEST_F(PacketRouterDeathTest, DoubleRegistrationOfReceiveModuleDisallowed) {
+ NiceMock<MockRtpRtcpInterface> module;
+
+ constexpr bool remb_candidate = false; // Value irrelevant.
+ packet_router_.AddReceiveRtpModule(&module, remb_candidate);
+ EXPECT_DEATH(packet_router_.AddReceiveRtpModule(&module, remb_candidate), "");
+
+ // Test tear-down
+ packet_router_.RemoveReceiveRtpModule(&module);
+}
+
+TEST_F(PacketRouterDeathTest, RemovalOfNeverAddedSendModuleDisallowed) {
+ NiceMock<MockRtpRtcpInterface> module;
+
+ EXPECT_DEATH(packet_router_.RemoveSendRtpModule(&module), "");
+}
+
+TEST_F(PacketRouterDeathTest, RemovalOfNeverAddedReceiveModuleDisallowed) {
+ NiceMock<MockRtpRtcpInterface> module;
+
+ EXPECT_DEATH(packet_router_.RemoveReceiveRtpModule(&module), "");
+}
+#endif // RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
+
+TEST(PacketRouterRembTest, ChangeSendRtpModuleChangeRembSender) {
+ rtc::ScopedFakeClock clock;
+ NiceMock<MockRtpRtcpInterface> rtp_send;
+ NiceMock<MockRtpRtcpInterface> rtp_recv;
+ PacketRouter packet_router;
+ packet_router.AddSendRtpModule(&rtp_send, true);
+ packet_router.AddReceiveRtpModule(&rtp_recv, true);
+
+ uint32_t bitrate_estimate = 456;
+ std::vector<uint32_t> ssrcs = {1234, 5678};
+
+ EXPECT_CALL(rtp_send, SetRemb(bitrate_estimate, ssrcs));
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ // Remove the sending module -> should get remb on the second module.
+ packet_router.RemoveSendRtpModule(&rtp_send);
+
+ EXPECT_CALL(rtp_recv, SetRemb(bitrate_estimate, ssrcs));
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ packet_router.RemoveReceiveRtpModule(&rtp_recv);
+}
+
+// Only register receiving modules and make sure we fallback to trigger a REMB
+// packet on this one.
+TEST(PacketRouterRembTest, NoSendingRtpModule) {
+ rtc::ScopedFakeClock clock;
+ NiceMock<MockRtpRtcpInterface> rtp;
+ PacketRouter packet_router;
+
+ packet_router.AddReceiveRtpModule(&rtp, true);
+
+ uint32_t bitrate_estimate = 456;
+ const std::vector<uint32_t> ssrcs = {1234};
+
+ EXPECT_CALL(rtp, SetRemb(bitrate_estimate, ssrcs));
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ // Lower the estimate to trigger a new packet REMB packet.
+ EXPECT_CALL(rtp, SetRemb(bitrate_estimate, ssrcs));
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ EXPECT_CALL(rtp, UnsetRemb());
+ packet_router.RemoveReceiveRtpModule(&rtp);
+}
+
+TEST(PacketRouterRembTest, NonCandidateSendRtpModuleNotUsedForRemb) {
+ rtc::ScopedFakeClock clock;
+ PacketRouter packet_router;
+ NiceMock<MockRtpRtcpInterface> module;
+
+ constexpr bool remb_candidate = false;
+
+ packet_router.AddSendRtpModule(&module, remb_candidate);
+
+ constexpr uint32_t bitrate_estimate = 456;
+ const std::vector<uint32_t> ssrcs = {1234};
+ EXPECT_CALL(module, SetRemb(_, _)).Times(0);
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ // Test tear-down
+ packet_router.RemoveSendRtpModule(&module);
+}
+
+TEST(PacketRouterRembTest, CandidateSendRtpModuleUsedForRemb) {
+ rtc::ScopedFakeClock clock;
+ PacketRouter packet_router;
+ NiceMock<MockRtpRtcpInterface> module;
+
+ constexpr bool remb_candidate = true;
+
+ packet_router.AddSendRtpModule(&module, remb_candidate);
+
+ constexpr uint32_t bitrate_estimate = 456;
+ const std::vector<uint32_t> ssrcs = {1234};
+ EXPECT_CALL(module, SetRemb(bitrate_estimate, ssrcs));
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ // Test tear-down
+ packet_router.RemoveSendRtpModule(&module);
+}
+
+TEST(PacketRouterRembTest, NonCandidateReceiveRtpModuleNotUsedForRemb) {
+ rtc::ScopedFakeClock clock;
+ PacketRouter packet_router;
+ NiceMock<MockRtpRtcpInterface> module;
+
+ constexpr bool remb_candidate = false;
+
+ packet_router.AddReceiveRtpModule(&module, remb_candidate);
+
+ constexpr uint32_t bitrate_estimate = 456;
+ const std::vector<uint32_t> ssrcs = {1234};
+ EXPECT_CALL(module, SetRemb(_, _)).Times(0);
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ // Test tear-down
+ packet_router.RemoveReceiveRtpModule(&module);
+}
+
+TEST(PacketRouterRembTest, CandidateReceiveRtpModuleUsedForRemb) {
+ rtc::ScopedFakeClock clock;
+ PacketRouter packet_router;
+ NiceMock<MockRtpRtcpInterface> module;
+
+ constexpr bool remb_candidate = true;
+
+ packet_router.AddReceiveRtpModule(&module, remb_candidate);
+
+ constexpr uint32_t bitrate_estimate = 456;
+ const std::vector<uint32_t> ssrcs = {1234};
+ EXPECT_CALL(module, SetRemb(bitrate_estimate, ssrcs));
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ // Test tear-down
+ packet_router.RemoveReceiveRtpModule(&module);
+}
+
+TEST(PacketRouterRembTest,
+ SendCandidatePreferredOverReceiveCandidate_SendModuleAddedFirst) {
+ rtc::ScopedFakeClock clock;
+ PacketRouter packet_router;
+ NiceMock<MockRtpRtcpInterface> send_module;
+ NiceMock<MockRtpRtcpInterface> receive_module;
+
+ constexpr bool remb_candidate = true;
+
+ // Send module added - activated.
+ packet_router.AddSendRtpModule(&send_module, remb_candidate);
+
+ // Receive module added - the send module remains the active one.
+ packet_router.AddReceiveRtpModule(&receive_module, remb_candidate);
+
+ constexpr uint32_t bitrate_estimate = 456;
+ const std::vector<uint32_t> ssrcs = {1234};
+ EXPECT_CALL(send_module, SetRemb(bitrate_estimate, ssrcs));
+ EXPECT_CALL(receive_module, SetRemb(_, _)).Times(0);
+
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ // Test tear-down
+ packet_router.RemoveReceiveRtpModule(&receive_module);
+ packet_router.RemoveSendRtpModule(&send_module);
+}
+
+TEST(PacketRouterRembTest,
+ SendCandidatePreferredOverReceiveCandidate_ReceiveModuleAddedFirst) {
+ rtc::ScopedFakeClock clock;
+ PacketRouter packet_router;
+ NiceMock<MockRtpRtcpInterface> send_module;
+ NiceMock<MockRtpRtcpInterface> receive_module;
+
+ constexpr bool remb_candidate = true;
+
+ // Receive module added - activated.
+ packet_router.AddReceiveRtpModule(&receive_module, remb_candidate);
+
+ // Send module added - replaces receive module as active.
+ packet_router.AddSendRtpModule(&send_module, remb_candidate);
+
+ constexpr uint32_t bitrate_estimate = 456;
+ const std::vector<uint32_t> ssrcs = {1234};
+ EXPECT_CALL(send_module, SetRemb(bitrate_estimate, ssrcs));
+ EXPECT_CALL(receive_module, SetRemb(_, _)).Times(0);
+
+ clock.AdvanceTime(TimeDelta::Millis(1000));
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ // Test tear-down
+ packet_router.RemoveReceiveRtpModule(&receive_module);
+ packet_router.RemoveSendRtpModule(&send_module);
+}
+
+TEST(PacketRouterRembTest, ReceiveModuleTakesOverWhenLastSendModuleRemoved) {
+ rtc::ScopedFakeClock clock;
+ PacketRouter packet_router;
+ NiceMock<MockRtpRtcpInterface> send_module;
+ NiceMock<MockRtpRtcpInterface> receive_module;
+
+ constexpr bool remb_candidate = true;
+
+ // Send module active, receive module inactive.
+ packet_router.AddSendRtpModule(&send_module, remb_candidate);
+ packet_router.AddReceiveRtpModule(&receive_module, remb_candidate);
+
+ // Send module removed - receive module becomes active.
+ packet_router.RemoveSendRtpModule(&send_module);
+ constexpr uint32_t bitrate_estimate = 456;
+ const std::vector<uint32_t> ssrcs = {1234};
+ EXPECT_CALL(send_module, SetRemb(_, _)).Times(0);
+ EXPECT_CALL(receive_module, SetRemb(bitrate_estimate, ssrcs));
+ packet_router.SendRemb(bitrate_estimate, ssrcs);
+
+ // Test tear-down
+ packet_router.RemoveReceiveRtpModule(&receive_module);
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc
new file mode 100644
index 0000000000..0c285c463a
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2022 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/pacing/prioritized_packet_queue.h"
+
+#include <utility>
+
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+namespace {
+
+constexpr int kAudioPrioLevel = 0;
+
+int GetPriorityForType(RtpPacketMediaType type) {
+ // Lower number takes priority over higher.
+ switch (type) {
+ case RtpPacketMediaType::kAudio:
+ // Audio is always prioritized over other packet types.
+ return kAudioPrioLevel;
+ case RtpPacketMediaType::kRetransmission:
+ // Send retransmissions before new media.
+ return kAudioPrioLevel + 1;
+ case RtpPacketMediaType::kVideo:
+ case RtpPacketMediaType::kForwardErrorCorrection:
+ // Video has "normal" priority, in the old speak.
+ // Send redundancy concurrently to video. If it is delayed it might have a
+ // lower chance of being useful.
+ return kAudioPrioLevel + 2;
+ case RtpPacketMediaType::kPadding:
+ // Packets that are in themselves likely useless, only sent to keep the
+ // BWE high.
+ return kAudioPrioLevel + 3;
+ }
+ RTC_CHECK_NOTREACHED();
+}
+
+} // namespace
+
+DataSize PrioritizedPacketQueue::QueuedPacket::PacketSize() const {
+ return DataSize::Bytes(packet->payload_size() + packet->padding_size());
+}
+
+PrioritizedPacketQueue::StreamQueue::StreamQueue(Timestamp creation_time)
+ : last_enqueue_time_(creation_time) {}
+
+bool PrioritizedPacketQueue::StreamQueue::EnqueuePacket(QueuedPacket packet,
+ int priority_level) {
+ bool first_packet_at_level = packets_[priority_level].empty();
+ packets_[priority_level].push_back(std::move(packet));
+ return first_packet_at_level;
+}
+
+PrioritizedPacketQueue::QueuedPacket
+PrioritizedPacketQueue::StreamQueue::DequeuePacket(int priority_level) {
+ RTC_DCHECK(!packets_[priority_level].empty());
+ QueuedPacket packet = std::move(packets_[priority_level].front());
+ packets_[priority_level].pop_front();
+ return packet;
+}
+
+bool PrioritizedPacketQueue::StreamQueue::HasPacketsAtPrio(
+ int priority_level) const {
+ return !packets_[priority_level].empty();
+}
+
+bool PrioritizedPacketQueue::StreamQueue::IsEmpty() const {
+ for (const std::deque<QueuedPacket>& queue : packets_) {
+ if (!queue.empty()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+Timestamp PrioritizedPacketQueue::StreamQueue::LeadingPacketEnqueueTime(
+ int priority_level) const {
+ RTC_DCHECK(!packets_[priority_level].empty());
+ return packets_[priority_level].begin()->enqueue_time;
+}
+
+Timestamp PrioritizedPacketQueue::StreamQueue::LastEnqueueTime() const {
+ return last_enqueue_time_;
+}
+
+std::array<std::deque<PrioritizedPacketQueue::QueuedPacket>,
+ PrioritizedPacketQueue::kNumPriorityLevels>
+PrioritizedPacketQueue::StreamQueue::DequeueAll() {
+ std::array<std::deque<QueuedPacket>, kNumPriorityLevels> packets_by_prio;
+ for (int i = 0; i < kNumPriorityLevels; ++i) {
+ packets_by_prio[i].swap(packets_[i]);
+ }
+ return packets_by_prio;
+}
+
+PrioritizedPacketQueue::PrioritizedPacketQueue(Timestamp creation_time)
+ : queue_time_sum_(TimeDelta::Zero()),
+ pause_time_sum_(TimeDelta::Zero()),
+ size_packets_(0),
+ size_packets_per_media_type_({}),
+ size_payload_(DataSize::Zero()),
+ last_update_time_(creation_time),
+ paused_(false),
+ last_culling_time_(creation_time),
+ top_active_prio_level_(-1) {}
+
+void PrioritizedPacketQueue::Push(Timestamp enqueue_time,
+ std::unique_ptr<RtpPacketToSend> packet) {
+ StreamQueue* stream_queue;
+ auto [it, inserted] = streams_.emplace(packet->Ssrc(), nullptr);
+ if (inserted) {
+ it->second = std::make_unique<StreamQueue>(enqueue_time);
+ }
+ stream_queue = it->second.get();
+
+ auto enqueue_time_iterator =
+ enqueue_times_.insert(enqueue_times_.end(), enqueue_time);
+ RTC_DCHECK(packet->packet_type().has_value());
+ RtpPacketMediaType packet_type = packet->packet_type().value();
+ int prio_level = GetPriorityForType(packet_type);
+ RTC_DCHECK_GE(prio_level, 0);
+ RTC_DCHECK_LT(prio_level, kNumPriorityLevels);
+ QueuedPacket queued_packed = {.packet = std::move(packet),
+ .enqueue_time = enqueue_time,
+ .enqueue_time_iterator = enqueue_time_iterator};
+ // In order to figure out how much time a packet has spent in the queue
+ // while not in a paused state, we subtract the total amount of time the
+ // queue has been paused so far, and when the packet is popped we subtract
+ // the total amount of time the queue has been paused at that moment. This
+ // way we subtract the total amount of time the packet has spent in the
+ // queue while in a paused state.
+ UpdateAverageQueueTime(enqueue_time);
+ queued_packed.enqueue_time -= pause_time_sum_;
+ ++size_packets_;
+ ++size_packets_per_media_type_[static_cast<size_t>(packet_type)];
+ size_payload_ += queued_packed.PacketSize();
+
+ if (stream_queue->EnqueuePacket(std::move(queued_packed), prio_level)) {
+ // Number packets at `prio_level` for this steam is now non-zero.
+ streams_by_prio_[prio_level].push_back(stream_queue);
+ }
+ if (top_active_prio_level_ < 0 || prio_level < top_active_prio_level_) {
+ top_active_prio_level_ = prio_level;
+ }
+
+ static constexpr TimeDelta kTimeout = TimeDelta::Millis(500);
+ if (enqueue_time - last_culling_time_ > kTimeout) {
+ for (auto it = streams_.begin(); it != streams_.end();) {
+ if (it->second->IsEmpty() &&
+ it->second->LastEnqueueTime() + kTimeout < enqueue_time) {
+ streams_.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+ last_culling_time_ = enqueue_time;
+ }
+}
+
+std::unique_ptr<RtpPacketToSend> PrioritizedPacketQueue::Pop() {
+ if (size_packets_ == 0) {
+ return nullptr;
+ }
+
+ RTC_DCHECK_GE(top_active_prio_level_, 0);
+ StreamQueue& stream_queue = *streams_by_prio_[top_active_prio_level_].front();
+ QueuedPacket packet = stream_queue.DequeuePacket(top_active_prio_level_);
+ DequeuePacketInternal(packet);
+
+ // Remove StreamQueue from head of fifo-queue for this prio level, and
+ // and add it to the end if it still has packets.
+ streams_by_prio_[top_active_prio_level_].pop_front();
+ if (stream_queue.HasPacketsAtPrio(top_active_prio_level_)) {
+ streams_by_prio_[top_active_prio_level_].push_back(&stream_queue);
+ } else {
+ MaybeUpdateTopPrioLevel();
+ }
+
+ return std::move(packet.packet);
+}
+
+int PrioritizedPacketQueue::SizeInPackets() const {
+ return size_packets_;
+}
+
+DataSize PrioritizedPacketQueue::SizeInPayloadBytes() const {
+ return size_payload_;
+}
+
+bool PrioritizedPacketQueue::Empty() const {
+ return size_packets_ == 0;
+}
+
+const std::array<int, kNumMediaTypes>&
+PrioritizedPacketQueue::SizeInPacketsPerRtpPacketMediaType() const {
+ return size_packets_per_media_type_;
+}
+
+Timestamp PrioritizedPacketQueue::LeadingPacketEnqueueTime(
+ RtpPacketMediaType type) const {
+ const int priority_level = GetPriorityForType(type);
+ if (streams_by_prio_[priority_level].empty()) {
+ return Timestamp::MinusInfinity();
+ }
+ return streams_by_prio_[priority_level].front()->LeadingPacketEnqueueTime(
+ priority_level);
+}
+
+Timestamp PrioritizedPacketQueue::OldestEnqueueTime() const {
+ return enqueue_times_.empty() ? Timestamp::MinusInfinity()
+ : enqueue_times_.front();
+}
+
+TimeDelta PrioritizedPacketQueue::AverageQueueTime() const {
+ if (size_packets_ == 0) {
+ return TimeDelta::Zero();
+ }
+ return queue_time_sum_ / size_packets_;
+}
+
+void PrioritizedPacketQueue::UpdateAverageQueueTime(Timestamp now) {
+ RTC_CHECK_GE(now, last_update_time_);
+ if (now == last_update_time_) {
+ return;
+ }
+
+ TimeDelta delta = now - last_update_time_;
+
+ if (paused_) {
+ pause_time_sum_ += delta;
+ } else {
+ queue_time_sum_ += delta * size_packets_;
+ }
+
+ last_update_time_ = now;
+}
+
+void PrioritizedPacketQueue::SetPauseState(bool paused, Timestamp now) {
+ UpdateAverageQueueTime(now);
+ paused_ = paused;
+}
+
+void PrioritizedPacketQueue::RemovePacketsForSsrc(uint32_t ssrc) {
+ auto kv = streams_.find(ssrc);
+ if (kv != streams_.end()) {
+ // Dequeue all packets from the queue for this SSRC.
+ StreamQueue& queue = *kv->second;
+ std::array<std::deque<QueuedPacket>, kNumPriorityLevels> packets_by_prio =
+ queue.DequeueAll();
+ for (int i = 0; i < kNumPriorityLevels; ++i) {
+ std::deque<QueuedPacket>& packet_queue = packets_by_prio[i];
+ if (packet_queue.empty()) {
+ continue;
+ }
+
+ // First erase all packets at this prio level.
+ while (!packet_queue.empty()) {
+ QueuedPacket packet = std::move(packet_queue.front());
+ packet_queue.pop_front();
+ DequeuePacketInternal(packet);
+ }
+
+ // Next, deregister this `StreamQueue` from the round-robin tables.
+ RTC_DCHECK(!streams_by_prio_[i].empty());
+ if (streams_by_prio_[i].size() == 1) {
+ // This is the last and only queue that had packets for this prio level.
+ // Update the global top prio level if neccessary.
+ RTC_DCHECK(streams_by_prio_[i].front() == &queue);
+ streams_by_prio_[i].pop_front();
+ if (i == top_active_prio_level_) {
+ MaybeUpdateTopPrioLevel();
+ }
+ } else {
+ // More than stream had packets at this prio level, filter this one out.
+ std::deque<StreamQueue*> filtered_queue;
+ for (StreamQueue* queue_ptr : streams_by_prio_[i]) {
+ if (queue_ptr != &queue) {
+ filtered_queue.push_back(queue_ptr);
+ }
+ }
+ streams_by_prio_[i].swap(filtered_queue);
+ }
+ }
+ }
+}
+
+void PrioritizedPacketQueue::DequeuePacketInternal(QueuedPacket& packet) {
+ --size_packets_;
+ RTC_DCHECK(packet.packet->packet_type().has_value());
+ RtpPacketMediaType packet_type = packet.packet->packet_type().value();
+ --size_packets_per_media_type_[static_cast<size_t>(packet_type)];
+ RTC_DCHECK_GE(size_packets_per_media_type_[static_cast<size_t>(packet_type)],
+ 0);
+ size_payload_ -= packet.PacketSize();
+
+ // Calculate the total amount of time spent by this packet in the queue
+ // while in a non-paused state. Note that the `pause_time_sum_ms_` was
+ // subtracted from `packet.enqueue_time_ms` when the packet was pushed, and
+ // by subtracting it now we effectively remove the time spent in in the
+ // queue while in a paused state.
+ TimeDelta time_in_non_paused_state =
+ last_update_time_ - packet.enqueue_time - pause_time_sum_;
+ queue_time_sum_ -= time_in_non_paused_state;
+
+ // Set the time spent in the send queue, which is the per-packet equivalent of
+ // totalPacketSendDelay. The notion of being paused is an implementation
+ // detail that we do not want to expose, so it makes sense to report the
+ // metric excluding the pause time. This also avoids spikes in the metric.
+ // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-totalpacketsenddelay
+ packet.packet->set_time_in_send_queue(time_in_non_paused_state);
+
+ RTC_DCHECK(size_packets_ > 0 || queue_time_sum_ == TimeDelta::Zero());
+
+ RTC_CHECK(packet.enqueue_time_iterator != enqueue_times_.end());
+ enqueue_times_.erase(packet.enqueue_time_iterator);
+}
+
+void PrioritizedPacketQueue::MaybeUpdateTopPrioLevel() {
+ if (streams_by_prio_[top_active_prio_level_].empty()) {
+ // No stream queues have packets at this prio level, find top priority
+ // that is not empty.
+ if (size_packets_ == 0) {
+ top_active_prio_level_ = -1;
+ } else {
+ for (int i = 0; i < kNumPriorityLevels; ++i) {
+ if (!streams_by_prio_[i].empty()) {
+ top_active_prio_level_ = i;
+ break;
+ }
+ }
+ }
+ }
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.h b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.h
new file mode 100644
index 0000000000..364b53af11
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2022 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_PACING_PRIORITIZED_PACKET_QUEUE_H_
+#define MODULES_PACING_PRIORITIZED_PACKET_QUEUE_H_
+
+#include <stddef.h>
+
+#include <array>
+#include <deque>
+#include <list>
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+#include "api/units/data_size.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
+
+namespace webrtc {
+
+class PrioritizedPacketQueue {
+ public:
+ explicit PrioritizedPacketQueue(Timestamp creation_time);
+ PrioritizedPacketQueue(const PrioritizedPacketQueue&) = delete;
+ PrioritizedPacketQueue& operator=(const PrioritizedPacketQueue&) = delete;
+
+ // Add a packet to the queue. The enqueue time is used for queue time stats
+ // and to report the leading packet enqueue time per packet type.
+ void Push(Timestamp enqueue_time, std::unique_ptr<RtpPacketToSend> packet);
+
+ // Remove the next packet from the queue. Packets a prioritized first
+ // according to packet type, in the following order:
+ // - audio, retransmissions, video / fec, padding
+ // For each packet type, we use one FIFO-queue per SSRC and emit from
+ // those queues in a round-robin fashion.
+ std::unique_ptr<RtpPacketToSend> Pop();
+
+ // Number of packets in the queue.
+ int SizeInPackets() const;
+
+ // Sum of all payload bytes in the queue, where the payload is calculated
+ // as `packet->payload_size() + packet->padding_size()`.
+ DataSize SizeInPayloadBytes() const;
+
+ // Convenience method for `SizeInPackets() == 0`.
+ bool Empty() const;
+
+ // Total packets in the queue per media type (RtpPacketMediaType values are
+ // used as lookup index).
+ const std::array<int, kNumMediaTypes>& SizeInPacketsPerRtpPacketMediaType()
+ const;
+
+ // The enqueue time of the next packet this queue will return via the Pop()
+ // method, for the given packet type. If queue has no packets, of that type,
+ // returns Timestamp::MinusInfinity().
+ Timestamp LeadingPacketEnqueueTime(RtpPacketMediaType type) const;
+
+ // Enqueue time of the oldest packet in the queue,
+ // Timestamp::MinusInfinity() if queue is empty.
+ Timestamp OldestEnqueueTime() const;
+
+ // Average queue time for the packets currently in the queue.
+ // The queuing time is calculated from Push() to the last UpdateQueueTime()
+ // call - with any time spent in a paused state subtracted.
+ // Returns TimeDelta::Zero() for an empty queue.
+ TimeDelta AverageQueueTime() const;
+
+ // Called during packet processing or when pause stats changes. Since the
+ // AverageQueueTime() method does not look at the wall time, this method
+ // needs to be called before querying queue time.
+ void UpdateAverageQueueTime(Timestamp now);
+
+ // Set the pause state, while `paused` is true queuing time is not counted.
+ void SetPauseState(bool paused, Timestamp now);
+
+ // Remove any packets matching the given SSRC.
+ void RemovePacketsForSsrc(uint32_t ssrc);
+
+ private:
+ static constexpr int kNumPriorityLevels = 4;
+
+ class QueuedPacket {
+ public:
+ DataSize PacketSize() const;
+
+ std::unique_ptr<RtpPacketToSend> packet;
+ Timestamp enqueue_time;
+ std::list<Timestamp>::iterator enqueue_time_iterator;
+ };
+
+ // Class containing packets for an RTP stream.
+ // For each priority level, packets are simply stored in a fifo queue.
+ class StreamQueue {
+ public:
+ explicit StreamQueue(Timestamp creation_time);
+ StreamQueue(StreamQueue&&) = default;
+ StreamQueue& operator=(StreamQueue&&) = default;
+
+ StreamQueue(const StreamQueue&) = delete;
+ StreamQueue& operator=(const StreamQueue&) = delete;
+
+ // Enqueue packet at the given priority level. Returns true if the packet
+ // count for that priority level went from zero to non-zero.
+ bool EnqueuePacket(QueuedPacket packet, int priority_level);
+
+ QueuedPacket DequeuePacket(int priority_level);
+
+ bool HasPacketsAtPrio(int priority_level) const;
+ bool IsEmpty() const;
+ Timestamp LeadingPacketEnqueueTime(int priority_level) const;
+ Timestamp LastEnqueueTime() const;
+
+ std::array<std::deque<QueuedPacket>, kNumPriorityLevels> DequeueAll();
+
+ private:
+ std::deque<QueuedPacket> packets_[kNumPriorityLevels];
+ Timestamp last_enqueue_time_;
+ };
+
+ // Remove the packet from the internal state, e.g. queue time / size etc.
+ void DequeuePacketInternal(QueuedPacket& packet);
+
+ // Check if the queue pointed to by `top_active_prio_level_` is empty and
+ // if so move it to the lowest non-empty index.
+ void MaybeUpdateTopPrioLevel();
+
+ // Cumulative sum, over all packets, of time spent in the queue.
+ TimeDelta queue_time_sum_;
+ // Cumulative sum of time the queue has spent in a paused state.
+ TimeDelta pause_time_sum_;
+ // Total number of packets stored in this queue.
+ int size_packets_;
+ // Total number of packets stored in this queue per RtpPacketMediaType.
+ std::array<int, kNumMediaTypes> size_packets_per_media_type_;
+ // Sum of payload sizes for all packts stored in this queue.
+ DataSize size_payload_;
+ // The last time queue/pause time sums were updated.
+ Timestamp last_update_time_;
+ bool paused_;
+
+ // Last time `streams_` was culled for inactive streams.
+ Timestamp last_culling_time_;
+
+ // Map from SSRC to packet queues for the associated RTP stream.
+ std::unordered_map<uint32_t, std::unique_ptr<StreamQueue>> streams_;
+
+ // For each priority level, a queue of StreamQueues which have at least one
+ // packet pending for that prio level.
+ std::deque<StreamQueue*> streams_by_prio_[kNumPriorityLevels];
+
+ // The first index into `stream_by_prio_` that is non-empty.
+ int top_active_prio_level_;
+
+ // Ordered list of enqueue times. Additions are always increasing and added to
+ // the end. QueuedPacket instances have a iterators into this list for fast
+ // removal.
+ std::list<Timestamp> enqueue_times_;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_PACING_PRIORITIZED_PACKET_QUEUE_H_
diff --git a/third_party/libwebrtc/modules/pacing/prioritized_packet_queue_unittest.cc b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue_unittest.cc
new file mode 100644
index 0000000000..964051c0c7
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/prioritized_packet_queue_unittest.cc
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 2022 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/pacing/prioritized_packet_queue.h"
+
+#include <utility>
+
+#include "api/units/time_delta.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
+#include "rtc_base/checks.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace {
+
+constexpr uint32_t kDefaultSsrc = 123;
+constexpr int kDefaultPayloadSize = 789;
+
+std::unique_ptr<RtpPacketToSend> CreatePacket(RtpPacketMediaType type,
+ uint16_t sequence_number,
+ uint32_t ssrc = kDefaultSsrc) {
+ auto packet = std::make_unique<RtpPacketToSend>(/*extensions=*/nullptr);
+ packet->set_packet_type(type);
+ packet->SetSsrc(ssrc);
+ packet->SetSequenceNumber(sequence_number);
+ packet->SetPayloadSize(kDefaultPayloadSize);
+ return packet;
+}
+
+} // namespace
+
+TEST(PrioritizedPacketQueue, ReturnsPacketsInPrioritizedOrder) {
+ Timestamp now = Timestamp::Zero();
+ PrioritizedPacketQueue queue(now);
+
+ // Add packets in low to high packet order.
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kPadding, /*seq=*/1));
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/2));
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kForwardErrorCorrection,
+ /*seq=*/3));
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kRetransmission, /*seq=*/4));
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kAudio, /*seq=*/5));
+
+ // Packets should be returned in high to low order.
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 5);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 4);
+ // Video and FEC prioritized equally - but video was enqueued first.
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 2);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 3);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 1);
+}
+
+TEST(PrioritizedPacketQueue, ReturnsEqualPrioPacketsInRoundRobinOrder) {
+ Timestamp now = Timestamp::Zero();
+ PrioritizedPacketQueue queue(now);
+
+ // Insert video packets (prioritized equally), simulating a simulcast-type use
+ // case.
+ queue.Push(now,
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/1, /*ssrc=*/100));
+
+ queue.Push(now,
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/2, /*ssrc=*/101));
+ queue.Push(now,
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/3, /*ssrc=*/101));
+
+ queue.Push(now,
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/4, /*ssrc=*/102));
+ queue.Push(now,
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/5, /*ssrc=*/102));
+ queue.Push(now,
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/6, /*ssrc=*/102));
+ queue.Push(now,
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/7, /*ssrc=*/102));
+
+ // First packet from each SSRC.
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 1);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 2);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 4);
+
+ // Second packets from streams that have packets left.
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 3);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 5);
+
+ // Only packets from last stream remaining.
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 6);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 7);
+}
+
+TEST(PrioritizedPacketQueue, ReportsSizeInPackets) {
+ PrioritizedPacketQueue queue(/*creation_time=*/Timestamp::Zero());
+ EXPECT_EQ(queue.SizeInPackets(), 0);
+
+ queue.Push(/*enqueue_time=*/Timestamp::Zero(),
+ CreatePacket(RtpPacketMediaType::kVideo,
+ /*seq_no=*/1));
+ EXPECT_EQ(queue.SizeInPackets(), 1);
+
+ queue.Pop();
+ EXPECT_EQ(queue.SizeInPackets(), 0);
+}
+
+TEST(PrioritizedPacketQueue, ReportsPayloadSize) {
+ PrioritizedPacketQueue queue(/*creation_time=*/Timestamp::Zero());
+ EXPECT_EQ(queue.SizeInPayloadBytes(), DataSize::Zero());
+
+ queue.Push(/*enqueue_time=*/Timestamp::Zero(),
+ CreatePacket(RtpPacketMediaType::kVideo,
+ /*seq_no=*/1));
+ EXPECT_EQ(queue.SizeInPayloadBytes(), DataSize::Bytes(kDefaultPayloadSize));
+
+ queue.Pop();
+ EXPECT_EQ(queue.SizeInPayloadBytes(), DataSize::Zero());
+}
+
+TEST(PrioritizedPacketQueue, ReportsPaddingSize) {
+ PrioritizedPacketQueue queue(/*creation_time=*/Timestamp::Zero());
+ EXPECT_EQ(queue.SizeInPayloadBytes(), DataSize::Zero());
+ static constexpr DataSize kPaddingSize = DataSize::Bytes(190);
+
+ auto packet = std::make_unique<RtpPacketToSend>(/*extensions=*/nullptr);
+ packet->set_packet_type(RtpPacketMediaType::kPadding);
+ packet->SetSsrc(kDefaultSsrc);
+ packet->SetSequenceNumber(/*seq=*/1);
+ packet->SetPadding(kPaddingSize.bytes());
+ queue.Push(/*enqueue_time=*/Timestamp::Zero(), std::move(packet));
+ EXPECT_EQ(queue.SizeInPayloadBytes(), kPaddingSize);
+
+ queue.Pop();
+ EXPECT_EQ(queue.SizeInPayloadBytes(), DataSize::Zero());
+}
+
+TEST(PrioritizedPacketQueue, ReportsOldestEnqueueTime) {
+ PrioritizedPacketQueue queue(/*creation_time=*/Timestamp::Zero());
+ EXPECT_EQ(queue.OldestEnqueueTime(), Timestamp::MinusInfinity());
+
+ // Add three packets, with the middle packet having higher prio.
+ queue.Push(Timestamp::Millis(10),
+ CreatePacket(RtpPacketMediaType::kPadding, /*seq=*/1));
+ queue.Push(Timestamp::Millis(20),
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/2));
+ queue.Push(Timestamp::Millis(30),
+ CreatePacket(RtpPacketMediaType::kPadding, /*seq=*/3));
+ EXPECT_EQ(queue.OldestEnqueueTime(), Timestamp::Millis(10));
+
+ queue.Pop(); // Pop packet with enqueue time 20.
+ EXPECT_EQ(queue.OldestEnqueueTime(), Timestamp::Millis(10));
+
+ queue.Pop(); // Pop packet with enqueue time 10.
+ EXPECT_EQ(queue.OldestEnqueueTime(), Timestamp::Millis(30));
+
+ queue.Pop(); // Pop packet with enqueue time 30, queue empty again.
+ EXPECT_EQ(queue.OldestEnqueueTime(), Timestamp::MinusInfinity());
+}
+
+TEST(PrioritizedPacketQueue, ReportsAverageQueueTime) {
+ PrioritizedPacketQueue queue(/*creation_time=*/Timestamp::Zero());
+ EXPECT_EQ(queue.AverageQueueTime(), TimeDelta::Zero());
+
+ // Add three packets, with the middle packet having higher prio.
+ queue.Push(Timestamp::Millis(10),
+ CreatePacket(RtpPacketMediaType::kPadding, /*seq=*/1));
+ queue.Push(Timestamp::Millis(20),
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/2));
+ queue.Push(Timestamp::Millis(30),
+ CreatePacket(RtpPacketMediaType::kPadding, /*seq=*/3));
+
+ queue.UpdateAverageQueueTime(Timestamp::Millis(40));
+ // Packets have waited 30, 20, 10 ms -> average = 20ms.
+ EXPECT_EQ(queue.AverageQueueTime(), TimeDelta::Millis(20));
+
+ queue.Pop(); // Pop packet with enqueue time 20.
+ EXPECT_EQ(queue.AverageQueueTime(), TimeDelta::Millis(20));
+
+ queue.Pop(); // Pop packet with enqueue time 10.
+ EXPECT_EQ(queue.AverageQueueTime(), TimeDelta::Millis(10));
+
+ queue.Pop(); // Pop packet with enqueue time 30, queue empty again.
+ EXPECT_EQ(queue.AverageQueueTime(), TimeDelta::Zero());
+}
+
+TEST(PrioritizedPacketQueue, SubtractsPusedTimeFromAverageQueueTime) {
+ PrioritizedPacketQueue queue(/*creation_time=*/Timestamp::Zero());
+ EXPECT_EQ(queue.AverageQueueTime(), TimeDelta::Zero());
+
+ // Add a packet and then enable paused state.
+ queue.Push(Timestamp::Millis(100),
+ CreatePacket(RtpPacketMediaType::kPadding, /*seq=*/1));
+ queue.SetPauseState(true, Timestamp::Millis(600));
+ EXPECT_EQ(queue.AverageQueueTime(), TimeDelta::Millis(500));
+
+ // Enqueue a packet 500ms into the paused state. Queue time of
+ // original packet is still seen as 500ms and new one has 0ms giving
+ // an average of 250ms.
+ queue.Push(Timestamp::Millis(1100),
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/2));
+ EXPECT_EQ(queue.AverageQueueTime(), TimeDelta::Millis(250));
+
+ // Unpause some time later, queue time still unchanged.
+ queue.SetPauseState(false, Timestamp::Millis(1600));
+ EXPECT_EQ(queue.AverageQueueTime(), TimeDelta::Millis(250));
+
+ // Update queue time 500ms after pause state ended.
+ queue.UpdateAverageQueueTime(Timestamp::Millis(2100));
+ EXPECT_EQ(queue.AverageQueueTime(), TimeDelta::Millis(750));
+}
+
+TEST(PrioritizedPacketQueue, ReportsLeadingPacketEnqueueTime) {
+ PrioritizedPacketQueue queue(/*creation_time=*/Timestamp::Zero());
+ EXPECT_EQ(queue.LeadingPacketEnqueueTime(RtpPacketMediaType::kAudio),
+ Timestamp::MinusInfinity());
+ EXPECT_EQ(queue.LeadingPacketEnqueueTime(RtpPacketMediaType::kVideo),
+ Timestamp::MinusInfinity());
+
+ queue.Push(Timestamp::Millis(10),
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/1));
+ EXPECT_EQ(queue.LeadingPacketEnqueueTime(RtpPacketMediaType::kAudio),
+ Timestamp::MinusInfinity());
+ EXPECT_EQ(queue.LeadingPacketEnqueueTime(RtpPacketMediaType::kVideo),
+ Timestamp::Millis(10));
+
+ queue.Push(Timestamp::Millis(20),
+ CreatePacket(RtpPacketMediaType::kAudio, /*seq=*/2));
+
+ EXPECT_EQ(queue.LeadingPacketEnqueueTime(RtpPacketMediaType::kAudio),
+ Timestamp::Millis(20));
+ EXPECT_EQ(queue.LeadingPacketEnqueueTime(RtpPacketMediaType::kVideo),
+ Timestamp::Millis(10));
+
+ queue.Pop(); // Pop audio packet.
+ EXPECT_EQ(queue.LeadingPacketEnqueueTime(RtpPacketMediaType::kAudio),
+ Timestamp::MinusInfinity());
+ EXPECT_EQ(queue.LeadingPacketEnqueueTime(RtpPacketMediaType::kVideo),
+ Timestamp::Millis(10));
+
+ queue.Pop(); // Pop video packet.
+ EXPECT_EQ(queue.LeadingPacketEnqueueTime(RtpPacketMediaType::kAudio),
+ Timestamp::MinusInfinity());
+ EXPECT_EQ(queue.LeadingPacketEnqueueTime(RtpPacketMediaType::kVideo),
+ Timestamp::MinusInfinity());
+}
+
+TEST(PrioritizedPacketQueue,
+ PushAndPopUpdatesSizeInPacketsPerRtpPacketMediaType) {
+ Timestamp now = Timestamp::Zero();
+ PrioritizedPacketQueue queue(now);
+
+ // Initially all sizes are zero.
+ for (size_t i = 0; i < kNumMediaTypes; ++i) {
+ EXPECT_EQ(queue.SizeInPacketsPerRtpPacketMediaType()[i], 0);
+ }
+
+ // Push packets.
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kAudio, 1));
+ EXPECT_EQ(queue.SizeInPacketsPerRtpPacketMediaType()[static_cast<size_t>(
+ RtpPacketMediaType::kAudio)],
+ 1);
+
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kVideo, 2));
+ EXPECT_EQ(queue.SizeInPacketsPerRtpPacketMediaType()[static_cast<size_t>(
+ RtpPacketMediaType::kVideo)],
+ 1);
+
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kRetransmission, 3));
+ EXPECT_EQ(queue.SizeInPacketsPerRtpPacketMediaType()[static_cast<size_t>(
+ RtpPacketMediaType::kRetransmission)],
+ 1);
+
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kForwardErrorCorrection, 4));
+ EXPECT_EQ(queue.SizeInPacketsPerRtpPacketMediaType()[static_cast<size_t>(
+ RtpPacketMediaType::kForwardErrorCorrection)],
+ 1);
+
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kPadding, 5));
+ EXPECT_EQ(queue.SizeInPacketsPerRtpPacketMediaType()[static_cast<size_t>(
+ RtpPacketMediaType::kPadding)],
+ 1);
+
+ // Now all sizes are 1.
+ for (size_t i = 0; i < kNumMediaTypes; ++i) {
+ EXPECT_EQ(queue.SizeInPacketsPerRtpPacketMediaType()[i], 1);
+ }
+
+ // Popping happens in a priority order based on media type. This test does not
+ // assert what this order is, only that the counter for the popped packet's
+ // media type is decremented.
+ for (size_t i = 0; i < kNumMediaTypes; ++i) {
+ auto popped_packet = queue.Pop();
+ EXPECT_EQ(queue.SizeInPacketsPerRtpPacketMediaType()[static_cast<size_t>(
+ popped_packet->packet_type().value())],
+ 0);
+ }
+
+ // We've popped all packets, so all sizes are zero.
+ for (size_t i = 0; i < kNumMediaTypes; ++i) {
+ EXPECT_EQ(queue.SizeInPacketsPerRtpPacketMediaType()[i], 0);
+ }
+}
+
+TEST(PrioritizedPacketQueue, ClearsPackets) {
+ Timestamp now = Timestamp::Zero();
+ PrioritizedPacketQueue queue(now);
+ const uint32_t kSsrc = 1;
+
+ // Add two packets of each type, all using the same SSRC.
+ int sequence_number = 0;
+ for (size_t i = 0; i < kNumMediaTypes; ++i) {
+ queue.Push(now, CreatePacket(static_cast<RtpPacketMediaType>(i),
+ sequence_number++, kSsrc));
+ queue.Push(now, CreatePacket(static_cast<RtpPacketMediaType>(i),
+ sequence_number++, kSsrc));
+ }
+ EXPECT_EQ(queue.SizeInPackets(), 2 * int{kNumMediaTypes});
+
+ // Remove all of them.
+ queue.RemovePacketsForSsrc(kSsrc);
+ EXPECT_TRUE(queue.Empty());
+}
+
+TEST(PrioritizedPacketQueue, ClearPacketsAffectsOnlySpecifiedSsrc) {
+ Timestamp now = Timestamp::Zero();
+ PrioritizedPacketQueue queue(now);
+ const uint32_t kRemovingSsrc = 1;
+ const uint32_t kStayingSsrc = 2;
+
+ // Add an audio packet and a retransmission for the SSRC we will remove,
+ // ensuring they are first in line.
+ queue.Push(
+ now, CreatePacket(RtpPacketMediaType::kAudio, /*seq=*/1, kRemovingSsrc));
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kRetransmission, /*seq=*/2,
+ kRemovingSsrc));
+
+ // Add a video packet and a retransmission for the SSRC that will remain.
+ // The retransmission packets now both have pointers to their respective qeues
+ // from the same prio level.
+ queue.Push(now,
+ CreatePacket(RtpPacketMediaType::kVideo, /*seq=*/3, kStayingSsrc));
+ queue.Push(now, CreatePacket(RtpPacketMediaType::kRetransmission, /*seq=*/4,
+ kStayingSsrc));
+
+ EXPECT_EQ(queue.SizeInPackets(), 4);
+
+ // Clear the first two packets.
+ queue.RemovePacketsForSsrc(kRemovingSsrc);
+ EXPECT_EQ(queue.SizeInPackets(), 2);
+
+ // We should get the single remaining retransmission first, then the video
+ // packet.
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 4);
+ EXPECT_EQ(queue.Pop()->SequenceNumber(), 3);
+ EXPECT_TRUE(queue.Empty());
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/rtp_packet_pacer.h b/third_party/libwebrtc/modules/pacing/rtp_packet_pacer.h
new file mode 100644
index 0000000000..e2cf806385
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/rtp_packet_pacer.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019 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_PACING_RTP_PACKET_PACER_H_
+#define MODULES_PACING_RTP_PACKET_PACER_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "absl/types/optional.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/rtp_rtcp/include/rtp_packet_sender.h"
+
+namespace webrtc {
+
+class RtpPacketPacer {
+ public:
+ virtual ~RtpPacketPacer() = default;
+
+ virtual void CreateProbeClusters(
+ std::vector<ProbeClusterConfig> probe_cluster_configs) = 0;
+
+ // Temporarily pause all sending.
+ virtual void Pause() = 0;
+
+ // Resume sending packets.
+ virtual void Resume() = 0;
+
+ virtual void SetCongested(bool congested) = 0;
+
+ // Sets the pacing rates. Must be called once before packets can be sent.
+ virtual void SetPacingRates(DataRate pacing_rate, DataRate padding_rate) = 0;
+
+ // Time since the oldest packet currently in the queue was added.
+ virtual TimeDelta OldestPacketWaitTime() const = 0;
+
+ // Sum of payload + padding bytes of all packets currently in the pacer queue.
+ virtual DataSize QueueSizeData() const = 0;
+
+ // Returns the time when the first packet was sent.
+ virtual absl::optional<Timestamp> FirstSentPacketTime() const = 0;
+
+ // Returns the expected number of milliseconds it will take to send the
+ // current packets in the queue, given the current size and bitrate, ignoring
+ // priority.
+ virtual TimeDelta ExpectedQueueTime() const = 0;
+
+ // Set the average upper bound on pacer queuing delay. The pacer may send at
+ // a higher rate than what was configured via SetPacingRates() in order to
+ // keep ExpectedQueueTimeMs() below `limit_ms` on average.
+ virtual void SetQueueTimeLimit(TimeDelta limit) = 0;
+
+ // Currently audio traffic is not accounted by pacer and passed through.
+ // With the introduction of audio BWE audio traffic will be accounted for
+ // the pacer budget calculation. The audio traffic still will be injected
+ // at high priority.
+ virtual void SetAccountForAudioPackets(bool account_for_audio) = 0;
+ virtual void SetIncludeOverhead() = 0;
+ virtual void SetTransportOverhead(DataSize overhead_per_packet) = 0;
+};
+
+} // namespace webrtc
+#endif // MODULES_PACING_RTP_PACKET_PACER_H_
diff --git a/third_party/libwebrtc/modules/pacing/task_queue_paced_sender.cc b/third_party/libwebrtc/modules/pacing/task_queue_paced_sender.cc
new file mode 100644
index 0000000000..4ba249582c
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/task_queue_paced_sender.cc
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2019 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/pacing/task_queue_paced_sender.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "api/task_queue/pending_task_safety_flag.h"
+#include "api/transport/network_types.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/experiments/field_trial_parser.h"
+#include "rtc_base/experiments/field_trial_units.h"
+#include "rtc_base/system/unused.h"
+#include "rtc_base/trace_event.h"
+
+namespace webrtc {
+
+namespace {
+
+constexpr const char* kBurstyPacerFieldTrial = "WebRTC-BurstyPacer";
+
+constexpr const char* kSlackedTaskQueuePacedSenderFieldTrial =
+ "WebRTC-SlackedTaskQueuePacedSender";
+
+} // namespace
+
+const int TaskQueuePacedSender::kNoPacketHoldback = -1;
+
+TaskQueuePacedSender::BurstyPacerFlags::BurstyPacerFlags(
+ const FieldTrialsView& field_trials)
+ : burst("burst") {
+ ParseFieldTrial({&burst}, field_trials.Lookup(kBurstyPacerFieldTrial));
+}
+
+TaskQueuePacedSender::SlackedPacerFlags::SlackedPacerFlags(
+ const FieldTrialsView& field_trials)
+ : allow_low_precision("Enabled"),
+ max_low_precision_expected_queue_time("max_queue_time"),
+ send_burst_interval("send_burst_interval") {
+ ParseFieldTrial({&allow_low_precision, &max_low_precision_expected_queue_time,
+ &send_burst_interval},
+ field_trials.Lookup(kSlackedTaskQueuePacedSenderFieldTrial));
+}
+
+TaskQueuePacedSender::TaskQueuePacedSender(
+ Clock* clock,
+ PacingController::PacketSender* packet_sender,
+ const FieldTrialsView& field_trials,
+ TaskQueueFactory* task_queue_factory,
+ TimeDelta max_hold_back_window,
+ int max_hold_back_window_in_packets,
+ absl::optional<TimeDelta> burst_interval)
+ : clock_(clock),
+ bursty_pacer_flags_(field_trials),
+ slacked_pacer_flags_(field_trials),
+ max_hold_back_window_(slacked_pacer_flags_.allow_low_precision
+ ? PacingController::kMinSleepTime
+ : max_hold_back_window),
+ max_hold_back_window_in_packets_(slacked_pacer_flags_.allow_low_precision
+ ? 0
+ : max_hold_back_window_in_packets),
+ pacing_controller_(clock, packet_sender, field_trials),
+ next_process_time_(Timestamp::MinusInfinity()),
+ is_started_(false),
+ is_shutdown_(false),
+ packet_size_(/*alpha=*/0.95),
+ include_overhead_(false),
+ task_queue_(field_trials, "TaskQueuePacedSender", task_queue_factory) {
+ RTC_DCHECK_GE(max_hold_back_window_, PacingController::kMinSleepTime);
+ // There are multiple field trials that can affect burst. If multiple bursts
+ // are specified we pick the largest of the values.
+ absl::optional<TimeDelta> burst = bursty_pacer_flags_.burst.GetOptional();
+ if (slacked_pacer_flags_.allow_low_precision &&
+ slacked_pacer_flags_.send_burst_interval) {
+ TimeDelta slacked_burst = slacked_pacer_flags_.send_burst_interval.Value();
+ if (!burst.has_value() || burst.value() < slacked_burst) {
+ burst = slacked_burst;
+ }
+ }
+ // If not overriden by an experiment, the burst is specified by the
+ // `burst_interval` argument.
+ if (!burst.has_value()) {
+ burst = burst_interval;
+ }
+ if (burst.has_value()) {
+ pacing_controller_.SetSendBurstInterval(burst.value());
+ }
+}
+
+TaskQueuePacedSender::~TaskQueuePacedSender() {
+ // Post an immediate task to mark the queue as shutting down.
+ // The rtc::TaskQueue destructor will wait for pending tasks to
+ // complete before continuing.
+ task_queue_.RunOrPost([&]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ is_shutdown_ = true;
+ });
+}
+
+void TaskQueuePacedSender::EnsureStarted() {
+ task_queue_.RunOrPost([this]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ is_started_ = true;
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ });
+}
+
+void TaskQueuePacedSender::CreateProbeClusters(
+ std::vector<ProbeClusterConfig> probe_cluster_configs) {
+ task_queue_.RunOrPost(
+ [this, probe_cluster_configs = std::move(probe_cluster_configs)]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ pacing_controller_.CreateProbeClusters(probe_cluster_configs);
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ });
+}
+
+void TaskQueuePacedSender::Pause() {
+ task_queue_.RunOrPost([this]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ pacing_controller_.Pause();
+ });
+}
+
+void TaskQueuePacedSender::Resume() {
+ task_queue_.RunOrPost([this]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ pacing_controller_.Resume();
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ });
+}
+
+void TaskQueuePacedSender::SetCongested(bool congested) {
+ task_queue_.RunOrPost([this, congested]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ pacing_controller_.SetCongested(congested);
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ });
+}
+
+void TaskQueuePacedSender::SetPacingRates(DataRate pacing_rate,
+ DataRate padding_rate) {
+ task_queue_.RunOrPost([this, pacing_rate, padding_rate]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ pacing_controller_.SetPacingRates(pacing_rate, padding_rate);
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ });
+}
+
+void TaskQueuePacedSender::EnqueuePackets(
+ std::vector<std::unique_ptr<RtpPacketToSend>> packets) {
+ task_queue_.TaskQueueForPost()->PostTask(task_queue_.MaybeSafeTask(
+ safety_.flag(), [this, packets = std::move(packets)]() mutable {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("webrtc"),
+ "TaskQueuePacedSender::EnqueuePackets");
+ for (auto& packet : packets) {
+ TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("webrtc"),
+ "TaskQueuePacedSender::EnqueuePackets::Loop",
+ "sequence_number", packet->SequenceNumber(),
+ "rtp_timestamp", packet->Timestamp());
+
+ size_t packet_size = packet->payload_size() + packet->padding_size();
+ if (include_overhead_) {
+ packet_size += packet->headers_size();
+ }
+ packet_size_.Apply(1, packet_size);
+ RTC_DCHECK_GE(packet->capture_time(), Timestamp::Zero());
+ pacing_controller_.EnqueuePacket(std::move(packet));
+ }
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ }));
+}
+
+void TaskQueuePacedSender::RemovePacketsForSsrc(uint32_t ssrc) {
+ task_queue_.RunOrPost([this, ssrc]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ pacing_controller_.RemovePacketsForSsrc(ssrc);
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ });
+}
+
+void TaskQueuePacedSender::SetAccountForAudioPackets(bool account_for_audio) {
+ task_queue_.RunOrPost([this, account_for_audio]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ pacing_controller_.SetAccountForAudioPackets(account_for_audio);
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ });
+}
+
+void TaskQueuePacedSender::SetIncludeOverhead() {
+ task_queue_.RunOrPost([this]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ include_overhead_ = true;
+ pacing_controller_.SetIncludeOverhead();
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ });
+}
+
+void TaskQueuePacedSender::SetTransportOverhead(DataSize overhead_per_packet) {
+ task_queue_.RunOrPost([this, overhead_per_packet]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ pacing_controller_.SetTransportOverhead(overhead_per_packet);
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ });
+}
+
+void TaskQueuePacedSender::SetQueueTimeLimit(TimeDelta limit) {
+ task_queue_.RunOrPost([this, limit]() {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+ pacing_controller_.SetQueueTimeLimit(limit);
+ MaybeProcessPackets(Timestamp::MinusInfinity());
+ });
+}
+
+TimeDelta TaskQueuePacedSender::ExpectedQueueTime() const {
+ return GetStats().expected_queue_time;
+}
+
+DataSize TaskQueuePacedSender::QueueSizeData() const {
+ return GetStats().queue_size;
+}
+
+absl::optional<Timestamp> TaskQueuePacedSender::FirstSentPacketTime() const {
+ return GetStats().first_sent_packet_time;
+}
+
+TimeDelta TaskQueuePacedSender::OldestPacketWaitTime() const {
+ Timestamp oldest_packet = GetStats().oldest_packet_enqueue_time;
+ if (oldest_packet.IsInfinite()) {
+ return TimeDelta::Zero();
+ }
+
+ // (webrtc:9716): The clock is not always monotonic.
+ Timestamp current = clock_->CurrentTime();
+ if (current < oldest_packet) {
+ return TimeDelta::Zero();
+ }
+
+ return current - oldest_packet;
+}
+
+void TaskQueuePacedSender::OnStatsUpdated(const Stats& stats) {
+ MutexLock lock(&stats_mutex_);
+ current_stats_ = stats;
+}
+
+void TaskQueuePacedSender::MaybeProcessPackets(
+ Timestamp scheduled_process_time) {
+ RTC_DCHECK_RUN_ON(&task_queue_);
+
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("webrtc"),
+ "TaskQueuePacedSender::MaybeProcessPackets");
+
+ if (is_shutdown_ || !is_started_) {
+ return;
+ }
+
+ Timestamp next_send_time = pacing_controller_.NextSendTime();
+ RTC_DCHECK(next_send_time.IsFinite());
+ const Timestamp now = clock_->CurrentTime();
+ TimeDelta early_execute_margin =
+ pacing_controller_.IsProbing()
+ ? PacingController::kMaxEarlyProbeProcessing
+ : TimeDelta::Zero();
+
+ // Process packets and update stats.
+ while (next_send_time <= now + early_execute_margin) {
+ pacing_controller_.ProcessPackets();
+ next_send_time = pacing_controller_.NextSendTime();
+ RTC_DCHECK(next_send_time.IsFinite());
+
+ // Probing state could change. Get margin after process packets.
+ early_execute_margin = pacing_controller_.IsProbing()
+ ? PacingController::kMaxEarlyProbeProcessing
+ : TimeDelta::Zero();
+ }
+ UpdateStats();
+
+ // Ignore retired scheduled task, otherwise reset `next_process_time_`.
+ if (scheduled_process_time.IsFinite()) {
+ if (scheduled_process_time != next_process_time_) {
+ return;
+ }
+ next_process_time_ = Timestamp::MinusInfinity();
+ }
+
+ // Do not hold back in probing.
+ TimeDelta hold_back_window = TimeDelta::Zero();
+ if (!pacing_controller_.IsProbing()) {
+ hold_back_window = max_hold_back_window_;
+ DataRate pacing_rate = pacing_controller_.pacing_rate();
+ if (max_hold_back_window_in_packets_ != kNoPacketHoldback &&
+ !pacing_rate.IsZero() &&
+ packet_size_.filtered() != rtc::ExpFilter::kValueUndefined) {
+ TimeDelta avg_packet_send_time =
+ DataSize::Bytes(packet_size_.filtered()) / pacing_rate;
+ hold_back_window =
+ std::min(hold_back_window,
+ avg_packet_send_time * max_hold_back_window_in_packets_);
+ }
+ }
+
+ // Calculate next process time.
+ TimeDelta time_to_next_process =
+ std::max(hold_back_window, next_send_time - now - early_execute_margin);
+ next_send_time = now + time_to_next_process;
+
+ // If no in flight task or in flight task is later than `next_send_time`,
+ // schedule a new one. Previous in flight task will be retired.
+ if (next_process_time_.IsMinusInfinity() ||
+ next_process_time_ > next_send_time) {
+ // Prefer low precision if allowed and not probing.
+ TaskQueueBase::DelayPrecision precision =
+ slacked_pacer_flags_.allow_low_precision &&
+ !pacing_controller_.IsProbing()
+ ? TaskQueueBase::DelayPrecision::kLow
+ : TaskQueueBase::DelayPrecision::kHigh;
+ // Check for cases where we need high precision.
+ if (precision == TaskQueueBase::DelayPrecision::kLow) {
+ auto& packets_per_type =
+ pacing_controller_.SizeInPacketsPerRtpPacketMediaType();
+ bool audio_or_retransmission_packets_in_queue =
+ packets_per_type[static_cast<size_t>(RtpPacketMediaType::kAudio)] >
+ 0 ||
+ packets_per_type[static_cast<size_t>(
+ RtpPacketMediaType::kRetransmission)] > 0;
+ bool queue_time_too_large =
+ slacked_pacer_flags_.max_low_precision_expected_queue_time &&
+ pacing_controller_.ExpectedQueueTime() >=
+ slacked_pacer_flags_.max_low_precision_expected_queue_time
+ .Value();
+ if (audio_or_retransmission_packets_in_queue || queue_time_too_large) {
+ precision = TaskQueueBase::DelayPrecision::kHigh;
+ }
+ }
+
+ task_queue_.TaskQueueForDelayedTasks()->PostDelayedTaskWithPrecision(
+ precision,
+ task_queue_.MaybeSafeTask(
+ safety_.flag(),
+ [this, next_send_time]() { MaybeProcessPackets(next_send_time); }),
+ time_to_next_process.RoundUpTo(TimeDelta::Millis(1)));
+ next_process_time_ = next_send_time;
+ }
+}
+
+void TaskQueuePacedSender::UpdateStats() {
+ Stats new_stats;
+ new_stats.expected_queue_time = pacing_controller_.ExpectedQueueTime();
+ new_stats.first_sent_packet_time = pacing_controller_.FirstSentPacketTime();
+ new_stats.oldest_packet_enqueue_time =
+ pacing_controller_.OldestPacketEnqueueTime();
+ new_stats.queue_size = pacing_controller_.QueueSizeData();
+ OnStatsUpdated(new_stats);
+}
+
+TaskQueuePacedSender::Stats TaskQueuePacedSender::GetStats() const {
+ MutexLock lock(&stats_mutex_);
+ return current_stats_;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/modules/pacing/task_queue_paced_sender.h b/third_party/libwebrtc/modules/pacing/task_queue_paced_sender.h
new file mode 100644
index 0000000000..ea335fd8e3
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/task_queue_paced_sender.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2019 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_PACING_TASK_QUEUE_PACED_SENDER_H_
+#define MODULES_PACING_TASK_QUEUE_PACED_SENDER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "api/field_trials_view.h"
+#include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_factory.h"
+#include "api/units/data_size.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "modules/pacing/pacing_controller.h"
+#include "modules/pacing/rtp_packet_pacer.h"
+#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
+#include "modules/utility/maybe_worker_thread.h"
+#include "rtc_base/experiments/field_trial_parser.h"
+#include "rtc_base/numerics/exp_filter.h"
+#include "rtc_base/thread_annotations.h"
+
+namespace webrtc {
+class Clock;
+
+class TaskQueuePacedSender : public RtpPacketPacer, public RtpPacketSender {
+ public:
+ static const int kNoPacketHoldback;
+
+ // The pacer can be configured using `field_trials` or specified parameters.
+ //
+ // The `hold_back_window` parameter sets a lower bound on time to sleep if
+ // there is currently a pacer queue and packets can't immediately be
+ // processed. Increasing this reduces thread wakeups at the expense of higher
+ // latency.
+ //
+ // If the `burst_interval` parameter is set, the pacer is allowed to build up
+ // a packet "debt" that correspond to approximately the send rate during the
+ // specified interval. This greatly reduced wake ups by not pacing packets
+ // within the allowed burst budget.
+ TaskQueuePacedSender(
+ Clock* clock,
+ PacingController::PacketSender* packet_sender,
+ const FieldTrialsView& field_trials,
+ TaskQueueFactory* task_queue_factory,
+ TimeDelta max_hold_back_window,
+ int max_hold_back_window_in_packets,
+ absl::optional<TimeDelta> burst_interval = absl::nullopt);
+
+ ~TaskQueuePacedSender() override;
+
+ // Ensure that necessary delayed tasks are scheduled.
+ void EnsureStarted();
+
+ // Methods implementing RtpPacketSender.
+
+ // Adds the packet to the queue and calls
+ // PacingController::PacketSender::SendPacket() when it's time to send.
+ void EnqueuePackets(
+ std::vector<std::unique_ptr<RtpPacketToSend>> packets) override;
+ // Remove any pending packets matching this SSRC from the packet queue.
+ void RemovePacketsForSsrc(uint32_t ssrc) override;
+
+ // Methods implementing RtpPacketPacer.
+
+ void CreateProbeClusters(
+ std::vector<ProbeClusterConfig> probe_cluster_configs) override;
+
+ // Temporarily pause all sending.
+ void Pause() override;
+
+ // Resume sending packets.
+ void Resume() override;
+
+ void SetCongested(bool congested) override;
+
+ // Sets the pacing rates. Must be called once before packets can be sent.
+ void SetPacingRates(DataRate pacing_rate, DataRate padding_rate) override;
+
+ // Currently audio traffic is not accounted for by pacer and passed through.
+ // With the introduction of audio BWE, audio traffic will be accounted for
+ // in the pacer budget calculation. The audio traffic will still be injected
+ // at high priority.
+ void SetAccountForAudioPackets(bool account_for_audio) override;
+
+ void SetIncludeOverhead() override;
+ void SetTransportOverhead(DataSize overhead_per_packet) override;
+
+ // Returns the time since the oldest queued packet was enqueued.
+ TimeDelta OldestPacketWaitTime() const override;
+
+ // Returns total size of all packets in the pacer queue.
+ DataSize QueueSizeData() const override;
+
+ // Returns the time when the first packet was sent;
+ absl::optional<Timestamp> FirstSentPacketTime() const override;
+
+ // Returns the number of milliseconds it will take to send the current
+ // packets in the queue, given the current size and bitrate, ignoring prio.
+ TimeDelta ExpectedQueueTime() const override;
+
+ // Set the max desired queuing delay, pacer will override the pacing rate
+ // specified by SetPacingRates() if needed to achieve this goal.
+ void SetQueueTimeLimit(TimeDelta limit) override;
+
+ protected:
+ // Exposed as protected for test.
+ struct Stats {
+ Stats()
+ : oldest_packet_enqueue_time(Timestamp::MinusInfinity()),
+ queue_size(DataSize::Zero()),
+ expected_queue_time(TimeDelta::Zero()) {}
+ Timestamp oldest_packet_enqueue_time;
+ DataSize queue_size;
+ TimeDelta expected_queue_time;
+ absl::optional<Timestamp> first_sent_packet_time;
+ };
+ void OnStatsUpdated(const Stats& stats);
+
+ private:
+ // Check if it is time to send packets, or schedule a delayed task if not.
+ // Use Timestamp::MinusInfinity() to indicate that this call has _not_
+ // been scheduled by the pacing controller. If this is the case, check if
+ // can execute immediately otherwise schedule a delay task that calls this
+ // method again with desired (finite) scheduled process time.
+ void MaybeProcessPackets(Timestamp scheduled_process_time);
+
+ void UpdateStats() RTC_RUN_ON(task_queue_);
+ Stats GetStats() const;
+
+ Clock* const clock_;
+ struct BurstyPacerFlags {
+ // Parses `kBurstyPacerFieldTrial`. Example:
+ // --force-fieldtrials=WebRTC-BurstyPacer/burst:20ms/
+ explicit BurstyPacerFlags(const FieldTrialsView& field_trials);
+ // If set, the pacer is allowed to build up a packet "debt" that correspond
+ // to approximately the send rate during the specified interval.
+ FieldTrialOptional<TimeDelta> burst;
+ };
+ const BurstyPacerFlags bursty_pacer_flags_;
+ struct SlackedPacerFlags {
+ // Parses `kSlackedTaskQueuePacedSenderFieldTrial`. Example:
+ // --force-fieldtrials=WebRTC-SlackedTaskQueuePacedSender/Enabled,max_queue_time:75ms/
+ explicit SlackedPacerFlags(const FieldTrialsView& field_trials);
+ // When "Enabled", delayed tasks invoking MaybeProcessPackets() are
+ // scheduled using low precision instead of high precision, resulting in
+ // less idle wake ups and packets being sent in bursts if the `task_queue_`
+ // implementation supports slack. When probing, high precision is used
+ // regardless to ensure good bandwidth estimation.
+ FieldTrialFlag allow_low_precision;
+ // Controlled via the "max_queue_time" experiment argument. If set, uses
+ // high precision scheduling of MaybeProcessPackets() whenever the expected
+ // queue time is greater than or equal to this value.
+ FieldTrialOptional<TimeDelta> max_low_precision_expected_queue_time;
+ // Controlled via "send_burst_interval" experiment argument. If set, the
+ // pacer is allowed to build up a packet "debt" that correspond to
+ // approximately the send rate during the specified interval.
+ FieldTrialOptional<TimeDelta> send_burst_interval;
+ };
+ const SlackedPacerFlags slacked_pacer_flags_;
+ // The holdback window prevents too frequent delayed MaybeProcessPackets()
+ // calls. These are only applicable if `allow_low_precision` is false.
+ const TimeDelta max_hold_back_window_;
+ const int max_hold_back_window_in_packets_;
+
+ PacingController pacing_controller_ RTC_GUARDED_BY(task_queue_);
+
+ // We want only one (valid) delayed process task in flight at a time.
+ // If the value of `next_process_time_` is finite, it is an id for a
+ // delayed task that will call MaybeProcessPackets() with that time
+ // as parameter.
+ // Timestamp::MinusInfinity() indicates no valid pending task.
+ Timestamp next_process_time_ RTC_GUARDED_BY(task_queue_);
+
+ // Indicates if this task queue is started. If not, don't allow
+ // posting delayed tasks yet.
+ bool is_started_ RTC_GUARDED_BY(task_queue_);
+
+ // Indicates if this task queue is shutting down. If so, don't allow
+ // posting any more delayed tasks as that can cause the task queue to
+ // never drain.
+ bool is_shutdown_ RTC_GUARDED_BY(task_queue_);
+
+ // Filtered size of enqueued packets, in bytes.
+ rtc::ExpFilter packet_size_ RTC_GUARDED_BY(task_queue_);
+ bool include_overhead_ RTC_GUARDED_BY(task_queue_);
+
+ // TODO(webrtc:14502): Remove stats_mutex_ when pacer runs on the worker
+ // thread.
+ mutable Mutex stats_mutex_;
+ Stats current_stats_ RTC_GUARDED_BY(stats_mutex_);
+
+ ScopedTaskSafety safety_;
+ MaybeWorkerThread task_queue_;
+};
+} // namespace webrtc
+#endif // MODULES_PACING_TASK_QUEUE_PACED_SENDER_H_
diff --git a/third_party/libwebrtc/modules/pacing/task_queue_paced_sender_unittest.cc b/third_party/libwebrtc/modules/pacing/task_queue_paced_sender_unittest.cc
new file mode 100644
index 0000000000..69c7b9b7ef
--- /dev/null
+++ b/third_party/libwebrtc/modules/pacing/task_queue_paced_sender_unittest.cc
@@ -0,0 +1,913 @@
+/*
+ * Copyright (c) 2019 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/pacing/task_queue_paced_sender.h"
+
+#include <algorithm>
+#include <atomic>
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/functional/any_invocable.h"
+#include "api/task_queue/task_queue_base.h"
+#include "api/transport/network_types.h"
+#include "api/units/data_rate.h"
+#include "modules/pacing/packet_router.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/scoped_key_value_config.h"
+#include "test/time_controller/simulated_time_controller.h"
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::Return;
+using ::testing::SaveArg;
+
+namespace webrtc {
+namespace {
+constexpr uint32_t kAudioSsrc = 12345;
+constexpr uint32_t kVideoSsrc = 234565;
+constexpr uint32_t kVideoRtxSsrc = 34567;
+constexpr uint32_t kFlexFecSsrc = 45678;
+constexpr size_t kDefaultPacketSize = 1234;
+
+class MockPacketRouter : public PacketRouter {
+ public:
+ MOCK_METHOD(void,
+ SendPacket,
+ (std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info),
+ (override));
+ MOCK_METHOD(std::vector<std::unique_ptr<RtpPacketToSend>>,
+ FetchFec,
+ (),
+ (override));
+ MOCK_METHOD(std::vector<std::unique_ptr<RtpPacketToSend>>,
+ GeneratePadding,
+ (DataSize target_size),
+ (override));
+};
+
+std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding(
+ DataSize target_size) {
+ // 224 bytes is the max padding size for plain padding packets generated by
+ // RTPSender::GeneratePadding().
+ const DataSize kMaxPaddingPacketSize = DataSize::Bytes(224);
+ DataSize padding_generated = DataSize::Zero();
+ std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets;
+ while (padding_generated < target_size) {
+ DataSize packet_size =
+ std::min(target_size - padding_generated, kMaxPaddingPacketSize);
+ padding_generated += packet_size;
+ auto padding_packet =
+ std::make_unique<RtpPacketToSend>(/*extensions=*/nullptr);
+ padding_packet->set_packet_type(RtpPacketMediaType::kPadding);
+ padding_packet->SetPadding(packet_size.bytes());
+ padding_packets.push_back(std::move(padding_packet));
+ }
+ return padding_packets;
+}
+
+class TaskQueueWithFakePrecisionFactory : public TaskQueueFactory {
+ public:
+ explicit TaskQueueWithFakePrecisionFactory(
+ TaskQueueFactory* task_queue_factory)
+ : task_queue_factory_(task_queue_factory) {}
+
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue(
+ absl::string_view name,
+ Priority priority) const override {
+ return std::unique_ptr<TaskQueueBase, TaskQueueDeleter>(
+ new TaskQueueWithFakePrecision(
+ const_cast<TaskQueueWithFakePrecisionFactory*>(this),
+ task_queue_factory_));
+ }
+
+ int delayed_low_precision_count() const {
+ return delayed_low_precision_count_;
+ }
+ int delayed_high_precision_count() const {
+ return delayed_high_precision_count_;
+ }
+
+ private:
+ friend class TaskQueueWithFakePrecision;
+
+ class TaskQueueWithFakePrecision : public TaskQueueBase {
+ public:
+ TaskQueueWithFakePrecision(
+ TaskQueueWithFakePrecisionFactory* parent_factory,
+ TaskQueueFactory* task_queue_factory)
+ : parent_factory_(parent_factory),
+ task_queue_(task_queue_factory->CreateTaskQueue(
+ "TaskQueueWithFakePrecision",
+ TaskQueueFactory::Priority::NORMAL)) {}
+ ~TaskQueueWithFakePrecision() override {}
+
+ void Delete() override {
+ // `task_queue_->Delete()` is implicitly called in the destructor due to
+ // TaskQueueDeleter.
+ delete this;
+ }
+ void PostTask(absl::AnyInvocable<void() &&> task) override {
+ task_queue_->PostTask(WrapTask(std::move(task)));
+ }
+ void PostDelayedTask(absl::AnyInvocable<void() &&> task,
+ TimeDelta delay) override {
+ ++parent_factory_->delayed_low_precision_count_;
+ task_queue_->PostDelayedTask(WrapTask(std::move(task)), delay);
+ }
+ void PostDelayedHighPrecisionTask(absl::AnyInvocable<void() &&> task,
+ TimeDelta delay) override {
+ ++parent_factory_->delayed_high_precision_count_;
+ task_queue_->PostDelayedHighPrecisionTask(WrapTask(std::move(task)),
+ delay);
+ }
+
+ private:
+ absl::AnyInvocable<void() &&> WrapTask(absl::AnyInvocable<void() &&> task) {
+ return [this, task = std::move(task)]() mutable {
+ CurrentTaskQueueSetter set_current(this);
+ std::move(task)();
+ };
+ }
+
+ TaskQueueWithFakePrecisionFactory* parent_factory_;
+ std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue_;
+ };
+
+ TaskQueueFactory* task_queue_factory_;
+ std::atomic<int> delayed_low_precision_count_ = 0u;
+ std::atomic<int> delayed_high_precision_count_ = 0u;
+};
+
+} // namespace
+
+namespace test {
+
+std::unique_ptr<RtpPacketToSend> BuildRtpPacket(RtpPacketMediaType type) {
+ auto packet = std::make_unique<RtpPacketToSend>(nullptr);
+ packet->set_packet_type(type);
+ switch (type) {
+ case RtpPacketMediaType::kAudio:
+ packet->SetSsrc(kAudioSsrc);
+ break;
+ case RtpPacketMediaType::kVideo:
+ packet->SetSsrc(kVideoSsrc);
+ break;
+ case RtpPacketMediaType::kRetransmission:
+ case RtpPacketMediaType::kPadding:
+ packet->SetSsrc(kVideoRtxSsrc);
+ break;
+ case RtpPacketMediaType::kForwardErrorCorrection:
+ packet->SetSsrc(kFlexFecSsrc);
+ break;
+ }
+
+ packet->SetPayloadSize(kDefaultPacketSize);
+ return packet;
+}
+
+std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePackets(
+ RtpPacketMediaType type,
+ size_t num_packets) {
+ std::vector<std::unique_ptr<RtpPacketToSend>> packets;
+ for (size_t i = 0; i < num_packets; ++i) {
+ packets.push_back(BuildRtpPacket(type));
+ }
+ return packets;
+}
+
+constexpr char kSendPacketOnWorkerThreadFieldTrial[] =
+ "WebRTC-SendPacketsOnWorkerThread/Enabled/";
+
+std::vector<std::string> ParameterizedFieldTrials() {
+ return {{""}, {kSendPacketOnWorkerThreadFieldTrial}};
+}
+
+bool UsingWorkerThread(absl::string_view field_trials) {
+ return field_trials.find(kSendPacketOnWorkerThreadFieldTrial) !=
+ std::string::npos;
+}
+
+class TaskQueuePacedSenderTest
+ : public ::testing::TestWithParam<std::string /*field_trials*/> {};
+
+INSTANTIATE_TEST_SUITE_P(TaskQueuePacedSenderTest,
+ TaskQueuePacedSenderTest,
+ testing::ValuesIn(ParameterizedFieldTrials()),
+ [](const testing::TestParamInfo<std::string>& info) {
+ return UsingWorkerThread(info.param) ? "UsingWt"
+ : "OwnedTQ";
+ });
+
+TEST_P(TaskQueuePacedSenderTest, PacesPackets) {
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ ScopedKeyValueConfig trials(GetParam());
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ PacingController::kMinSleepTime,
+ TaskQueuePacedSender::kNoPacketHoldback);
+
+ // Insert a number of packets, covering one second.
+ static constexpr size_t kPacketsToSend = 42;
+ SequenceChecker sequence_checker;
+ pacer.SetPacingRates(
+ DataRate::BitsPerSec(kDefaultPacketSize * 8 * kPacketsToSend),
+ DataRate::Zero());
+ pacer.EnsureStarted();
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kPacketsToSend));
+
+ // Expect all of them to be sent.
+ size_t packets_sent = 0;
+ Timestamp end_time = Timestamp::PlusInfinity();
+ EXPECT_CALL(packet_router, SendPacket)
+ .WillRepeatedly([&](std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) {
+ ++packets_sent;
+ if (packets_sent == kPacketsToSend) {
+ end_time = time_controller.GetClock()->CurrentTime();
+ }
+ EXPECT_EQ(sequence_checker.IsCurrent(), UsingWorkerThread(GetParam()));
+ });
+
+ const Timestamp start_time = time_controller.GetClock()->CurrentTime();
+
+ // Packets should be sent over a period of close to 1s. Expect a little
+ // lower than this since initial probing is a bit quicker.
+ time_controller.AdvanceTime(TimeDelta::Seconds(1));
+ EXPECT_EQ(packets_sent, kPacketsToSend);
+ ASSERT_TRUE(end_time.IsFinite());
+ EXPECT_NEAR((end_time - start_time).ms<double>(), 1000.0, 50.0);
+}
+
+// Same test as above, but with 0.5s of burst applied.
+TEST_P(TaskQueuePacedSenderTest, PacesPacketsWithBurst) {
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ ScopedKeyValueConfig trials(GetParam());
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ PacingController::kMinSleepTime,
+ TaskQueuePacedSender::kNoPacketHoldback,
+ // Half a second of bursting.
+ TimeDelta::Seconds(0.5));
+
+ // Insert a number of packets, covering one second.
+ static constexpr size_t kPacketsToSend = 42;
+ SequenceChecker sequence_checker;
+ pacer.SetPacingRates(
+ DataRate::BitsPerSec(kDefaultPacketSize * 8 * kPacketsToSend),
+ DataRate::Zero());
+ pacer.EnsureStarted();
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kPacketsToSend));
+
+ // Expect all of them to be sent.
+ size_t packets_sent = 0;
+ Timestamp end_time = Timestamp::PlusInfinity();
+ EXPECT_CALL(packet_router, SendPacket)
+ .WillRepeatedly([&](std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) {
+ ++packets_sent;
+ if (packets_sent == kPacketsToSend) {
+ end_time = time_controller.GetClock()->CurrentTime();
+ }
+ EXPECT_EQ(sequence_checker.IsCurrent(), UsingWorkerThread(GetParam()));
+ });
+
+ const Timestamp start_time = time_controller.GetClock()->CurrentTime();
+
+ // Packets should be sent over a period of close to 1s. Expect a little
+ // lower than this since initial probing is a bit quicker.
+ time_controller.AdvanceTime(TimeDelta::Seconds(1));
+ EXPECT_EQ(packets_sent, kPacketsToSend);
+ ASSERT_TRUE(end_time.IsFinite());
+ // Because of half a second of burst, what would normally have been paced over
+ // ~1 second now takes ~0.5 seconds.
+ EXPECT_NEAR((end_time - start_time).ms<double>(), 500.0, 50.0);
+}
+
+TEST_P(TaskQueuePacedSenderTest, ReschedulesProcessOnRateChange) {
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ ScopedKeyValueConfig trials(GetParam());
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ PacingController::kMinSleepTime,
+ TaskQueuePacedSender::kNoPacketHoldback);
+
+ // Insert a number of packets to be sent 200ms apart.
+ const size_t kPacketsPerSecond = 5;
+ const DataRate kPacingRate =
+ DataRate::BitsPerSec(kDefaultPacketSize * 8 * kPacketsPerSecond);
+ pacer.SetPacingRates(kPacingRate, DataRate::Zero());
+ pacer.EnsureStarted();
+
+ // Send some initial packets to be rid of any probes.
+ EXPECT_CALL(packet_router, SendPacket).Times(kPacketsPerSecond);
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kPacketsPerSecond));
+ time_controller.AdvanceTime(TimeDelta::Seconds(1));
+
+ // Insert three packets, and record send time of each of them.
+ // After the second packet is sent, double the send rate so we can
+ // check the third packets is sent after half the wait time.
+ Timestamp first_packet_time = Timestamp::MinusInfinity();
+ Timestamp second_packet_time = Timestamp::MinusInfinity();
+ Timestamp third_packet_time = Timestamp::MinusInfinity();
+
+ EXPECT_CALL(packet_router, SendPacket)
+ .Times(3)
+ .WillRepeatedly([&](std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) {
+ if (first_packet_time.IsInfinite()) {
+ first_packet_time = time_controller.GetClock()->CurrentTime();
+ } else if (second_packet_time.IsInfinite()) {
+ second_packet_time = time_controller.GetClock()->CurrentTime();
+ // Avoid invoke SetPacingRate in the context of sending a packet.
+ time_controller.GetMainThread()->PostTask(
+ [&] { pacer.SetPacingRates(2 * kPacingRate, DataRate::Zero()); });
+ } else {
+ third_packet_time = time_controller.GetClock()->CurrentTime();
+ }
+ });
+
+ pacer.EnqueuePackets(GeneratePackets(RtpPacketMediaType::kVideo, 3));
+ time_controller.AdvanceTime(TimeDelta::Millis(500));
+ ASSERT_TRUE(third_packet_time.IsFinite());
+ EXPECT_NEAR((second_packet_time - first_packet_time).ms<double>(), 200.0,
+ 1.0);
+ EXPECT_NEAR((third_packet_time - second_packet_time).ms<double>(), 100.0,
+ 1.0);
+}
+
+TEST_P(TaskQueuePacedSenderTest, SendsAudioImmediately) {
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ ScopedKeyValueConfig trials(GetParam());
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ PacingController::kMinSleepTime,
+ TaskQueuePacedSender::kNoPacketHoldback);
+
+ const DataRate kPacingDataRate = DataRate::KilobitsPerSec(125);
+ const DataSize kPacketSize = DataSize::Bytes(kDefaultPacketSize);
+ const TimeDelta kPacketPacingTime = kPacketSize / kPacingDataRate;
+
+ pacer.SetPacingRates(kPacingDataRate, DataRate::Zero());
+ pacer.EnsureStarted();
+
+ // Add some initial video packets, only one should be sent.
+ EXPECT_CALL(packet_router, SendPacket);
+ pacer.EnqueuePackets(GeneratePackets(RtpPacketMediaType::kVideo, 10));
+ time_controller.AdvanceTime(TimeDelta::Zero());
+ ::testing::Mock::VerifyAndClearExpectations(&packet_router);
+
+ // Advance time, but still before next packet should be sent.
+ time_controller.AdvanceTime(kPacketPacingTime / 2);
+
+ // Insert an audio packet, it should be sent immediately.
+ EXPECT_CALL(packet_router, SendPacket);
+ pacer.EnqueuePackets(GeneratePackets(RtpPacketMediaType::kAudio, 1));
+ time_controller.AdvanceTime(TimeDelta::Zero());
+ ::testing::Mock::VerifyAndClearExpectations(&packet_router);
+}
+
+TEST_P(TaskQueuePacedSenderTest, SleepsDuringCoalscingWindow) {
+ const TimeDelta kCoalescingWindow = TimeDelta::Millis(5);
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ ScopedKeyValueConfig trials(GetParam());
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ kCoalescingWindow,
+ TaskQueuePacedSender::kNoPacketHoldback);
+
+ // Set rates so one packet adds one ms of buffer level.
+ const DataSize kPacketSize = DataSize::Bytes(kDefaultPacketSize);
+ const TimeDelta kPacketPacingTime = TimeDelta::Millis(1);
+ const DataRate kPacingDataRate = kPacketSize / kPacketPacingTime;
+
+ pacer.SetPacingRates(kPacingDataRate, DataRate::Zero());
+ pacer.EnsureStarted();
+
+ // Add 10 packets. The first should be sent immediately since the buffers
+ // are clear.
+ EXPECT_CALL(packet_router, SendPacket);
+ pacer.EnqueuePackets(GeneratePackets(RtpPacketMediaType::kVideo, 10));
+ time_controller.AdvanceTime(TimeDelta::Zero());
+ ::testing::Mock::VerifyAndClearExpectations(&packet_router);
+
+ // Advance time to 1ms before the coalescing window ends. No packets should
+ // be sent.
+ EXPECT_CALL(packet_router, SendPacket).Times(0);
+ time_controller.AdvanceTime(kCoalescingWindow - TimeDelta::Millis(1));
+
+ // Advance time to where coalescing window ends. All packets that should
+ // have been sent up til now will be sent.
+ EXPECT_CALL(packet_router, SendPacket).Times(5);
+ time_controller.AdvanceTime(TimeDelta::Millis(1));
+ ::testing::Mock::VerifyAndClearExpectations(&packet_router);
+}
+
+TEST_P(TaskQueuePacedSenderTest, ProbingOverridesCoalescingWindow) {
+ const TimeDelta kCoalescingWindow = TimeDelta::Millis(5);
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ ScopedKeyValueConfig trials(GetParam());
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ kCoalescingWindow,
+ TaskQueuePacedSender::kNoPacketHoldback);
+
+ // Set rates so one packet adds one ms of buffer level.
+ const DataSize kPacketSize = DataSize::Bytes(kDefaultPacketSize);
+ const TimeDelta kPacketPacingTime = TimeDelta::Millis(1);
+ const DataRate kPacingDataRate = kPacketSize / kPacketPacingTime;
+
+ pacer.SetPacingRates(kPacingDataRate, DataRate::Zero());
+ pacer.EnsureStarted();
+
+ // Add 10 packets. The first should be sent immediately since the buffers
+ // are clear. This will also trigger the probe to start.
+ EXPECT_CALL(packet_router, SendPacket).Times(AtLeast(1));
+ pacer.CreateProbeClusters(
+ {{.at_time = time_controller.GetClock()->CurrentTime(),
+ .target_data_rate = kPacingDataRate * 2,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = 17}});
+ pacer.EnqueuePackets(GeneratePackets(RtpPacketMediaType::kVideo, 10));
+ time_controller.AdvanceTime(TimeDelta::Zero());
+ ::testing::Mock::VerifyAndClearExpectations(&packet_router);
+
+ // Advance time to 1ms before the coalescing window ends. Packets should be
+ // flying.
+ EXPECT_CALL(packet_router, SendPacket).Times(AtLeast(1));
+ time_controller.AdvanceTime(kCoalescingWindow - TimeDelta::Millis(1));
+}
+
+TEST_P(TaskQueuePacedSenderTest, SchedulesProbeAtSentTime) {
+ ScopedKeyValueConfig trials(
+ GetParam() + "WebRTC-Bwe-ProbingBehavior/min_probe_delta:1ms/");
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ PacingController::kMinSleepTime,
+ TaskQueuePacedSender::kNoPacketHoldback);
+
+ // Set rates so one packet adds 4ms of buffer level.
+ const DataSize kPacketSize = DataSize::Bytes(kDefaultPacketSize);
+ const TimeDelta kPacketPacingTime = TimeDelta::Millis(4);
+ const DataRate kPacingDataRate = kPacketSize / kPacketPacingTime;
+ pacer.SetPacingRates(kPacingDataRate, /*padding_rate=*/DataRate::Zero());
+ pacer.EnsureStarted();
+ EXPECT_CALL(packet_router, FetchFec).WillRepeatedly([]() {
+ return std::vector<std::unique_ptr<RtpPacketToSend>>();
+ });
+ EXPECT_CALL(packet_router, GeneratePadding(_))
+ .WillRepeatedly(
+ [](DataSize target_size) { return GeneratePadding(target_size); });
+
+ // Enqueue two packets, only the first is sent immediately and the next
+ // will be scheduled for sending in 4ms.
+ pacer.EnqueuePackets(GeneratePackets(RtpPacketMediaType::kVideo, 2));
+ const int kNotAProbe = PacedPacketInfo::kNotAProbe;
+ EXPECT_CALL(packet_router,
+ SendPacket(_, ::testing::Field(&PacedPacketInfo::probe_cluster_id,
+ kNotAProbe)));
+ // Advance to less than 3ms before next packet send time.
+ time_controller.AdvanceTime(TimeDelta::Micros(1001));
+
+ // Trigger a probe at 2x the current pacing rate and insert the number of
+ // packets the probe needs.
+ const DataRate kProbeRate = 2 * kPacingDataRate;
+ const int kProbeClusterId = 1;
+ pacer.CreateProbeClusters(
+ {{.at_time = time_controller.GetClock()->CurrentTime(),
+ .target_data_rate = kProbeRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 4,
+ .id = kProbeClusterId}});
+
+ // Expected size for each probe in a cluster is twice the expected bits sent
+ // during min_probe_delta.
+ // Expect one additional call since probe always starts with a small (1 byte)
+ // padding packet that's not counted into the probe rate here.
+ const TimeDelta kProbeTimeDelta = TimeDelta::Millis(2);
+ const DataSize kProbeSize = kProbeRate * kProbeTimeDelta;
+ const size_t kNumPacketsInProbe =
+ (kProbeSize + kPacketSize - DataSize::Bytes(1)) / kPacketSize;
+ EXPECT_CALL(packet_router,
+ SendPacket(_, ::testing::Field(&PacedPacketInfo::probe_cluster_id,
+ kProbeClusterId)))
+ .Times(kNumPacketsInProbe + 1);
+
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kNumPacketsInProbe));
+ time_controller.AdvanceTime(TimeDelta::Zero());
+
+ // The pacer should have scheduled the next probe to be sent in
+ // kProbeTimeDelta. That there was existing scheduled call less than
+ // PacingController::kMinSleepTime before this should not matter.
+ EXPECT_CALL(packet_router,
+ SendPacket(_, ::testing::Field(&PacedPacketInfo::probe_cluster_id,
+ kProbeClusterId)))
+ .Times(AtLeast(1));
+ time_controller.AdvanceTime(TimeDelta::Millis(2));
+}
+
+TEST_P(TaskQueuePacedSenderTest, NoMinSleepTimeWhenProbing) {
+ // Set min_probe_delta to be less than kMinSleepTime (1ms).
+ const TimeDelta kMinProbeDelta = TimeDelta::Micros(200);
+ ScopedKeyValueConfig trials(
+ GetParam() + "WebRTC-Bwe-ProbingBehavior/min_probe_delta:200us/");
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ PacingController::kMinSleepTime,
+ TaskQueuePacedSender::kNoPacketHoldback);
+
+ // Set rates so one packet adds 4ms of buffer level.
+ const DataSize kPacketSize = DataSize::Bytes(kDefaultPacketSize);
+ const TimeDelta kPacketPacingTime = TimeDelta::Millis(4);
+ const DataRate kPacingDataRate = kPacketSize / kPacketPacingTime;
+ pacer.SetPacingRates(kPacingDataRate, /*padding_rate=*/DataRate::Zero());
+ pacer.EnsureStarted();
+ EXPECT_CALL(packet_router, FetchFec).WillRepeatedly([]() {
+ return std::vector<std::unique_ptr<RtpPacketToSend>>();
+ });
+ EXPECT_CALL(packet_router, GeneratePadding)
+ .WillRepeatedly(
+ [](DataSize target_size) { return GeneratePadding(target_size); });
+
+ // Set a high probe rate.
+ const int kProbeClusterId = 1;
+ DataRate kProbingRate = kPacingDataRate * 10;
+
+ pacer.CreateProbeClusters(
+ {{.at_time = time_controller.GetClock()->CurrentTime(),
+ .target_data_rate = kProbingRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 5,
+ .id = kProbeClusterId}});
+
+ // Advance time less than PacingController::kMinSleepTime, probing packets
+ // for the first millisecond should be sent immediately. Min delta between
+ // probes is 200us, meaning 4 times per ms we will get least one call to
+ // SendPacket().
+ DataSize data_sent = DataSize::Zero();
+ EXPECT_CALL(packet_router,
+ SendPacket(_, ::testing::Field(&PacedPacketInfo::probe_cluster_id,
+ kProbeClusterId)))
+ .Times(AtLeast(4))
+ .WillRepeatedly([&](std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo&) {
+ data_sent +=
+ DataSize::Bytes(packet->payload_size() + packet->padding_size());
+ });
+
+ // Add one packet to kickstart probing, the rest will be padding packets.
+ pacer.EnqueuePackets(GeneratePackets(RtpPacketMediaType::kVideo, 1));
+ time_controller.AdvanceTime(kMinProbeDelta);
+
+ // Verify the amount of probing data sent.
+ // Probe always starts with a small (1 byte) padding packet that's not
+ // counted into the probe rate here.
+ const DataSize kMinProbeSize = kMinProbeDelta * kProbingRate;
+ EXPECT_EQ(data_sent, DataSize::Bytes(1) + kPacketSize + 4 * kMinProbeSize);
+}
+
+TEST_P(TaskQueuePacedSenderTest, PacketBasedCoalescing) {
+ const TimeDelta kFixedCoalescingWindow = TimeDelta::Millis(10);
+ const int kPacketBasedHoldback = 5;
+
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ ScopedKeyValueConfig trials(GetParam());
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ kFixedCoalescingWindow, kPacketBasedHoldback);
+
+ // Set rates so one packet adds one ms of buffer level.
+ const DataSize kPacketSize = DataSize::Bytes(kDefaultPacketSize);
+ const TimeDelta kPacketPacingTime = TimeDelta::Millis(1);
+ const DataRate kPacingDataRate = kPacketSize / kPacketPacingTime;
+ const TimeDelta kExpectedHoldbackWindow =
+ kPacketPacingTime * kPacketBasedHoldback;
+ // `kFixedCoalescingWindow` sets the upper bound for the window.
+ ASSERT_GE(kFixedCoalescingWindow, kExpectedHoldbackWindow);
+
+ pacer.SetPacingRates(kPacingDataRate, DataRate::Zero());
+ EXPECT_CALL(packet_router, FetchFec).WillRepeatedly([]() {
+ return std::vector<std::unique_ptr<RtpPacketToSend>>();
+ });
+ pacer.EnsureStarted();
+
+ // Add some packets and wait till all have been sent, so that the pacer
+ // has a valid estimate of packet size.
+ const int kNumWarmupPackets = 40;
+ EXPECT_CALL(packet_router, SendPacket).Times(kNumWarmupPackets);
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kNumWarmupPackets));
+ // Wait until all packes have been sent, with a 2x margin.
+ time_controller.AdvanceTime(kPacketPacingTime * (kNumWarmupPackets * 2));
+
+ // Enqueue packets. Expect only the first one to be sent immediately.
+ EXPECT_CALL(packet_router, SendPacket).Times(1);
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kPacketBasedHoldback));
+ time_controller.AdvanceTime(TimeDelta::Zero());
+
+ // Advance time to 1ms before the coalescing window ends.
+ EXPECT_CALL(packet_router, SendPacket).Times(0);
+ time_controller.AdvanceTime(kExpectedHoldbackWindow - TimeDelta::Millis(1));
+
+ // Advance past where the coalescing window should end.
+ EXPECT_CALL(packet_router, SendPacket).Times(kPacketBasedHoldback - 1);
+ time_controller.AdvanceTime(TimeDelta::Millis(1));
+}
+
+TEST_P(TaskQueuePacedSenderTest, FixedHoldBackHasPriorityOverPackets) {
+ const TimeDelta kFixedCoalescingWindow = TimeDelta::Millis(2);
+ const int kPacketBasedHoldback = 5;
+
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ ScopedKeyValueConfig trials(GetParam());
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ kFixedCoalescingWindow, kPacketBasedHoldback);
+
+ // Set rates so one packet adds one ms of buffer level.
+ const DataSize kPacketSize = DataSize::Bytes(kDefaultPacketSize);
+ const TimeDelta kPacketPacingTime = TimeDelta::Millis(1);
+ const DataRate kPacingDataRate = kPacketSize / kPacketPacingTime;
+ const TimeDelta kExpectedPacketHoldbackWindow =
+ kPacketPacingTime * kPacketBasedHoldback;
+ // |kFixedCoalescingWindow| sets the upper bound for the window.
+ ASSERT_LT(kFixedCoalescingWindow, kExpectedPacketHoldbackWindow);
+
+ pacer.SetPacingRates(kPacingDataRate, DataRate::Zero());
+ EXPECT_CALL(packet_router, FetchFec).WillRepeatedly([]() {
+ return std::vector<std::unique_ptr<RtpPacketToSend>>();
+ });
+ pacer.EnsureStarted();
+
+ // Add some packets and wait till all have been sent, so that the pacer
+ // has a valid estimate of packet size.
+ const int kNumWarmupPackets = 40;
+ EXPECT_CALL(packet_router, SendPacket).Times(kNumWarmupPackets);
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kNumWarmupPackets));
+ // Wait until all packes have been sent, with a 2x margin.
+ time_controller.AdvanceTime(kPacketPacingTime * (kNumWarmupPackets * 2));
+
+ // Enqueue packets. Expect onlt the first one to be sent immediately.
+ EXPECT_CALL(packet_router, SendPacket).Times(1);
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kPacketBasedHoldback));
+ time_controller.AdvanceTime(TimeDelta::Zero());
+
+ // Advance time to the fixed coalescing window, that should take presedence so
+ // at least some of the packets should be sent.
+ EXPECT_CALL(packet_router, SendPacket).Times(AtLeast(1));
+ time_controller.AdvanceTime(kFixedCoalescingWindow);
+}
+
+TEST_P(TaskQueuePacedSenderTest, ProbingStopDuringSendLoop) {
+ // Set a low `min_probe_delta` to let probing finish during send loop.
+ ScopedKeyValueConfig trials(
+ GetParam() + "WebRTC-Bwe-ProbingBehavior/min_probe_delta:100us/");
+
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ MockPacketRouter packet_router;
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ PacingController::kMinSleepTime,
+ TaskQueuePacedSender::kNoPacketHoldback);
+
+ // Set rates so 2 packets adds 1ms of buffer level.
+ const DataSize kPacketSize = DataSize::Bytes(kDefaultPacketSize);
+ const TimeDelta kPacketPacingTime = TimeDelta::Millis(1);
+ const DataRate kPacingDataRate = 2 * kPacketSize / kPacketPacingTime;
+
+ pacer.SetPacingRates(kPacingDataRate, DataRate::Zero());
+ pacer.EnsureStarted();
+
+ EXPECT_CALL(packet_router, FetchFec).WillRepeatedly([]() {
+ return std::vector<std::unique_ptr<RtpPacketToSend>>();
+ });
+ EXPECT_CALL(packet_router, GeneratePadding(_))
+ .WillRepeatedly(
+ [](DataSize target_size) { return GeneratePadding(target_size); });
+
+ // Set probe rate.
+ const int kProbeClusterId = 1;
+ const DataRate kProbingRate = kPacingDataRate;
+
+ pacer.CreateProbeClusters(
+ {{.at_time = time_controller.GetClock()->CurrentTime(),
+ .target_data_rate = kProbingRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 4,
+ .id = kProbeClusterId}});
+
+ const int kPacketsToSend = 100;
+ const TimeDelta kPacketsPacedTime =
+ std::max(kPacketsToSend * kPacketSize / kPacingDataRate,
+ kPacketsToSend * kPacketSize / kProbingRate);
+
+ // Expect all packets and one padding packet sent.
+ EXPECT_CALL(packet_router, SendPacket).Times(kPacketsToSend + 1);
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kPacketsToSend));
+ time_controller.AdvanceTime(kPacketsPacedTime + TimeDelta::Millis(1));
+}
+
+TEST_P(TaskQueuePacedSenderTest, Stats) {
+ static constexpr Timestamp kStartTime = Timestamp::Millis(1234);
+ GlobalSimulatedTimeController time_controller(kStartTime);
+ MockPacketRouter packet_router;
+ ScopedKeyValueConfig trials(GetParam());
+ TaskQueuePacedSender pacer(time_controller.GetClock(), &packet_router, trials,
+ time_controller.GetTaskQueueFactory(),
+ PacingController::kMinSleepTime,
+ TaskQueuePacedSender::kNoPacketHoldback);
+
+ // Simulate ~2mbps video stream, covering one second.
+ static constexpr size_t kPacketsToSend = 200;
+ static constexpr DataRate kPacingRate =
+ DataRate::BytesPerSec(kDefaultPacketSize * kPacketsToSend);
+ pacer.SetPacingRates(kPacingRate, DataRate::Zero());
+ pacer.EnsureStarted();
+
+ // Allowed `QueueSizeData` and `ExpectedQueueTime` deviation.
+ static constexpr size_t kAllowedPacketsDeviation = 1;
+ static constexpr DataSize kAllowedQueueSizeDeviation =
+ DataSize::Bytes(kDefaultPacketSize * kAllowedPacketsDeviation);
+ static constexpr TimeDelta kAllowedQueueTimeDeviation =
+ kAllowedQueueSizeDeviation / kPacingRate;
+
+ DataSize expected_queue_size = DataSize::MinusInfinity();
+ TimeDelta expected_queue_time = TimeDelta::MinusInfinity();
+
+ EXPECT_CALL(packet_router, SendPacket).Times(kPacketsToSend);
+
+ // Stats before insert any packets.
+ EXPECT_TRUE(pacer.OldestPacketWaitTime().IsZero());
+ EXPECT_FALSE(pacer.FirstSentPacketTime().has_value());
+ EXPECT_TRUE(pacer.QueueSizeData().IsZero());
+ EXPECT_TRUE(pacer.ExpectedQueueTime().IsZero());
+
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kPacketsToSend));
+
+ // Advance to 200ms.
+ time_controller.AdvanceTime(TimeDelta::Millis(200));
+ EXPECT_EQ(pacer.OldestPacketWaitTime(), TimeDelta::Millis(200));
+ EXPECT_EQ(pacer.FirstSentPacketTime(), kStartTime);
+
+ expected_queue_size = kPacingRate * TimeDelta::Millis(800);
+ expected_queue_time = expected_queue_size / kPacingRate;
+ EXPECT_NEAR(pacer.QueueSizeData().bytes(), expected_queue_size.bytes(),
+ kAllowedQueueSizeDeviation.bytes());
+ EXPECT_NEAR(pacer.ExpectedQueueTime().ms(), expected_queue_time.ms(),
+ kAllowedQueueTimeDeviation.ms());
+
+ // Advance to 500ms.
+ time_controller.AdvanceTime(TimeDelta::Millis(300));
+ EXPECT_EQ(pacer.OldestPacketWaitTime(), TimeDelta::Millis(500));
+ EXPECT_EQ(pacer.FirstSentPacketTime(), kStartTime);
+
+ expected_queue_size = kPacingRate * TimeDelta::Millis(500);
+ expected_queue_time = expected_queue_size / kPacingRate;
+ EXPECT_NEAR(pacer.QueueSizeData().bytes(), expected_queue_size.bytes(),
+ kAllowedQueueSizeDeviation.bytes());
+ EXPECT_NEAR(pacer.ExpectedQueueTime().ms(), expected_queue_time.ms(),
+ kAllowedQueueTimeDeviation.ms());
+
+ // Advance to 1000ms+, expect all packets to be sent.
+ time_controller.AdvanceTime(TimeDelta::Millis(500) +
+ kAllowedQueueTimeDeviation);
+ EXPECT_TRUE(pacer.OldestPacketWaitTime().IsZero());
+ EXPECT_EQ(pacer.FirstSentPacketTime(), kStartTime);
+ EXPECT_TRUE(pacer.QueueSizeData().IsZero());
+ EXPECT_TRUE(pacer.ExpectedQueueTime().IsZero());
+}
+
+// TODO(webrtc:14502): Rewrite these tests if the functionality is needed if
+// pacing is done on the worker thread.
+TEST(TaskQueuePacedSenderTest, HighPrecisionPacingWhenSlackIsDisabled) {
+ ScopedKeyValueConfig trials("WebRTC-SlackedTaskQueuePacedSender/Disabled/");
+
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ TaskQueueWithFakePrecisionFactory task_queue_factory(
+ time_controller.GetTaskQueueFactory());
+
+ MockPacketRouter packet_router;
+ TaskQueuePacedSender pacer(
+ time_controller.GetClock(), &packet_router, trials, &task_queue_factory,
+ PacingController::kMinSleepTime, TaskQueuePacedSender::kNoPacketHoldback);
+
+ // Send enough packets (covering one second) that pacing is triggered, i.e.
+ // delayed tasks being scheduled.
+ static constexpr size_t kPacketsToSend = 42;
+ static constexpr DataRate kPacingRate =
+ DataRate::BitsPerSec(kDefaultPacketSize * 8 * kPacketsToSend);
+ pacer.SetPacingRates(kPacingRate, DataRate::Zero());
+ pacer.EnsureStarted();
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kPacketsToSend));
+ // Expect all of them to be sent.
+ size_t packets_sent = 0;
+ EXPECT_CALL(packet_router, SendPacket)
+ .WillRepeatedly(
+ [&](std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) { ++packets_sent; });
+ time_controller.AdvanceTime(TimeDelta::Seconds(1));
+ EXPECT_EQ(packets_sent, kPacketsToSend);
+
+ // Expect pacing to make use of high precision.
+ EXPECT_EQ(task_queue_factory.delayed_low_precision_count(), 0);
+ EXPECT_GT(task_queue_factory.delayed_high_precision_count(), 0);
+
+ // Create probe cluster which is also high precision.
+ pacer.CreateProbeClusters(
+ {{.at_time = time_controller.GetClock()->CurrentTime(),
+ .target_data_rate = kPacingRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 4,
+ .id = 123}});
+ pacer.EnqueuePackets(GeneratePackets(RtpPacketMediaType::kVideo, 1));
+ time_controller.AdvanceTime(TimeDelta::Seconds(1));
+ EXPECT_EQ(task_queue_factory.delayed_low_precision_count(), 0);
+ EXPECT_GT(task_queue_factory.delayed_high_precision_count(), 0);
+}
+
+// TODO(webrtc:14502): Rewrite these tests if the functionality is needed if
+// pacing is done on the worker thread.
+TEST(TaskQueuePacedSenderTest, LowPrecisionPacingWhenSlackIsEnabled) {
+ ScopedKeyValueConfig trials("WebRTC-SlackedTaskQueuePacedSender/Enabled/");
+
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1234));
+ TaskQueueWithFakePrecisionFactory task_queue_factory(
+ time_controller.GetTaskQueueFactory());
+
+ MockPacketRouter packet_router;
+ TaskQueuePacedSender pacer(
+ time_controller.GetClock(), &packet_router, trials, &task_queue_factory,
+ PacingController::kMinSleepTime, TaskQueuePacedSender::kNoPacketHoldback);
+
+ // Send enough packets (covering one second) that pacing is triggered, i.e.
+ // delayed tasks being scheduled.
+ static constexpr size_t kPacketsToSend = 42;
+ static constexpr DataRate kPacingRate =
+ DataRate::BitsPerSec(kDefaultPacketSize * 8 * kPacketsToSend);
+ pacer.SetPacingRates(kPacingRate, DataRate::Zero());
+ pacer.EnsureStarted();
+ pacer.EnqueuePackets(
+ GeneratePackets(RtpPacketMediaType::kVideo, kPacketsToSend));
+ // Expect all of them to be sent.
+ size_t packets_sent = 0;
+ EXPECT_CALL(packet_router, SendPacket)
+ .WillRepeatedly(
+ [&](std::unique_ptr<RtpPacketToSend> packet,
+ const PacedPacketInfo& cluster_info) { ++packets_sent; });
+ time_controller.AdvanceTime(TimeDelta::Seconds(1));
+ EXPECT_EQ(packets_sent, kPacketsToSend);
+
+ // Expect pacing to make use of low precision.
+ EXPECT_GT(task_queue_factory.delayed_low_precision_count(), 0);
+ EXPECT_EQ(task_queue_factory.delayed_high_precision_count(), 0);
+
+ // Create probe cluster, which uses high precision despite regular pacing
+ // being low precision.
+ pacer.CreateProbeClusters(
+ {{.at_time = time_controller.GetClock()->CurrentTime(),
+ .target_data_rate = kPacingRate,
+ .target_duration = TimeDelta::Millis(15),
+ .target_probe_count = 4,
+ .id = 123}});
+ pacer.EnqueuePackets(GeneratePackets(RtpPacketMediaType::kVideo, 1));
+ time_controller.AdvanceTime(TimeDelta::Seconds(1));
+ EXPECT_GT(task_queue_factory.delayed_high_precision_count(), 0);
+}
+
+} // namespace test
+} // namespace webrtc