Create field trial for setting a minimum value for Opus encoder packet loss rate

Bug: webrtc:9848
Change-Id: I0663ee3af7729a220de7aff08cd74545e1a7409a
Reviewed-on: https://webrtc-review.googlesource.com/c/104800
Reviewed-by: Minyue Li <minyue@webrtc.org>
Commit-Queue: Jakob Ivarsson‎ <jakobi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25081}
diff --git a/modules/audio_coding/codecs/opus/audio_encoder_opus.cc b/modules/audio_coding/codecs/opus/audio_encoder_opus.cc
index e6240e6..a35ada8 100644
--- a/modules/audio_coding/codecs/opus/audio_encoder_opus.cc
+++ b/modules/audio_coding/codecs/opus/audio_encoder_opus.cc
@@ -214,6 +214,27 @@
   return *config.bitrate_bps;
 }
 
+float GetMinPacketLossRate() {
+  constexpr char kPacketLossFieldTrial[] = "WebRTC-Audio-OpusMinPacketLossRate";
+  const bool use_opus_min_packet_loss_rate =
+      webrtc::field_trial::IsEnabled(kPacketLossFieldTrial);
+  if (use_opus_min_packet_loss_rate) {
+    const std::string field_trial_string =
+        webrtc::field_trial::FindFullName(kPacketLossFieldTrial);
+    constexpr int kDefaultMinPacketLossRate = 1;
+    int value = kDefaultMinPacketLossRate;
+    if (sscanf(field_trial_string.c_str(), "Enabled-%d", &value) == 1 &&
+        (value < 0 || value > 100)) {
+      RTC_LOG(LS_WARNING) << "Invalid parameter for " << kPacketLossFieldTrial
+                          << ", using default value: "
+                          << kDefaultMinPacketLossRate;
+      value = kDefaultMinPacketLossRate;
+    }
+    return static_cast<float>(value) / 100;
+  }
+  return 0.0;
+}
+
 }  // namespace
 
 void AudioEncoderOpusImpl::AppendSupportedEncoders(
@@ -402,6 +423,7 @@
           webrtc::field_trial::IsEnabled("WebRTC-AdjustOpusBandwidth")),
       bitrate_changed_(true),
       packet_loss_rate_(0.0),
+      min_packet_loss_rate_(GetMinPacketLossRate()),
       inst_(nullptr),
       packet_loss_fraction_smoother_(new PacketLossFractionSmoother()),
       audio_network_adaptor_creator_(audio_network_adaptor_creator),
@@ -414,6 +436,7 @@
   RTC_CHECK(config.payload_type == -1 || config.payload_type == payload_type);
 
   RTC_CHECK(RecreateEncoderInstance(config));
+  SetProjectedPacketLossRate(packet_loss_rate_);
 }
 
 AudioEncoderOpusImpl::AudioEncoderOpusImpl(const CodecInst& codec_inst)
@@ -739,6 +762,7 @@
 
 void AudioEncoderOpusImpl::SetProjectedPacketLossRate(float fraction) {
   float opt_loss_rate = OptimizePacketLossRate(fraction, packet_loss_rate_);
+  opt_loss_rate = std::max(opt_loss_rate, min_packet_loss_rate_);
   if (packet_loss_rate_ != opt_loss_rate) {
     packet_loss_rate_ = opt_loss_rate;
     RTC_CHECK_EQ(
diff --git a/modules/audio_coding/codecs/opus/audio_encoder_opus.h b/modules/audio_coding/codecs/opus/audio_encoder_opus.h
index ea4b265..d34e220 100644
--- a/modules/audio_coding/codecs/opus/audio_encoder_opus.h
+++ b/modules/audio_coding/codecs/opus/audio_encoder_opus.h
@@ -158,6 +158,7 @@
   const bool adjust_bandwidth_;
   bool bitrate_changed_;
   float packet_loss_rate_;
+  const float min_packet_loss_rate_;
   std::vector<int16_t> input_buffer_;
   OpusEncInst* inst_;
   uint32_t first_timestamp_in_buffer_;
diff --git a/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc b/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
index 7a6d5fd..0420c63 100644
--- a/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
+++ b/modules/audio_coding/codecs/opus/audio_encoder_opus_unittest.cc
@@ -272,6 +272,28 @@
   // clang-format on
 }
 
+TEST(AudioEncoderOpusTest, PacketLossRateLowerBounded) {
+  test::ScopedFieldTrials override_field_trails(
+      "WebRTC-Audio-OpusMinPacketLossRate/Enabled-5/");
+  auto states = CreateCodec(1);
+  auto I = [](float a, float b) { return IntervalSteps(a, b, 10); };
+  constexpr float eps = 1e-8f;
+
+  // clang-format off
+  TestSetPacketLossRate(states.get(), I(0.00f      , 0.01f - eps), 0.05f);
+  TestSetPacketLossRate(states.get(), I(0.01f + eps, 0.06f - eps), 0.05f);
+  TestSetPacketLossRate(states.get(), I(0.06f + eps, 0.11f - eps), 0.05f);
+  TestSetPacketLossRate(states.get(), I(0.11f + eps, 0.22f - eps), 0.10f);
+  TestSetPacketLossRate(states.get(), I(0.22f + eps, 1.00f      ), 0.20f);
+
+  TestSetPacketLossRate(states.get(), I(1.00f      , 0.18f + eps), 0.20f);
+  TestSetPacketLossRate(states.get(), I(0.18f - eps, 0.09f + eps), 0.10f);
+  TestSetPacketLossRate(states.get(), I(0.09f - eps, 0.04f + eps), 0.05f);
+  TestSetPacketLossRate(states.get(), I(0.04f - eps, 0.01f + eps), 0.05f);
+  TestSetPacketLossRate(states.get(), I(0.01f - eps, 0.00f      ), 0.05f);
+  // clang-format on
+}
+
 TEST(AudioEncoderOpusTest, SetReceiverFrameLengthRange) {
   auto states = CreateCodec(2);
   // Before calling to |SetReceiverFrameLengthRange|,
@@ -446,6 +468,29 @@
   EXPECT_EQ(kMaxBitrateBps, states->encoder->GetTargetBitrate());
 }
 
+TEST(AudioEncoderOpusTest, MinPacketLossRate) {
+  constexpr float kDefaultMinPacketLossRate = 0.01;
+  {
+    test::ScopedFieldTrials override_field_trails(
+        "WebRTC-Audio-OpusMinPacketLossRate/Enabled/");
+    auto states = CreateCodec(1);
+    EXPECT_EQ(kDefaultMinPacketLossRate, states->encoder->packet_loss_rate());
+  }
+  {
+    test::ScopedFieldTrials override_field_trails(
+        "WebRTC-Audio-OpusMinPacketLossRate/Enabled-200/");
+    auto states = CreateCodec(1);
+    EXPECT_EQ(kDefaultMinPacketLossRate, states->encoder->packet_loss_rate());
+  }
+  {
+    test::ScopedFieldTrials override_field_trails(
+        "WebRTC-Audio-OpusMinPacketLossRate/Enabled-50/");
+    constexpr float kMinPacketLossRate = 0.5;
+    auto states = CreateCodec(1);
+    EXPECT_EQ(kMinPacketLossRate, states->encoder->packet_loss_rate());
+  }
+}
+
 // Verifies that the complexity adaptation in the config works as intended.
 TEST(AudioEncoderOpusTest, ConfigComplexityAdaptation) {
   AudioEncoderOpusConfig config;