Add NetEq decision logic unit tests.

- Add buffer level filter and delay manager mocks and make them
 injectable for easier testing.
- Add a basic set of tests for simple cases and recently added features.

Bug: webrtc:10333
Change-Id: I8b6f73b8ad99ad6859ed1279086c0bd68b7687be
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/188623
Commit-Queue: Jakob Ivarsson <jakobi@webrtc.org>
Reviewed-by: Ivo Creusen <ivoc@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#32433}
diff --git a/modules/audio_coding/BUILD.gn b/modules/audio_coding/BUILD.gn
index cdf7821..9d33249 100644
--- a/modules/audio_coding/BUILD.gn
+++ b/modules/audio_coding/BUILD.gn
@@ -1976,7 +1976,9 @@
       "neteq/expand_unittest.cc",
       "neteq/histogram_unittest.cc",
       "neteq/merge_unittest.cc",
+      "neteq/mock/mock_buffer_level_filter.h",
       "neteq/mock/mock_decoder_database.h",
+      "neteq/mock/mock_delay_manager.h",
       "neteq/mock/mock_dtmf_buffer.h",
       "neteq/mock/mock_dtmf_tone_generator.h",
       "neteq/mock/mock_expand.h",
diff --git a/modules/audio_coding/neteq/decision_logic.cc b/modules/audio_coding/neteq/decision_logic.cc
index fb26b99..9c0ee96 100644
--- a/modules/audio_coding/neteq/decision_logic.cc
+++ b/modules/audio_coding/neteq/decision_logic.cc
@@ -34,9 +34,18 @@
 namespace webrtc {
 
 DecisionLogic::DecisionLogic(NetEqController::Config config)
-    : delay_manager_(DelayManager::Create(config.max_packets_in_buffer,
-                                          config.base_min_delay_ms,
-                                          config.tick_timer)),
+    : DecisionLogic(config,
+                    DelayManager::Create(config.max_packets_in_buffer,
+                                         config.base_min_delay_ms,
+                                         config.tick_timer),
+                    std::make_unique<BufferLevelFilter>()) {}
+
+DecisionLogic::DecisionLogic(
+    NetEqController::Config config,
+    std::unique_ptr<DelayManager> delay_manager,
+    std::unique_ptr<BufferLevelFilter> buffer_level_filter)
+    : delay_manager_(std::move(delay_manager)),
+      buffer_level_filter_(std::move(buffer_level_filter)),
       tick_timer_(config.tick_timer),
       disallow_time_stretching_(!config.allow_time_stretching),
       timescale_countdown_(
@@ -82,7 +91,7 @@
       tick_timer_->GetNewCountdown(kMinTimescaleInterval + 1);
   time_stretched_cn_samples_ = 0;
   delay_manager_->Reset();
-  buffer_level_filter_.Reset();
+  buffer_level_filter_->Reset();
 }
 
 void DecisionLogic::SetSampleRate(int fs_hz, size_t output_size_samples) {
@@ -221,7 +230,7 @@
 }
 
 void DecisionLogic::FilterBufferLevel(size_t buffer_size_samples) {
-  buffer_level_filter_.SetTargetBufferLevel(delay_manager_->TargetDelayMs());
+  buffer_level_filter_->SetTargetBufferLevel(delay_manager_->TargetDelayMs());
 
   int time_stretched_samples = time_stretched_cn_samples_;
   if (prev_time_scale_) {
@@ -229,7 +238,7 @@
     timescale_countdown_ = tick_timer_->GetNewCountdown(kMinTimescaleInterval);
   }
 
-  buffer_level_filter_.Update(buffer_size_samples, time_stretched_samples);
+  buffer_level_filter_->Update(buffer_size_samples, time_stretched_samples);
   prev_time_scale_ = false;
   time_stretched_cn_samples_ = 0;
 }
@@ -300,7 +309,7 @@
         std::max(target_level_samples, low_limit + 20 * samples_per_ms);
 
     const int buffer_level_samples =
-        buffer_level_filter_.filtered_current_level();
+        buffer_level_filter_->filtered_current_level();
     if (buffer_level_samples >= high_limit << 2)
       return NetEq::Operation::kFastAccelerate;
     if (TimescaleAllowed()) {
@@ -402,7 +411,7 @@
 }
 
 bool DecisionLogic::UnderTargetLevel() const {
-  return buffer_level_filter_.filtered_current_level() <
+  return buffer_level_filter_->filtered_current_level() <
          delay_manager_->TargetDelayMs() * sample_rate_ / 1000;
 }
 
diff --git a/modules/audio_coding/neteq/decision_logic.h b/modules/audio_coding/neteq/decision_logic.h
index d69c249..08feba6 100644
--- a/modules/audio_coding/neteq/decision_logic.h
+++ b/modules/audio_coding/neteq/decision_logic.h
@@ -11,6 +11,8 @@
 #ifndef MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_H_
 #define MODULES_AUDIO_CODING_NETEQ_DECISION_LOGIC_H_
 
+#include <memory>
+
 #include "api/neteq/neteq.h"
 #include "api/neteq/neteq_controller.h"
 #include "api/neteq/tick_timer.h"
@@ -29,6 +31,9 @@
 
   // Constructor.
   DecisionLogic(NetEqController::Config config);
+  DecisionLogic(NetEqController::Config config,
+                std::unique_ptr<DelayManager> delay_manager,
+                std::unique_ptr<BufferLevelFilter> buffer_level_filter);
 
   ~DecisionLogic() override;
 
@@ -95,7 +100,7 @@
   bool PeakFound() const override { return false; }
 
   int GetFilteredBufferLevel() const override {
-    return buffer_level_filter_.filtered_current_level();
+    return buffer_level_filter_->filtered_current_level();
   }
 
   // Accessors and mutators.
@@ -168,7 +173,7 @@
   bool MaxWaitForPacket() const;
 
   std::unique_ptr<DelayManager> delay_manager_;
-  BufferLevelFilter buffer_level_filter_;
+  std::unique_ptr<BufferLevelFilter> buffer_level_filter_;
   const TickTimer* tick_timer_;
   int sample_rate_;
   size_t output_size_samples_;
diff --git a/modules/audio_coding/neteq/decision_logic_unittest.cc b/modules/audio_coding/neteq/decision_logic_unittest.cc
index 56e8b84..c29350e 100644
--- a/modules/audio_coding/neteq/decision_logic_unittest.cc
+++ b/modules/audio_coding/neteq/decision_logic_unittest.cc
@@ -15,34 +15,197 @@
 #include "api/neteq/neteq_controller.h"
 #include "api/neteq/tick_timer.h"
 #include "modules/audio_coding/neteq/buffer_level_filter.h"
-#include "modules/audio_coding/neteq/decoder_database.h"
 #include "modules/audio_coding/neteq/delay_manager.h"
-#include "modules/audio_coding/neteq/packet_buffer.h"
-#include "modules/audio_coding/neteq/statistics_calculator.h"
+#include "modules/audio_coding/neteq/mock/mock_buffer_level_filter.h"
+#include "modules/audio_coding/neteq/mock/mock_delay_manager.h"
+#include "test/field_trial.h"
 #include "test/gtest.h"
-#include "test/mock_audio_decoder_factory.h"
 
 namespace webrtc {
 
-TEST(DecisionLogic, CreateAndDestroy) {
-  int fs_hz = 8000;
-  int output_size_samples = fs_hz / 100;  // Samples per 10 ms.
-  DecoderDatabase decoder_database(
-      new rtc::RefCountedObject<MockAudioDecoderFactory>, absl::nullopt);
-  TickTimer tick_timer;
-  StatisticsCalculator stats;
-  PacketBuffer packet_buffer(10, &tick_timer);
-  BufferLevelFilter buffer_level_filter;
-  NetEqController::Config config;
-  config.tick_timer = &tick_timer;
-  config.base_min_delay_ms = 0;
-  config.max_packets_in_buffer = 240;
-  config.enable_rtx_handling = false;
-  config.allow_time_stretching = true;
-  auto logic = std::make_unique<DecisionLogic>(std::move(config));
-  logic->SetSampleRate(fs_hz, output_size_samples);
+namespace {
+
+constexpr int kSampleRate = 8000;
+constexpr int kSamplesPerMs = kSampleRate / 1000;
+constexpr int kOutputSizeSamples = kSamplesPerMs * 10;
+constexpr int kMinTimescaleInterval = 5;
+
+NetEqController::NetEqStatus CreateNetEqStatus(NetEq::Mode last_mode,
+                                               int current_delay_ms) {
+  NetEqController::NetEqStatus status;
+  status.play_dtmf = false;
+  status.last_mode = last_mode;
+  status.target_timestamp = 1234;
+  status.generated_noise_samples = 0;
+  status.expand_mutefactor = 0;
+  status.packet_buffer_info.num_samples = current_delay_ms * kSamplesPerMs;
+  status.packet_buffer_info.span_samples = current_delay_ms * kSamplesPerMs;
+  status.packet_buffer_info.span_samples_no_dtx =
+      current_delay_ms * kSamplesPerMs;
+  status.packet_buffer_info.dtx_or_cng = false;
+  status.next_packet = {status.target_timestamp, false, false};
+  return status;
 }
 
-// TODO(jakobi): Write more tests.
+using ::testing::Return;
+
+}  // namespace
+
+class DecisionLogicTest : public ::testing::Test {
+ protected:
+  DecisionLogicTest() {
+    test::ScopedFieldTrials field_trial(
+        "WebRTC-Audio-NetEqDecisionLogicSettings/"
+        "estimate_dtx_delay:true,time_stretch_cn:true/");
+
+    NetEqController::Config config;
+    config.tick_timer = &tick_timer_;
+    config.allow_time_stretching = true;
+    std::unique_ptr<Histogram> histogram =
+        std::make_unique<Histogram>(200, 12345, 2);
+    auto delay_manager = std::make_unique<MockDelayManager>(
+        200, 0, 12300, config.tick_timer, std::move(histogram));
+    mock_delay_manager_ = delay_manager.get();
+    auto buffer_level_filter = std::make_unique<MockBufferLevelFilter>();
+    mock_buffer_level_filter_ = buffer_level_filter.get();
+    decision_logic_ = std::make_unique<DecisionLogic>(
+        config, std::move(delay_manager), std::move(buffer_level_filter));
+    decision_logic_->SetSampleRate(kSampleRate, kOutputSizeSamples);
+  }
+
+  TickTimer tick_timer_;
+  std::unique_ptr<DecisionLogic> decision_logic_;
+  MockDelayManager* mock_delay_manager_;
+  MockBufferLevelFilter* mock_buffer_level_filter_;
+};
+
+TEST_F(DecisionLogicTest, NormalOperation) {
+  EXPECT_CALL(*mock_delay_manager_, TargetDelayMs())
+      .WillRepeatedly(Return(100));
+  EXPECT_CALL(*mock_buffer_level_filter_, filtered_current_level())
+      .WillRepeatedly(Return(90 * kSamplesPerMs));
+
+  bool reset_decoder = false;
+  tick_timer_.Increment(kMinTimescaleInterval + 1);
+  EXPECT_EQ(decision_logic_->GetDecision(
+                CreateNetEqStatus(NetEq::Mode::kNormal, 100), &reset_decoder),
+            NetEq::Operation::kNormal);
+  EXPECT_FALSE(reset_decoder);
+}
+
+TEST_F(DecisionLogicTest, Accelerate) {
+  EXPECT_CALL(*mock_delay_manager_, TargetDelayMs())
+      .WillRepeatedly(Return(100));
+  EXPECT_CALL(*mock_buffer_level_filter_, filtered_current_level())
+      .WillRepeatedly(Return(110 * kSamplesPerMs));
+
+  bool reset_decoder = false;
+  tick_timer_.Increment(kMinTimescaleInterval + 1);
+  EXPECT_EQ(decision_logic_->GetDecision(
+                CreateNetEqStatus(NetEq::Mode::kNormal, 100), &reset_decoder),
+            NetEq::Operation::kAccelerate);
+  EXPECT_FALSE(reset_decoder);
+}
+
+TEST_F(DecisionLogicTest, FastAccelerate) {
+  EXPECT_CALL(*mock_delay_manager_, TargetDelayMs())
+      .WillRepeatedly(Return(100));
+  EXPECT_CALL(*mock_buffer_level_filter_, filtered_current_level())
+      .WillRepeatedly(Return(400 * kSamplesPerMs));
+
+  bool reset_decoder = false;
+  tick_timer_.Increment(kMinTimescaleInterval + 1);
+  EXPECT_EQ(decision_logic_->GetDecision(
+                CreateNetEqStatus(NetEq::Mode::kNormal, 100), &reset_decoder),
+            NetEq::Operation::kFastAccelerate);
+  EXPECT_FALSE(reset_decoder);
+}
+
+TEST_F(DecisionLogicTest, PreemptiveExpand) {
+  EXPECT_CALL(*mock_delay_manager_, TargetDelayMs())
+      .WillRepeatedly(Return(100));
+  EXPECT_CALL(*mock_buffer_level_filter_, filtered_current_level())
+      .WillRepeatedly(Return(50 * kSamplesPerMs));
+
+  bool reset_decoder = false;
+  tick_timer_.Increment(kMinTimescaleInterval + 1);
+  EXPECT_EQ(decision_logic_->GetDecision(
+                CreateNetEqStatus(NetEq::Mode::kNormal, 100), &reset_decoder),
+            NetEq::Operation::kPreemptiveExpand);
+  EXPECT_FALSE(reset_decoder);
+}
+
+TEST_F(DecisionLogicTest, DecelerationTargetLevelOffset) {
+  EXPECT_CALL(*mock_delay_manager_, TargetDelayMs())
+      .WillRepeatedly(Return(500));
+  EXPECT_CALL(*mock_buffer_level_filter_, filtered_current_level())
+      .WillRepeatedly(Return(400 * kSamplesPerMs));
+
+  bool reset_decoder = false;
+  tick_timer_.Increment(kMinTimescaleInterval + 1);
+  EXPECT_EQ(decision_logic_->GetDecision(
+                CreateNetEqStatus(NetEq::Mode::kNormal, 400), &reset_decoder),
+            NetEq::Operation::kPreemptiveExpand);
+  EXPECT_FALSE(reset_decoder);
+}
+
+TEST_F(DecisionLogicTest, PostponeDecodeAfterExpand) {
+  EXPECT_CALL(*mock_delay_manager_, TargetDelayMs())
+      .WillRepeatedly(Return(500));
+
+  // Below 50% target delay threshold.
+  bool reset_decoder = false;
+  EXPECT_EQ(decision_logic_->GetDecision(
+                CreateNetEqStatus(NetEq::Mode::kExpand, 200), &reset_decoder),
+            NetEq::Operation::kExpand);
+  EXPECT_FALSE(reset_decoder);
+
+  // Above 50% target delay threshold.
+  EXPECT_EQ(decision_logic_->GetDecision(
+                CreateNetEqStatus(NetEq::Mode::kExpand, 250), &reset_decoder),
+            NetEq::Operation::kNormal);
+  EXPECT_FALSE(reset_decoder);
+}
+
+TEST_F(DecisionLogicTest, TimeStrechComfortNoise) {
+  EXPECT_CALL(*mock_delay_manager_, TargetDelayMs())
+      .WillRepeatedly(Return(500));
+
+  {
+    bool reset_decoder = false;
+    // Below target window.
+    auto status = CreateNetEqStatus(NetEq::Mode::kCodecInternalCng, 400);
+    status.generated_noise_samples = 400 * kSamplesPerMs;
+    status.next_packet->timestamp =
+        status.target_timestamp + 400 * kSamplesPerMs;
+    EXPECT_EQ(decision_logic_->GetDecision(status, &reset_decoder),
+              NetEq::Operation::kCodecInternalCng);
+    EXPECT_FALSE(reset_decoder);
+  }
+
+  {
+    bool reset_decoder = false;
+    // Above target window.
+    auto status = CreateNetEqStatus(NetEq::Mode::kCodecInternalCng, 600);
+    status.generated_noise_samples = 200 * kSamplesPerMs;
+    status.next_packet->timestamp =
+        status.target_timestamp + 400 * kSamplesPerMs;
+    EXPECT_EQ(decision_logic_->GetDecision(status, &reset_decoder),
+              NetEq::Operation::kNormal);
+    EXPECT_FALSE(reset_decoder);
+
+    // The buffer level filter should be adjusted with the number of samples
+    // that was skipped.
+    int timestamp_leap = status.next_packet->timestamp -
+                         status.target_timestamp -
+                         status.generated_noise_samples;
+    EXPECT_CALL(*mock_buffer_level_filter_,
+                Update(400 * kSamplesPerMs, timestamp_leap));
+    EXPECT_EQ(decision_logic_->GetDecision(
+                  CreateNetEqStatus(NetEq::Mode::kNormal, 400), &reset_decoder),
+              NetEq::Operation::kNormal);
+    EXPECT_FALSE(reset_decoder);
+  }
+}
 
 }  // namespace webrtc
diff --git a/modules/audio_coding/neteq/mock/mock_buffer_level_filter.h b/modules/audio_coding/neteq/mock/mock_buffer_level_filter.h
new file mode 100644
index 0000000..503f6ac
--- /dev/null
+++ b/modules/audio_coding/neteq/mock/mock_buffer_level_filter.h
@@ -0,0 +1,28 @@
+/*
+ *  Copyright (c) 2020 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 MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_
+#define MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_
+
+#include "modules/audio_coding/neteq/buffer_level_filter.h"
+#include "test/gmock.h"
+
+namespace webrtc {
+
+class MockBufferLevelFilter : public BufferLevelFilter {
+ public:
+  MOCK_METHOD(void,
+              Update,
+              (size_t buffer_size_samples, int time_stretched_samples));
+  MOCK_METHOD(int, filtered_current_level, (), (const));
+};
+
+}  // namespace webrtc
+#endif  // MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_BUFFER_LEVEL_FILTER_H_
diff --git a/modules/audio_coding/neteq/mock/mock_delay_manager.h b/modules/audio_coding/neteq/mock/mock_delay_manager.h
new file mode 100644
index 0000000..0631f6f
--- /dev/null
+++ b/modules/audio_coding/neteq/mock/mock_delay_manager.h
@@ -0,0 +1,39 @@
+/*
+ *  Copyright (c) 2020 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 MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_MANAGER_H_
+#define MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_MANAGER_H_
+
+#include <memory>
+#include <utility>
+
+#include "api/neteq/tick_timer.h"
+#include "modules/audio_coding/neteq/delay_manager.h"
+#include "test/gmock.h"
+
+namespace webrtc {
+
+class MockDelayManager : public DelayManager {
+ public:
+  MockDelayManager(size_t max_packets_in_buffer,
+                   int base_minimum_delay_ms,
+                   int histogram_quantile,
+                   const TickTimer* tick_timer,
+                   std::unique_ptr<Histogram> histogram)
+      : DelayManager(max_packets_in_buffer,
+                     base_minimum_delay_ms,
+                     histogram_quantile,
+                     tick_timer,
+                     std::move(histogram)) {}
+  MOCK_METHOD(int, TargetDelayMs, (), (const));
+};
+
+}  // namespace webrtc
+#endif  // MODULES_AUDIO_CODING_NETEQ_MOCK_MOCK_DELAY_MANAGER_H_