Extend WebRTC-Video-MinVideoBitrate to experiment per-codec

The experiment was extended to support per-codec minimum bitrates
for the following codecs:
 * VP8
 * VP9
 * H.264

The old semantic meaning for the field trial is retained, in that
specifying "br:" applies a minimum bitrate to all codecs. If "br:"
is not specified, the per-codec minimum config is consulted.

Bug: webrtc:11024
Change-Id: I89630262c7710771d5e25d039fe35f0bd217b58a
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/156171
Commit-Queue: Elad Alon <eladalon@webrtc.org>
Reviewed-by: Åsa Persson <asapersson@webrtc.org>
Reviewed-by: Ying Wang <yinwa@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29450}
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 726a66d..3c8008a 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -274,6 +274,7 @@
     "../api/transport:datagram_transport_interface",
     "../api/transport/media:media_transport_interface",
     "../api/transport/rtp:rtp_source",
+    "../api/units:data_rate",
     "../api/video:video_bitrate_allocation",
     "../api/video:video_bitrate_allocator_factory",
     "../api/video:video_codec_constants",
@@ -302,6 +303,7 @@
     "../rtc_base:stringutils",
     "../rtc_base/experiments:experimental_screenshare_settings",
     "../rtc_base/experiments:field_trial_parser",
+    "../rtc_base/experiments:min_video_bitrate_experiment",
     "../rtc_base/experiments:normalize_simulcast_size_experiment",
     "../rtc_base/system:rtc_export",
     "../rtc_base/third_party/base64",
@@ -565,6 +567,7 @@
       "../rtc_base:rtc_base_tests_utils",
       "../rtc_base:rtc_task_queue",
       "../rtc_base:stringutils",
+      "../rtc_base/experiments:min_video_bitrate_experiment",
       "../rtc_base/third_party/sigslot",
       "../test:audio_codec_mocks",
       "../test:field_trial",
diff --git a/media/engine/constants.cc b/media/engine/constants.cc
index 9adfa41..12d6ddd 100644
--- a/media/engine/constants.cc
+++ b/media/engine/constants.cc
@@ -12,9 +12,8 @@
 
 namespace cricket {
 
-const int kMinVideoBitrateBps = 30000;
 const int kVideoMtu = 1200;
 const int kVideoRtpSendBufferSize = 65536;
 const int kVideoRtpRecvBufferSize = 262144;
-const char kMinVideoBitrateExperiment[] = "WebRTC-Video-MinVideoBitrate";
+
 }  // namespace cricket
diff --git a/media/engine/constants.h b/media/engine/constants.h
index d52505d..0abae3d 100644
--- a/media/engine/constants.h
+++ b/media/engine/constants.h
@@ -19,9 +19,6 @@
 
 extern const char kH264CodecName[];
 
-extern const int kMinVideoBitrateBps;
-extern const char kMinVideoBitrateExperiment[];
-
 }  // namespace cricket
 
 #endif  // MEDIA_ENGINE_CONSTANTS_H_
diff --git a/media/engine/simulcast.cc b/media/engine/simulcast.cc
index b8e7a6c..40135f4 100644
--- a/media/engine/simulcast.cc
+++ b/media/engine/simulcast.cc
@@ -19,11 +19,11 @@
 #include "absl/types/optional.h"
 #include "api/video/video_codec_constants.h"
 #include "media/base/media_constants.h"
-#include "media/engine/constants.h"
 #include "modules/video_coding/utility/simulcast_rate_allocator.h"
 #include "rtc_base/arraysize.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/experiments/experimental_screenshare_settings.h"
+#include "rtc_base/experiments/min_video_bitrate_experiment.h"
 #include "rtc_base/experiments/normalize_simulcast_size_experiment.h"
 #include "rtc_base/logging.h"
 #include "system_wrappers/include/field_trial.h"
@@ -335,7 +335,7 @@
   layers[0].height = height;
   layers[0].max_qp = max_qp;
   layers[0].max_framerate = 5;
