| /* | |
| * Copyright 2025 The WebRTC Project Authors. All rights reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "modules/congestion_controller/scream/scream_network_controller.h" | |
| #include "api/environment/environment.h" | |
| #include "api/transport/network_control.h" | |
| #include "api/transport/network_types.h" | |
| #include "api/units/data_rate.h" | |
| #include "api/units/time_delta.h" | |
| #include "api/units/timestamp.h" | |
| #include "modules/congestion_controller/scream/test/cc_feedback_generator.h" | |
| #include "system_wrappers/include/clock.h" | |
| #include "test/create_test_environment.h" | |
| #include "test/gmock.h" | |
| #include "test/gtest.h" | |
| namespace webrtc { | |
| namespace { | |
| using ::testing::Eq; | |
| using ::testing::Field; | |
| using ::testing::Lt; | |
| using ::testing::Optional; | |
| constexpr double kPacingFactor = 1.1; | |
| TEST(ScreamControllerTest, CanConstruct) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| NetworkControllerConfig config(env); | |
| ScreamNetworkController scream_controller(config); | |
| } | |
| TEST(ScreamControllerTest, OnNetworkAvailabilityUpdatesTargetRateAndPacerRate) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| NetworkControllerConfig config(env); | |
| config.constraints.starting_rate = DataRate::KilobitsPerSec(123); | |
| config.stream_based_config.max_total_allocated_bitrate = | |
| DataRate::KilobitsPerSec(456); | |
| ScreamNetworkController scream_controller(config); | |
| NetworkControlUpdate update = | |
| scream_controller.OnNetworkAvailability({.network_available = true}); | |
| ASSERT_TRUE(update.has_updates()); | |
| ASSERT_TRUE(update.target_rate.has_value()); | |
| EXPECT_EQ(update.target_rate->target_rate, config.constraints.starting_rate); | |
| ASSERT_TRUE(update.pacer_config); | |
| EXPECT_EQ(update.pacer_config->data_window, | |
| *config.constraints.starting_rate * kPacingFactor * | |
| PacerConfig::kDefaultTimeInterval); | |
| } | |
| TEST(ScreamControllerTest, | |
| OnTransportPacketsFeedbackUpdatesTargetRateAndPacerRate) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| NetworkControllerConfig config(env); | |
| ScreamNetworkController scream_controller(config); | |
| // Simulation with infinite capacity. | |
| CcFeedbackGenerator feedback_generator({}); | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback(DataRate::KilobitsPerSec(100), | |
| clock); | |
| NetworkControlUpdate update = | |
| scream_controller.OnTransportPacketsFeedback(feedback); | |
| ASSERT_TRUE(update.has_updates()); | |
| ASSERT_TRUE(update.target_rate.has_value()); | |
| EXPECT_GT(update.target_rate->target_rate, DataRate::KilobitsPerSec(100)); | |
| ASSERT_TRUE(update.pacer_config); | |
| EXPECT_EQ(update.pacer_config->data_window, | |
| update.target_rate->target_rate * kPacingFactor * | |
| PacerConfig::kDefaultTimeInterval); | |
| } | |
| TEST(ScreamControllerTest, | |
| OnNetworkRouteChangeResetsScreamAndUpdatesTargetRate) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| NetworkControllerConfig config(env); | |
| config.constraints.starting_rate = DataRate::KilobitsPerSec(50); | |
| config.stream_based_config.max_total_allocated_bitrate = | |
| DataRate::KilobitsPerSec(1000); | |
| ScreamNetworkController scream_controller(config); | |
| scream_controller.OnNetworkAvailability({.network_available = true}); | |
| CcFeedbackGenerator feedback_generator({}); | |
| DataRate send_rate = DataRate::KilobitsPerSec(100); | |
| for (int i = 0; i < 10; ++i) { | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback(send_rate, clock); | |
| NetworkControlUpdate update = | |
| scream_controller.OnTransportPacketsFeedback(feedback); | |
| if (update.target_rate.has_value()) { | |
| send_rate = update.target_rate->target_rate; | |
| } | |
| } | |
| ASSERT_GT(send_rate, DataRate::KilobitsPerSec(50)); | |
| NetworkRouteChange route_change; | |
| route_change.constraints.starting_rate = config.constraints.starting_rate = | |
| DataRate::KilobitsPerSec(123); | |
| route_change.at_time = clock.CurrentTime(); | |
| NetworkControlUpdate update = | |
| scream_controller.OnNetworkRouteChange(route_change); | |
| ASSERT_TRUE(update.has_updates()); | |
| ASSERT_TRUE(update.target_rate.has_value()); | |
| EXPECT_EQ(update.target_rate->target_rate, | |
| route_change.constraints.starting_rate); | |
| ASSERT_TRUE(update.pacer_config); | |
| EXPECT_EQ(update.pacer_config->data_window, | |
| *route_change.constraints.starting_rate * kPacingFactor * | |
| PacerConfig::kDefaultTimeInterval); | |
| } | |
| TEST(ScreamControllerTest, TargetRateRampsUptoTargetConstraints) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| NetworkControllerConfig config(env); | |
| config.constraints.max_data_rate = DataRate::KilobitsPerSec(300); | |
| ScreamNetworkController scream_controller(config); | |
| // Simulation with infinite capacity. | |
| CcFeedbackGenerator feedback_generator({}); | |
| DataRate target_rate = DataRate::KilobitsPerSec(100); | |
| for (int i = 0; i < 10; ++i) { | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback(target_rate, clock); | |
| NetworkControlUpdate update = | |
| scream_controller.OnTransportPacketsFeedback(feedback); | |
| if (update.target_rate.has_value()) { | |
| target_rate = update.target_rate->target_rate; | |
| } | |
| } | |
| EXPECT_EQ(target_rate, DataRate::KilobitsPerSec(300)); | |
| // Reduce the constraints and expect the next target rate is bound by it. | |
| TargetRateConstraints constraints; | |
| constraints.max_data_rate = DataRate::KilobitsPerSec(200); | |
| scream_controller.OnTargetRateConstraints(constraints); | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback(target_rate, clock); | |
| NetworkControlUpdate update = | |
| scream_controller.OnTransportPacketsFeedback(feedback); | |
| ASSERT_TRUE(update.target_rate.has_value()); | |
| EXPECT_EQ(update.target_rate->target_rate, DataRate::KilobitsPerSec(200)); | |
| } | |
| TEST(ScreamControllerTest, TargetRateLimitedByRemoteBitrateReport) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| NetworkControllerConfig config(env); | |
| config.constraints.max_data_rate = DataRate::KilobitsPerSec(1000); | |
| ScreamNetworkController scream_controller(config); | |
| // Simulation with infinite capacity. | |
| CcFeedbackGenerator feedback_generator({}); | |
| DataRate target_rate = DataRate::KilobitsPerSec(100); | |
| for (int i = 0; i < 10; ++i) { | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback( | |
| target_rate, clock, [&](const SentPacket& packet) { | |
| scream_controller.OnSentPacket(packet); | |
| }); | |
| NetworkControlUpdate update = | |
| scream_controller.OnTransportPacketsFeedback(feedback); | |
| if (update.target_rate.has_value()) { | |
| target_rate = update.target_rate->target_rate; | |
| } | |
| } | |
| EXPECT_EQ(target_rate, DataRate::KilobitsPerSec(1000)); | |
| RemoteBitrateReport msg; | |
| msg.bandwidth = DataRate::KilobitsPerSec(500); | |
| msg.receive_time = clock.CurrentTime(); | |
| NetworkControlUpdate update = scream_controller.OnRemoteBitrateReport(msg); | |
| ASSERT_TRUE(update.target_rate.has_value()); | |
| EXPECT_EQ(update.target_rate->target_rate, DataRate::KilobitsPerSec(500)); | |
| for (int i = 0; i < 2; ++i) { | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback( | |
| target_rate, clock, [&](const SentPacket& packet) { | |
| scream_controller.OnSentPacket(packet); | |
| }); | |
| update = scream_controller.OnTransportPacketsFeedback(feedback); | |
| if (update.target_rate.has_value()) { | |
| EXPECT_EQ(update.target_rate->target_rate, DataRate::KilobitsPerSec(500)); | |
| } | |
| } | |
| } | |
| TEST(ScreamControllerTest, PacingWindowReducedIfCeCongestedStreamsConfigured) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| CcFeedbackGenerator feedback_generator({ | |
| .network_config = {.link_capacity = DataRate::KilobitsPerSec(900)}, | |
| .send_as_ect1 = true, | |
| }); | |
| NetworkControllerConfig config(env); | |
| ScreamNetworkController scream_controller(config); | |
| StreamsConfig streams_config; | |
| streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000); | |
| scream_controller.OnStreamsConfig(streams_config); | |
| NetworkControlUpdate update; | |
| DataRate send_rate = DataRate::KilobitsPerSec(500); | |
| for (int i = 0; i < 20; ++i) { | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback( | |
| send_rate, clock, [&](const SentPacket& packet) { | |
| scream_controller.OnSentPacket(packet); | |
| }); | |
| update = scream_controller.OnTransportPacketsFeedback(feedback); | |
| if (update.target_rate.has_value()) { | |
| send_rate = update.target_rate->target_rate; | |
| } | |
| } | |
| EXPECT_THAT(update.pacer_config, | |
| Optional(Field(&PacerConfig::time_window, | |
| Lt(PacerConfig::kDefaultTimeInterval)))); | |
| } | |
| TEST(ScreamControllerTest, | |
| PacingWindowReducedIfDelayCongestedStreamsConfigured) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| CcFeedbackGenerator feedback_generator( | |
| {.network_config = {.link_capacity = DataRate::KilobitsPerSec(900)}, | |
| .send_as_ect1 = false}); // Scream will react to delay increase, not CE. | |
| NetworkControllerConfig config(env); | |
| ScreamNetworkController scream_controller(config); | |
| StreamsConfig streams_config; | |
| streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000); | |
| scream_controller.OnStreamsConfig(streams_config); | |
| NetworkControlUpdate update; | |
| DataRate send_rate = DataRate::KilobitsPerSec(500); | |
| for (int i = 0; i < 30; ++i) { | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback( | |
| send_rate, clock, [&](const SentPacket& packet) { | |
| scream_controller.OnSentPacket(packet); | |
| }); | |
| update = scream_controller.OnTransportPacketsFeedback(feedback); | |
| if (update.target_rate.has_value()) { | |
| send_rate = update.target_rate->target_rate; | |
| } | |
| } | |
| EXPECT_THAT(update.pacer_config, | |
| Optional(Field(&PacerConfig::time_window, | |
| Lt(PacerConfig::kDefaultTimeInterval)))); | |
| } | |
| TEST(ScreamControllerTest, PacingWindowNotReducedIfNotCongested) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| CcFeedbackGenerator feedback_generator( | |
| {.network_config = {.link_capacity = DataRate::KilobitsPerSec(9000)}, | |
| .send_as_ect1 = false}); // Scream will react to delay increase, not CE. | |
| NetworkControllerConfig config(env); | |
| ScreamNetworkController scream_controller(config); | |
| StreamsConfig streams_config; | |
| streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000); | |
| scream_controller.OnStreamsConfig(streams_config); | |
| NetworkControlUpdate update; | |
| DataRate send_rate = DataRate::KilobitsPerSec(500); | |
| for (int i = 0; i < 30; ++i) { | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback( | |
| send_rate, clock, [&](const SentPacket& packet) { | |
| scream_controller.OnSentPacket(packet); | |
| }); | |
| update = scream_controller.OnTransportPacketsFeedback(feedback); | |
| if (update.target_rate.has_value()) { | |
| send_rate = update.target_rate->target_rate; | |
| } | |
| } | |
| EXPECT_THAT(update.pacer_config, | |
| Optional(Field(&PacerConfig::time_window, | |
| Eq(PacerConfig::kDefaultTimeInterval)))); | |
| } | |
| TEST(ScreamControllerTest, InitiallyPaddingIsAllowedToReachNeededRate) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| NetworkControllerConfig config(env); | |
| ScreamNetworkController scream_controller(config); | |
| CcFeedbackGenerator feedback_generator( | |
| {.network_config = {.queue_delay_ms = 10, | |
| .link_capacity = DataRate::KilobitsPerSec(5000)}, | |
| .send_as_ect1 = true}); | |
| StreamsConfig streams_config; | |
| streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000); | |
| scream_controller.OnStreamsConfig(streams_config); | |
| DataRate send_rate = DataRate::KilobitsPerSec(50); | |
| DataRate target_rate = DataRate::Zero(); | |
| bool padding_set = false; | |
| Timestamp padding_stop = Timestamp::Zero(); | |
| Timestamp start_time = clock.CurrentTime(); | |
| while (clock.CurrentTime() < start_time + TimeDelta::Seconds(1)) { | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback( | |
| send_rate, clock, | |
| [&](SentPacket packet) { scream_controller.OnSentPacket(packet); }); | |
| NetworkControlUpdate update = | |
| scream_controller.OnTransportPacketsFeedback(feedback); | |
| if (update.pacer_config.has_value()) { | |
| if (update.pacer_config->pad_rate() != DataRate::Zero()) { | |
| padding_set = true; | |
| // Set the send rate equal to the padding rate. | |
| send_rate = update.pacer_config->pad_rate(); | |
| // Pacing rate is rounded. | |
| EXPECT_GT( | |
| update.pacer_config->pad_rate(), | |
| update.target_rate->target_rate - DataRate::KilobitsPerSec(1)); | |
| EXPECT_LT( | |
| update.pacer_config->pad_rate(), | |
| update.target_rate->target_rate + DataRate::KilobitsPerSec(1)); | |
| } else if (padding_set && padding_stop.IsZero()) { | |
| padding_stop = clock.CurrentTime(); | |
| } | |
| } | |
| if (update.target_rate) { | |
| target_rate = update.target_rate->target_rate; | |
| } | |
| } | |
| EXPECT_TRUE(padding_set); | |
| // Target rate should reach max needed rate. | |
| EXPECT_GE(target_rate, (*streams_config.max_total_allocated_bitrate)); | |
| // But not much more, since seen data in flight should limit the target rate | |
| // increase. | |
| EXPECT_LE(target_rate, 1.5 * (*streams_config.max_total_allocated_bitrate)); | |
| // Padding should stop when target is reached. | |
| EXPECT_LT(padding_stop - start_time, TimeDelta::Seconds(1)); | |
| } | |
| struct PaddingTestResult { | |
| DataRate target_rate; | |
| Timestamp padding_start; | |
| Timestamp padding_stop; | |
| }; | |
| PaddingTestResult ProcessUntilPaddingStartAndStop( | |
| SimulatedClock& clock, | |
| ScreamNetworkController& scream_controller, | |
| CcFeedbackGenerator& feedback_generator, | |
| bool increase_send_rate = true) { | |
| DataRate target_rate = DataRate::Zero(); | |
| Timestamp padding_start = Timestamp::Zero(); | |
| Timestamp padding_stop = Timestamp::Zero(); | |
| Timestamp start_time = clock.CurrentTime(); | |
| DataRate send_rate = DataRate::KilobitsPerSec(50); | |
| while (clock.CurrentTime() < start_time + TimeDelta::Seconds(10)) { | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback( | |
| send_rate, clock, | |
| [&](SentPacket packet) { scream_controller.OnSentPacket(packet); }); | |
| NetworkControlUpdate update = | |
| scream_controller.OnTransportPacketsFeedback(feedback); | |
| if (update.pacer_config.has_value()) { | |
| if (update.pacer_config->pad_rate() != DataRate::Zero()) { | |
| padding_start = clock.CurrentTime(); | |
| if (increase_send_rate) { | |
| // Set the send rate equal to the padding rate. | |
| send_rate = update.pacer_config->pad_rate(); | |
| } | |
| } else if (!padding_start.IsZero() && padding_stop.IsZero()) { | |
| padding_stop = clock.CurrentTime(); | |
| } | |
| } | |
| if (update.target_rate) { | |
| target_rate = update.target_rate->target_rate; | |
| } | |
| if (!padding_stop.IsZero()) { | |
| break; | |
| } | |
| } | |
| EXPECT_FALSE(padding_start.IsZero()); | |
| EXPECT_FALSE(padding_stop.IsZero()); | |
| return {.target_rate = target_rate, | |
| .padding_start = padding_start, | |
| .padding_stop = padding_stop}; | |
| } | |
| TEST(ScreamControllerTest, PaddingStopIfNetworkCongested) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| NetworkControllerConfig config(env); | |
| ScreamNetworkController scream_controller(config); | |
| CcFeedbackGenerator feedback_generator( | |
| {.network_config = {.queue_delay_ms = 10, | |
| .link_capacity = DataRate::KilobitsPerSec(500)}, | |
| .send_as_ect1 = true}); | |
| StreamsConfig streams_config; | |
| streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000); | |
| scream_controller.OnStreamsConfig(streams_config); | |
| PaddingTestResult result = ProcessUntilPaddingStartAndStop( | |
| clock, scream_controller, feedback_generator); | |
| EXPECT_LT(result.target_rate, DataRate::KilobitsPerSec(700)); | |
| // Padding should stop when congestion is detected. | |
| EXPECT_LT(result.padding_stop - result.padding_start, TimeDelta::Seconds(1)); | |
| } | |
| TEST(ScreamControllerTest, PeriodicallyAllowPadding) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| CcFeedbackGenerator feedback_generator( | |
| {.network_config = {.queue_delay_ms = 10, | |
| .link_capacity = DataRate::KilobitsPerSec(15000)}, | |
| .send_as_ect1 = true}); | |
| NetworkControllerConfig config(env); | |
| ScreamNetworkController scream_controller(config); | |
| StreamsConfig streams_config; | |
| streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000); | |
| scream_controller.OnStreamsConfig(streams_config); | |
| PaddingTestResult result_1 = ProcessUntilPaddingStartAndStop( | |
| clock, scream_controller, feedback_generator, | |
| /*increase_send_rate=*/false); | |
| PaddingTestResult result_2 = ProcessUntilPaddingStartAndStop( | |
| clock, scream_controller, feedback_generator); | |
| TimeDelta padding_duration = result_1.padding_stop - result_1.padding_start; | |
| TimeDelta time_between_padding = | |
| result_2.padding_start - result_1.padding_stop; | |
| EXPECT_GT(padding_duration, TimeDelta::Millis(2800)); | |
| EXPECT_LT(padding_duration, TimeDelta::Millis(3100)); | |
| EXPECT_GT(time_between_padding, TimeDelta::Millis(2900)); | |
| EXPECT_LT(time_between_padding, TimeDelta::Millis(3300)); | |
| } | |
| TEST(ScreamControllerTest, PadsToMinOf2xCurrentMaxAndEverSeenMax) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| NetworkControllerConfig config(env); | |
| ScreamNetworkController scream_controller(config); | |
| CcFeedbackGenerator feedback_generator( | |
| {.network_config = {.queue_delay_ms = 50, | |
| .link_capacity = DataRate::KilobitsPerSec(5000)}, | |
| .send_as_ect1 = true}); | |
| StreamsConfig streams_config; | |
| streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000); | |
| scream_controller.OnStreamsConfig(streams_config); | |
| // Even if max_total_allocated_bitrate is lowered, padding is still allowed up | |
| // to 2x the new max and previous max. | |
| streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(300); | |
| scream_controller.OnStreamsConfig(streams_config); | |
| PaddingTestResult result_1 = ProcessUntilPaddingStartAndStop( | |
| clock, scream_controller, feedback_generator); | |
| EXPECT_LT(result_1.target_rate, DataRate::KilobitsPerSec(700)); | |
| streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(800); | |
| scream_controller.OnStreamsConfig(streams_config); | |
| PaddingTestResult result_2 = ProcessUntilPaddingStartAndStop( | |
| clock, scream_controller, feedback_generator); | |
| EXPECT_LT(result_2.target_rate, DataRate::KilobitsPerSec(1100)); | |
| } | |
| TEST(ScreamControllerTest, CanSetStartBitrate) { | |
| SimulatedClock clock(Timestamp::Seconds(1'234)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| NetworkControllerConfig config(env); | |
| config.constraints.starting_rate = DataRate::KilobitsPerSec(3000); | |
| ScreamNetworkController scream_controller(config); | |
| CcFeedbackGenerator feedback_generator( | |
| {.network_config = {.queue_delay_ms = 50, | |
| .link_capacity = DataRate::KilobitsPerSec(5000)}}); | |
| TransportPacketsFeedback feedback = | |
| feedback_generator.ProcessUntilNextFeedback( | |
| /*send_rate=*/DataRate::KilobitsPerSec(100), clock, | |
| [&](SentPacket packet) { scream_controller.OnSentPacket(packet); }); | |
| NetworkControlUpdate update = | |
| scream_controller.OnTransportPacketsFeedback(feedback); | |
| EXPECT_GE(update.target_rate->target_rate, DataRate::KilobitsPerSec(2980)); | |
| EXPECT_LT(update.target_rate->target_rate, DataRate::KilobitsPerSec(3300)); | |
| } | |
| } // namespace | |
| } // namespace webrtc |