Adds fixed PL loss mode to neteq_quality_test.
It will be available in all inheriting tests.
The mode allows setting start time and duration for every loss event.

Bug: webrtc:8877
Change-Id: Ife36db6d431387083ac22406a0814e02117100bc
Reviewed-on: https://webrtc-review.googlesource.com/51822
Commit-Queue: Alex Narest <alexnarest@webrtc.org>
Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22005}
diff --git a/modules/audio_coding/neteq/tools/neteq_quality_test.cc b/modules/audio_coding/neteq/tools/neteq_quality_test.cc
index af5328e..82fa90e 100644
--- a/modules/audio_coding/neteq/tools/neteq_quality_test.cc
+++ b/modules/audio_coding/neteq/tools/neteq_quality_test.cc
@@ -62,8 +62,10 @@
 
 DEFINE_int(packet_loss_rate, 10, "Percentile of packet loss.");
 
-DEFINE_int(random_loss_mode, 1,
-    "Random loss mode: 0--no loss, 1--uniform loss, 2--Gilbert Elliot loss.");
+DEFINE_int(random_loss_mode,
+           kUniformLoss,
+           "Random loss mode: 0--no loss, 1--uniform loss, 2--Gilbert Elliot "
+           "loss, 3--fixed loss.");
 
 DEFINE_int(burst_length, 30,
     "Burst length in milliseconds, only valid for Gilbert Elliot loss.");
@@ -72,6 +74,12 @@
 
 DEFINE_int(preload_packets, 0, "Preload the buffer with this many packets.");
 
+DEFINE_string(loss_events,
+              "",
+              "List of loss events time and duration separated by comma: "
+              "<first_event_time> <first_event_duration>, <second_event_time> "
+              "<second_event_duration>, ...");
+
 // ProbTrans00Solver() is to calculate the transition probability from no-loss
 // state to itself in a modified Gilbert Elliot packet loss model. The result is
 // to achieve the target packet loss rate |loss_rate|, when a packet is not
@@ -157,8 +165,9 @@
   RTC_CHECK(FLAG_packet_loss_rate >= 0 && FLAG_packet_loss_rate <= 100)
       << "Invalid packet loss percentile, should be between 0 and 100.";
 
-  RTC_CHECK(FLAG_random_loss_mode >= 0 && FLAG_random_loss_mode <= 2)
-      << "Invalid random packet loss mode, should be between 0 and 2.";
+  RTC_CHECK(FLAG_random_loss_mode >= 0 && FLAG_random_loss_mode < kLastLossMode)
+      << "Invalid random packet loss mode, should be between 0 and "
+      << kLastLossMode - 1 << ".";
 
   RTC_CHECK_GE(FLAG_burst_length, kPacketLossTimeUnitMs)
       << "Invalid burst length, should be greater than or equal to "
@@ -197,7 +206,7 @@
   log_file_.close();
 }
 
