/* * 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/audio_coding/audio_network_adaptor/fec_controller_plr_based.h" #include #include "common_audio/mocks/mock_smoothing_filter.h" #include "test/gtest.h" namespace webrtc { using ::testing::_; using ::testing::NiceMock; using ::testing::Return; namespace { // The test uses the following settings: // // packet-loss ^ | | // | A| C| FEC // | \ \ ON // | FEC \ D\_______ // | OFF B\_________ // |-----------------> bandwidth // // A : (kDisablingBandwidthLow, kDisablingPacketLossAtLowBw) // B : (kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw) // C : (kEnablingBandwidthLow, kEnablingPacketLossAtLowBw) // D : (kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw) constexpr int kDisablingBandwidthLow = 15000; constexpr float kDisablingPacketLossAtLowBw = 0.08f; constexpr int kDisablingBandwidthHigh = 64000; constexpr float kDisablingPacketLossAtHighBw = 0.01f; constexpr int kEnablingBandwidthLow = 17000; constexpr float kEnablingPacketLossAtLowBw = 0.1f; constexpr int kEnablingBandwidthHigh = 64000; constexpr float kEnablingPacketLossAtHighBw = 0.05f; constexpr float kEpsilon = 1e-5f; struct FecControllerPlrBasedTestStates { std::unique_ptr controller; MockSmoothingFilter* packet_loss_smoother; }; FecControllerPlrBasedTestStates CreateFecControllerPlrBased( bool initial_fec_enabled, const ThresholdCurve& enabling_curve, const ThresholdCurve& disabling_curve) { FecControllerPlrBasedTestStates states; std::unique_ptr mock_smoothing_filter( new NiceMock()); states.packet_loss_smoother = mock_smoothing_filter.get(); states.controller.reset(new FecControllerPlrBased( FecControllerPlrBased::Config(initial_fec_enabled, enabling_curve, disabling_curve, 0), std::move(mock_smoothing_filter))); return states; } FecControllerPlrBasedTestStates CreateFecControllerPlrBased( bool initial_fec_enabled) { return CreateFecControllerPlrBased( initial_fec_enabled, ThresholdCurve(kEnablingBandwidthLow, kEnablingPacketLossAtLowBw, kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw), ThresholdCurve(kDisablingBandwidthLow, kDisablingPacketLossAtLowBw, kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw)); } void UpdateNetworkMetrics(FecControllerPlrBasedTestStates* states, const absl::optional& uplink_bandwidth_bps, const absl::optional& uplink_packet_loss) { // UpdateNetworkMetrics can accept multiple network metric updates at once. // However, currently, the most used case is to update one metric at a time. // To reflect this fact, we separate the calls. if (uplink_bandwidth_bps) { Controller::NetworkMetrics network_metrics; network_metrics.uplink_bandwidth_bps = uplink_bandwidth_bps; states->controller->UpdateNetworkMetrics(network_metrics); } if (uplink_packet_loss) { Controller::NetworkMetrics network_metrics; network_metrics.uplink_packet_loss_fraction = uplink_packet_loss; EXPECT_CALL(*states->packet_loss_smoother, AddSample(*uplink_packet_loss)); states->controller->UpdateNetworkMetrics(network_metrics); // This is called during CheckDecision(). EXPECT_CALL(*states->packet_loss_smoother, GetAverage()) .WillOnce(Return(*uplink_packet_loss)); } } // Checks that the FEC decision and `uplink_packet_loss_fraction` given by // `states->controller->MakeDecision` matches `expected_enable_fec` and // `expected_uplink_packet_loss_fraction`, respectively. void CheckDecision(FecControllerPlrBasedTestStates* states, bool expected_enable_fec, float expected_uplink_packet_loss_fraction) { AudioEncoderRuntimeConfig config; states->controller->MakeDecision(&config); EXPECT_EQ(expected_enable_fec, config.enable_fec); EXPECT_EQ(expected_uplink_packet_loss_fraction, config.uplink_packet_loss_fraction); } } // namespace TEST(FecControllerPlrBasedTest, OutputInitValueBeforeAnyInputsAreReceived) { for (bool initial_fec_enabled : {false, true}) { auto states = CreateFecControllerPlrBased(initial_fec_enabled); CheckDecision(&states, initial_fec_enabled, 0); } } TEST(FecControllerPlrBasedTest, OutputInitValueWhenUplinkBandwidthUnknown) { // Regardless of the initial FEC state and the packet-loss rate, // the initial FEC state is maintained as long as the BWE is unknown. for (bool initial_fec_enabled : {false, true}) { for (float packet_loss : {kDisablingPacketLossAtLowBw - kEpsilon, kDisablingPacketLossAtLowBw, kDisablingPacketLossAtLowBw + kEpsilon, kEnablingPacketLossAtLowBw - kEpsilon, kEnablingPacketLossAtLowBw, kEnablingPacketLossAtLowBw + kEpsilon}) { auto states = CreateFecControllerPlrBased(initial_fec_enabled); UpdateNetworkMetrics(&states, absl::nullopt, packet_loss); CheckDecision(&states, initial_fec_enabled, packet_loss); } } } TEST(FecControllerPlrBasedTest, OutputInitValueWhenUplinkPacketLossFractionUnknown) { // Regardless of the initial FEC state and the BWE, the initial FEC state // is maintained as long as the packet-loss rate is unknown. for (bool initial_fec_enabled : {false, true}) { for (int bandwidth : {kDisablingBandwidthLow - 1, kDisablingBandwidthLow, kDisablingBandwidthLow + 1, kEnablingBandwidthLow - 1, kEnablingBandwidthLow, kEnablingBandwidthLow + 1}) { auto states = CreateFecControllerPlrBased(initial_fec_enabled); UpdateNetworkMetrics(&states, bandwidth, absl::nullopt); CheckDecision(&states, initial_fec_enabled, 0.0); } } } TEST(FecControllerPlrBasedTest, EnableFecForHighBandwidth) { auto states = CreateFecControllerPlrBased(false); UpdateNetworkMetrics(&states, kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw); CheckDecision(&states, true, kEnablingPacketLossAtHighBw); } TEST(FecControllerPlrBasedTest, UpdateMultipleNetworkMetricsAtOnce) { // This test is similar to EnableFecForHighBandwidth. But instead of // using ::UpdateNetworkMetrics(...), which calls // FecControllerPlrBased::UpdateNetworkMetrics(...) multiple times, we // we call it only once. This is to verify that // FecControllerPlrBased::UpdateNetworkMetrics(...) can handle multiple // network updates at once. This is, however, not a common use case in current // audio_network_adaptor_impl.cc. auto states = CreateFecControllerPlrBased(false); Controller::NetworkMetrics network_metrics; network_metrics.uplink_bandwidth_bps = kEnablingBandwidthHigh; network_metrics.uplink_packet_loss_fraction = kEnablingPacketLossAtHighBw; EXPECT_CALL(*states.packet_loss_smoother, GetAverage()) .WillOnce(Return(kEnablingPacketLossAtHighBw)); states.controller->UpdateNetworkMetrics(network_metrics); CheckDecision(&states, true, kEnablingPacketLossAtHighBw); } TEST(FecControllerPlrBasedTest, MaintainFecOffForHighBandwidth) { auto states = CreateFecControllerPlrBased(false); constexpr float kPacketLoss = kEnablingPacketLossAtHighBw * 0.99f; UpdateNetworkMetrics(&states, kEnablingBandwidthHigh, kPacketLoss); CheckDecision(&states, false, kPacketLoss); } TEST(FecControllerPlrBasedTest, EnableFecForMediumBandwidth) { auto states = CreateFecControllerPlrBased(false); constexpr float kPacketLoss = (kEnablingPacketLossAtLowBw + kEnablingPacketLossAtHighBw) / 2.0; UpdateNetworkMetrics(&states, (kEnablingBandwidthHigh + kEnablingBandwidthLow) / 2, kPacketLoss); CheckDecision(&states, true, kPacketLoss); } TEST(FecControllerPlrBasedTest, MaintainFecOffForMediumBandwidth) { auto states = CreateFecControllerPlrBased(false); constexpr float kPacketLoss = kEnablingPacketLossAtLowBw * 0.49f + kEnablingPacketLossAtHighBw * 0.51f; UpdateNetworkMetrics(&states, (kEnablingBandwidthHigh + kEnablingBandwidthLow) / 2, kPacketLoss); CheckDecision(&states, false, kPacketLoss); } TEST(FecControllerPlrBasedTest, EnableFecForLowBandwidth) { auto states = CreateFecControllerPlrBased(false); UpdateNetworkMetrics(&states, kEnablingBandwidthLow, kEnablingPacketLossAtLowBw); CheckDecision(&states, true, kEnablingPacketLossAtLowBw); } TEST(FecControllerPlrBasedTest, MaintainFecOffForLowBandwidth) { auto states = CreateFecControllerPlrBased(false); constexpr float kPacketLoss = kEnablingPacketLossAtLowBw * 0.99f; UpdateNetworkMetrics(&states, kEnablingBandwidthLow, kPacketLoss); CheckDecision(&states, false, kPacketLoss); } TEST(FecControllerPlrBasedTest, MaintainFecOffForVeryLowBandwidth) { auto states = CreateFecControllerPlrBased(false); // Below `kEnablingBandwidthLow`, no packet loss fraction can cause FEC to // turn on. UpdateNetworkMetrics(&states, kEnablingBandwidthLow - 1, 1.0); CheckDecision(&states, false, 1.0); } TEST(FecControllerPlrBasedTest, DisableFecForHighBandwidth) { auto states = CreateFecControllerPlrBased(true); constexpr float kPacketLoss = kDisablingPacketLossAtHighBw - kEpsilon; UpdateNetworkMetrics(&states, kDisablingBandwidthHigh, kPacketLoss); CheckDecision(&states, false, kPacketLoss); } TEST(FecControllerPlrBasedTest, MaintainFecOnForHighBandwidth) { // Note: Disabling happens when the value is strictly below the threshold. auto states = CreateFecControllerPlrBased(true); UpdateNetworkMetrics(&states, kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw); CheckDecision(&states, true, kDisablingPacketLossAtHighBw); } TEST(FecControllerPlrBasedTest, DisableFecOnMediumBandwidth) { auto states = CreateFecControllerPlrBased(true); constexpr float kPacketLoss = (kDisablingPacketLossAtLowBw + kDisablingPacketLossAtHighBw) / 2.0f - kEpsilon; UpdateNetworkMetrics(&states, (kDisablingBandwidthHigh + kDisablingBandwidthLow) / 2, kPacketLoss); CheckDecision(&states, false, kPacketLoss); } TEST(FecControllerPlrBasedTest, MaintainFecOnForMediumBandwidth) { auto states = CreateFecControllerPlrBased(true); constexpr float kPacketLoss = kDisablingPacketLossAtLowBw * 0.51f + kDisablingPacketLossAtHighBw * 0.49f - kEpsilon; UpdateNetworkMetrics(&states, (kEnablingBandwidthHigh + kDisablingBandwidthLow) / 2, kPacketLoss); CheckDecision(&states, true, kPacketLoss); } TEST(FecControllerPlrBasedTest, DisableFecForLowBandwidth) { auto states = CreateFecControllerPlrBased(true); constexpr float kPacketLoss = kDisablingPacketLossAtLowBw - kEpsilon; UpdateNetworkMetrics(&states, kDisablingBandwidthLow, kPacketLoss); CheckDecision(&states, false, kPacketLoss); } TEST(FecControllerPlrBasedTest, DisableFecForVeryLowBandwidth) { auto states = CreateFecControllerPlrBased(true); // Below `kEnablingBandwidthLow`, any packet loss fraction can cause FEC to // turn off. UpdateNetworkMetrics(&states, kDisablingBandwidthLow - 1, 1.0); CheckDecision(&states, false, 1.0); } TEST(FecControllerPlrBasedTest, CheckBehaviorOnChangingNetworkMetrics) { // In this test, we let the network metrics to traverse from 1 to 5. // packet-loss ^ 1 | | // | | 2| // | \ \ 3 // | \4 \_______ // | \_________ // |---------5-------> bandwidth auto states = CreateFecControllerPlrBased(true); UpdateNetworkMetrics(&states, kDisablingBandwidthLow - 1, 1.0); CheckDecision(&states, false, 1.0); UpdateNetworkMetrics(&states, kEnablingBandwidthLow, kEnablingPacketLossAtLowBw * 0.99f); CheckDecision(&states, false, kEnablingPacketLossAtLowBw * 0.99f); UpdateNetworkMetrics(&states, kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw); CheckDecision(&states, true, kEnablingPacketLossAtHighBw); UpdateNetworkMetrics(&states, kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw); CheckDecision(&states, true, kDisablingPacketLossAtHighBw); UpdateNetworkMetrics(&states, kDisablingBandwidthHigh + 1, 0.0); CheckDecision(&states, false, 0.0); } TEST(FecControllerPlrBasedTest, CheckBehaviorOnSpecialCurves) { // We test a special configuration, where the points to define the FEC // enabling/disabling curves are placed like the following, otherwise the test // is the same as CheckBehaviorOnChangingNetworkMetrics. // // packet-loss ^ | | // | | C| // | | | // | | D|_______ // | A|___B______ // |-----------------> bandwidth constexpr int kEnablingBandwidthHigh = kEnablingBandwidthLow; constexpr float kDisablingPacketLossAtLowBw = kDisablingPacketLossAtHighBw; FecControllerPlrBasedTestStates states; std::unique_ptr mock_smoothing_filter( new NiceMock()); states.packet_loss_smoother = mock_smoothing_filter.get(); states.controller.reset(new FecControllerPlrBased( FecControllerPlrBased::Config( true, ThresholdCurve(kEnablingBandwidthLow, kEnablingPacketLossAtLowBw, kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw), ThresholdCurve(kDisablingBandwidthLow, kDisablingPacketLossAtLowBw, kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw), 0), std::move(mock_smoothing_filter))); UpdateNetworkMetrics(&states, kDisablingBandwidthLow - 1, 1.0); CheckDecision(&states, false, 1.0); UpdateNetworkMetrics(&states, kEnablingBandwidthLow, kEnablingPacketLossAtHighBw * 0.99f); CheckDecision(&states, false, kEnablingPacketLossAtHighBw * 0.99f); UpdateNetworkMetrics(&states, kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw); CheckDecision(&states, true, kEnablingPacketLossAtHighBw); UpdateNetworkMetrics(&states, kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw); CheckDecision(&states, true, kDisablingPacketLossAtHighBw); UpdateNetworkMetrics(&states, kDisablingBandwidthHigh + 1, 0.0); CheckDecision(&states, false, 0.0); } TEST(FecControllerPlrBasedTest, SingleThresholdCurveForEnablingAndDisabling) { // Note: To avoid numerical errors, keep kPacketLossAtLowBw and // kPacketLossAthighBw as (negative) integer powers of 2. // This is mostly relevant for the O3 case. constexpr int kBandwidthLow = 10000; constexpr float kPacketLossAtLowBw = 0.25f; constexpr int kBandwidthHigh = 20000; constexpr float kPacketLossAtHighBw = 0.125f; auto curve = ThresholdCurve(kBandwidthLow, kPacketLossAtLowBw, kBandwidthHigh, kPacketLossAtHighBw); // B* stands for "below-curve", O* for "on-curve", and A* for "above-curve". // // // // packet-loss ^ // // | | // // | B1 O1 // // | | // // | O2 // // | \ A1 // // | \ // // | O3 A2 // // | B2 \ // // | \ // // | O4--O5---- // // | // // | B3 // // |-----------------> bandwidth // struct NetworkState { int bandwidth; float packet_loss; }; std::vector below{ {kBandwidthLow - 1, kPacketLossAtLowBw + 0.1f}, // B1 {(kBandwidthLow + kBandwidthHigh) / 2, (kPacketLossAtLowBw + kPacketLossAtHighBw) / 2 - kEpsilon}, // B2 {kBandwidthHigh + 1, kPacketLossAtHighBw - kEpsilon} // B3 }; std::vector on{ {kBandwidthLow, kPacketLossAtLowBw + 0.1f}, // O1 {kBandwidthLow, kPacketLossAtLowBw}, // O2 {(kBandwidthLow + kBandwidthHigh) / 2, (kPacketLossAtLowBw + kPacketLossAtHighBw) / 2}, // O3 {kBandwidthHigh, kPacketLossAtHighBw}, // O4 {kBandwidthHigh + 1, kPacketLossAtHighBw}, // O5 }; std::vector above{ {(kBandwidthLow + kBandwidthHigh) / 2, (kPacketLossAtLowBw + kPacketLossAtHighBw) / 2 + kEpsilon}, // A1 {kBandwidthHigh + 1, kPacketLossAtHighBw + kEpsilon}, // A2 }; // Test that FEC is turned off whenever we're below the curve, independent // of the starting FEC state. for (NetworkState net_state : below) { for (bool initial_fec_enabled : {false, true}) { auto states = CreateFecControllerPlrBased(initial_fec_enabled, curve, curve); UpdateNetworkMetrics(&states, net_state.bandwidth, net_state.packet_loss); CheckDecision(&states, false, net_state.packet_loss); } } // Test that FEC is turned on whenever we're on the curve or above it, // independent of the starting FEC state. for (const std::vector& states_list : {on, above}) { for (NetworkState net_state : states_list) { for (bool initial_fec_enabled : {false, true}) { auto states = CreateFecControllerPlrBased(initial_fec_enabled, curve, curve); UpdateNetworkMetrics(&states, net_state.bandwidth, net_state.packet_loss); CheckDecision(&states, true, net_state.packet_loss); } } } } TEST(FecControllerPlrBasedTest, FecAlwaysOff) { ThresholdCurve always_off_curve(0, 1.0f + kEpsilon, 0, 1.0f + kEpsilon); for (bool initial_fec_enabled : {false, true}) { for (int bandwidth : {0, 10000}) { for (float packet_loss : {0.0f, 0.5f, 1.0f}) { auto states = CreateFecControllerPlrBased( initial_fec_enabled, always_off_curve, always_off_curve); UpdateNetworkMetrics(&states, bandwidth, packet_loss); CheckDecision(&states, false, packet_loss); } } } } TEST(FecControllerPlrBasedTest, FecAlwaysOn) { ThresholdCurve always_on_curve(0, 0.0f, 0, 0.0f); for (bool initial_fec_enabled : {false, true}) { for (int bandwidth : {0, 10000}) { for (float packet_loss : {0.0f, 0.5f, 1.0f}) { auto states = CreateFecControllerPlrBased( initial_fec_enabled, always_on_curve, always_on_curve); UpdateNetworkMetrics(&states, bandwidth, packet_loss); CheckDecision(&states, true, packet_loss); } } } } #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) TEST(FecControllerPlrBasedDeathTest, InvalidConfig) { FecControllerPlrBasedTestStates states; std::unique_ptr mock_smoothing_filter( new NiceMock()); states.packet_loss_smoother = mock_smoothing_filter.get(); EXPECT_DEATH( states.controller.reset(new FecControllerPlrBased( FecControllerPlrBased::Config( true, ThresholdCurve(kDisablingBandwidthLow - 1, kEnablingPacketLossAtLowBw, kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw), ThresholdCurve( kDisablingBandwidthLow, kDisablingPacketLossAtLowBw, kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw), 0), std::move(mock_smoothing_filter))), "Check failed"); } #endif } // namespace webrtc