diff options
Diffstat (limited to 'third_party/libwebrtc/modules/pacing')
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 |