-bool NoLoss::Lost() {
+bool NoLoss::Lost(int now_ms) {
   return false;
 }
 
@@ -205,7 +214,7 @@
     : loss_rate_(loss_rate) {
 }
 
-bool UniformLoss::Lost() {
+bool UniformLoss::Lost(int now_ms) {
   int drop_this = rand();
   return (drop_this < loss_rate_ * RAND_MAX);
 }
@@ -219,20 +228,41 @@
 
 GilbertElliotLoss::~GilbertElliotLoss() {}
 
-bool GilbertElliotLoss::Lost() {
+bool GilbertElliotLoss::Lost(int now_ms) {
   // Simulate bursty channel (Gilbert model).
   // (1st order) Markov chain model with memory of the previous/last
   // packet state (lost or received).
   if (lost_last_) {
     // Previous packet was not received.
     uniform_loss_model_->set_loss_rate(prob_trans_11_);
-    return lost_last_ = uniform_loss_model_->Lost();
+    return lost_last_ = uniform_loss_model_->Lost(now_ms);
   } else {
     uniform_loss_model_->set_loss_rate(prob_trans_01_);
-    return lost_last_ = uniform_loss_model_->Lost();
+    return lost_last_ = uniform_loss_model_->Lost(now_ms);
   }
 }
 
+FixedLossModel::FixedLossModel(
+    std::set<FixedLossEvent, FixedLossEventCmp> loss_events)
+    : loss_events_(loss_events) {
+  loss_events_it_ = loss_events_.begin();
+}
+
+FixedLossModel::~FixedLossModel() {}
+
+bool FixedLossModel::Lost(int now_ms) {
+  if (loss_events_it_ != loss_events_.end() &&
+      now_ms > loss_events_it_->start_ms) {
+    if (now_ms <= loss_events_it_->start_ms + loss_events_it_->duration_ms) {
+      return true;
+    } else {
+      ++loss_events_it_;
+      return false;
+    }
+  }
+  return false;
+}
+
 void NetEqQualityTest::SetUp() {
   ASSERT_EQ(0,
             neteq_->RegisterPayloadType(decoder_type_, "noname", kPayloadType));
@@ -240,7 +270,7 @@
 
   int units = block_duration_ms_ / kPacketLossTimeUnitMs;
   switch (FLAG_random_loss_mode) {
-    case 1: {
+    case kUniformLoss: {
       // |unit_loss_rate| is the packet loss rate for each unit time interval
       // (kPacketLossTimeUnitMs). Since a packet loss event is generated if any
       // of |block_duration_ms_ / kPacketLossTimeUnitMs| unit time intervals of
@@ -252,7 +282,7 @@
       loss_model_.reset(new UniformLoss(unit_loss_rate));
       break;
     }
-    case 2: {
+    case kGilbertElliotLoss: {
       // |FLAG_burst_length| should be integer times of kPacketLossTimeUnitMs.
       ASSERT_EQ(0, FLAG_burst_length % kPacketLossTimeUnitMs);
 
@@ -278,6 +308,25 @@
                                               1.0f - prob_trans_00));
       break;
     }
+    case kFixedLoss: {
+      std::istringstream loss_events_stream(FLAG_loss_events);
+      std::string loss_event_string;
+      std::set<FixedLossEvent, FixedLossEventCmp> loss_events;
+      while (std::getline(loss_events_stream, loss_event_string, ',')) {
+        std::vector<int> loss_event_params;
+        std::istringstream loss_event_params_stream(loss_event_string);
+        std::copy(std::istream_iterator<int>(loss_event_params_stream),
+                  std::istream_iterator<int>(),
+                  std::back_inserter(loss_event_params));
+        RTC_CHECK_EQ(loss_event_params.size(), 2);
+        auto result = loss_events.insert(
+            FixedLossEvent(loss_event_params[0], loss_event_params[1]));
+        RTC_CHECK(result.second);
+      }
+      RTC_CHECK_GT(loss_events.size(), 0);
+      loss_model_.reset(new FixedLossModel(loss_events));
+      break;
+    }
     default: {
       loss_model_.reset(new NoLoss);
       break;
@@ -299,7 +348,7 @@
   // same packet loss profile.
   bool lost = false;
   for (int idx = 0; idx < cycles; idx ++) {
-    if (loss_model_->Lost()) {
+    if (loss_model_->Lost(decoded_time_ms_)) {
       // The packet will be lost if any of the drawings indicates a loss, but
       // the loop has to go on to make sure that codecs with different block
       // lengths keep the same pace.
diff --git a/modules/audio_coding/neteq/tools/neteq_quality_test.h b/modules/audio_coding/neteq/tools/neteq_quality_test.h
index 8532f25..531a080 100644
--- a/modules/audio_coding/neteq/tools/neteq_quality_test.h
+++ b/modules/audio_coding/neteq/tools/neteq_quality_test.h
@@ -27,21 +27,29 @@
 namespace webrtc {
 namespace test {
 
+enum LossModes {
+  kNoLoss,
+  kUniformLoss,
+  kGilbertElliotLoss,
+  kFixedLoss,
+  kLastLossMode
+};
+
 class LossModel {
  public:
   virtual ~LossModel() {};
-  virtual bool Lost() = 0;
+  virtual bool Lost(int now_ms) = 0;
 };
 
 class NoLoss : public LossModel {
  public:
-  bool Lost() override;
+  bool Lost(int now_ms) override;
 };
 
 class UniformLoss : public LossModel {
  public:
   UniformLoss(double loss_rate);
-  bool Lost() override;
+  bool Lost(int now_ms) override;
   void set_loss_rate(double loss_rate) { loss_rate_ = loss_rate; }
 
  private:
@@ -52,7 +60,7 @@
  public:
   GilbertElliotLoss(double prob_trans_11, double prob_trans_01);
   ~GilbertElliotLoss() override;
-  bool Lost() override;
+  bool Lost(int now_ms) override;
 
  private:
   // Prob. of losing current packet, when previous packet is lost.
@@ -63,6 +71,31 @@
   std::unique_ptr<UniformLoss> uniform_loss_model_;
 };
 
+struct FixedLossEvent {
+  int start_ms;
+  int duration_ms;
+  FixedLossEvent(int start_ms, int duration_ms)
+      : start_ms(start_ms), duration_ms(duration_ms) {}
+};
+
+struct FixedLossEventCmp {
+  bool operator()(const FixedLossEvent& l_event,
+                  const FixedLossEvent& r_event) const {
+    return l_event.start_ms < r_event.start_ms;
+  }
+};
+
+class FixedLossModel : public LossModel {
+ public:
+  FixedLossModel(std::set<FixedLossEvent, FixedLossEventCmp> loss_events);
+  ~FixedLossModel() override;
+  bool Lost(int now_ms) override;
+
+ private:
+  std::set<FixedLossEvent, FixedLossEventCmp> loss_events_;
+  std::set<FixedLossEvent, FixedLossEventCmp>::iterator loss_events_it_;
+};
+
 class NetEqQualityTest : public ::testing::Test {
  protected:
   NetEqQualityTest(int block_duration_ms,