diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_plr_based.cc b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_plr_based.cc index d9b259b350..481f78d7cd 100644 --- a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_plr_based.cc +++ b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_plr_based.cc @@ -107,8 +107,8 @@ bool FecControllerPlrBased::FecDisablingDecision( if (!uplink_bandwidth_bps_ || !packet_loss) { return false; } else { - // Disable when below the curve or exactly on it. - return !config_.fec_disabling_threshold.IsAboveCurve( + // Disable when below the curve. + return config_.fec_disabling_threshold.IsBelowCurve( {static_cast(*uplink_bandwidth_bps_), *packet_loss}); } } diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_plr_based_unittest.cc b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_plr_based_unittest.cc index fceae874f3..0e23cb01f2 100644 --- a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_plr_based_unittest.cc +++ b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_plr_based_unittest.cc @@ -45,29 +45,38 @@ 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) { + 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, - ThresholdCurve(kEnablingBandwidthLow, kEnablingPacketLossAtLowBw, - kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw), - ThresholdCurve(kDisablingBandwidthLow, kDisablingPacketLossAtLowBw, - kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw), - 0), + 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 rtc::Optional& uplink_bandwidth_bps, const rtc::Optional& uplink_packet_loss) { @@ -90,6 +99,15 @@ void UpdateNetworkMetrics(FecControllerPlrBasedTestStates* states, } } +// TODO(eladalon): In a separate CL (to make reviewers' lives easier), use +// this where applicable. +void UpdateNetworkMetrics(FecControllerPlrBasedTestStates* states, + int uplink_bandwidth_bps, + float uplink_packet_loss) { + UpdateNetworkMetrics(states, rtc::Optional(uplink_bandwidth_bps), + rtc::Optional(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. @@ -105,25 +123,44 @@ void CheckDecision(FecControllerPlrBasedTestStates* states, } // 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) { - constexpr bool kInitialFecEnabled = true; - auto states = CreateFecControllerPlrBased(kInitialFecEnabled); - // Let uplink packet loss fraction be so low that would cause FEC to turn off - // if uplink bandwidth was known. - UpdateNetworkMetrics(&states, rtc::Optional(), - rtc::Optional(kDisablingPacketLossAtHighBw)); - CheckDecision(&states, kInitialFecEnabled, kDisablingPacketLossAtHighBw); + // 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, rtc::Optional(), + rtc::Optional(packet_loss)); + CheckDecision(&states, initial_fec_enabled, packet_loss); + } + } } TEST(FecControllerPlrBasedTest, OutputInitValueWhenUplinkPacketLossFractionUnknown) { - constexpr bool kInitialFecEnabled = true; - auto states = CreateFecControllerPlrBased(kInitialFecEnabled); - // Let uplink bandwidth be so low that would cause FEC to turn off if uplink - // bandwidth packet loss fraction was known. - UpdateNetworkMetrics(&states, rtc::Optional(kDisablingBandwidthLow - 1), - rtc::Optional()); - CheckDecision(&states, kInitialFecEnabled, 0.0); + // 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, rtc::Optional(bandwidth), + rtc::Optional()); + CheckDecision(&states, initial_fec_enabled, 0.0); + } + } } TEST(FecControllerPlrBasedTest, EnableFecForHighBandwidth) { @@ -209,23 +246,25 @@ TEST(FecControllerPlrBasedTest, MaintainFecOffForVeryLowBandwidth) { TEST(FecControllerPlrBasedTest, DisableFecForHighBandwidth) { auto states = CreateFecControllerPlrBased(true); + constexpr float kPacketLoss = kDisablingPacketLossAtHighBw - kEpsilon; UpdateNetworkMetrics(&states, rtc::Optional(kDisablingBandwidthHigh), - rtc::Optional(kDisablingPacketLossAtHighBw)); - CheckDecision(&states, false, kDisablingPacketLossAtHighBw); + rtc::Optional(kPacketLoss)); + CheckDecision(&states, false, kPacketLoss); } TEST(FecControllerPlrBasedTest, MaintainFecOnForHighBandwidth) { + // Note: Disabling happens when the value is strictly below the threshold. auto states = CreateFecControllerPlrBased(true); - constexpr float kPacketLoss = kDisablingPacketLossAtHighBw * 1.01f; UpdateNetworkMetrics(&states, rtc::Optional(kDisablingBandwidthHigh), - rtc::Optional(kPacketLoss)); - CheckDecision(&states, true, kPacketLoss); + rtc::Optional(kDisablingPacketLossAtHighBw)); + CheckDecision(&states, true, kDisablingPacketLossAtHighBw); } TEST(FecControllerPlrBasedTest, DisableFecOnMediumBandwidth) { auto states = CreateFecControllerPlrBased(true); constexpr float kPacketLoss = - (kDisablingPacketLossAtLowBw + kDisablingPacketLossAtHighBw) / 2.0f; + (kDisablingPacketLossAtLowBw + kDisablingPacketLossAtHighBw) / 2.0f - + kEpsilon; UpdateNetworkMetrics( &states, rtc::Optional((kDisablingBandwidthHigh + kDisablingBandwidthLow) / @@ -237,7 +276,7 @@ TEST(FecControllerPlrBasedTest, DisableFecOnMediumBandwidth) { TEST(FecControllerPlrBasedTest, MaintainFecOnForMediumBandwidth) { auto states = CreateFecControllerPlrBased(true); constexpr float kPacketLoss = kDisablingPacketLossAtLowBw * 0.51f + - kDisablingPacketLossAtHighBw * 0.49f; + kDisablingPacketLossAtHighBw * 0.49f - kEpsilon; UpdateNetworkMetrics( &states, rtc::Optional((kEnablingBandwidthHigh + kDisablingBandwidthLow) / 2), @@ -247,9 +286,10 @@ TEST(FecControllerPlrBasedTest, MaintainFecOnForMediumBandwidth) { TEST(FecControllerPlrBasedTest, DisableFecForLowBandwidth) { auto states = CreateFecControllerPlrBased(true); + constexpr float kPacketLoss = kDisablingPacketLossAtLowBw - kEpsilon; UpdateNetworkMetrics(&states, rtc::Optional(kDisablingBandwidthLow), - rtc::Optional(kDisablingPacketLossAtLowBw)); - CheckDecision(&states, false, kDisablingPacketLossAtLowBw); + rtc::Optional(kPacketLoss)); + CheckDecision(&states, false, kPacketLoss); } TEST(FecControllerPlrBasedTest, DisableFecForVeryLowBandwidth) { @@ -284,10 +324,9 @@ TEST(FecControllerPlrBasedTest, CheckBehaviorOnChangingNetworkMetrics) { rtc::Optional(kEnablingPacketLossAtHighBw)); CheckDecision(&states, true, kEnablingPacketLossAtHighBw); - UpdateNetworkMetrics( - &states, rtc::Optional(kDisablingBandwidthHigh), - rtc::Optional(kDisablingPacketLossAtHighBw * 1.01f)); - CheckDecision(&states, true, kDisablingPacketLossAtHighBw * 1.01f); + UpdateNetworkMetrics(&states, rtc::Optional(kDisablingBandwidthHigh), + rtc::Optional(kDisablingPacketLossAtHighBw)); + CheckDecision(&states, true, kDisablingPacketLossAtHighBw); UpdateNetworkMetrics(&states, rtc::Optional(kDisablingBandwidthHigh + 1), rtc::Optional(0.0)); @@ -335,16 +374,125 @@ TEST(FecControllerPlrBasedTest, CheckBehaviorOnSpecialCurves) { rtc::Optional(kEnablingPacketLossAtHighBw)); CheckDecision(&states, true, kEnablingPacketLossAtHighBw); - UpdateNetworkMetrics( - &states, rtc::Optional(kDisablingBandwidthHigh), - rtc::Optional(kDisablingPacketLossAtHighBw * 1.01f)); - CheckDecision(&states, true, kDisablingPacketLossAtHighBw * 1.01f); + UpdateNetworkMetrics(&states, rtc::Optional(kDisablingBandwidthHigh), + rtc::Optional(kDisablingPacketLossAtHighBw)); + CheckDecision(&states, true, kDisablingPacketLossAtHighBw); UpdateNetworkMetrics(&states, rtc::Optional(kDisablingBandwidthHigh + 1), rtc::Optional(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 (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; diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_rplr_based.cc b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_rplr_based.cc index d21e8bef52..a51c277b66 100644 --- a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_rplr_based.cc +++ b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_rplr_based.cc @@ -68,8 +68,8 @@ bool FecControllerRplrBased::FecDisablingDecision() const { if (!uplink_bandwidth_bps_ || !uplink_recoverable_packet_loss_) { return false; } else { - // Disable when below the curve or exactly on it. - return !config_.fec_disabling_threshold.IsAboveCurve( + // Disable when below the curve. + return config_.fec_disabling_threshold.IsBelowCurve( {static_cast(*uplink_bandwidth_bps_), *uplink_recoverable_packet_loss_}); } diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_rplr_based_unittest.cc b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_rplr_based_unittest.cc index 3884df61a0..f0272d4831 100644 --- a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_rplr_based_unittest.cc +++ b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_rplr_based_unittest.cc @@ -42,6 +42,8 @@ constexpr float kEnablingRecoverablePacketLossAtLowBw = 0.1f; constexpr int kEnablingBandwidthHigh = 64000; constexpr float kEnablingRecoverablePacketLossAtHighBw = 0.05f; +constexpr float kEpsilon = 1e-5f; + rtc::Optional GetRandomProbabilityOrUnknown() { std::random_device rd; std::mt19937 generator(rd()); @@ -98,7 +100,7 @@ void UpdateNetworkMetrics( FecControllerRplrBased* controller, const rtc::Optional& uplink_bandwidth_bps, const rtc::Optional& uplink_recoveralbe_packet_loss) { - // FecControllerRplrBased doesn't current use the PLR (general packet-loss + // FecControllerRplrBased doesn't currently use the PLR (general packet-loss // rate) at all. (This might be changed in the future.) The unit-tests will // use a random value (including unknown), to show this does not interfere. UpdateNetworkMetrics(controller, uplink_bandwidth_bps, @@ -106,6 +108,15 @@ void UpdateNetworkMetrics( uplink_recoveralbe_packet_loss); } +// TODO(eladalon): In a separate CL (to make reviewers' lives easier), use +// this where applicable. +void UpdateNetworkMetrics(FecControllerRplrBased* controller, + int uplink_bandwidth_bps, + float uplink_recoveralbe_packet_loss) { + UpdateNetworkMetrics(controller, rtc::Optional(uplink_bandwidth_bps), + rtc::Optional(uplink_recoveralbe_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. @@ -129,29 +140,46 @@ void CheckDecision(FecControllerRplrBased* controller, } // namespace -TEST(FecControllerRplrBasedTest, OutputInitValueWhenUplinkBandwidthUnknown) { +TEST(FecControllerRplrBasedTest, OutputInitValueBeforeAnyInputsAreReceived) { for (bool initial_fec_enabled : {false, true}) { auto controller = CreateFecControllerRplrBased(initial_fec_enabled); - // Let uplink recoverable packet loss fraction be so low that it - // would cause FEC to turn off if uplink bandwidth was known. - UpdateNetworkMetrics( - controller.get(), rtc::Optional(), - rtc::Optional(kDisablingRecoverablePacketLossAtHighBw)); - CheckDecision(controller.get(), initial_fec_enabled, - kDisablingRecoverablePacketLossAtHighBw); + CheckDecision(controller.get(), initial_fec_enabled, 0); + } +} + +TEST(FecControllerRplrBasedTest, OutputInitValueWhenUplinkBandwidthUnknown) { + // Regardless of the initial FEC state and the recoverable-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 recoverable_packet_loss : + {kDisablingRecoverablePacketLossAtHighBw - kEpsilon, + kDisablingRecoverablePacketLossAtHighBw, + kDisablingRecoverablePacketLossAtHighBw + kEpsilon, + kEnablingRecoverablePacketLossAtHighBw - kEpsilon, + kEnablingRecoverablePacketLossAtHighBw, + kEnablingRecoverablePacketLossAtHighBw + kEpsilon}) { + auto controller = CreateFecControllerRplrBased(initial_fec_enabled); + UpdateNetworkMetrics(controller.get(), rtc::Optional(), + rtc::Optional(recoverable_packet_loss)); + CheckDecision(controller.get(), initial_fec_enabled, + recoverable_packet_loss); + } } } TEST(FecControllerRplrBasedTest, - OutputInitValueWhenUplinkPacketLossFractionUnknown) { + OutputInitValueWhenUplinkRecoverablePacketLossFractionUnknown) { + // Regardless of the initial FEC state and the BWE, the initial FEC state + // is maintained as long as the recoverable-packet-loss rate is unknown. for (bool initial_fec_enabled : {false, true}) { - auto controller = CreateFecControllerRplrBased(initial_fec_enabled); - // Let uplink bandwidth be so low that it would cause FEC to turn off - // if uplink bandwidth packet loss fraction was known. - UpdateNetworkMetrics(controller.get(), - rtc::Optional(kDisablingBandwidthLow - 1), - rtc::Optional()); - CheckDecision(controller.get(), initial_fec_enabled, 0.0); + for (int bandwidth : {kDisablingBandwidthLow - 1, kDisablingBandwidthLow, + kDisablingBandwidthLow + 1, kEnablingBandwidthLow - 1, + kEnablingBandwidthLow, kEnablingBandwidthLow + 1}) { + auto controller = CreateFecControllerRplrBased(initial_fec_enabled); + UpdateNetworkMetrics(controller.get(), rtc::Optional(bandwidth), + rtc::Optional()); + CheckDecision(controller.get(), initial_fec_enabled, 0.0); + } } } @@ -185,34 +213,36 @@ TEST(FecControllerRplrBasedTest, UpdateMultipleNetworkMetricsAtOnce) { TEST(FecControllerRplrBasedTest, MaintainFecOffForHighBandwidth) { auto controller = CreateFecControllerRplrBased(false); - constexpr float kPacketLoss = kEnablingRecoverablePacketLossAtHighBw * 0.99f; + constexpr float kRecoverablePacketLoss = + kEnablingRecoverablePacketLossAtHighBw * 0.99f; UpdateNetworkMetrics(controller.get(), rtc::Optional(kEnablingBandwidthHigh), - rtc::Optional(kPacketLoss)); - CheckDecision(controller.get(), false, kPacketLoss); + rtc::Optional(kRecoverablePacketLoss)); + CheckDecision(controller.get(), false, kRecoverablePacketLoss); } TEST(FecControllerRplrBasedTest, EnableFecForMediumBandwidth) { auto controller = CreateFecControllerRplrBased(false); - constexpr float kPacketLoss = (kEnablingRecoverablePacketLossAtLowBw + - kEnablingRecoverablePacketLossAtHighBw) / - 2.0; + constexpr float kRecoverablePacketLoss = + (kEnablingRecoverablePacketLossAtLowBw + + kEnablingRecoverablePacketLossAtHighBw) / 2.0; UpdateNetworkMetrics( controller.get(), rtc::Optional((kEnablingBandwidthHigh + kEnablingBandwidthLow) / 2), - rtc::Optional(kPacketLoss)); - CheckDecision(controller.get(), true, kPacketLoss); + rtc::Optional(kRecoverablePacketLoss)); + CheckDecision(controller.get(), true, kRecoverablePacketLoss); } TEST(FecControllerRplrBasedTest, MaintainFecOffForMediumBandwidth) { auto controller = CreateFecControllerRplrBased(false); - constexpr float kPacketLoss = kEnablingRecoverablePacketLossAtLowBw * 0.49f + - kEnablingRecoverablePacketLossAtHighBw * 0.51f; + constexpr float kRecoverablePacketLoss = + kEnablingRecoverablePacketLossAtLowBw * 0.49f + + kEnablingRecoverablePacketLossAtHighBw * 0.51f; UpdateNetworkMetrics( controller.get(), rtc::Optional((kEnablingBandwidthHigh + kEnablingBandwidthLow) / 2), - rtc::Optional(kPacketLoss)); - CheckDecision(controller.get(), false, kPacketLoss); + rtc::Optional(kRecoverablePacketLoss)); + CheckDecision(controller.get(), false, kRecoverablePacketLoss); } TEST(FecControllerRplrBasedTest, EnableFecForLowBandwidth) { @@ -225,11 +255,12 @@ TEST(FecControllerRplrBasedTest, EnableFecForLowBandwidth) { TEST(FecControllerRplrBasedTest, MaintainFecOffForLowBandwidth) { auto controller = CreateFecControllerRplrBased(false); - constexpr float kPacketLoss = kEnablingRecoverablePacketLossAtLowBw * 0.99f; + constexpr float kRecoverablePacketLoss = + kEnablingRecoverablePacketLossAtLowBw * 0.99f; UpdateNetworkMetrics(controller.get(), rtc::Optional(kEnablingBandwidthLow), - rtc::Optional(kPacketLoss)); - CheckDecision(controller.get(), false, kPacketLoss); + rtc::Optional(kRecoverablePacketLoss)); + CheckDecision(controller.get(), false, kRecoverablePacketLoss); } TEST(FecControllerRplrBasedTest, MaintainFecOffForVeryLowBandwidth) { @@ -244,53 +275,57 @@ TEST(FecControllerRplrBasedTest, MaintainFecOffForVeryLowBandwidth) { TEST(FecControllerRplrBasedTest, DisableFecForHighBandwidth) { auto controller = CreateFecControllerRplrBased(true); - UpdateNetworkMetrics( - controller.get(), rtc::Optional(kDisablingBandwidthHigh), - rtc::Optional(kDisablingRecoverablePacketLossAtHighBw)); - CheckDecision(controller.get(), false, - kDisablingRecoverablePacketLossAtHighBw); + constexpr float kRecoverablePacketLoss = + kDisablingRecoverablePacketLossAtHighBw - kEpsilon; + UpdateNetworkMetrics(controller.get(), + rtc::Optional(kDisablingBandwidthHigh), + rtc::Optional(kRecoverablePacketLoss)); + CheckDecision(controller.get(), false, kRecoverablePacketLoss); } TEST(FecControllerRplrBasedTest, MaintainFecOnForHighBandwidth) { + // Note: Disabling happens when the value is strictly below the threshold. auto controller = CreateFecControllerRplrBased(true); - constexpr float kPacketLoss = kDisablingRecoverablePacketLossAtHighBw * 1.01f; - UpdateNetworkMetrics(controller.get(), - rtc::Optional(kDisablingBandwidthHigh), - rtc::Optional(kPacketLoss)); - CheckDecision(controller.get(), true, kPacketLoss); + UpdateNetworkMetrics( + controller.get(), rtc::Optional(kDisablingBandwidthHigh), + rtc::Optional(kDisablingRecoverablePacketLossAtHighBw)); + CheckDecision(controller.get(), true, + kDisablingRecoverablePacketLossAtHighBw); } TEST(FecControllerRplrBasedTest, DisableFecOnMediumBandwidth) { auto controller = CreateFecControllerRplrBased(true); - constexpr float kPacketLoss = (kDisablingRecoverablePacketLossAtLowBw + - kDisablingRecoverablePacketLossAtHighBw) / - 2.0f; + constexpr float kRecoverablePacketLoss = + ((kDisablingRecoverablePacketLossAtLowBw + + kDisablingRecoverablePacketLossAtHighBw) / 2.0f) - kEpsilon; UpdateNetworkMetrics( controller.get(), rtc::Optional((kDisablingBandwidthHigh + kDisablingBandwidthLow) / 2), - rtc::Optional(kPacketLoss)); - CheckDecision(controller.get(), false, kPacketLoss); + rtc::Optional(kRecoverablePacketLoss)); + CheckDecision(controller.get(), false, kRecoverablePacketLoss); } TEST(FecControllerRplrBasedTest, MaintainFecOnForMediumBandwidth) { auto controller = CreateFecControllerRplrBased(true); - constexpr float kPacketLoss = kDisablingRecoverablePacketLossAtLowBw * 0.51f + - kDisablingRecoverablePacketLossAtHighBw * 0.49f; + constexpr float kRecoverablePacketLoss = + kDisablingRecoverablePacketLossAtLowBw * 0.51f + + kDisablingRecoverablePacketLossAtHighBw * 0.49f - kEpsilon; UpdateNetworkMetrics( controller.get(), rtc::Optional((kEnablingBandwidthHigh + kDisablingBandwidthLow) / 2), - rtc::Optional(kPacketLoss)); - CheckDecision(controller.get(), true, kPacketLoss); + rtc::Optional(kRecoverablePacketLoss)); + CheckDecision(controller.get(), true, kRecoverablePacketLoss); } TEST(FecControllerRplrBasedTest, DisableFecForLowBandwidth) { auto controller = CreateFecControllerRplrBased(true); - UpdateNetworkMetrics( - controller.get(), rtc::Optional(kDisablingBandwidthLow), - rtc::Optional(kDisablingRecoverablePacketLossAtLowBw)); - CheckDecision(controller.get(), false, - kDisablingRecoverablePacketLossAtLowBw); + constexpr float kRecoverablePacketLoss = + kDisablingRecoverablePacketLossAtLowBw - kEpsilon; + UpdateNetworkMetrics(controller.get(), + rtc::Optional(kDisablingBandwidthLow), + rtc::Optional(kRecoverablePacketLoss)); + CheckDecision(controller.get(), false, kRecoverablePacketLoss); } TEST(FecControllerRplrBasedTest, DisableFecForVeryLowBandwidth) { @@ -333,9 +368,9 @@ TEST(FecControllerRplrBasedTest, CheckBehaviorOnChangingNetworkMetrics) { UpdateNetworkMetrics( controller.get(), rtc::Optional(kDisablingBandwidthHigh), - rtc::Optional(kDisablingRecoverablePacketLossAtHighBw * 1.01f)); + rtc::Optional(kDisablingRecoverablePacketLossAtHighBw)); CheckDecision(controller.get(), true, - kDisablingRecoverablePacketLossAtHighBw * 1.01f); + kDisablingRecoverablePacketLossAtHighBw); UpdateNetworkMetrics(controller.get(), rtc::Optional(kDisablingBandwidthHigh + 1), @@ -386,9 +421,8 @@ TEST(FecControllerRplrBasedTest, CheckBehaviorOnSpecialCurves) { UpdateNetworkMetrics( &controller, rtc::Optional(kDisablingBandwidthHigh), - rtc::Optional(kDisablingRecoverablePacketLossAtHighBw * 1.01f)); - CheckDecision(&controller, true, - kDisablingRecoverablePacketLossAtHighBw * 1.01f); + rtc::Optional(kDisablingRecoverablePacketLossAtHighBw)); + CheckDecision(&controller, true, kDisablingRecoverablePacketLossAtHighBw); UpdateNetworkMetrics(&controller, rtc::Optional(kDisablingBandwidthHigh + 1), @@ -396,6 +430,120 @@ TEST(FecControllerRplrBasedTest, CheckBehaviorOnSpecialCurves) { CheckDecision(&controller, false, 0.0); } +TEST(FecControllerRplrBasedTest, SingleThresholdCurveForEnablingAndDisabling) { + // Note: To avoid numerical errors, keep kRecoverablePacketLossAtLowBw and + // kRecoverablePacketLossAthighBw as (negative) integer powers of 2. + // This is mostly relevant for the O3 case. + constexpr int kBandwidthLow = 10000; + constexpr float kRecoverablePacketLossAtLowBw = 0.25f; + constexpr int kBandwidthHigh = 20000; + constexpr float kRecoverablePacketLossAtHighBw = 0.125f; + auto curve = ThresholdCurve(kBandwidthLow, kRecoverablePacketLossAtLowBw, + kBandwidthHigh, kRecoverablePacketLossAtHighBw); + + // B* stands for "below-curve", O* for "on-curve", and A* for "above-curve". + // + // // + // recoverable ^ // + // packet-loss | | // + // | B1 O1 // + // | | // + // | O2 // + // | \ A1 // + // | \ // + // | O3 A2 // + // | B2 \ // + // | \ // + // | O4--O5---- // + // | // + // | B3 // + // |-----------------> bandwidth // + + struct NetworkState { + int bandwidth; + float recoverable_packet_loss; + }; + + std::vector below{ + {kBandwidthLow - 1, kRecoverablePacketLossAtLowBw + 0.1f}, // B1 + {(kBandwidthLow + kBandwidthHigh) / 2, + (kRecoverablePacketLossAtLowBw + kRecoverablePacketLossAtHighBw) / 2 - + kEpsilon}, // B2 + {kBandwidthHigh + 1, kRecoverablePacketLossAtHighBw - kEpsilon} // B3 + }; + + std::vector on{ + {kBandwidthLow, kRecoverablePacketLossAtLowBw + 0.1f}, // O1 + {kBandwidthLow, kRecoverablePacketLossAtLowBw}, // O2 + {(kBandwidthLow + kBandwidthHigh) / 2, + (kRecoverablePacketLossAtLowBw + kRecoverablePacketLossAtHighBw) / + 2}, // O3 + {kBandwidthHigh, kRecoverablePacketLossAtHighBw}, // O4 + {kBandwidthHigh + 1, kRecoverablePacketLossAtHighBw}, // O5 + }; + + std::vector above{ + {(kBandwidthLow + kBandwidthHigh) / 2, + (kRecoverablePacketLossAtLowBw + kRecoverablePacketLossAtHighBw) / 2 + + kEpsilon}, // A1 + {kBandwidthHigh + 1, kRecoverablePacketLossAtHighBw + 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}) { + FecControllerRplrBased controller( + FecControllerRplrBased::Config(initial_fec_enabled, curve, curve)); + UpdateNetworkMetrics(&controller, net_state.bandwidth, + net_state.recoverable_packet_loss); + CheckDecision(&controller, false, net_state.recoverable_packet_loss); + } + } + + // Test that FEC is turned on whenever we're on the curve or above it, + // independent of the starting FEC state. + for (std::vector states_list : {on, above}) { + for (NetworkState net_state : states_list) { + for (bool initial_fec_enabled : {false, true}) { + FecControllerRplrBased controller( + FecControllerRplrBased::Config(initial_fec_enabled, curve, curve)); + UpdateNetworkMetrics(&controller, net_state.bandwidth, + net_state.recoverable_packet_loss); + CheckDecision(&controller, true, net_state.recoverable_packet_loss); + } + } + } +} + +TEST(FecControllerRplrBasedTest, 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 recoverable_packet_loss : {0.0f, 0.5f, 1.0f}) { + FecControllerRplrBased controller(FecControllerRplrBased::Config( + initial_fec_enabled, always_off_curve, always_off_curve)); + UpdateNetworkMetrics(&controller, bandwidth, recoverable_packet_loss); + CheckDecision(&controller, false, recoverable_packet_loss); + } + } + } +} + +TEST(FecControllerRplrBasedTest, 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 recoverable_packet_loss : {0.0f, 0.5f, 1.0f}) { + FecControllerRplrBased controller(FecControllerRplrBased::Config( + initial_fec_enabled, always_on_curve, always_on_curve)); + UpdateNetworkMetrics(&controller, bandwidth, recoverable_packet_loss); + CheckDecision(&controller, true, recoverable_packet_loss); + } + } + } +} + #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) TEST(FecControllerRplrBasedDeathTest, InvalidConfig) { EXPECT_DEATH(