[Interop 2026] Apply simulcast scaling defaults

Apply scale_resolution_down_by defaults to video encodings passed to
AddTransceiver. If any encoding provides a value, fill missing values
with 1.0 before trimming the list. Otherwise, assign descending powers
of two after trimming.

This aligns sender parameters with the WebRTC addTransceiver algorithm
and fixes two Interop 2026 WPT subtests in Chromium.

R=perkj@webrtc.org, tommi@webrtc.org

Bug: chromium:344943229
Change-Id: I490175e44f1b840e537d4e24718ca7cbef45389a
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/497360
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#48439}
diff --git a/AUTHORS b/AUTHORS
index fc3625b..bc63173 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -97,6 +97,7 @@
 Maxim Potapov <vopatop.skam@gmail.com>
 Michael Iedema <michael@kapsulate.com>
 Michael Klingbeil <klingm@amazon.com>
+Michael Reeves <michael.reeves077@gmail.com>
 Michał Zarach <michalzaq12@gmail.com>
 Michel Promonet <michel.promonet.1@gmail.com>
 Miguel Paris <mparisdiaz@gmail.com>
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 2430287..eb91c0f 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -3263,6 +3263,7 @@
       "../api/audio_codecs:builtin_audio_encoder_factory",
       "../api/units:data_rate",
       "../api/video:render_resolution",
+      "../api/video:video_codec_constants",
       "../api/video_codecs:video_codecs_api",
       "../api/video_codecs:video_decoder_factory_template",
       "../api/video_codecs:video_decoder_factory_template_dav1d_adapter",
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 8c60c14..d91dd1a 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -1064,6 +1064,20 @@
   RtpParameters parameters;
   parameters.encodings = init.send_encodings;
 
+  const bool has_scale_resolution_down_by =
+      media_type == MediaType::VIDEO &&
+      absl::c_any_of(parameters.encodings,
+                     [](const RtpEncodingParameters& encoding) {
+                       return encoding.scale_resolution_down_by.has_value();
+                     });
+  if (has_scale_resolution_down_by) {
+    for (RtpEncodingParameters& encoding : parameters.encodings) {
+      if (!encoding.scale_resolution_down_by.has_value()) {
+        encoding.scale_resolution_down_by = 1.0;
+      }
+    }
+  }
+
   // Encodings are dropped from the tail if too many are provided.
   size_t max_simulcast_streams =
       media_type == MediaType::VIDEO ? kMaxSimulcastStreams : 1u;
@@ -1093,6 +1107,15 @@
     parameters.encodings.push_back({});
   }
 
