Move EncoderStreamFactory into own file

This cl/ is a NOP refactoring,
moving the EncoderStreamFactory from within webrtc_video_engine.cc
into own file in video/. simulcast.cc is collateral.

Bug: webrtc:14451
Change-Id: Ia69b9241d8cd8a12be6628d887701f2e244c07cc
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/276861
Reviewed-by: Artem Titov <titovartem@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38224}
diff --git a/media/BUILD.gn b/media/BUILD.gn
index b26be06..59b8c62 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -331,6 +331,7 @@
     "../rtc_base/third_party/base64",
     "../system_wrappers",
     "../system_wrappers:metrics",
+    "../video/config:streams_config",
   ]
   absl_deps = [
     "//third_party/abseil-cpp/absl/algorithm:container",
@@ -344,8 +345,6 @@
     "engine/null_webrtc_video_engine.h",
     "engine/payload_type_mapper.cc",
     "engine/payload_type_mapper.h",
-    "engine/simulcast.cc",
-    "engine/simulcast.h",
     "engine/unhandled_packets_buffer.cc",
     "engine/unhandled_packets_buffer.h",
     "engine/webrtc_media_engine.cc",
@@ -654,6 +653,7 @@
         "../test:test_support",
         "../test:video_test_common",
         "../test/time_controller",
+        "../video/config:streams_config",
       ]
 
       if (enable_libaom) {
@@ -683,7 +683,6 @@
         "engine/null_webrtc_video_engine_unittest.cc",
         "engine/payload_type_mapper_unittest.cc",
         "engine/simulcast_encoder_adapter_unittest.cc",
-        "engine/simulcast_unittest.cc",
         "engine/unhandled_packets_buffer_unittest.cc",
         "engine/webrtc_media_engine_unittest.cc",
         "engine/webrtc_video_engine_unittest.cc",
diff --git a/media/DEPS b/media/DEPS
index a495631..25b8c63 100644
--- a/media/DEPS
+++ b/media/DEPS
@@ -22,4 +22,10 @@
   "win32devicemanager\.cc": [
     "+third_party/logitech/files/logitechquickcam.h",
   ],
+  ".*webrtc_video_engine\.h": [
+    "+video/config",
+  ],
+  ".*webrtc_video_engine_unittest\.cc": [
+    "+video/config",
+  ],
 }
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index feaa780..f86ac97 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -20,7 +20,6 @@
 #include "absl/algorithm/container.h"
 #include "absl/strings/match.h"
 #include "api/media_stream_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"
@@ -28,7 +27,6 @@
 #include "api/video_codecs/video_encoder.h"
 #include "api/video_codecs/video_encoder_factory.h"
 #include "call/call.h"
-#include "media/engine/simulcast.h"
 #include "media/engine/webrtc_media_engine.h"
 #include "media/engine/webrtc_voice_engine.h"
 #include "modules/rtp_rtcp/source/rtp_util.h"
@@ -37,8 +35,6 @@
 #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/experiments/normalize_simulcast_size_experiment.h"
 #include "rtc_base/logging.h"
 #include "rtc_base/numerics/safe_conversions.h"
 #include "rtc_base/strings/string_builder.h"
@@ -52,25 +48,12 @@
 using ::webrtc::ParseRtpPayloadType;
 using ::webrtc::ParseRtpSsrc;
 
-const int kMinLayerSize = 16;
 constexpr int64_t kUnsignaledSsrcCooldownMs = rtc::kNumMillisecsPerSec / 2;
 
 // TODO(bugs.webrtc.org/13166): Remove AV1X when backwards compatibility is not
 // needed.
 constexpr char kAv1xCodecName[] = "AV1X";
 
