Adding FecController to audio network adaptor.

BUG=webrtc:6303

Review-Url: https://codereview.webrtc.org/2337103006
Cr-Commit-Position: refs/heads/master@{#14351}
diff --git a/webrtc/modules/BUILD.gn b/webrtc/modules/BUILD.gn
index 11a5ef0..6b7928f 100644
--- a/webrtc/modules/BUILD.gn
+++ b/webrtc/modules/BUILD.gn
@@ -243,6 +243,7 @@
       "audio_coding/audio_network_adaptor/channel_controller_unittest.cc",
       "audio_coding/audio_network_adaptor/controller_manager_unittest.cc",
       "audio_coding/audio_network_adaptor/dtx_controller_unittest.cc",
+      "audio_coding/audio_network_adaptor/fec_controller_unittest.cc",
       "audio_coding/audio_network_adaptor/frame_length_controller_unittest.cc",
       "audio_coding/audio_network_adaptor/mock/mock_controller.h",
       "audio_coding/audio_network_adaptor/mock/mock_controller_manager.h",
diff --git a/webrtc/modules/audio_coding/BUILD.gn b/webrtc/modules/audio_coding/BUILD.gn
index b55829d..eaf2e74 100644
--- a/webrtc/modules/audio_coding/BUILD.gn
+++ b/webrtc/modules/audio_coding/BUILD.gn
@@ -711,6 +711,8 @@
     "audio_network_adaptor/controller_manager.h",
     "audio_network_adaptor/dtx_controller.cc",
     "audio_network_adaptor/dtx_controller.h",
+    "audio_network_adaptor/fec_controller.cc",
+    "audio_network_adaptor/fec_controller.h",
     "audio_network_adaptor/frame_length_controller.cc",
     "audio_network_adaptor/frame_length_controller.h",
     "audio_network_adaptor/include/audio_network_adaptor.h",
diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/audio_network_adaptor.gypi b/webrtc/modules/audio_coding/audio_network_adaptor/audio_network_adaptor.gypi
index ea3ef13..af6ba55 100644
--- a/webrtc/modules/audio_coding/audio_network_adaptor/audio_network_adaptor.gypi
+++ b/webrtc/modules/audio_coding/audio_network_adaptor/audio_network_adaptor.gypi
@@ -24,6 +24,8 @@
         'controller_manager.h',
         'dtx_controller.h',
         'dtx_controller.cc',
+        'fec_controller.h',
+        'fec_controller.cc',
         'frame_length_controller.cc',
         'frame_length_controller.h',
         'include/audio_network_adaptor.h',
diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller.cc b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller.cc
new file mode 100644
index 0000000..fcf1959
--- /dev/null
+++ b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller.cc
@@ -0,0 +1,136 @@
+/*
+ *  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 "webrtc/modules/audio_coding/audio_network_adaptor/fec_controller.h"
+
+#include <limits>
+#include <utility>
+
+#include "webrtc/base/checks.h"
+
+namespace webrtc {
+
+FecController::Config::Threshold::Threshold(int low_bandwidth_bps,
+                                            float low_bandwidth_packet_loss,
+                                            int high_bandwidth_bps,
+                                            float high_bandwidth_packet_loss)
+    : low_bandwidth_bps(low_bandwidth_bps),
+      low_bandwidth_packet_loss(low_bandwidth_packet_loss),
+      high_bandwidth_bps(high_bandwidth_bps),
+      high_bandwidth_packet_loss(high_bandwidth_packet_loss) {}
+
+FecController::Config::Config(bool initial_fec_enabled,
+                              const Threshold& fec_enabling_threshold,
+                              const Threshold& fec_disabling_threshold,
+                              int time_constant_ms,
+                              Clock* clock)
+    : initial_fec_enabled(initial_fec_enabled),
+      fec_enabling_threshold(fec_enabling_threshold),
+      fec_disabling_threshold(fec_disabling_threshold),
+      time_constant_ms(time_constant_ms),
+      clock(clock) {}
+
+FecController::FecController(const Config& config)
+    : config_(config),
+      fec_enabled_(config.initial_fec_enabled),
+      packet_loss_smoothed_(
+          new SmoothingFilterImpl(config_.time_constant_ms, config_.clock)),
+      fec_enabling_threshold_info_(config_.fec_enabling_threshold),
+      fec_disabling_threshold_info_(config_.fec_disabling_threshold) {
+  RTC_DCHECK_LE(fec_enabling_threshold_info_.slope, 0);
+  RTC_DCHECK_LE(fec_enabling_threshold_info_.slope, 0);
+  RTC_DCHECK_LE(
+      GetPacketLossThreshold(config_.fec_enabling_threshold.low_bandwidth_bps,
+                             config_.fec_disabling_threshold,
+                             fec_disabling_threshold_info_),
+      config_.fec_enabling_threshold.low_bandwidth_packet_loss);
+  RTC_DCHECK_LE(
+      GetPacketLossThreshold(config_.fec_enabling_threshold.high_bandwidth_bps,
+                             config_.fec_disabling_threshold,
+                             fec_disabling_threshold_info_),
+      config_.fec_enabling_threshold.high_bandwidth_packet_loss);
+}
+
+FecController::FecController(const Config& config,
+                             std::unique_ptr<SmoothingFilter> smoothing_filter)
+    : FecController(config) {
+  packet_loss_smoothed_ = std::move(smoothing_filter);
+}
+
+FecController::~FecController() = default;
+
+void FecController::MakeDecision(
+    const NetworkMetrics& metrics,
+    AudioNetworkAdaptor::EncoderRuntimeConfig* config) {
+  RTC_DCHECK(!config->enable_fec);
+  RTC_DCHECK(!config->uplink_packet_loss_fraction);
+
+  if (metrics.uplink_packet_loss_fraction)
+    packet_loss_smoothed_->AddSample(*metrics.uplink_packet_loss_fraction);
+
+  fec_enabled_ = fec_enabled_ ? !FecDisablingDecision(metrics)
+                              : FecEnablingDecision(metrics);
+
+  config->enable_fec = rtc::Optional<bool>(fec_enabled_);
+
+  auto packet_loss_fraction = packet_loss_smoothed_->GetAverage();
+  config->uplink_packet_loss_fraction = rtc::Optional<float>(
+      packet_loss_fraction ? *packet_loss_fraction : 0.0);
+}
+
+FecController::ThresholdInfo::ThresholdInfo(
+    const Config::Threshold& threshold) {
+  int bandwidth_diff_bps =
+      threshold.high_bandwidth_bps - threshold.low_bandwidth_bps;
+  float packet_loss_diff = threshold.high_bandwidth_packet_loss -
+                           threshold.low_bandwidth_packet_loss;
+  slope = bandwidth_diff_bps == 0 ? 0.0 : packet_loss_diff / bandwidth_diff_bps;
+  offset =
+      threshold.low_bandwidth_packet_loss - slope * threshold.low_bandwidth_bps;
+}
+
+float FecController::GetPacketLossThreshold(
+    int bandwidth_bps,
+    const Config::Threshold& threshold,
+    const ThresholdInfo& threshold_info) const {
+  if (bandwidth_bps < threshold.low_bandwidth_bps)
+    return std::numeric_limits<float>::max();
+  if (bandwidth_bps >= threshold.high_bandwidth_bps)
+    return threshold.high_bandwidth_packet_loss;
+  return threshold_info.offset + threshold_info.slope * bandwidth_bps;
+}
+
+bool FecController::FecEnablingDecision(const NetworkMetrics& metrics) const {
+  if (!metrics.uplink_bandwidth_bps)
+    return false;
+
+  auto packet_loss = packet_loss_smoothed_->GetAverage();
+  if (!packet_loss)
+    return false;
+
+  return *packet_loss >= GetPacketLossThreshold(*metrics.uplink_bandwidth_bps,
+                                                config_.fec_enabling_threshold,
+                                                fec_enabling_threshold_info_);
+}
+
+bool FecController::FecDisablingDecision(const NetworkMetrics& metrics) const {
+  if (!metrics.uplink_bandwidth_bps)
+    return false;
+
+  auto packet_loss = packet_loss_smoothed_->GetAverage();
+  if (!packet_loss)
+    return false;
+
+  return *packet_loss <= GetPacketLossThreshold(*metrics.uplink_bandwidth_bps,
+                                                config_.fec_disabling_threshold,
+                                                fec_disabling_threshold_info_);
+}
+
+}  // namespace webrtc
diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller.h b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller.h
new file mode 100644
index 0000000..17aa65f
--- /dev/null
+++ b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller.h
@@ -0,0 +1,105 @@
+/*
+ *  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 WEBRTC_MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_FEC_CONTROLLER_H_
+#define WEBRTC_MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_FEC_CONTROLLER_H_
+
+#include <memory>
+
+#include "webrtc/base/constructormagic.h"
+#include "webrtc/modules/audio_coding/audio_network_adaptor/controller.h"
+#include "webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter.h"
+
+namespace webrtc {
+
+class FecController final : public Controller {
+ public:
+  struct Config {
+    struct Threshold {
+      // Threshold defines a curve in the bandwidth/packet-loss domain. The
+      // curve is characterized by the two conjunction points: A and B.
+      //
+      // packet ^  |
+      //  loss  | A|
+      //        |   \        A: (low_bandwidth_bps, low_bandwidth_packet_loss)
+      //        |    \       B: (high_bandwidth_bps, high_bandwidth_packet_loss)
+      //        |    B\________
+      //        |---------------> bandwidth
+      Threshold(int low_bandwidth_bps,
+                float low_bandwidth_packet_loss,
+                int high_bandwidth_bps,
+                float high_bandwidth_packet_loss);
+      int low_bandwidth_bps;
+      float low_bandwidth_packet_loss;
+      int high_bandwidth_bps;
+      float high_bandwidth_packet_loss;
+    };
+
+    // |fec_enabling_threshold| defines a curve, above which FEC should be
+    // enabled. |fec_disabling_threshold| defines a curve, under which FEC
+    // should be disabled. See below
+    //
+    // packet-loss ^   |  |
+    //             |   |  |   FEC
+    //             |    \  \   ON
+    //             | FEC \  \_______ fec_enabling_threshold
+    //             | OFF  \_________ fec_disabling_threshold
+    //             |-----------------> bandwidth
+    Config(bool initial_fec_enabled,
+           const Threshold& fec_enabling_threshold,
+           const Threshold& fec_disabling_threshold,
+           int time_constant_ms,
+           Clock* clock);
+    bool initial_fec_enabled;
+    Threshold fec_enabling_threshold;
+    Threshold fec_disabling_threshold;
+    int time_constant_ms;
+    Clock* clock;
+  };
+
+  explicit FecController(const Config& config);
+
+  // Dependency injection for testing.
+  FecController(const Config& config,
+                std::unique_ptr<SmoothingFilter> smoothing_filter);
+
+  ~FecController() override;
+
+  void MakeDecision(const NetworkMetrics& metrics,
+                    AudioNetworkAdaptor::EncoderRuntimeConfig* config) override;
+
+ private:
+  // Characterize Threshold with packet_loss = slope * bandwidth + offset.
+  struct ThresholdInfo {
+    explicit ThresholdInfo(const Config::Threshold& threshold);
+    float slope;
+    float offset;
+  };
+
+  float GetPacketLossThreshold(int bandwidth_bps,
+                               const Config::Threshold& threshold,
+                               const ThresholdInfo& threshold_info) const;
+
+  bool FecEnablingDecision(const NetworkMetrics& metrics) const;
+  bool FecDisablingDecision(const NetworkMetrics& metrics) const;
+
+  const Config config_;
+  bool fec_enabled_;
+  std::unique_ptr<SmoothingFilter> packet_loss_smoothed_;
+
+  const ThresholdInfo fec_enabling_threshold_info_;
+  const ThresholdInfo fec_disabling_threshold_info_;
+
+  RTC_DISALLOW_COPY_AND_ASSIGN(FecController);
+};
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_FEC_CONTROLLER_H_
diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_unittest.cc b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_unittest.cc
new file mode 100644
index 0000000..c66482f
--- /dev/null
+++ b/webrtc/modules/audio_coding/audio_network_adaptor/fec_controller_unittest.cc
@@ -0,0 +1,312 @@
+/*
+ *  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 <utility>
+
+#include "webrtc/modules/audio_coding/audio_network_adaptor/fec_controller.h"
+#include "webrtc/modules/audio_coding/audio_network_adaptor/mock/mock_smoothing_filter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc {
+
+using ::testing::NiceMock;
+using ::testing::Return;
+using ::testing::_;
+
+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;
+
+struct FecControllerStates {
+  std::unique_ptr<FecController> controller;
+  MockSmoothingFilter* packet_loss_smoothed;
+};
+
+FecControllerStates CreateFecController(bool initial_fec_enabled) {
+  FecControllerStates states;
+  std::unique_ptr<MockSmoothingFilter> mock_smoothing_filter(
+      new NiceMock<MockSmoothingFilter>());
+  states.packet_loss_smoothed = mock_smoothing_filter.get();
+  EXPECT_CALL(*states.packet_loss_smoothed, Die());
+  using Threshold = FecController::Config::Threshold;
+  states.controller.reset(new FecController(
+      FecController::Config(
+          initial_fec_enabled,
+          Threshold(kEnablingBandwidthLow, kEnablingPacketLossAtLowBw,
+                    kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw),
+          Threshold(kDisablingBandwidthLow, kDisablingPacketLossAtLowBw,
+                    kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw),
+          0, nullptr),
+      std::move(mock_smoothing_filter)));
+  return states;
+}
+
+// Checks that the FEC decision given by |states->controller->MakeDecision|
+// matches |expected_enable_fec|. It also checks that
+// |uplink_packet_loss_fraction| returned by |states->controller->MakeDecision|
+// matches |uplink_packet_loss|.
+void CheckDecision(FecControllerStates* states,
+                   const rtc::Optional<int>& uplink_bandwidth_bps,
+                   const rtc::Optional<float>& uplink_packet_loss,
+                   bool expected_enable_fec) {
+  Controller::NetworkMetrics metrics;
+  metrics.uplink_bandwidth_bps = uplink_bandwidth_bps;
+  metrics.uplink_packet_loss_fraction = uplink_packet_loss;
+
+  if (uplink_packet_loss) {
+    // Check that smoothing filter is updated.
+    EXPECT_CALL(*states->packet_loss_smoothed, AddSample(*uplink_packet_loss));
+  }
+
+  EXPECT_CALL(*states->packet_loss_smoothed, GetAverage())
+      .WillRepeatedly(Return(uplink_packet_loss));
+
+  AudioNetworkAdaptor::EncoderRuntimeConfig config;
+  states->controller->MakeDecision(metrics, &config);
+  EXPECT_EQ(rtc::Optional<bool>(expected_enable_fec), config.enable_fec);
+
+  // Check that |config.uplink_packet_loss_fraction| is properly filled.
+  EXPECT_EQ(uplink_packet_loss ? uplink_packet_loss : rtc::Optional<float>(0.0),
+            config.uplink_packet_loss_fraction);
+}
+
+}  // namespace
+
+TEST(FecControllerTest, OutputInitValueWhenUplinkBandwidthUnknown) {
+  constexpr bool kInitialFecEnabled = true;
+  auto states = CreateFecController(kInitialFecEnabled);
+  // Let uplink packet loss fraction be so low that would cause FEC to turn off
+  // if uplink bandwidth was known.
+  CheckDecision(&states, rtc::Optional<int>(),
+                rtc::Optional<float>(kDisablingPacketLossAtHighBw),
+                kInitialFecEnabled);
+}
+
+TEST(FecControllerTest, OutputInitValueWhenUplinkPacketLossFractionUnknown) {
+  constexpr bool kInitialFecEnabled = true;
+  auto states = CreateFecController(kInitialFecEnabled);
+  // Let uplink bandwidth be so low that would cause FEC to turn off if uplink
+  // bandwidth packet loss fraction was known.
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthLow - 1),
+                rtc::Optional<float>(), kInitialFecEnabled);
+}
+
+TEST(FecControllerTest, EnableFecForHighBandwidth) {
+  auto states = CreateFecController(false);
+  CheckDecision(&states, rtc::Optional<int>(kEnablingBandwidthHigh),
+                rtc::Optional<float>(kEnablingPacketLossAtHighBw), true);
+}
+
+TEST(FecControllerTest, MaintainFecOffForHighBandwidth) {
+  auto states = CreateFecController(false);
+  CheckDecision(&states, rtc::Optional<int>(kEnablingBandwidthHigh),
+                rtc::Optional<float>(kEnablingPacketLossAtHighBw * 0.99f),
+                false);
+}
+
+TEST(FecControllerTest, EnableFecForMediumBandwidth) {
+  auto states = CreateFecController(false);
+  CheckDecision(
+      &states,
+      rtc::Optional<int>((kEnablingBandwidthHigh + kEnablingBandwidthLow) / 2),
+      rtc::Optional<float>(
+          (kEnablingPacketLossAtLowBw + kEnablingPacketLossAtHighBw) / 2.0),
+      true);
+}
+
+TEST(FecControllerTest, MaintainFecOffForMediumBandwidth) {
+  auto states = CreateFecController(false);
+  CheckDecision(
+      &states,
+      rtc::Optional<int>((kEnablingBandwidthHigh + kEnablingBandwidthLow) / 2),
+      rtc::Optional<float>(kEnablingPacketLossAtLowBw * 0.49f +
+                           kEnablingPacketLossAtHighBw * 0.51f),
+      false);
+}
+
+TEST(FecControllerTest, EnableFecForLowBandwidth) {
+  auto states = CreateFecController(false);
+  CheckDecision(&states, rtc::Optional<int>(kEnablingBandwidthLow),
+                rtc::Optional<float>(kEnablingPacketLossAtLowBw), true);
+}
+
+TEST(FecControllerTest, MaintainFecOffForLowBandwidth) {
+  auto states = CreateFecController(false);
+  CheckDecision(&states, rtc::Optional<int>(kEnablingBandwidthLow),
+                rtc::Optional<float>(kEnablingPacketLossAtLowBw * 0.99f),
+                false);
+}
+
+TEST(FecControllerTest, MaintainFecOffForVeryLowBandwidth) {
+  auto states = CreateFecController(false);
+  // Below |kEnablingBandwidthLow|, no packet loss fraction can cause FEC to
+  // turn on.
+  CheckDecision(&states, rtc::Optional<int>(kEnablingBandwidthLow - 1),
+                rtc::Optional<float>(1.0), false);
+}
+
+TEST(FecControllerTest, DisableFecForHighBandwidth) {
+  auto states = CreateFecController(true);
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthHigh),
+                rtc::Optional<float>(kDisablingPacketLossAtHighBw), false);
+}
+
+TEST(FecControllerTest, MaintainFecOnForHighBandwidth) {
+  auto states = CreateFecController(true);
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthHigh),
+                rtc::Optional<float>(kDisablingPacketLossAtHighBw * 1.01f),
+                true);
+}
+
+TEST(FecControllerTest, DisableFecOnMediumBandwidth) {
+  auto states = CreateFecController(true);
+  CheckDecision(
+      &states, rtc::Optional<int>(
+                   (kDisablingBandwidthHigh + kDisablingBandwidthLow) / 2),
+      rtc::Optional<float>(
+          (kDisablingPacketLossAtLowBw + kDisablingPacketLossAtHighBw) / 2.0f),
+      false);
+}
+
+TEST(FecControllerTest, MaintainFecOnForMediumBandwidth) {
+  auto states = CreateFecController(true);
+  CheckDecision(
+      &states,
+      rtc::Optional<int>((kEnablingBandwidthHigh + kDisablingBandwidthLow) / 2),
+      rtc::Optional<float>(kDisablingPacketLossAtLowBw * 0.51f +
+                           kDisablingPacketLossAtHighBw * 0.49f),
+      true);
+}
+
+TEST(FecControllerTest, DisableFecForLowBandwidth) {
+  auto states = CreateFecController(true);
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthLow),
+                rtc::Optional<float>(kDisablingPacketLossAtLowBw), false);
+}
+
+TEST(FecControllerTest, DisableFecForVeryLowBandwidth) {
+  auto states = CreateFecController(true);
+  // Below |kEnablingBandwidthLow|, any packet loss fraction can cause FEC to
+  // turn off.
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthLow - 1),
+                rtc::Optional<float>(1.0), false);
+}
+
+TEST(FecControllerTest, 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 = CreateFecController(true);
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthLow - 1),
+                rtc::Optional<float>(1.0), false);
+  CheckDecision(&states, rtc::Optional<int>(kEnablingBandwidthLow),
+                rtc::Optional<float>(kEnablingPacketLossAtLowBw * 0.99f),
+                false);
+  CheckDecision(&states, rtc::Optional<int>(kEnablingBandwidthHigh),
+                rtc::Optional<float>(kEnablingPacketLossAtHighBw), true);
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthHigh),
+                rtc::Optional<float>(kDisablingPacketLossAtHighBw * 1.01f),
+                true);
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthHigh + 1),
+                rtc::Optional<float>(0.0), false);
+}
+
+TEST(FecControllerTest, 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;
+  FecControllerStates states;
+  std::unique_ptr<MockSmoothingFilter> mock_smoothing_filter(
+      new NiceMock<MockSmoothingFilter>());
+  states.packet_loss_smoothed = mock_smoothing_filter.get();
+  EXPECT_CALL(*states.packet_loss_smoothed, Die());
+  using Threshold = FecController::Config::Threshold;
+  states.controller.reset(new FecController(
+      FecController::Config(
+          true, Threshold(kEnablingBandwidthLow, kEnablingPacketLossAtLowBw,
+                          kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw),
+          Threshold(kDisablingBandwidthLow, kDisablingPacketLossAtLowBw,
+                    kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw),
+          0, nullptr),
+      std::move(mock_smoothing_filter)));
+
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthLow - 1),
+                rtc::Optional<float>(1.0), false);
+  CheckDecision(&states, rtc::Optional<int>(kEnablingBandwidthLow),
+                rtc::Optional<float>(kEnablingPacketLossAtHighBw * 0.99f),
+                false);
+  CheckDecision(&states, rtc::Optional<int>(kEnablingBandwidthHigh),
+                rtc::Optional<float>(kEnablingPacketLossAtHighBw), true);
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthHigh),
+                rtc::Optional<float>(kDisablingPacketLossAtHighBw * 1.01f),
+                true);
+  CheckDecision(&states, rtc::Optional<int>(kDisablingBandwidthHigh + 1),
+                rtc::Optional<float>(0.0), false);
+}
+
+#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
+TEST(FecControllerDeathTest, InvalidConfig) {
+  FecControllerStates states;
+  std::unique_ptr<MockSmoothingFilter> mock_smoothing_filter(
+      new NiceMock<MockSmoothingFilter>());
+  states.packet_loss_smoothed = mock_smoothing_filter.get();
+  EXPECT_CALL(*states.packet_loss_smoothed, Die());
+  using Threshold = FecController::Config::Threshold;
+  EXPECT_DEATH(
+      states.controller.reset(new FecController(
+          FecController::Config(
+              true,
+              Threshold(kDisablingBandwidthLow - 1, kEnablingPacketLossAtLowBw,
+                        kEnablingBandwidthHigh, kEnablingPacketLossAtHighBw),
+              Threshold(kDisablingBandwidthLow, kDisablingPacketLossAtLowBw,
+                        kDisablingBandwidthHigh, kDisablingPacketLossAtHighBw),
+              0, nullptr),
+          std::move(mock_smoothing_filter))),
+      "Check failed");
+}
+#endif
+
+}  // namespace webrtc
diff --git a/webrtc/modules/audio_coding/audio_network_adaptor/mock/mock_smoothing_filter.h b/webrtc/modules/audio_coding/audio_network_adaptor/mock/mock_smoothing_filter.h
new file mode 100644
index 0000000..f004d1b
--- /dev/null
+++ b/webrtc/modules/audio_coding/audio_network_adaptor/mock/mock_smoothing_filter.h
@@ -0,0 +1,29 @@
+/*
+ *  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 WEBRTC_MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_SMOOTHING_FILTER_H_
+#define WEBRTC_MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_SMOOTHING_FILTER_H_
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "webrtc/modules/audio_coding/audio_network_adaptor/smoothing_filter.h"
+
+namespace webrtc {
+
+class MockSmoothingFilter : public SmoothingFilter {
+ public:
+  virtual ~MockSmoothingFilter() { Die(); }
+  MOCK_METHOD0(Die, void());
+  MOCK_METHOD1(AddSample, void(float));
+  MOCK_CONST_METHOD0(GetAverage, rtc::Optional<float>());
+};
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_MODULES_AUDIO_CODING_AUDIO_NETWORK_ADAPTOR_MOCK_MOCK_SMOOTHING_FILTER_H_