-  layers[0].min_bitrate_bps = kMinVideoBitrateBps;
+  layers[0].min_bitrate_bps = webrtc::kDefaultMinVideoBitrateBps;
   layers[0].target_bitrate_bps = kScreenshareDefaultTl0BitrateKbps * 1000;
   layers[0].max_bitrate_bps = kScreenshareDefaultTl1BitrateKbps * 1000;
   layers[0].num_temporal_layers = temporal_layers_supported ? 2 : 0;
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 96a426d..7bce942 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -20,6 +20,7 @@
 #include "absl/algorithm/container.h"
 #include "absl/strings/match.h"
 #include "api/transport/datagram_transport_interface.h"
+#include "api/units/data_rate.h"
 #include "api/video/video_codec_constants.h"
 #include "api/video/video_codec_type.h"
 #include "api/video_codecs/sdp_video_format.h"
@@ -33,7 +34,9 @@
 #include "rtc_base/copy_on_write_buffer.h"
 #include "rtc_base/experiments/field_trial_parser.h"
 #include "rtc_base/experiments/field_trial_units.h"
+#include "rtc_base/experiments/min_video_bitrate_experiment.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/numerics/safe_conversions.h"
 #include "rtc_base/strings/string_builder.h"
 #include "rtc_base/time_utils.h"
 #include "rtc_base/trace_event.h"
@@ -298,46 +301,6 @@
   return absl::nullopt;
 }
 
-const char kForcedFallbackFieldTrial[] =
-    "WebRTC-VP8-Forced-Fallback-Encoder-v2";
-
-absl::optional<int> GetFallbackMinBpsFromFieldTrial(
-    webrtc::VideoCodecType type) {
-  if (type != webrtc::kVideoCodecVP8)
-    return absl::nullopt;
-
-  if (!webrtc::field_trial::IsEnabled(kForcedFallbackFieldTrial))
-    return absl::nullopt;
-
-  std::string group =
-      webrtc::field_trial::FindFullName(kForcedFallbackFieldTrial);
-  if (group.empty())
-    return absl::nullopt;
-
-  int min_pixels;
-  int max_pixels;
-  int min_bps;
-  if (sscanf(group.c_str(), "Enabled-%d,%d,%d", &min_pixels, &max_pixels,
-             &min_bps) != 3) {
-    return absl::nullopt;
-  }
-
-  if (min_bps <= 0)
-    return absl::nullopt;
-
-  return min_bps;
-}
-
-int GetMinVideoBitrateBps(webrtc::VideoCodecType type) {
-  if (GetFallbackMinBpsFromFieldTrial(type).has_value()) {
-    return GetFallbackMinBpsFromFieldTrial(type).value();
-  }
-  if (webrtc::field_trial::IsEnabled(kMinVideoBitrateExperiment)) {
-    return MinVideoBitrateConfig().min_video_bitrate->bps();
-  }
-  return kMinVideoBitrateBps;
-}
-
 // Returns its smallest positive argument. If neither argument is positive,
 // returns an arbitrary nonpositive value.
 int MinPositive(int a, int b) {
@@ -3069,6 +3032,9 @@
                 encoder_config.number_of_streams);
   std::vector<webrtc::VideoStream> layers;
 