-int ScaleDownResolution(int resolution,
-                        double scale_down_by,
-                        int min_resolution) {
-  // Resolution is never scalied down to smaller than min_resolution.
-  // If the input resolution is already smaller than min_resolution,
-  // no scaling should be done at all.
-  if (resolution <= min_resolution)
-    return resolution;
-  return std::max(static_cast<int>(resolution / scale_down_by + 0.5),
-                  min_resolution);
-}
-
 const char* StreamTypeToString(
     webrtc::VideoSendStream::StreamStats::StreamType type) {
   switch (type) {
@@ -92,20 +75,6 @@
   return absl::StartsWith(trials.Lookup(name), "Disabled");
 }
 
-bool PowerOfTwo(int value) {
-  return (value > 0) && ((value & (value - 1)) == 0);
-}
-
-bool IsScaleFactorsPowerOfTwo(const webrtc::VideoEncoderConfig& config) {
-  for (const auto& layer : config.simulcast_layers) {
-    double scale = std::max(layer.scale_resolution_down_by, 1.0);
-    if (std::round(scale) != scale || !PowerOfTwo(scale)) {
-      return false;
-    }
-  }
-  return true;
-}
-
 void AddDefaultFeedbackParams(VideoCodec* codec,
                               const webrtc::FieldTrialsView& trials) {
   // Don't add any feedback params for RED and ULPFEC.
@@ -269,12 +238,6 @@
   return output_codecs;
 }
 
-bool IsTemporalLayersSupported(const std::string& codec_name) {
-  return absl::EqualsIgnoreCase(codec_name, kVp8CodecName) ||
-         absl::EqualsIgnoreCase(codec_name, kVp9CodecName) ||
-         absl::EqualsIgnoreCase(codec_name, kAv1CodecName);
-}
-
 static std::string CodecVectorToString(const std::vector<VideoCodec>& codecs) {
   rtc::StringBuilder out;
   out << "{";
@@ -356,26 +319,6 @@
   return false;
 }
 
-// The selected thresholds for QVGA and VGA corresponded to a QP around 10.
-// The change in QP declined above the selected bitrates.
-static int GetMaxDefaultVideoBitrateKbps(int width,
-                                         int height,
-                                         bool is_screenshare) {
-  int max_bitrate;
-  if (width * height <= 320 * 240) {
-    max_bitrate = 600;
-  } else if (width * height <= 640 * 480) {
-    max_bitrate = 1700;
-  } else if (width * height <= 960 * 540) {
-    max_bitrate = 2000;
-  } else {
-    max_bitrate = 2500;
-  }
-  if (is_screenshare)
-    max_bitrate = std::max(max_bitrate, 1200);
-  return max_bitrate;
-}
-
 // Returns its smallest positive argument. If neither argument is positive,
 // returns an arbitrary nonpositive value.
 int MinPositive(int a, int b) {
@@ -394,17 +337,6 @@
          (!layer.max_framerate || *layer.max_framerate > 0);
 }
 
-size_t FindRequiredActiveLayers(
-    const webrtc::VideoEncoderConfig& encoder_config) {
-  // Need enough layers so that at least the first active one is present.
-  for (size_t i = 0; i < encoder_config.number_of_streams; ++i) {
-    if (encoder_config.simulcast_layers[i].active) {
-      return i + 1;
-    }
-  }
-  return 0;
-}
-
 int NumActiveStreams(const webrtc::RtpParameters& rtp_parameters) {
   int res = 0;
   for (size_t i = 0; i < rtp_parameters.encodings.size(); ++i) {
@@ -3636,309 +3568,4 @@
   }
 }
 
-// TODO(bugs.webrtc.org/8785): Consider removing max_qp as member of
-// EncoderStreamFactory and instead set this value individually for each stream
-// in the VideoEncoderConfig.simulcast_layers.
-EncoderStreamFactory::EncoderStreamFactory(
-    std::string codec_name,
-    int max_qp,
-    bool is_screenshare,
-    bool conference_mode,
-    const webrtc::FieldTrialsView* trials)
-
-    : codec_name_(codec_name),
-      max_qp_(max_qp),
-      is_screenshare_(is_screenshare),
-      conference_mode_(conference_mode),
-      trials_(trials ? *trials : fallback_trials_) {}
-
-std::vector<webrtc::VideoStream> EncoderStreamFactory::CreateEncoderStreams(
-    int width,
-    int height,
-    const webrtc::VideoEncoderConfig& encoder_config) {
-  RTC_DCHECK_GT(encoder_config.number_of_streams, 0);
-  RTC_DCHECK_GE(encoder_config.simulcast_layers.size(),
-                encoder_config.number_of_streams);
-
-  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)) &&
-       is_screenshare_ && conference_mode_)) {
-    return CreateSimulcastOrConferenceModeScreenshareStreams(
-        width, height, encoder_config, experimental_min_bitrate);
-  }
-
-  return CreateDefaultVideoStreams(width, height, encoder_config,
-                                   experimental_min_bitrate);
-}
-
-std::vector<webrtc::VideoStream>
-EncoderStreamFactory::CreateDefaultVideoStreams(
-    int width,
-    int height,
-    const webrtc::VideoEncoderConfig& encoder_config,
-    const absl::optional<webrtc::DataRate>& experimental_min_bitrate) const {
-  std::vector<webrtc::VideoStream> layers;
-
-  // For unset max bitrates set default bitrate for non-simulcast.
-  int max_bitrate_bps =
-      (encoder_config.max_bitrate_bps > 0)
-          ? encoder_config.max_bitrate_bps
-          : GetMaxDefaultVideoBitrateKbps(width, height, is_screenshare_) *
-                1000;
-
-  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;
-    // If only min bitrate is configured, make sure max is above min.
-    if (encoder_config.max_bitrate_bps <= 0)
-      max_bitrate_bps = std::max(min_bitrate_bps, max_bitrate_bps);
-  }
-  int max_framerate = (encoder_config.simulcast_layers[0].max_framerate > 0)
-                          ? encoder_config.simulcast_layers[0].max_framerate
-                          : kDefaultVideoMaxFramerate;
-
-  webrtc::VideoStream layer;
-  layer.width = width;
-  layer.height = height;
-  layer.max_framerate = max_framerate;
-  // Note: VP9 seems to have be sending if any layer is active,
-  // (see `UpdateSendState`) and still use parameters only from
-  // encoder_config.simulcast_layers[0].
-  layer.active = absl::c_any_of(encoder_config.simulcast_layers,
-                                [](const auto& layer) { return layer.active; });
-
-  if (encoder_config.simulcast_layers[0].scale_resolution_down_by > 1.) {
-    layer.width = ScaleDownResolution(
-        layer.width,
-        encoder_config.simulcast_layers[0].scale_resolution_down_by,
-        kMinLayerSize);
-    layer.height = ScaleDownResolution(
-        layer.height,
-        encoder_config.simulcast_layers[0].scale_resolution_down_by,
-        kMinLayerSize);
-  }
-
-  if (absl::EqualsIgnoreCase(codec_name_, kVp9CodecName)) {
-    RTC_DCHECK(encoder_config.encoder_specific_settings);
-    // Use VP9 SVC layering from codec settings which might be initialized
-    // though field trial in ConfigureVideoEncoderSettings.
-    webrtc::VideoCodecVP9 vp9_settings;
-    encoder_config.encoder_specific_settings->FillVideoCodecVp9(&vp9_settings);
-    layer.num_temporal_layers = vp9_settings.numberOfTemporalLayers;
-
-    // Number of spatial layers is signalled differently from different call
-    // sites (sigh), pick the max as we are interested in the upper bound.
-    int num_spatial_layers =
-        std::max({encoder_config.simulcast_layers.size(),
-                  encoder_config.spatial_layers.size(),
-                  size_t{vp9_settings.numberOfSpatialLayers}});
-
-    if (width * height > 0 &&
-        (layer.num_temporal_layers > 1u || num_spatial_layers > 1)) {
-      // In SVC mode, the VP9 max bitrate is determined by SvcConfig, instead of
-      // GetMaxDefaultVideoBitrateKbps().
-      std::vector<webrtc::SpatialLayer> svc_layers =
-          webrtc::GetSvcConfig(width, height, max_framerate,
-                               /*first_active_layer=*/0, num_spatial_layers,
-                               *layer.num_temporal_layers, is_screenshare_);
-      int sum_max_bitrates_kbps = 0;
-      for (const webrtc::SpatialLayer& spatial_layer : svc_layers) {
-        sum_max_bitrates_kbps += spatial_layer.maxBitrate;
-      }
-      RTC_DCHECK_GE(sum_max_bitrates_kbps, 0);
-      if (encoder_config.max_bitrate_bps <= 0) {
-        max_bitrate_bps = sum_max_bitrates_kbps * 1000;
-      } else {
-        max_bitrate_bps =
-            std::min(max_bitrate_bps, sum_max_bitrates_kbps * 1000);
-      }
-      max_bitrate_bps = std::max(min_bitrate_bps, max_bitrate_bps);
-    }
-  }
-
-  // In the case that the application sets a max bitrate that's lower than the
-  // min bitrate, we adjust it down (see bugs.webrtc.org/9141).
-  layer.min_bitrate_bps = std::min(min_bitrate_bps, max_bitrate_bps);
-  if (encoder_config.simulcast_layers[0].target_bitrate_bps <= 0) {
-    layer.target_bitrate_bps = max_bitrate_bps;
-  } else {
-    layer.target_bitrate_bps = std::min(
-        encoder_config.simulcast_layers[0].target_bitrate_bps, max_bitrate_bps);
-  }
-  layer.max_bitrate_bps = max_bitrate_bps;
-  layer.max_qp = max_qp_;
-  layer.bitrate_priority = encoder_config.bitrate_priority;
-
-  if (IsTemporalLayersSupported(codec_name_)) {
-    // Use configured number of temporal layers if set.
-    if (encoder_config.simulcast_layers[0].num_temporal_layers) {
-      layer.num_temporal_layers =
-          *encoder_config.simulcast_layers[0].num_temporal_layers;
-    }
-  }
-  layer.scalability_mode = encoder_config.simulcast_layers[0].scalability_mode;
-  layers.push_back(layer);
-  return layers;
-}
-
-std::vector<webrtc::VideoStream>
-EncoderStreamFactory::CreateSimulcastOrConferenceModeScreenshareStreams(
-    int width,
-    int height,
-    const webrtc::VideoEncoderConfig& encoder_config,
-    const absl::optional<webrtc::DataRate>& experimental_min_bitrate) const {
-  std::vector<webrtc::VideoStream> layers;
-
-  const bool temporal_layers_supported =
-      absl::EqualsIgnoreCase(codec_name_, kVp8CodecName) ||
-      absl::EqualsIgnoreCase(codec_name_, kH264CodecName);
-  // Use legacy simulcast screenshare if conference mode is explicitly enabled
-  // or use the regular simulcast configuration path which is generic.
-  layers = GetSimulcastConfig(FindRequiredActiveLayers(encoder_config),
-                              encoder_config.number_of_streams, width, height,
-                              encoder_config.bitrate_priority, max_qp_,
-                              is_screenshare_ && conference_mode_,
-                              temporal_layers_supported, trials_);
-  // 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());
-  }
-  // Update the active simulcast layers and configured bitrates.
-  bool is_highest_layer_max_bitrate_configured = false;
-  const bool has_scale_resolution_down_by = absl::c_any_of(
-      encoder_config.simulcast_layers, [](const webrtc::VideoStream& layer) {
-        return layer.scale_resolution_down_by != -1.;
-      });
-
-  bool default_scale_factors_used = true;
-  if (has_scale_resolution_down_by) {
-    default_scale_factors_used = IsScaleFactorsPowerOfTwo(encoder_config);
-  }
-  const bool norm_size_configured =
-      webrtc::NormalizeSimulcastSizeExperiment::GetBase2Exponent().has_value();
-  const int normalized_width =
-      (default_scale_factors_used || norm_size_configured) &&
-              (width >= kMinLayerSize)
-          ? NormalizeSimulcastSize(width, encoder_config.number_of_streams)
-          : width;
-  const int normalized_height =
-      (default_scale_factors_used || norm_size_configured) &&
-              (height >= kMinLayerSize)
-          ? NormalizeSimulcastSize(height, encoder_config.number_of_streams)
-          : height;
-  for (size_t i = 0; i < layers.size(); ++i) {
-    layers[i].active = encoder_config.simulcast_layers[i].active;
-    layers[i].scalability_mode =
-        encoder_config.simulcast_layers[i].scalability_mode;
-    // Update with configured num temporal layers if supported by codec.
-    if (encoder_config.simulcast_layers[i].num_temporal_layers &&
-        IsTemporalLayersSupported(codec_name_)) {
-      layers[i].num_temporal_layers =
-          *encoder_config.simulcast_layers[i].num_temporal_layers;
-    }
-    if (encoder_config.simulcast_layers[i].max_framerate > 0) {
-      layers[i].max_framerate =
-          encoder_config.simulcast_layers[i].max_framerate;
-    }
-    if (has_scale_resolution_down_by) {
-      const double scale_resolution_down_by = std::max(
-          encoder_config.simulcast_layers[i].scale_resolution_down_by, 1.0);
-      layers[i].width = ScaleDownResolution(
-          normalized_width, scale_resolution_down_by, kMinLayerSize);
-      layers[i].height = ScaleDownResolution(
-          normalized_height, scale_resolution_down_by, kMinLayerSize);
-    }
-    // Update simulcast bitrates with configured min and max bitrate.
-    if (encoder_config.simulcast_layers[i].min_bitrate_bps > 0) {
-      layers[i].min_bitrate_bps =
-          encoder_config.simulcast_layers[i].min_bitrate_bps;
-    }
-    if (encoder_config.simulcast_layers[i].max_bitrate_bps > 0) {
-      layers[i].max_bitrate_bps =
-          encoder_config.simulcast_layers[i].max_bitrate_bps;
-    }
-    if (encoder_config.simulcast_layers[i].target_bitrate_bps > 0) {
-      layers[i].target_bitrate_bps =
-          encoder_config.simulcast_layers[i].target_bitrate_bps;
-    }
-    if (encoder_config.simulcast_layers[i].min_bitrate_bps > 0 &&
-        encoder_config.simulcast_layers[i].max_bitrate_bps > 0) {
-      // Min and max bitrate are configured.
-      // Set target to 3/4 of the max bitrate (or to max if below min).
-      if (encoder_config.simulcast_layers[i].target_bitrate_bps <= 0)
-        layers[i].target_bitrate_bps = layers[i].max_bitrate_bps * 3 / 4;
-      if (layers[i].target_bitrate_bps < layers[i].min_bitrate_bps)
-        layers[i].target_bitrate_bps = layers[i].max_bitrate_bps;
-    } else if (encoder_config.simulcast_layers[i].min_bitrate_bps > 0) {
-      // Only min bitrate is configured, make sure target/max are above min.
-      layers[i].target_bitrate_bps =
-          std::max(layers[i].target_bitrate_bps, layers[i].min_bitrate_bps);
-      layers[i].max_bitrate_bps =
-          std::max(layers[i].max_bitrate_bps, layers[i].min_bitrate_bps);
-    } else if (encoder_config.simulcast_layers[i].max_bitrate_bps > 0) {
-      // Only max bitrate is configured, make sure min/target are below max.
-      // Keep target bitrate if it is set explicitly in encoding config.
-      // Otherwise set target bitrate to 3/4 of the max bitrate
-      // or the one calculated from GetSimulcastConfig() which is larger.
-      layers[i].min_bitrate_bps =
-          std::min(layers[i].min_bitrate_bps, layers[i].max_bitrate_bps);
-      if (encoder_config.simulcast_layers[i].target_bitrate_bps <= 0) {
-        layers[i].target_bitrate_bps = std::max(
-            layers[i].target_bitrate_bps, layers[i].max_bitrate_bps * 3 / 4);
-      }
-      layers[i].target_bitrate_bps = std::max(
-          std::min(layers[i].target_bitrate_bps, layers[i].max_bitrate_bps),
-          layers[i].min_bitrate_bps);
-    }
-    if (i == layers.size() - 1) {
-      is_highest_layer_max_bitrate_configured =
-          encoder_config.simulcast_layers[i].max_bitrate_bps > 0;
-    }
-  }
-  if (!is_screenshare_ && !is_highest_layer_max_bitrate_configured &&
-      encoder_config.max_bitrate_bps > 0) {
-    // No application-configured maximum for the largest layer.
-    // If there is bitrate leftover, give it to the largest layer.
-    BoostMaxSimulcastLayer(
-        webrtc::DataRate::BitsPerSec(encoder_config.max_bitrate_bps), &layers);
-  }
-
-  // Sort the layers by max_bitrate_bps, they might not always be from
-  // smallest to biggest
-  std::vector<size_t> index(layers.size());
-  std::iota(index.begin(), index.end(), 0);
-  std::stable_sort(index.begin(), index.end(), [&layers](size_t a, size_t b) {
-    return layers[a].max_bitrate_bps < layers[b].max_bitrate_bps;
-  });
-
-  if (!layers[index[0]].active) {
-    // Adjust min bitrate of the first active layer to allow it to go as low as
-    // the lowest (now inactive) layer could.
-    // Otherwise, if e.g. a single HD stream is active, it would have 600kbps
-    // min bitrate, which would always be allocated to the stream.
-    // This would lead to congested network, dropped frames and overall bad
-    // experience.
-
-    const int min_configured_bitrate = layers[index[0]].min_bitrate_bps;
-    for (size_t i = 0; i < layers.size(); ++i) {
-      if (layers[index[i]].active) {
-        layers[index[i]].min_bitrate_bps = min_configured_bitrate;
-        break;
-      }
-    }
-  }
-
-  return layers;
-}
-
 }  // namespace cricket
diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h
index 3993c663..ec31bf7 100644
--- a/media/engine/webrtc_video_engine.h
+++ b/media/engine/webrtc_video_engine.h
@@ -37,6 +37,7 @@
 #include "rtc_base/synchronization/mutex.h"
 #include "rtc_base/system/no_unique_address.h"
 #include "rtc_base/thread_annotations.h"
+#include "video/config/encoder_stream_factory.h"
 
 namespace webrtc {
 class VideoDecoderFactory;
@@ -683,54 +684,6 @@
   bool allow_codec_switching_ = false;
 };
 
-class EncoderStreamFactory
-    : public webrtc::VideoEncoderConfig::VideoStreamFactoryInterface {
- public:
-  EncoderStreamFactory(std::string codec_name,
-                       int max_qp,
-                       bool is_screenshare,
-                       bool conference_mode)
-      : EncoderStreamFactory(codec_name,
-                             max_qp,
-                             is_screenshare,
-                             conference_mode,
-                             nullptr) {}
-
-  EncoderStreamFactory(std::string codec_name,
-                       int max_qp,
-                       bool is_screenshare,
-                       bool conference_mode,
-                       const webrtc::FieldTrialsView* trials);
-
- private:
-  std::vector<webrtc::VideoStream> CreateEncoderStreams(
-      int width,
-      int height,
-      const webrtc::VideoEncoderConfig& encoder_config) override;
-
-  std::vector<webrtc::VideoStream> CreateDefaultVideoStreams(
-      int width,
-      int height,
-      const webrtc::VideoEncoderConfig& encoder_config,
-      const absl::optional<webrtc::DataRate>& experimental_min_bitrate) const;
-
-  std::vector<webrtc::VideoStream>
-  CreateSimulcastOrConferenceModeScreenshareStreams(
-      int width,
-      int height,
-      const webrtc::VideoEncoderConfig& encoder_config,
-      const absl::optional<webrtc::DataRate>& experimental_min_bitrate) const;
-
-  const std::string codec_name_;
-  const int max_qp_;
-  const bool is_screenshare_;
-  // Allows a screenshare specific configuration, which enables temporal
-  // layering and various settings.
-  const bool conference_mode_;
-  const webrtc::FieldTrialBasedConfig fallback_trials_;
-  const webrtc::FieldTrialsView& trials_;
-};
-
 }  // namespace cricket
 
 #endif  // MEDIA_ENGINE_WEBRTC_VIDEO_ENGINE_H_
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index d3c4211..e7e43fc 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -51,7 +51,6 @@
 #include "media/base/test_utils.h"
 #include "media/engine/fake_webrtc_call.h"
 #include "media/engine/fake_webrtc_video_engine.h"
