blob: 743b0871633fd4f8eb137053899c99af2f117568 [file] [log] [blame]
/*
* 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 <utility>
#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<FecControllerPlrBased> controller;
MockSmoothingFilter* packet_loss_smoother;
};
FecControllerPlrBasedTestStates CreateFecControllerPlrBased(
bool initial_fec_enabled,
const ThresholdCurve& enabling_curve,
const ThresholdCurve& disabling_curve) {
FecControllerPlrBasedTestStates states;
std::unique_ptr<MockSmoothingFilter> mock_smoothing_filter(
new NiceMock<MockSmoothingFilter>());
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<int>& uplink_bandwidth_bps,
const absl::optional<float>& 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<MockSmoothingFilter> mock_smoothing_filter(
new NiceMock<MockSmoothingFilter>());
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<NetworkState> below{
{kBandwidthLow - 1, kPacketLossAtLowBw + 0.1f}, // B1
{(kBandwidthLow + kBandwidthHigh) / 2,
(kPacketLossAtLowBw + kPacketLossAtHighBw) / 2 - kEpsilon}, // B2
{kBandwidthHigh + 1, kPacketLossAtHighBw - kEpsilon} // B3
};
std::vector<NetworkState> 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<NetworkState> 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<NetworkState>& 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<MockSmoothingFilter> mock_smoothing_filter(
new NiceMock<MockSmoothingFilter>());
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