+  if (media_type == MediaType::VIDEO && !has_scale_resolution_down_by) {
+    double scale_resolution_down_by = 1.0;
+    for (auto encoding = parameters.encodings.rbegin();
+         encoding != parameters.encodings.rend(); ++encoding) {
+      encoding->scale_resolution_down_by = scale_resolution_down_by;
+      scale_resolution_down_by *= 2.0;
+    }
+  }
+
   if (UnimplementedRtpParameterHasValue(parameters)) {
     return RTC_LOG_ERROR(
         RTCError(RTCErrorType::UNSUPPORTED_PARAMETER)
diff --git a/pc/peer_connection_encodings_integrationtest.cc b/pc/peer_connection_encodings_integrationtest.cc
index 31c83fb..daa20af 100644
--- a/pc/peer_connection_encodings_integrationtest.cc
+++ b/pc/peer_connection_encodings_integrationtest.cc
@@ -657,6 +657,14 @@
       GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9");
   transceiver->SetCodecPreferences(codecs);
 
+  scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
+  RtpParameters parameters = sender->GetParameters();
+  // Keep this legacy SVC test independent of simulcast's default scaling.
+  for (RtpEncodingParameters& encoding : parameters.encodings) {
+    encoding.scale_resolution_down_by = 1.0;
+  }
+  ASSERT_TRUE(sender->SetParameters(parameters).ok());
+
   NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
@@ -678,7 +686,6 @@
   // Despite SVC being used on a single RTP stream, GetParameters() returns the
   // three encodings that we configured earlier (this is not spec-compliant but
   // it is how legacy SVC behaves).
-  scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
   std::vector<RtpEncodingParameters> encodings =
       sender->GetParameters().encodings;
   ASSERT_EQ(encodings.size(), 3u);
@@ -1229,6 +1236,10 @@
   // disabling the bottom two spatial layers resulting in L1T3.
   scoped_refptr<RtpSenderInterface> sender = transceiver->sender();
   RtpParameters parameters = sender->GetParameters();
+  // Keep this legacy SVC test independent of simulcast's default scaling.
+  for (RtpEncodingParameters& encoding : parameters.encodings) {
+    encoding.scale_resolution_down_by = 1.0;
+  }
   parameters.encodings[0].active = false;
   parameters.encodings[1].active = false;
   parameters.encodings[2].active = true;
diff --git a/pc/peer_connection_rtp_unittest.cc b/pc/peer_connection_rtp_unittest.cc
index c5a6dbb..daa826884 100644
--- a/pc/peer_connection_rtp_unittest.cc
+++ b/pc/peer_connection_rtp_unittest.cc
@@ -36,6 +36,7 @@
 #include "api/set_remote_description_observer_interface.h"
 #include "api/units/data_rate.h"
 #include "api/video/render_resolution.h"
+#include "api/video/video_codec_constants.h"
 #include "api/video_codecs/sdp_video_format.h"
 #include "api/video_codecs/video_decoder_factory_template.h"
 #include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h"
@@ -75,6 +76,7 @@
 
 using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
 using ::testing::ElementsAre;
+using ::testing::Eq;
 using ::testing::UnorderedElementsAre;
 using ::testing::Values;
 
@@ -1843,6 +1845,57 @@
   init.send_encodings = default_send_encodings;
 }
 
+TEST_F(PeerConnectionRtpTestUnifiedPlan,
+       DefaultsMissingScaleResolutionDownByToOne) {
+  auto caller = CreatePeerConnection();
+
+  RtpTransceiverInit init;
+  init.send_encodings.resize(2);
+  init.send_encodings[1].scale_resolution_down_by = 3.0;
+
+  auto result = caller->pc()->AddTransceiver(MediaType::VIDEO, init);
+  ASSERT_TRUE(result.ok());
+
+  const auto parameters = result.value()->sender()->GetParameters();
+  ASSERT_EQ(parameters.encodings.size(), 2u);
+  EXPECT_THAT(parameters.encodings[0].scale_resolution_down_by, Eq(1.0));
+  EXPECT_THAT(parameters.encodings[1].scale_resolution_down_by, Eq(3.0));
+}
+
+TEST_F(PeerConnectionRtpTestUnifiedPlan,
+       DefaultsScaleResolutionDownByToPowersOfTwo) {
+  auto caller = CreatePeerConnection();
+
+  RtpTransceiverInit init;
+  init.send_encodings.resize(2);
+
+  auto result = caller->pc()->AddTransceiver(MediaType::VIDEO, init);
+  ASSERT_TRUE(result.ok());
+
+  const auto parameters = result.value()->sender()->GetParameters();
+  ASSERT_EQ(parameters.encodings.size(), 2u);
+  EXPECT_THAT(parameters.encodings[0].scale_resolution_down_by, Eq(2.0));
+  EXPECT_THAT(parameters.encodings[1].scale_resolution_down_by, Eq(1.0));
+}
+
+TEST_F(PeerConnectionRtpTestUnifiedPlan,
+       DefaultsScaleResolutionDownByBeforeTrimmingEncodings) {
+  auto caller = CreatePeerConnection();
+
+  RtpTransceiverInit init;
+  init.send_encodings.resize(kMaxSimulcastStreams + 1);
+  init.send_encodings.back().scale_resolution_down_by = 3.0;
+
+  auto result = caller->pc()->AddTransceiver(MediaType::VIDEO, init);
+  ASSERT_TRUE(result.ok());
+
+  const auto parameters = result.value()->sender()->GetParameters();
+  ASSERT_EQ(parameters.encodings.size(), kMaxSimulcastStreams);
+  for (const RtpEncodingParameters& encoding : parameters.encodings) {
+    EXPECT_THAT(encoding.scale_resolution_down_by, Eq(1.0));
+  }
+}
+
 // Test that AddTransceiver transfers the send_encodings to the sender and they
 // are retained after SetLocalDescription().
 TEST_F(PeerConnectionRtpTestUnifiedPlan, SendEncodingsPassedToSender) {