-#include "media/engine/simulcast.h"
 #include "media/engine/webrtc_voice_engine.h"
 #include "modules/rtp_rtcp/source/rtp_packet.h"
 #include "rtc_base/arraysize.h"
@@ -66,6 +65,7 @@
 #include "test/gmock.h"
 #include "test/scoped_key_value_config.h"
 #include "test/time_controller/simulated_time_controller.h"
+#include "video/config/simulcast.h"
 
 using ::testing::_;
 using ::testing::Contains;
diff --git a/modules/video_coding/BUILD.gn b/modules/video_coding/BUILD.gn
index da2a0a4..b54b8bd 100644
--- a/modules/video_coding/BUILD.gn
+++ b/modules/video_coding/BUILD.gn
@@ -989,6 +989,7 @@
       "../../test:test_support",
       "../../test:video_test_common",
       "../../test:video_test_support",
+      "../../video/config:streams_config",
     ]
     absl_deps = [
       "//third_party/abseil-cpp/absl/strings:strings",
diff --git a/modules/video_coding/DEPS b/modules/video_coding/DEPS
index 3a7629e..2aac37a 100644
--- a/modules/video_coding/DEPS
+++ b/modules/video_coding/DEPS
@@ -19,5 +19,6 @@
   ".*test.*\.cc": [
      "+media/base",
      "+media/engine",
+     "+video/config",
   ],
 }
diff --git a/modules/video_coding/codecs/test/videocodec_test_fixture_impl.cc b/modules/video_coding/codecs/test/videocodec_test_fixture_impl.cc
index 9d20373..e30fe96 100644
--- a/modules/video_coding/codecs/test/videocodec_test_fixture_impl.cc
+++ b/modules/video_coding/codecs/test/videocodec_test_fixture_impl.cc
@@ -46,7 +46,6 @@
 #include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h"
 #include "common_video/h264/h264_common.h"
 #include "media/base/media_constants.h"
-#include "media/engine/simulcast.h"
 #include "modules/video_coding/codecs/h264/include/h264_globals.h"
 #include "modules/video_coding/codecs/vp9/svc_config.h"
 #include "modules/video_coding/utility/ivf_file_writer.h"
@@ -61,6 +60,7 @@
 #include "test/testsupport/file_utils.h"
 #include "test/testsupport/frame_writer.h"
 #include "test/video_codec_settings.h"