+  const absl::optional<webrtc::DataRate> experimental_min_bitrate =
+      GetExperimentalMinVideoBitrate(encoder_config.codec_type);
+
   if (encoder_config.number_of_streams > 1 ||
       ((absl::EqualsIgnoreCase(codec_name_, kVp8CodecName) ||
         absl::EqualsIgnoreCase(codec_name_, kH264CodecName)) &&
@@ -3082,6 +3048,12 @@
                                 encoder_config.bitrate_priority, max_qp_,
                                 is_screenshare_ && conference_mode_,
                                 temporal_layers_supported);
+    // Allow an experiment to override the minimum bitrate for the lowest
+    // spatial layer. The experiment's configuration has the lowest priority.
+    if (experimental_min_bitrate) {
+      layers[0].min_bitrate_bps =
+          rtc::saturated_cast<int>(experimental_min_bitrate->bps());
+    }
     // The maximum |max_framerate| is currently used for video.
     const int max_framerate = GetMaxFramerate(encoder_config, layers.size());
     // Update the active simulcast layers and configured bitrates.
@@ -3170,7 +3142,10 @@
           : GetMaxDefaultVideoBitrateKbps(width, height, is_screenshare_) *
                 1000;
 
-  int min_bitrate_bps = GetMinVideoBitrateBps(encoder_config.codec_type);
+  int min_bitrate_bps =
+      experimental_min_bitrate
+          ? rtc::saturated_cast<int>(experimental_min_bitrate->bps())
+          : webrtc::kDefaultMinVideoBitrateBps;
   if (encoder_config.simulcast_layers[0].min_bitrate_bps > 0) {
     // Use set min bitrate.
     min_bitrate_bps = encoder_config.simulcast_layers[0].min_bitrate_bps;
diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h
index b989e22..6e48304 100644
--- a/media/engine/webrtc_video_engine.h
+++ b/media/engine/webrtc_video_engine.h
@@ -33,11 +33,9 @@
 #include "media/engine/unhandled_packets_buffer.h"
 #include "rtc_base/async_invoker.h"
 #include "rtc_base/critical_section.h"
-#include "rtc_base/experiments/field_trial_parser.h"
 #include "rtc_base/network_route.h"
 #include "rtc_base/thread_annotations.h"
 #include "rtc_base/thread_checker.h"
-#include "system_wrappers/include/field_trial.h"
 
 namespace webrtc {
 class VideoDecoderFactory;
@@ -51,17 +49,6 @@
 
 namespace cricket {
 
-struct MinVideoBitrateConfig {
-  webrtc::FieldTrialParameter<webrtc::DataRate> min_video_bitrate;
-
-  MinVideoBitrateConfig()
-      : min_video_bitrate("br", webrtc::DataRate::bps(kMinVideoBitrateBps)) {
-    webrtc::ParseFieldTrial(
-        {&min_video_bitrate},
-        webrtc::field_trial::FindFullName(kMinVideoBitrateExperiment));
-  }
-};
-
 class WebRtcVideoChannel;
 
 class UnsignalledSsrcHandler {
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index 1ed3dc3..b4a0a61 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -52,6 +52,7 @@
 #include "media/engine/simulcast.h"
 #include "media/engine/webrtc_voice_engine.h"
 #include "rtc_base/arraysize.h"
+#include "rtc_base/experiments/min_video_bitrate_experiment.h"
 #include "rtc_base/fake_clock.h"
 #include "rtc_base/gunit.h"
 #include "rtc_base/numerics/safe_conversions.h"
@@ -3489,7 +3490,7 @@
 TEST_F(WebRtcVideoChannelTest, VerifyMinBitrate) {
   std::vector<webrtc::VideoStream> streams = AddSendStream()->GetVideoStreams();
   ASSERT_EQ(1u, streams.size());
-  EXPECT_EQ(cricket::kMinVideoBitrateBps, streams[0].min_bitrate_bps);
+  EXPECT_EQ(webrtc::kDefaultMinVideoBitrateBps, streams[0].min_bitrate_bps);
 }
 
 TEST_F(WebRtcVideoChannelTest, VerifyMinBitrateWithForcedFallbackFieldTrial) {
@@ -5862,12 +5863,13 @@
   // we are just testing the behavior of
   // EncoderStreamFactory::CreateEncoderStreams.
   ASSERT_EQ(1UL, stream->GetVideoStreams().size());
-  EXPECT_EQ(kMinVideoBitrateBps, stream->GetVideoStreams()[0].min_bitrate_bps);
+  EXPECT_EQ(webrtc::kDefaultMinVideoBitrateBps,
+            stream->GetVideoStreams()[0].min_bitrate_bps);
 
   // Set a low max bitrate & check that VideoStream.min_bitrate_bps is limited
   // by this amount.
   parameters = channel_->GetRtpSendParameters(last_ssrc_);
-  int low_max_bitrate_bps = kMinVideoBitrateBps - 1000;
+  int low_max_bitrate_bps = webrtc::kDefaultMinVideoBitrateBps - 1000;
   parameters.encodings[0].max_bitrate_bps = low_max_bitrate_bps;
   EXPECT_TRUE(channel_->SetRtpSendParameters(last_ssrc_, parameters).ok());
 
@@ -5905,7 +5907,8 @@
   ExpectSetMaxBitrate(send_parameters_.max_bandwidth_bps);
   ASSERT_TRUE(channel_->SetSendParameters(send_parameters_));
   ASSERT_EQ(1UL, stream->GetVideoStreams().size());
-  EXPECT_EQ(kMinVideoBitrateBps, stream->GetVideoStreams()[0].min_bitrate_bps);
+  EXPECT_EQ(webrtc::kDefaultMinVideoBitrateBps,
+            stream->GetVideoStreams()[0].min_bitrate_bps);
   EXPECT_EQ(send_parameters_.max_bandwidth_bps,
             stream->GetVideoStreams()[0].max_bitrate_bps);
 
@@ -7070,7 +7073,7 @@
   // FakeVideoSendStream calls CreateEncoderStreams, test that the vector of
   // VideoStreams are created appropriately.
   EXPECT_EQ(1u, stream->GetVideoStreams().size());
-  EXPECT_EQ(cricket::kMinVideoBitrateBps,
+  EXPECT_EQ(webrtc::kDefaultMinVideoBitrateBps,
             stream->GetVideoStreams()[0].min_bitrate_bps);
   EXPECT_GT(stream->GetVideoStreams()[0].max_bitrate_bps,
             stream->GetVideoStreams()[0].min_bitrate_bps);
@@ -7565,7 +7568,7 @@
       stream.width = capture_width;
       stream.height = capture_height;
       stream.max_framerate = kDefaultVideoMaxFramerate;
-      stream.min_bitrate_bps = cricket::kMinVideoBitrateBps;
+      stream.min_bitrate_bps = webrtc::kDefaultMinVideoBitrateBps;
       stream.target_bitrate_bps = stream.max_bitrate_bps =
           GetMaxDefaultBitrateBps(capture_width, capture_height);
       stream.max_qp = kDefaultQpMax;
diff --git a/rtc_base/experiments/BUILD.gn b/rtc_base/experiments/BUILD.gn
index 68afd8e..a167605 100644
--- a/rtc_base/experiments/BUILD.gn
+++ b/rtc_base/experiments/BUILD.gn
@@ -192,6 +192,22 @@
   ]
 }
 
+rtc_static_library("min_video_bitrate_experiment") {
+  sources = [
+    "min_video_bitrate_experiment.cc",
+    "min_video_bitrate_experiment.h",
+  ]
+  deps = [
+    ":field_trial_parser",
+    "../../api/units:data_rate",
+    "../../api/video:video_frame",
+    "../../rtc_base:checks",
+    "../../rtc_base:logging",
+    "../../system_wrappers:field_trial",
+    "//third_party/abseil-cpp/absl/types:optional",
+  ]
+}
+
 if (rtc_include_tests) {
   rtc_source_set("experiments_unittests") {
     testonly = true
@@ -203,6 +219,7 @@
       "field_trial_parser_unittest.cc",
       "field_trial_units_unittest.cc",
       "keyframe_interval_settings_unittest.cc",
+      "min_video_bitrate_experiment_unittest.cc",
       "normalize_simulcast_size_experiment_unittest.cc",
       "quality_scaler_settings_unittest.cc",
       "quality_scaling_experiment_unittest.cc",
@@ -216,6 +233,7 @@
       ":cpu_speed_experiment",
       ":field_trial_parser",
       ":keyframe_interval_settings_experiment",
+      ":min_video_bitrate_experiment",
       ":normalize_simulcast_size_experiment",
       ":quality_scaler_settings",
       ":quality_scaling_experiment",
@@ -224,6 +242,8 @@
       ":stable_target_rate_experiment",
       "..:gunit_helpers",
       "../:rtc_base_tests_utils",
+      "../../api/units:data_rate",
+      "../../api/video:video_frame",
       "../../api/video_codecs:video_codecs_api",
       "../../system_wrappers:field_trial",
       "../../test:field_trial",
diff --git a/rtc_base/experiments/min_video_bitrate_experiment.cc b/rtc_base/experiments/min_video_bitrate_experiment.cc
new file mode 100644
index 0000000..c3cf937
--- /dev/null
+++ b/rtc_base/experiments/min_video_bitrate_experiment.cc
@@ -0,0 +1,110 @@
+/*
+ *  Copyright 2019 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 "rtc_base/experiments/min_video_bitrate_experiment.h"
+
+#include <string>
+
+#include "rtc_base/checks.h"
+#include "rtc_base/experiments/field_trial_parser.h"
+#include "rtc_base/logging.h"
+#include "system_wrappers/include/field_trial.h"
+
+namespace webrtc {
+
+const int kDefaultMinVideoBitrateBps = 30000;
+
+namespace {
+const char kForcedFallbackFieldTrial[] =
+    "WebRTC-VP8-Forced-Fallback-Encoder-v2";
+const char kMinVideoBitrateExperiment[] = "WebRTC-Video-MinVideoBitrate";
+
+absl::optional<int> GetFallbackMinBpsFromFieldTrial(VideoCodecType type) {
+  if (type != kVideoCodecVP8) {
+    return absl::nullopt;
+  }
+
+  if (!webrtc::field_trial::IsEnabled(kForcedFallbackFieldTrial)) {
+    return absl::nullopt;
+  }
+
+  const std::string group =
+      webrtc::field_trial::FindFullName(kForcedFallbackFieldTrial);
+  if (group.empty()) {
+    return absl::nullopt;
+  }
+
+  int min_pixels;  // Ignored.
+  int max_pixels;  // Ignored.
+  int min_bps;
+  if (sscanf(group.c_str(), "Enabled-%d,%d,%d", &min_pixels, &max_pixels,
+             &min_bps) != 3) {
+    return absl::nullopt;
+  }
+
+  if (min_bps <= 0) {
+    return absl::nullopt;
+  }
+
+  return min_bps;
+}
+}  // namespace
+
+absl::optional<DataRate> GetExperimentalMinVideoBitrate(VideoCodecType type) {
+  const absl::optional<int> fallback_min_bitrate_bps =
+      GetFallbackMinBpsFromFieldTrial(type);
+  if (fallback_min_bitrate_bps) {
+    return DataRate::bps(*fallback_min_bitrate_bps);
+  }
+
+  if (webrtc::field_trial::IsEnabled(kMinVideoBitrateExperiment)) {
+    webrtc::FieldTrialFlag enabled("Enabled");
+
+    // Backwards-compatibility with an old experiment - a generic minimum which,
+    // if set, applies to all codecs.
+    webrtc::FieldTrialOptional<webrtc::DataRate> min_video_bitrate("br");
+
+    // New experiment - per-codec minimum bitrate.
+    webrtc::FieldTrialOptional<webrtc::DataRate> min_bitrate_vp8("vp8_br");
+    webrtc::FieldTrialOptional<webrtc::DataRate> min_bitrate_vp9("vp9_br");
+    webrtc::FieldTrialOptional<webrtc::DataRate> min_bitrate_h264("h264_br");
+
+    webrtc::ParseFieldTrial(
+        {&enabled, &min_video_bitrate, &min_bitrate_vp8, &min_bitrate_vp9,
+         &min_bitrate_h264},
+        webrtc::field_trial::FindFullName(kMinVideoBitrateExperiment));
+
+    if (min_video_bitrate) {
+      if (min_bitrate_vp8 || min_bitrate_vp9 || min_bitrate_h264) {
+        // "br" is mutually-exclusive with the other configuration possibilites.
+        RTC_LOG(LS_WARNING) << "Self-contradictory experiment config.";
+      }
+      return *min_video_bitrate;
+    }
+
+    switch (type) {
+      case kVideoCodecVP8:
+        return min_bitrate_vp8.GetOptional();
+      case kVideoCodecVP9:
+        return min_bitrate_vp9.GetOptional();
+      case kVideoCodecH264:
+        return min_bitrate_h264.GetOptional();
+      case kVideoCodecGeneric:
+      case kVideoCodecMultiplex:
+        return absl::nullopt;
+    }
+
+    RTC_NOTREACHED();
+  }
+
+  return absl::nullopt;
+}
+
+}  // namespace webrtc
diff --git a/rtc_base/experiments/min_video_bitrate_experiment.h b/rtc_base/experiments/min_video_bitrate_experiment.h
new file mode 100644
index 0000000..9ea8783
--- /dev/null
+++ b/rtc_base/experiments/min_video_bitrate_experiment.h
@@ -0,0 +1,28 @@
+/*
+ *  Copyright 2019 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 RTC_BASE_EXPERIMENTS_MIN_VIDEO_BITRATE_EXPERIMENT_H_
+#define RTC_BASE_EXPERIMENTS_MIN_VIDEO_BITRATE_EXPERIMENT_H_
+
+#include "absl/types/optional.h"
+#include "api/units/data_rate.h"
+#include "api/video/video_codec_type.h"
+
+namespace webrtc {
+
+extern const int kDefaultMinVideoBitrateBps;
+
+// Return the experiment-driven minimum video bitrate.
+// If no experiment is effective, returns nullopt.
+absl::optional<DataRate> GetExperimentalMinVideoBitrate(VideoCodecType type);
+
+}  // namespace webrtc
+
+#endif  // RTC_BASE_EXPERIMENTS_MIN_VIDEO_BITRATE_EXPERIMENT_H_
diff --git a/rtc_base/experiments/min_video_bitrate_experiment_unittest.cc b/rtc_base/experiments/min_video_bitrate_experiment_unittest.cc
new file mode 100644
index 0000000..ca0550d
--- /dev/null
+++ b/rtc_base/experiments/min_video_bitrate_experiment_unittest.cc
@@ -0,0 +1,161 @@
+/*
+ *  Copyright 2019 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 "rtc_base/experiments/min_video_bitrate_experiment.h"
+
+#include "absl/types/optional.h"
+#include "api/units/data_rate.h"
+#include "api/video/video_codec_type.h"
+#include "test/field_trial.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace {
+
+TEST(GetExperimentalMinVideoBitrateTest,
+     NulloptForAllCodecsIfFieldTrialUndefined) {
+  test::ScopedFieldTrials field_trials("");
+
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecGeneric),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP8),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP9),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecH264),
+            absl::nullopt);
+  EXPECT_EQ(
+      GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecMultiplex),
+      absl::nullopt);
+}
+
+TEST(GetExperimentalMinVideoBitrateTest,
+     NulloptForAllCodecsIfFieldTrialDisabled) {
+  test::ScopedFieldTrials field_trials(
+      "WebRTC-Video-MinVideoBitrate/Disabled,br:123kbps/");
+
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecGeneric),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP8),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP9),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecH264),
+            absl::nullopt);
+  EXPECT_EQ(
+      GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecMultiplex),
+      absl::nullopt);
+}
+
+TEST(GetExperimentalMinVideoBitrateTest, BrForAllCodecsIfDefined) {
+  test::ScopedFieldTrials field_trials(
+      "WebRTC-Video-MinVideoBitrate/Enabled,br:123kbps/");
+
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecGeneric),
+            absl::make_optional(DataRate::kbps(123)));
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP8),
+            absl::make_optional(DataRate::kbps(123)));
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP9),
+            absl::make_optional(DataRate::kbps(123)));
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecH264),
+            absl::make_optional(DataRate::kbps(123)));
+  EXPECT_EQ(
+      GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecMultiplex),
+      absl::make_optional(DataRate::kbps(123)));
+}
+
+TEST(GetExperimentalMinVideoBitrateTest, BrTrumpsSpecificCodecConfigs) {
+  test::ScopedFieldTrials field_trials(
+      "WebRTC-Video-MinVideoBitrate/"
+      "Enabled,br:123kbps,vp8_br:100kbps,vp9_br:200kbps,h264_br:300kbps/");
+
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecGeneric),
+            absl::make_optional(DataRate::kbps(123)));
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP8),
+            absl::make_optional(DataRate::kbps(123)));
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP9),
+            absl::make_optional(DataRate::kbps(123)));
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecH264),
+            absl::make_optional(DataRate::kbps(123)));
+  EXPECT_EQ(
+      GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecMultiplex),
+      absl::make_optional(DataRate::kbps(123)));
+}
+
+TEST(GetExperimentalMinVideoBitrateTest,
+     SpecificCodecConfigsIgnoredIfExpDisabled) {
+  test::ScopedFieldTrials field_trials(
+      "WebRTC-Video-MinVideoBitrate/"
+      "Disabled,vp8_br:100kbps,vp9_br:200kbps,h264_br:300kbps/");
+
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecGeneric),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP8),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP9),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecH264),
+            absl::nullopt);
+  EXPECT_EQ(
+      GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecMultiplex),
+      absl::nullopt);
+}
+
+TEST(GetExperimentalMinVideoBitrateTest, SpecificCodecConfigsUsedIfExpEnabled) {
+  test::ScopedFieldTrials field_trials(
+      "WebRTC-Video-MinVideoBitrate/"
+      "Enabled,vp8_br:100kbps,vp9_br:200kbps,h264_br:300kbps/");
+
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecGeneric),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP8),
+            absl::make_optional(DataRate::kbps(100)));
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP9),
+            absl::make_optional(DataRate::kbps(200)));
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecH264),
+            absl::make_optional(DataRate::kbps(300)));
+  EXPECT_EQ(
+      GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecMultiplex),
+      absl::nullopt);
+}
+
+TEST(GetExperimentalMinVideoBitrateTest,
+     Vp8BitrateValueTakenFromFallbackIfAvailable) {
+  test::ScopedFieldTrials field_trials(
+      "WebRTC-Video-MinVideoBitrate/"
+      "Enabled,vp8_br:100kbps,vp9_br:200kbps,h264_br:300kbps/"
+      "WebRTC-VP8-Forced-Fallback-Encoder-v2/"
+      "Enabled-444444,555555,666666/");
+
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP8),
+            absl::make_optional(DataRate::bps(666666)));
+}
+
+TEST(GetExperimentalMinVideoBitrateTest,
+     NonVp8BitrateValuesTakenFromMinVideoBitrate) {
+  test::ScopedFieldTrials field_trials(
+      "WebRTC-Video-MinVideoBitrate/"
+      "Enabled,vp8_br:100kbps,vp9_br:200kbps,h264_br:300kbps/"
+      "WebRTC-VP8-Forced-Fallback-Encoder-v2/"
+      "Enabled-444444,555555,666666/");
+
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecGeneric),
+            absl::nullopt);
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecVP9),
+            absl::make_optional(DataRate::kbps(200)));
+  EXPECT_EQ(GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecH264),
+            absl::make_optional(DataRate::kbps(300)));
+  EXPECT_EQ(
+      GetExperimentalMinVideoBitrate(VideoCodecType::kVideoCodecMultiplex),
+      absl::nullopt);
+}
+
+}  // namespace
+}  // namespace webrtc
diff --git a/video/BUILD.gn b/video/BUILD.gn
index 06c0c49..b1d1b9d 100644
--- a/video/BUILD.gn
+++ b/video/BUILD.gn
@@ -106,6 +106,7 @@
     "../rtc_base/experiments:alr_experiment",
     "../rtc_base/experiments:field_trial_parser",
     "../rtc_base/experiments:keyframe_interval_settings_experiment",
+    "../rtc_base/experiments:min_video_bitrate_experiment",
     "../rtc_base/experiments:quality_scaling_experiment",
     "../rtc_base/experiments:rate_control_settings",
     "../rtc_base/synchronization:sequence_checker",
diff --git a/video/video_send_stream_impl.cc b/video/video_send_stream_impl.cc
index e333091..4b65ea8 100644
--- a/video/video_send_stream_impl.cc
+++ b/video/video_send_stream_impl.cc
@@ -28,6 +28,7 @@
 #include "rtc_base/checks.h"
 #include "rtc_base/experiments/alr_experiment.h"
 #include "rtc_base/experiments/field_trial_parser.h"
+#include "rtc_base/experiments/min_video_bitrate_experiment.h"
 #include "rtc_base/experiments/rate_control_settings.h"
 #include "rtc_base/logging.h"
 #include "rtc_base/numerics/safe_conversions.h"
@@ -55,60 +56,6 @@
   });
 }
 
-const char kForcedFallbackFieldTrial[] =
-    "WebRTC-VP8-Forced-Fallback-Encoder-v2";
-
-const int kDefaultEncoderMinBitrateBps = 30000;
-const char kMinVideoBitrateExperiment[] = "WebRTC-Video-MinVideoBitrate";
-
-struct MinVideoBitrateConfig {
-  webrtc::FieldTrialParameter<webrtc::DataRate> min_video_bitrate;
-
-  MinVideoBitrateConfig()
-      : min_video_bitrate("br",
-                          webrtc::DataRate::bps(kDefaultEncoderMinBitrateBps)) {
-    webrtc::ParseFieldTrial(
-        {&min_video_bitrate},
-        webrtc::field_trial::FindFullName(kMinVideoBitrateExperiment));
-  }
-};
-
-absl::optional<int> GetFallbackMinBpsFromFieldTrial(VideoCodecType type) {
-  if (type != kVideoCodecVP8)
-    return absl::nullopt;
-
-  if (!webrtc::field_trial::IsEnabled(kForcedFallbackFieldTrial))
-    return absl::nullopt;
-
-  std::string group =
-      webrtc::field_trial::FindFullName(kForcedFallbackFieldTrial);
-  if (group.empty())
-    return absl::nullopt;
-
-  int min_pixels;
-  int max_pixels;
-  int min_bps;
-  if (sscanf(group.c_str(), "Enabled-%d,%d,%d", &min_pixels, &max_pixels,
-             &min_bps) != 3) {
-    return absl::nullopt;
-  }
-
-  if (min_bps <= 0)
-    return absl::nullopt;
-
-  return min_bps;
-}
-
-int GetEncoderMinBitrateBps(VideoCodecType type) {
-  if (GetFallbackMinBpsFromFieldTrial(type).has_value()) {
-    return GetFallbackMinBpsFromFieldTrial(type).value();
-  }
-  if (webrtc::field_trial::IsEnabled(kMinVideoBitrateExperiment)) {
-    return MinVideoBitrateConfig().min_video_bitrate->bps();
-  }
-  return kDefaultEncoderMinBitrateBps;
-}
-
 // Calculate max padding bitrate for a multi layer codec.
 int CalculateMaxPadBitrateBps(const std::vector<VideoStream>& streams,
                               VideoEncoderConfig::ContentType content_type,
@@ -554,10 +501,18 @@
   RTC_DCHECK_GE(config_->rtp.ssrcs.size(), streams.size());
   RTC_DCHECK_RUN_ON(worker_queue_);
 
+  const VideoCodecType codec_type =
+      PayloadStringToCodecType(config_->rtp.payload_name);
+
+  const absl::optional<DataRate> experimental_min_bitrate =
+      GetExperimentalMinVideoBitrate(codec_type);
+  const int min_bitrate_bps =
+      experimental_min_bitrate
+          ? rtc::saturated_cast<int>(experimental_min_bitrate->bps())
+          : kDefaultMinVideoBitrateBps;
+
   encoder_min_bitrate_bps_ =
-      std::max(streams[0].min_bitrate_bps,
-               GetEncoderMinBitrateBps(
-                   PayloadStringToCodecType(config_->rtp.payload_name)));
+      std::max(streams[0].min_bitrate_bps, min_bitrate_bps);
   encoder_max_bitrate_bps_ = 0;
   double stream_bitrate_priority_sum = 0;
   for (const auto& stream : streams) {
@@ -575,8 +530,6 @@
                encoder_max_bitrate_bps_);
 
   // TODO(bugs.webrtc.org/10266): Query the VideoBitrateAllocator instead.
-  const VideoCodecType codec_type =
-      PayloadStringToCodecType(config_->rtp.payload_name);
   if (codec_type == kVideoCodecVP9) {
     max_padding_bitrate_ = has_alr_probing_ ? streams[0].min_bitrate_bps
                                             : streams[0].target_bitrate_bps;