+#include "video/config/simulcast.h"
 
 namespace webrtc {
 namespace test {
diff --git a/rtc_tools/BUILD.gn b/rtc_tools/BUILD.gn
index 19b885d..9cf7790 100644
--- a/rtc_tools/BUILD.gn
+++ b/rtc_tools/BUILD.gn
@@ -218,6 +218,7 @@
       "../test:fileutils",
       "../test:rtp_test_utils",
       "../test:video_test_common",
+      "../video/config:streams_config",
       "//third_party/abseil-cpp/absl/flags:flag",
       "//third_party/abseil-cpp/absl/flags:parse",
       "//third_party/abseil-cpp/absl/flags:usage",
diff --git a/rtc_tools/DEPS b/rtc_tools/DEPS
index 3cf6080..4d99061 100644
--- a/rtc_tools/DEPS
+++ b/rtc_tools/DEPS
@@ -17,6 +17,7 @@
   "+system_wrappers",
   "+p2p",
   "+third_party/libyuv",
+  "+video/config",
 ]
 
 specific_include_rules = {
diff --git a/rtc_tools/rtp_generator/rtp_generator.cc b/rtc_tools/rtp_generator/rtp_generator.cc
index 8a3acee..d93655a 100644
--- a/rtc_tools/rtp_generator/rtp_generator.cc
+++ b/rtc_tools/rtp_generator/rtp_generator.cc
@@ -25,6 +25,7 @@
 #include "rtc_base/system/file_wrapper.h"
 #include "rtc_base/thread.h"
 #include "test/testsupport/file_utils.h"
+#include "video/config/encoder_stream_factory.h"
 
 namespace webrtc {
 namespace {
diff --git a/test/DEPS b/test/DEPS
index ed994b7..a9e9a7b 100644
--- a/test/DEPS
+++ b/test/DEPS
@@ -22,6 +22,7 @@
   "+sdk",
   "+system_wrappers",
   "+third_party/libyuv",
+  "+video/config",
 ]
 
 specific_include_rules = {
diff --git a/test/scenario/BUILD.gn b/test/scenario/BUILD.gn
index acd77b4..e5c1901 100644
--- a/test/scenario/BUILD.gn
+++ b/test/scenario/BUILD.gn
@@ -142,7 +142,7 @@
       "../../rtc_base/task_utils:repeating_task",
       "../../system_wrappers",
       "../../system_wrappers:field_trial",
-      "../../video",
+      "../../video/config:streams_config",
       "../logging:log_writer",
       "../network:emulated_network",
       "../time_controller",
diff --git a/test/scenario/video_stream.cc b/test/scenario/video_stream.cc
index 3d1253a..60bf5ef 100644
--- a/test/scenario/video_stream.cc
+++ b/test/scenario/video_stream.cc
@@ -27,6 +27,7 @@
 #include "test/fake_encoder.h"
 #include "test/scenario/hardware_codecs.h"
 #include "test/testsupport/file_utils.h"
+#include "video/config/encoder_stream_factory.h"
 
 namespace webrtc {
 namespace test {
diff --git a/video/BUILD.gn b/video/BUILD.gn
index 76367fd..7dc15ff 100644
--- a/video/BUILD.gn
+++ b/video/BUILD.gn
@@ -71,6 +71,8 @@
     "../api/crypto:options",
     "../api/task_queue",
     "../api/task_queue:pending_task_safety_flag",
+    "../api/transport:field_trial_based_config",
+    "../api/units:data_rate",
     "../api/units:frequency",
     "../api/units:time_delta",
     "../api/units:timestamp",
@@ -102,6 +104,7 @@
     "../modules/video_coding:packet_buffer",
     "../modules/video_coding:video_codec_interface",
     "../modules/video_coding:video_coding_utility",
+    "../modules/video_coding:webrtc_vp9_helpers",
     "../modules/video_coding/timing:timing_module",
     "../rtc_base:checks",
     "../rtc_base:event_tracer",
@@ -125,6 +128,7 @@
     "../rtc_base/experiments:field_trial_parser",
     "../rtc_base/experiments:keyframe_interval_settings_experiment",
     "../rtc_base/experiments:min_video_bitrate_experiment",
+    "../rtc_base/experiments:normalize_simulcast_size_experiment",
     "../rtc_base/experiments:rate_control_settings",
     "../rtc_base/synchronization:mutex",
     "../rtc_base/system:no_unique_address",
@@ -530,6 +534,7 @@
         "../test:test_support_test_artifacts",
         "../test:video_test_common",
         "../test:video_test_support",
+        "config:streams_config",
       ]
       absl_deps = [
         "//third_party/abseil-cpp/absl/algorithm:container",
@@ -799,6 +804,7 @@
       "../api/test/metrics:global_metrics_logger_and_exporter",
       "../api/test/metrics:metric",
       "../api/test/video:function_video_factory",
+      "../api/transport:field_trial_based_config",
       "../api/units:data_rate",
       "../api/units:frequency",
       "../api/units:time_delta",
@@ -899,6 +905,7 @@
       "../test:video_test_common",
       "../test/time_controller",
       "adaptation:video_adaptation",
+      "config:streams_config",
     ]
     absl_deps = [
       "//third_party/abseil-cpp/absl/algorithm:container",
diff --git a/video/config/BUILD.gn b/video/config/BUILD.gn
new file mode 100644
index 0000000..7902c0b
--- /dev/null
+++ b/video/config/BUILD.gn
@@ -0,0 +1,68 @@
+# Copyright (c) 2022 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.
+
+import("../../webrtc.gni")
+
+rtc_library("streams_config") {
+  sources = [
+    "encoder_stream_factory.cc",
+    "encoder_stream_factory.h",
+    "simulcast.cc",
+    "simulcast.h",
+  ]
+
+  deps = [
+    "../../api:field_trials_view",
+    "../../api/transport:field_trial_based_config",
+    "../../api/units:data_rate",
+    "../../api/video:video_codec_constants",
+    "../../api/video_codecs:video_codecs_api",
+    "../../media:rtc_media_base",
+    "../../modules/video_coding:video_coding_utility",
+    "../../modules/video_coding:webrtc_vp9_helpers",
+    "../../rtc_base:checks",
+    "../../rtc_base:logging",
+    "../../rtc_base/experiments:field_trial_parser",
+    "../../rtc_base/experiments:min_video_bitrate_experiment",
+    "../../rtc_base/experiments:normalize_simulcast_size_experiment",
+    "../../rtc_base/experiments:rate_control_settings",
+  ]
+  absl_deps = [
+    "//third_party/abseil-cpp/absl/algorithm:container",
+    "//third_party/abseil-cpp/absl/memory",
+    "//third_party/abseil-cpp/absl/strings",
+    "//third_party/abseil-cpp/absl/types:optional",
+  ]
+}
+
+if (rtc_include_tests) {
+  rtc_library("video_config_tests") {
+    testonly = true
+
+    defines = []
+    sources = [ "simulcast_unittest.cc" ]
+    deps = [
+      ":streams_config",
+      "../../api/transport:field_trial_based_config",
+      "../../test:field_trial",
+      "../../test:test_support",
+    ]
+    absl_deps = [
+      "//third_party/abseil-cpp/absl/algorithm:container",
+      "//third_party/abseil-cpp/absl/functional:any_invocable",
+      "//third_party/abseil-cpp/absl/functional:bind_front",
+      "//third_party/abseil-cpp/absl/memory",
+      "//third_party/abseil-cpp/absl/strings",
+      "//third_party/abseil-cpp/absl/types:optional",
+      "//third_party/abseil-cpp/absl/types:variant",
+    ]
+    if (!build_with_mozilla) {
+      deps += [ "../../media:rtc_media_base" ]
+    }
+  }
+}
diff --git a/video/config/encoder_stream_factory.cc b/video/config/encoder_stream_factory.cc
new file mode 100644
index 0000000..f615610
--- /dev/null
+++ b/video/config/encoder_stream_factory.cc
@@ -0,0 +1,401 @@
+/*
+ *  Copyright (c) 2022 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 "video/config/encoder_stream_factory.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "absl/algorithm/container.h"
+#include "absl/strings/match.h"
+#include "api/video/video_codec_constants.h"
+#include "media/base/media_constants.h"
+#include "modules/video_coding/codecs/vp9/svc_config.h"
+#include "rtc_base/experiments/min_video_bitrate_experiment.h"
+#include "rtc_base/experiments/normalize_simulcast_size_experiment.h"
+#include "video/config/simulcast.h"
+
+namespace cricket {
+namespace {
+
+const int kMinLayerSize = 16;
+
+int ScaleDownResolution(int resolution,
+                        double scale_down_by,
+                        int min_resolution) {
+  // Resolution is never scalied down to smaller than min_resolution.
+  // If the input resolution is already smaller than min_resolution,
+  // no scaling should be done at all.
+  if (resolution <= min_resolution)
+    return resolution;
+  return std::max(static_cast<int>(resolution / scale_down_by + 0.5),
+                  min_resolution);
+}
+
+bool PowerOfTwo(int value) {
+  return (value > 0) && ((value & (value - 1)) == 0);
+}
+
+bool IsScaleFactorsPowerOfTwo(const webrtc::VideoEncoderConfig& config) {
+  for (const auto& layer : config.simulcast_layers) {
+    double scale = std::max(layer.scale_resolution_down_by, 1.0);
+    if (std::round(scale) != scale || !PowerOfTwo(scale)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool IsTemporalLayersSupported(const std::string& codec_name) {
+  return absl::EqualsIgnoreCase(codec_name, kVp8CodecName) ||
+         absl::EqualsIgnoreCase(codec_name, kVp9CodecName) ||
+         absl::EqualsIgnoreCase(codec_name, kAv1CodecName);
+}
+
+size_t FindRequiredActiveLayers(
+    const webrtc::VideoEncoderConfig& encoder_config) {
+  // Need enough layers so that at least the first active one is present.
+  for (size_t i = 0; i < encoder_config.number_of_streams; ++i) {
+    if (encoder_config.simulcast_layers[i].active) {
+      return i + 1;
+    }
+  }
+  return 0;
+}
+
+// The selected thresholds for QVGA and VGA corresponded to a QP around 10.
+// The change in QP declined above the selected bitrates.
+static int GetMaxDefaultVideoBitrateKbps(int width,
+                                         int height,
+                                         bool is_screenshare) {
+  int max_bitrate;
+  if (width * height <= 320 * 240) {
+    max_bitrate = 600;
+  } else if (width * height <= 640 * 480) {
+    max_bitrate = 1700;
+  } else if (width * height <= 960 * 540) {
+    max_bitrate = 2000;
+  } else {
+    max_bitrate = 2500;
+  }
+  if (is_screenshare)
+    max_bitrate = std::max(max_bitrate, 1200);
+  return max_bitrate;
+}
+
+}  // namespace
+
+// TODO(bugs.webrtc.org/8785): Consider removing max_qp as member of
+// EncoderStreamFactory and instead set this value individually for each stream
+// in the VideoEncoderConfig.simulcast_layers.
+EncoderStreamFactory::EncoderStreamFactory(
+    std::string codec_name,
+    int max_qp,
+    bool is_screenshare,
+    bool conference_mode,
+    const webrtc::FieldTrialsView* trials)
+
+    : codec_name_(codec_name),
+      max_qp_(max_qp),
+      is_screenshare_(is_screenshare),
+      conference_mode_(conference_mode),
+      trials_(trials ? *trials : fallback_trials_) {}
+
+std::vector<webrtc::VideoStream> EncoderStreamFactory::CreateEncoderStreams(
+    int width,
+    int height,
+    const webrtc::VideoEncoderConfig& encoder_config) {
+  RTC_DCHECK_GT(encoder_config.number_of_streams, 0);
+  RTC_DCHECK_GE(encoder_config.simulcast_layers.size(),
+                encoder_config.number_of_streams);
+
+  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)) &&
+       is_screenshare_ && conference_mode_)) {
+    return CreateSimulcastOrConferenceModeScreenshareStreams(
+        width, height, encoder_config, experimental_min_bitrate);
+  }
+
+  return CreateDefaultVideoStreams(width, height, encoder_config,
+                                   experimental_min_bitrate);
+}
+
+std::vector<webrtc::VideoStream>
+EncoderStreamFactory::CreateDefaultVideoStreams(
+    int width,
+    int height,
+    const webrtc::VideoEncoderConfig& encoder_config,
+    const absl::optional<webrtc::DataRate>& experimental_min_bitrate) const {
+  std::vector<webrtc::VideoStream> layers;
+
+  // For unset max bitrates set default bitrate for non-simulcast.
+  int max_bitrate_bps =
+      (encoder_config.max_bitrate_bps > 0)
+          ? encoder_config.max_bitrate_bps
+          : GetMaxDefaultVideoBitrateKbps(width, height, is_screenshare_) *
+                1000;
+
+  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;
+    // If only min bitrate is configured, make sure max is above min.
+    if (encoder_config.max_bitrate_bps <= 0)
+      max_bitrate_bps = std::max(min_bitrate_bps, max_bitrate_bps);
+  }
+  int max_framerate = (encoder_config.simulcast_layers[0].max_framerate > 0)
+                          ? encoder_config.simulcast_layers[0].max_framerate
+                          : kDefaultVideoMaxFramerate;
+
+  webrtc::VideoStream layer;
+  layer.width = width;
+  layer.height = height;
+  layer.max_framerate = max_framerate;
+  // Note: VP9 seems to have be sending if any layer is active,
+  // (see `UpdateSendState`) and still use parameters only from
+  // encoder_config.simulcast_layers[0].
+  layer.active = absl::c_any_of(encoder_config.simulcast_layers,
+                                [](const auto& layer) { return layer.active; });
+
+  if (encoder_config.simulcast_layers[0].scale_resolution_down_by > 1.) {
+    layer.width = ScaleDownResolution(
+        layer.width,
+        encoder_config.simulcast_layers[0].scale_resolution_down_by,
+        kMinLayerSize);
+    layer.height = ScaleDownResolution(
+        layer.height,
+        encoder_config.simulcast_layers[0].scale_resolution_down_by,
+        kMinLayerSize);
+  }
+
+  if (absl::EqualsIgnoreCase(codec_name_, kVp9CodecName)) {
+    RTC_DCHECK(encoder_config.encoder_specific_settings);
+    // Use VP9 SVC layering from codec settings which might be initialized
+    // though field trial in ConfigureVideoEncoderSettings.
+    webrtc::VideoCodecVP9 vp9_settings;
+    encoder_config.encoder_specific_settings->FillVideoCodecVp9(&vp9_settings);
+    layer.num_temporal_layers = vp9_settings.numberOfTemporalLayers;
+
+    // Number of spatial layers is signalled differently from different call
+    // sites (sigh), pick the max as we are interested in the upper bound.
+    int num_spatial_layers =
+        std::max({encoder_config.simulcast_layers.size(),
+                  encoder_config.spatial_layers.size(),
+                  size_t{vp9_settings.numberOfSpatialLayers}});
+
+    if (width * height > 0 &&
+        (layer.num_temporal_layers > 1u || num_spatial_layers > 1)) {
+      // In SVC mode, the VP9 max bitrate is determined by SvcConfig, instead of
+      // GetMaxDefaultVideoBitrateKbps().
+      std::vector<webrtc::SpatialLayer> svc_layers =
+          webrtc::GetSvcConfig(width, height, max_framerate,
+                               /*first_active_layer=*/0, num_spatial_layers,
+                               *layer.num_temporal_layers, is_screenshare_);
+      int sum_max_bitrates_kbps = 0;
+      for (const webrtc::SpatialLayer& spatial_layer : svc_layers) {
+        sum_max_bitrates_kbps += spatial_layer.maxBitrate;
+      }
+      RTC_DCHECK_GE(sum_max_bitrates_kbps, 0);
+      if (encoder_config.max_bitrate_bps <= 0) {
+        max_bitrate_bps = sum_max_bitrates_kbps * 1000;
+      } else {
+        max_bitrate_bps =
+            std::min(max_bitrate_bps, sum_max_bitrates_kbps * 1000);
+      }
+      max_bitrate_bps = std::max(min_bitrate_bps, max_bitrate_bps);
+    }
+  }
+
+  // In the case that the application sets a max bitrate that's lower than the
+  // min bitrate, we adjust it down (see bugs.webrtc.org/9141).
+  layer.min_bitrate_bps = std::min(min_bitrate_bps, max_bitrate_bps);
+  if (encoder_config.simulcast_layers[0].target_bitrate_bps <= 0) {
+    layer.target_bitrate_bps = max_bitrate_bps;
+  } else {
+    layer.target_bitrate_bps = std::min(
+        encoder_config.simulcast_layers[0].target_bitrate_bps, max_bitrate_bps);
+  }
+  layer.max_bitrate_bps = max_bitrate_bps;
+  layer.max_qp = max_qp_;
+  layer.bitrate_priority = encoder_config.bitrate_priority;
+
+  if (IsTemporalLayersSupported(codec_name_)) {
+    // Use configured number of temporal layers if set.
+    if (encoder_config.simulcast_layers[0].num_temporal_layers) {
+      layer.num_temporal_layers =
+          *encoder_config.simulcast_layers[0].num_temporal_layers;
+    }
+  }
+  layer.scalability_mode = encoder_config.simulcast_layers[0].scalability_mode;
+  layers.push_back(layer);
+  return layers;
+}
+
+std::vector<webrtc::VideoStream>
+EncoderStreamFactory::CreateSimulcastOrConferenceModeScreenshareStreams(
+    int width,
+    int height,
+    const webrtc::VideoEncoderConfig& encoder_config,
+    const absl::optional<webrtc::DataRate>& experimental_min_bitrate) const {
+  std::vector<webrtc::VideoStream> layers;
+
+  const bool temporal_layers_supported =
+      absl::EqualsIgnoreCase(codec_name_, kVp8CodecName) ||
+      absl::EqualsIgnoreCase(codec_name_, kH264CodecName);
+  // Use legacy simulcast screenshare if conference mode is explicitly enabled
+  // or use the regular simulcast configuration path which is generic.
+  layers = GetSimulcastConfig(FindRequiredActiveLayers(encoder_config),
+                              encoder_config.number_of_streams, width, height,
+                              encoder_config.bitrate_priority, max_qp_,
+                              is_screenshare_ && conference_mode_,
+                              temporal_layers_supported, trials_);
+  // 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());
+  }
+  // Update the active simulcast layers and configured bitrates.
+  bool is_highest_layer_max_bitrate_configured = false;
+  const bool has_scale_resolution_down_by = absl::c_any_of(
+      encoder_config.simulcast_layers, [](const webrtc::VideoStream& layer) {
+        return layer.scale_resolution_down_by != -1.;
+      });
+
+  bool default_scale_factors_used = true;
+  if (has_scale_resolution_down_by) {
+    default_scale_factors_used = IsScaleFactorsPowerOfTwo(encoder_config);
+  }
+  const bool norm_size_configured =
+      webrtc::NormalizeSimulcastSizeExperiment::GetBase2Exponent().has_value();
+  const int normalized_width =
+      (default_scale_factors_used || norm_size_configured) &&
+              (width >= kMinLayerSize)
+          ? NormalizeSimulcastSize(width, encoder_config.number_of_streams)
+          : width;
+  const int normalized_height =
+      (default_scale_factors_used || norm_size_configured) &&
+              (height >= kMinLayerSize)
+          ? NormalizeSimulcastSize(height, encoder_config.number_of_streams)
+          : height;
+  for (size_t i = 0; i < layers.size(); ++i) {
+    layers[i].active = encoder_config.simulcast_layers[i].active;
+    layers[i].scalability_mode =
+        encoder_config.simulcast_layers[i].scalability_mode;
+    // Update with configured num temporal layers if supported by codec.
+    if (encoder_config.simulcast_layers[i].num_temporal_layers &&
+        IsTemporalLayersSupported(codec_name_)) {
+      layers[i].num_temporal_layers =
+          *encoder_config.simulcast_layers[i].num_temporal_layers;
+    }
+    if (encoder_config.simulcast_layers[i].max_framerate > 0) {
+      layers[i].max_framerate =
+          encoder_config.simulcast_layers[i].max_framerate;
+    }
+    if (has_scale_resolution_down_by) {
+      const double scale_resolution_down_by = std::max(
+          encoder_config.simulcast_layers[i].scale_resolution_down_by, 1.0);
+      layers[i].width = ScaleDownResolution(
+          normalized_width, scale_resolution_down_by, kMinLayerSize);
+      layers[i].height = ScaleDownResolution(
+          normalized_height, scale_resolution_down_by, kMinLayerSize);
+    }
+    // Update simulcast bitrates with configured min and max bitrate.
+    if (encoder_config.simulcast_layers[i].min_bitrate_bps > 0) {
+      layers[i].min_bitrate_bps =
+          encoder_config.simulcast_layers[i].min_bitrate_bps;
+    }
+    if (encoder_config.simulcast_layers[i].max_bitrate_bps > 0) {
+      layers[i].max_bitrate_bps =
+          encoder_config.simulcast_layers[i].max_bitrate_bps;
+    }
+    if (encoder_config.simulcast_layers[i].target_bitrate_bps > 0) {
+      layers[i].target_bitrate_bps =
+          encoder_config.simulcast_layers[i].target_bitrate_bps;
+    }
+    if (encoder_config.simulcast_layers[i].min_bitrate_bps > 0 &&
+        encoder_config.simulcast_layers[i].max_bitrate_bps > 0) {
+      // Min and max bitrate are configured.
+      // Set target to 3/4 of the max bitrate (or to max if below min).
+      if (encoder_config.simulcast_layers[i].target_bitrate_bps <= 0)
+        layers[i].target_bitrate_bps = layers[i].max_bitrate_bps * 3 / 4;
+      if (layers[i].target_bitrate_bps < layers[i].min_bitrate_bps)
+        layers[i].target_bitrate_bps = layers[i].max_bitrate_bps;
+    } else if (encoder_config.simulcast_layers[i].min_bitrate_bps > 0) {
+      // Only min bitrate is configured, make sure target/max are above min.
+      layers[i].target_bitrate_bps =
+          std::max(layers[i].target_bitrate_bps, layers[i].min_bitrate_bps);
+      layers[i].max_bitrate_bps =
+          std::max(layers[i].max_bitrate_bps, layers[i].min_bitrate_bps);
+    } else if (encoder_config.simulcast_layers[i].max_bitrate_bps > 0) {
+      // Only max bitrate is configured, make sure min/target are below max.
+      // Keep target bitrate if it is set explicitly in encoding config.
+      // Otherwise set target bitrate to 3/4 of the max bitrate
+      // or the one calculated from GetSimulcastConfig() which is larger.
+      layers[i].min_bitrate_bps =
+          std::min(layers[i].min_bitrate_bps, layers[i].max_bitrate_bps);
+      if (encoder_config.simulcast_layers[i].target_bitrate_bps <= 0) {
+        layers[i].target_bitrate_bps = std::max(
+            layers[i].target_bitrate_bps, layers[i].max_bitrate_bps * 3 / 4);
+      }
+      layers[i].target_bitrate_bps = std::max(
+          std::min(layers[i].target_bitrate_bps, layers[i].max_bitrate_bps),
+          layers[i].min_bitrate_bps);
+    }
+    if (i == layers.size() - 1) {
+      is_highest_layer_max_bitrate_configured =
+          encoder_config.simulcast_layers[i].max_bitrate_bps > 0;
+    }
+  }
+  if (!is_screenshare_ && !is_highest_layer_max_bitrate_configured &&
+      encoder_config.max_bitrate_bps > 0) {
+    // No application-configured maximum for the largest layer.
+    // If there is bitrate leftover, give it to the largest layer.
+    BoostMaxSimulcastLayer(
+        webrtc::DataRate::BitsPerSec(encoder_config.max_bitrate_bps), &layers);
+  }
+
+  // Sort the layers by max_bitrate_bps, they might not always be from
+  // smallest to biggest
+  std::vector<size_t> index(layers.size());
+  std::iota(index.begin(), index.end(), 0);
+  std::stable_sort(index.begin(), index.end(), [&layers](size_t a, size_t b) {
+    return layers[a].max_bitrate_bps < layers[b].max_bitrate_bps;
+  });
+
+  if (!layers[index[0]].active) {
+    // Adjust min bitrate of the first active layer to allow it to go as low as
+    // the lowest (now inactive) layer could.
+    // Otherwise, if e.g. a single HD stream is active, it would have 600kbps
+    // min bitrate, which would always be allocated to the stream.
+    // This would lead to congested network, dropped frames and overall bad
+    // experience.
+
+    const int min_configured_bitrate = layers[index[0]].min_bitrate_bps;
+    for (size_t i = 0; i < layers.size(); ++i) {
+      if (layers[index[i]].active) {
+        layers[index[i]].min_bitrate_bps = min_configured_bitrate;
+        break;
+      }
+    }
+  }
+
+  return layers;
+}
+
+}  // namespace cricket
diff --git a/video/config/encoder_stream_factory.h b/video/config/encoder_stream_factory.h
new file mode 100644
index 0000000..971eed4
--- /dev/null
+++ b/video/config/encoder_stream_factory.h
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (c) 2022 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 VIDEO_CONFIG_ENCODER_STREAM_FACTORY_H_
+#define VIDEO_CONFIG_ENCODER_STREAM_FACTORY_H_
+
+#include <string>
+#include <vector>
+
+#include "api/transport/field_trial_based_config.h"
+#include "api/units/data_rate.h"
+#include "api/video_codecs/video_encoder_config.h"
+
+namespace cricket {
+
+class EncoderStreamFactory
+    : public webrtc::VideoEncoderConfig::VideoStreamFactoryInterface {
+ public:
+  EncoderStreamFactory(std::string codec_name,
+                       int max_qp,
+                       bool is_screenshare,
+                       bool conference_mode)
+      : EncoderStreamFactory(codec_name,
+                             max_qp,
+                             is_screenshare,
+                             conference_mode,
+                             nullptr) {}
+
+  EncoderStreamFactory(std::string codec_name,
+                       int max_qp,
+                       bool is_screenshare,
+                       bool conference_mode,
+                       const webrtc::FieldTrialsView* trials);
+
+ private:
+  std::vector<webrtc::VideoStream> CreateEncoderStreams(
+      int width,
+      int height,
+      const webrtc::VideoEncoderConfig& encoder_config) override;
+
+  std::vector<webrtc::VideoStream> CreateDefaultVideoStreams(
+      int width,
+      int height,
+      const webrtc::VideoEncoderConfig& encoder_config,
+      const absl::optional<webrtc::DataRate>& experimental_min_bitrate) const;
+
+  std::vector<webrtc::VideoStream>
+  CreateSimulcastOrConferenceModeScreenshareStreams(
+      int width,
+      int height,
+      const webrtc::VideoEncoderConfig& encoder_config,
+      const absl::optional<webrtc::DataRate>& experimental_min_bitrate) const;
+
+  const std::string codec_name_;
+  const int max_qp_;
+  const bool is_screenshare_;
+  // Allows a screenshare specific configuration, which enables temporal
+  // layering and various settings.
+  const bool conference_mode_;
+  const webrtc::FieldTrialBasedConfig fallback_trials_;
+  const webrtc::FieldTrialsView& trials_;
+};
+
+}  // namespace cricket
+
+#endif  // VIDEO_CONFIG_ENCODER_STREAM_FACTORY_H_
diff --git a/media/engine/simulcast.cc b/video/config/simulcast.cc
similarity index 99%
rename from media/engine/simulcast.cc
rename to video/config/simulcast.cc
index fb10c0f..2bd4ac0 100644
--- a/media/engine/simulcast.cc
+++ b/video/config/simulcast.cc
@@ -8,7 +8,7 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "media/engine/simulcast.h"
+#include "video/config/simulcast.h"
 
 #include <stdint.h>
 #include <stdio.h>
diff --git a/media/engine/simulcast.h b/video/config/simulcast.h
similarity index 94%
rename from media/engine/simulcast.h
rename to video/config/simulcast.h
index e367830..aa48058 100644
--- a/media/engine/simulcast.h
+++ b/video/config/simulcast.h
@@ -8,8 +8,8 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#ifndef MEDIA_ENGINE_SIMULCAST_H_
-#define MEDIA_ENGINE_SIMULCAST_H_
+#ifndef VIDEO_CONFIG_SIMULCAST_H_
+#define VIDEO_CONFIG_SIMULCAST_H_
 
 #include <stddef.h>
 
@@ -69,4 +69,4 @@
 
 }  // namespace cricket
 
-#endif  // MEDIA_ENGINE_SIMULCAST_H_
+#endif  // VIDEO_CONFIG_SIMULCAST_H_
diff --git a/media/engine/simulcast_unittest.cc b/video/config/simulcast_unittest.cc
similarity index 99%
rename from media/engine/simulcast_unittest.cc
rename to video/config/simulcast_unittest.cc
index 47a9db7..152a0f9 100644
--- a/media/engine/simulcast_unittest.cc
+++ b/video/config/simulcast_unittest.cc
@@ -8,7 +8,7 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "media/engine/simulcast.h"
+#include "video/config/simulcast.h"
 
 #include "api/transport/field_trial_based_config.h"
 #include "media/base/media_constants.h"
diff --git a/video/end_to_end_tests/resolution_bitrate_limits_tests.cc b/video/end_to_end_tests/resolution_bitrate_limits_tests.cc
index 8cdfaad..5d905b8 100644
--- a/video/end_to_end_tests/resolution_bitrate_limits_tests.cc
+++ b/video/end_to_end_tests/resolution_bitrate_limits_tests.cc
@@ -18,6 +18,7 @@
 #include "test/field_trial.h"
 #include "test/gtest.h"
 #include "test/video_encoder_proxy_factory.h"
+#include "video/config/encoder_stream_factory.h"
 
 namespace webrtc {
 namespace test {
diff --git a/video/video_quality_test.cc b/video/video_quality_test.cc
index 1b5b3a8..d055431 100644
--- a/video/video_quality_test.cc
+++ b/video/video_quality_test.cc
@@ -54,6 +54,7 @@
 #ifdef WEBRTC_WIN
 #include "modules/audio_device/include/audio_device_factory.h"
 #endif
+#include "video/config/encoder_stream_factory.h"
 
 namespace webrtc {
 
diff --git a/video/video_send_stream_tests.cc b/video/video_send_stream_tests.cc
index 965073b..33a607b 100644
--- a/video/video_send_stream_tests.cc
+++ b/video/video_send_stream_tests.cc
@@ -69,6 +69,7 @@
 #include "test/null_transport.h"
 #include "test/rtcp_packet_parser.h"
 #include "test/video_encoder_proxy_factory.h"
+#include "video/config/encoder_stream_factory.h"
 #include "video/send_statistics_proxy.h"
 #include "video/transport_adapter.h"
 #include "video/video_send_stream.h"
diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc
index 3baa9c5..961f592 100644
--- a/video/video_stream_encoder_unittest.cc
+++ b/video/video_stream_encoder_unittest.cc
@@ -1,3 +1,4 @@
+
 /*
  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  *
@@ -7,7 +8,6 @@
  *  in the file PATENTS.  All contributing project authors may
  *  be found in the AUTHORS file in the root of the source tree.
  */
-
 #include "video/video_stream_encoder.h"
 
 #include <algorithm>
@@ -70,6 +70,7 @@
 #include "test/time_controller/simulated_time_controller.h"
 #include "test/video_encoder_nullable_proxy_factory.h"
 #include "test/video_encoder_proxy_factory.h"
+#include "video/config/encoder_stream_factory.h"
 #include "video/frame_cadence_adapter.h"
 #include "video/send_statistics_proxy.h"