RtpEncodingParameters::request_resolution patch 1

This patch adds RtpEncodingParameters::request_resolution
with documentation and plumming. No behaviour is changed yet.

Bug: webrtc:14451
Change-Id: I1f4f83a312ee8c293e3d8f02b950751e62048304
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/276262
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38172}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index 9445e3c..5bf7396 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -454,6 +454,7 @@
     "../rtc_base:checks",
     "../rtc_base:stringutils",
     "../rtc_base/system:rtc_export",
+    "video:resolution",
     "video_codecs:scalability_mode",
   ]
   absl_deps = [
diff --git a/api/rtp_parameters.h b/api/rtp_parameters.h
index e311577..0d3c9df 100644
--- a/api/rtp_parameters.h
+++ b/api/rtp_parameters.h
@@ -23,6 +23,7 @@
 #include "api/media_types.h"
 #include "api/priority.h"
 #include "api/rtp_transceiver_direction.h"
+#include "api/video/resolution.h"
 #include "api/video_codecs/scalability_mode.h"
 #include "rtc_base/system/rtc_export.h"
 
@@ -502,6 +503,24 @@
   // https://w3c.github.io/webrtc-svc/#rtcrtpencodingparameters
   absl::optional<std::string> scalability_mode;
 
+  // Requested encode resolution.
+  //
+  // This field provides an alternative to `scale_resolution_down_by`
+  // that is not dependent on the video source.
+  //
+  // When setting requested_resolution it is not necessary to adapt the
+  // video source using OnOutputFormatRequest, since the VideoStreamEncoder
+  // will apply downscaling if necessary. requested_resolution will also be
+  // propagated to the video source, this allows downscaling earlier in the
+  // pipeline which can be beneficial if the source is consumed by multiple
+  // encoders, but is not strictly necessary.
+  //
+  // The `requested_resolution` is subject to resource adaptation.
+  //
+  // It is an error to set both `requested_resolution` and
+  // `scale_resolution_down_by`.
+  absl::optional<Resolution> requested_resolution;
+
   // For an RtpSender, set to true to cause this encoding to be encoded and
   // sent, and false for it not to be encoded and sent. This allows control
   // across multiple encodings of a sender for turning simulcast layers on and
@@ -527,7 +546,8 @@
            num_temporal_layers == o.num_temporal_layers &&
            scale_resolution_down_by == o.scale_resolution_down_by &&
            active == o.active && rid == o.rid &&
-           adaptive_ptime == o.adaptive_ptime;
+           adaptive_ptime == o.adaptive_ptime &&
+           requested_resolution == o.requested_resolution;
   }
   bool operator!=(const RtpEncodingParameters& o) const {
     return !(*this == o);
diff --git a/api/video/BUILD.gn b/api/video/BUILD.gn
index 060b9e4..ee62abd 100644
--- a/api/video/BUILD.gn
+++ b/api/video/BUILD.gn
@@ -131,6 +131,11 @@
   public = [ "render_resolution.h" ]
 }
 
+rtc_source_set("resolution") {
+  visibility = [ "*" ]
+  public = [ "resolution.h" ]
+}
+
 rtc_library("encoded_image") {
   visibility = [ "*" ]
   sources = [
diff --git a/api/video/recordable_encoded_frame.h b/api/video/recordable_encoded_frame.h
index 702f4d7..47ea23f 100644
--- a/api/video/recordable_encoded_frame.h
+++ b/api/video/recordable_encoded_frame.h
@@ -24,6 +24,7 @@
 class RecordableEncodedFrame {
  public:
   // Encoded resolution in pixels
+  // TODO(bugs.webrtc.org/12114) : remove in favor of Resolution.
   struct EncodedResolution {
     bool empty() const { return width == 0 && height == 0; }
 
diff --git a/api/video/render_resolution.h b/api/video/render_resolution.h
index edcf8f8..fcf4f12 100644
--- a/api/video/render_resolution.h
+++ b/api/video/render_resolution.h
@@ -13,6 +13,7 @@
 
 namespace webrtc {
 
+// TODO(bugs.webrtc.org/12114) : remove in favor of Resolution.
 class RenderResolution {
  public:
   constexpr RenderResolution() = default;
diff --git a/api/video/resolution.h b/api/video/resolution.h
new file mode 100644
index 0000000..99cb622
--- /dev/null
+++ b/api/video/resolution.h
@@ -0,0 +1,32 @@
+/*
+ *  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 API_VIDEO_RESOLUTION_H_
+#define API_VIDEO_RESOLUTION_H_
+
+namespace webrtc {
+
+// A struct representing a video resolution in pixels.
+struct Resolution {
+  int width = 0;
+  int height = 0;
+};
+
+inline bool operator==(const Resolution& lhs, const Resolution& rhs) {
+  return lhs.width == rhs.width && lhs.height == rhs.height;
+}
+
+inline bool operator!=(const Resolution& lhs, const Resolution& rhs) {
+  return !(lhs == rhs);
+}
+
+}  // namespace webrtc
+
+#endif  // API_VIDEO_RESOLUTION_H_
diff --git a/api/video/video_source_interface.h b/api/video/video_source_interface.h
index 5eb4ebf..72937c7 100644
--- a/api/video/video_source_interface.h
+++ b/api/video/video_source_interface.h
@@ -80,6 +80,24 @@
   // Note that the `resolutions` can change while frames are in flight and
   // should only be used as a hint when constructing the webrtc::VideoFrame.
   std::vector<FrameSize> resolutions;
+
+  // This is the resolution requested by the user using RtpEncodingParameters.
+  absl::optional<FrameSize> requested_resolution;
+
+  // `active` : is (any) of the layers/sink(s) active.
+  bool is_active = false;
+
+  // This sub-struct contains information computed by VideoBroadcaster
+  // that aggregates several VideoSinkWants (and sends them to
+  // AdaptedVideoTrackSource).
+  struct Aggregates {
+    // `active_without_requested_resolution` is set by VideoBroadcaster
+    // when aggregating sink wants if there exists any sink (encoder) that is
+    // active but has not set the `requested_resolution`, i.e is relying on
+    // OnOutputFormatRequest to handle encode resolution.
+    bool any_active_without_requested_resolution = false;
+  };
+  absl::optional<Aggregates> aggregates;
 };
 
 inline bool operator==(const VideoSinkWants::FrameSize& a,
@@ -87,6 +105,11 @@
   return a.width == b.width && a.height == b.height;
 }
 
+inline bool operator!=(const VideoSinkWants::FrameSize& a,
+                       const VideoSinkWants::FrameSize& b) {
+  return !(a == b);
+}
+
 template <typename VideoFrameT>
 class VideoSourceInterface {
  public:
diff --git a/api/video_codecs/BUILD.gn b/api/video_codecs/BUILD.gn
index 3f933b9..d6b7392 100644
--- a/api/video_codecs/BUILD.gn
+++ b/api/video_codecs/BUILD.gn
@@ -84,6 +84,7 @@
     "../units:data_rate",
     "../video:encoded_image",
     "../video:render_resolution",
+    "../video:resolution",
     "../video:video_bitrate_allocation",
     "../video:video_codec_constants",
     "../video:video_frame",
diff --git a/api/video_codecs/video_encoder_config.h b/api/video_codecs/video_encoder_config.h
index 86d89d5..3d1b176 100644
--- a/api/video_codecs/video_encoder_config.h
+++ b/api/video_codecs/video_encoder_config.h
@@ -18,6 +18,7 @@
 
 #include "absl/types/optional.h"
 #include "api/scoped_refptr.h"
+#include "api/video/resolution.h"
 #include "api/video_codecs/scalability_mode.h"
 #include "api/video_codecs/sdp_video_format.h"
 #include "api/video_codecs/video_codec.h"
@@ -32,10 +33,11 @@
   VideoStream(const VideoStream& other);
   std::string ToString() const;
 
-  // Width in pixels.
+  // Width/Height in pixels.
+  // This is the actual width and height used to configure encoder,
+  // which might be less than `requested_resolution` due to adaptation
+  // or due to the source providing smaller frames than requested.
   size_t width;
-
-  // Height in pixels.
   size_t height;
 
   // Frame rate in fps.
@@ -69,6 +71,17 @@
 
   // If this stream is enabled by the user, or not.
   bool active;
+
+  // An optional user supplied max_frame_resolution
+  // than can be set independently of (adapted) VideoSource.
+  // This value is set from RtpEncodingParameters::requested_resolution
+  // (i.e. used for signaling app-level settings).
+  //
+  // The actual encode resolution is in `width` and `height`,
+  // which can be lower than requested_resolution,
+  // e.g. if source only provides lower resolution or
+  // if resource adaptation is active.
+  absl::optional<Resolution> requested_resolution;
 };
 
 class VideoEncoderConfig {
diff --git a/media/base/media_engine.cc b/media/base/media_engine.cc
index 813657e..694e690 100644
--- a/media/base/media_engine.cc
+++ b/media/base/media_engine.cc
@@ -106,6 +106,13 @@
                              "num_temporal_layers to an invalid number.");
       }
     }
+
+    if (rtp_parameters.encodings[i].requested_resolution &&
+        rtp_parameters.encodings[i].scale_resolution_down_by) {
+      LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_RANGE,
+                           "Attempted to set scale_resolution_down_by and "
+                           "requested_resolution simultaniously.");
+    }
   }
 
   return webrtc::RTCError::OK();
diff --git a/media/base/video_broadcaster.cc b/media/base/video_broadcaster.cc
index 1167d7f..60c0e1c 100644
--- a/media/base/video_broadcaster.cc
+++ b/media/base/video_broadcaster.cc
@@ -10,6 +10,7 @@
 
 #include "media/base/video_broadcaster.h"
 
+#include <algorithm>
 #include <vector>
 
 #include "absl/types/optional.h"
@@ -123,6 +124,7 @@
   VideoSinkWants wants;
   wants.rotation_applied = false;
   wants.resolution_alignment = 1;
+  wants.aggregates.emplace(VideoSinkWants::Aggregates());
   for (auto& sink : sink_pairs()) {
     // wants.rotation_applied == ANY(sink.wants.rotation_applied)
     if (sink.wants.rotation_applied) {
@@ -147,6 +149,25 @@
     }
     wants.resolution_alignment = cricket::LeastCommonMultiple(
         wants.resolution_alignment, sink.wants.resolution_alignment);
+
+    // Pick MAX(requested_resolution) since the actual can be downscaled
+    // in encoder instead.
+    if (sink.wants.requested_resolution) {
+      if (!wants.requested_resolution) {
+        wants.requested_resolution = sink.wants.requested_resolution;
+      } else {
+        wants.requested_resolution->width =
+            std::max(wants.requested_resolution->width,
+                     sink.wants.requested_resolution->width);
+        wants.requested_resolution->height =
+            std::max(wants.requested_resolution->height,
+                     sink.wants.requested_resolution->height);
+      }
+    } else if (sink.wants.is_active) {
+      wants.aggregates->any_active_without_requested_resolution = true;
+    }
+
+    wants.is_active |= sink.wants.is_active;
   }
 
   if (wants.target_pixel_count &&
diff --git a/media/base/video_broadcaster_unittest.cc b/media/base/video_broadcaster_unittest.cc
index fe1a1cb..52f015c 100644
--- a/media/base/video_broadcaster_unittest.cc
+++ b/media/base/video_broadcaster_unittest.cc
@@ -24,6 +24,7 @@
 using cricket::FakeVideoRenderer;
 using rtc::VideoBroadcaster;
 using rtc::VideoSinkWants;
+using FrameSize = rtc::VideoSinkWants::FrameSize;
 
 using ::testing::AllOf;
 using ::testing::Eq;
@@ -330,3 +331,70 @@
           Field(&webrtc::VideoTrackSourceConstraints::max_fps, Optional(3)))));
   broadcaster.ProcessConstraints(webrtc::VideoTrackSourceConstraints{2, 3});
 }
+
+TEST(VideoBroadcasterTest, AppliesMaxOfSinkWantsRequestedResolution) {
+  VideoBroadcaster broadcaster;
+
+  FakeVideoRenderer sink1;
+  VideoSinkWants wants1;
+  wants1.requested_resolution = FrameSize(640, 360);
+
+  broadcaster.AddOrUpdateSink(&sink1, wants1);
+  EXPECT_EQ(FrameSize(640, 360), *broadcaster.wants().requested_resolution);
+
+  FakeVideoRenderer sink2;
+  VideoSinkWants wants2;
+  wants2.requested_resolution = FrameSize(650, 350);
+  broadcaster.AddOrUpdateSink(&sink2, wants2);
+  EXPECT_EQ(FrameSize(650, 360), *broadcaster.wants().requested_resolution);
+
+  broadcaster.RemoveSink(&sink2);
+  EXPECT_EQ(FrameSize(640, 360), *broadcaster.wants().requested_resolution);
+}
+
+TEST(VideoBroadcasterTest, AnyActive) {
+  VideoBroadcaster broadcaster;
+
+  FakeVideoRenderer sink1;
+  VideoSinkWants wants1;
+  wants1.is_active = false;
+
+  broadcaster.AddOrUpdateSink(&sink1, wants1);
+  EXPECT_EQ(false, broadcaster.wants().is_active);
+
+  FakeVideoRenderer sink2;
+  VideoSinkWants wants2;
+  wants2.is_active = true;
+  broadcaster.AddOrUpdateSink(&sink2, wants2);
+  EXPECT_EQ(true, broadcaster.wants().is_active);
+
+  broadcaster.RemoveSink(&sink2);
+  EXPECT_EQ(false, broadcaster.wants().is_active);
+}
+
+TEST(VideoBroadcasterTest, AnyActiveWithoutRequestedResolution) {
+  VideoBroadcaster broadcaster;
+
+  FakeVideoRenderer sink1;
+  VideoSinkWants wants1;
+  wants1.is_active = true;
+  wants1.requested_resolution = FrameSize(640, 360);
+
+  broadcaster.AddOrUpdateSink(&sink1, wants1);
+  EXPECT_EQ(
+      false,
+      broadcaster.wants().aggregates->any_active_without_requested_resolution);
+
+  FakeVideoRenderer sink2;
+  VideoSinkWants wants2;
+  wants2.is_active = true;
+  broadcaster.AddOrUpdateSink(&sink2, wants2);
+  EXPECT_EQ(
+      true,
+      broadcaster.wants().aggregates->any_active_without_requested_resolution);
+
+  broadcaster.RemoveSink(&sink2);
+  EXPECT_EQ(
+      false,
+      broadcaster.wants().aggregates->any_active_without_requested_resolution);
+}
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 5651f59..2a9c0a8 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -2350,7 +2350,9 @@
         (new_parameters.encodings[i].scale_resolution_down_by !=
          rtp_parameters_.encodings[i].scale_resolution_down_by) ||
         (new_parameters.encodings[i].num_temporal_layers !=
-         rtp_parameters_.encodings[i].num_temporal_layers)) {
+         rtp_parameters_.encodings[i].num_temporal_layers) ||
+        (new_parameters.encodings[i].requested_resolution !=
+         rtp_parameters_.encodings[i].requested_resolution)) {
       new_param = true;
       break;
     }
@@ -2562,6 +2564,8 @@
       encoder_config.simulcast_layers[i].num_temporal_layers =
           *rtp_parameters_.encodings[i].num_temporal_layers;
     }
+    encoder_config.simulcast_layers[i].requested_resolution =
+        rtp_parameters_.encodings[i].requested_resolution;
   }
 
   encoder_config.legacy_conference_mode = parameters_.conference_mode;
@@ -3705,6 +3709,7 @@
   layer.width = width;
   layer.height = height;
   layer.max_framerate = max_framerate;
+  layer.active = encoder_config.simulcast_layers[0].active;
 
   if (encoder_config.simulcast_layers[0].scale_resolution_down_by > 1.) {
     layer.width = ScaleDownResolution(
diff --git a/video/video_receive_stream2_unittest.cc b/video/video_receive_stream2_unittest.cc
index 79c680f..a05524f 100644
--- a/video/video_receive_stream2_unittest.cc
+++ b/video/video_receive_stream2_unittest.cc
@@ -158,7 +158,7 @@
   TimeController* const time_controller_;
 };
 
-MATCHER_P2(Resolution, w, h, "") {
+MATCHER_P2(MatchResolution, w, h, "") {
   return arg.resolution().width == w && arg.resolution().height == h;
 }
 
@@ -740,8 +740,9 @@
       /*generate_key_frame=*/false);
 
   InSequence s;
-  EXPECT_CALL(callback, Call(Resolution(test::FakeDecoder::kDefaultWidth,
-                                        test::FakeDecoder::kDefaultHeight)));
+  EXPECT_CALL(callback,
+              Call(MatchResolution(test::FakeDecoder::kDefaultWidth,
+                                   test::FakeDecoder::kDefaultHeight)));
   EXPECT_CALL(callback, Call);
 
   video_receive_stream_->OnCompleteFrame(
@@ -763,7 +764,7 @@
       /*generate_key_frame=*/false);
 
   InSequence s;
-  EXPECT_CALL(callback, Call(Resolution(1080u, 720u)));
+  EXPECT_CALL(callback, Call(MatchResolution(1080u, 720u)));
   EXPECT_CALL(callback, Call);
 
   video_receive_stream_->OnCompleteFrame(
diff --git a/video/video_source_sink_controller.cc b/video/video_source_sink_controller.cc
index cf3b649..2f7b375 100644
--- a/video/video_source_sink_controller.cc
+++ b/video/video_source_sink_controller.cc
@@ -20,29 +20,6 @@
 
 namespace webrtc {
 
-namespace {
-
-std::string WantsToString(const rtc::VideoSinkWants& wants) {
-  rtc::StringBuilder ss;
-
-  ss << "max_fps=" << wants.max_framerate_fps
-     << " max_pixel_count=" << wants.max_pixel_count << " target_pixel_count="
-     << (wants.target_pixel_count.has_value()
-             ? std::to_string(wants.target_pixel_count.value())
-             : "null")
-     << " resolutions={";
-  for (size_t i = 0; i < wants.resolutions.size(); ++i) {
-    if (i != 0)
-      ss << ",";
-    ss << wants.resolutions[i].width << "x" << wants.resolutions[i].height;
-  }
-  ss << "}";
-
-  return ss.Release();
-}
-
-}  // namespace
-
 VideoSourceSinkController::VideoSourceSinkController(
     rtc::VideoSinkInterface<VideoFrame>* sink,
     rtc::VideoSourceInterface<VideoFrame>* source)
@@ -86,8 +63,6 @@
   if (!source_)
     return;
   rtc::VideoSinkWants wants = CurrentSettingsToSinkWants();
-  RTC_LOG(LS_INFO) << "Pushing SourceSink restrictions: "
-                   << WantsToString(wants);
   source_->AddOrUpdateSink(sink_, wants);
 }
 
@@ -124,6 +99,17 @@
   return resolutions_;
 }
 
+bool VideoSourceSinkController::active() const {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  return active_;
+}
+
+absl::optional<rtc::VideoSinkWants::FrameSize>
+VideoSourceSinkController::requested_resolution() const {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  return requested_resolution_;
+}
+
 void VideoSourceSinkController::SetRestrictions(
     VideoSourceRestrictions restrictions) {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
@@ -159,6 +145,17 @@
   resolutions_ = std::move(resolutions);
 }
 
+void VideoSourceSinkController::SetActive(bool active) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  active_ = active;
+}
+
+void VideoSourceSinkController::SetRequestedResolution(
+    absl::optional<rtc::VideoSinkWants::FrameSize> requested_resolution) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  requested_resolution_ = std::move(requested_resolution);
+}
+
 // RTC_EXCLUSIVE_LOCKS_REQUIRED(sequence_checker_)
 rtc::VideoSinkWants VideoSourceSinkController::CurrentSettingsToSinkWants()
     const {
@@ -188,6 +185,8 @@
                    ? static_cast<int>(frame_rate_upper_limit_.value())
                    : std::numeric_limits<int>::max());
   wants.resolutions = resolutions_;
+  wants.is_active = active_;
+  wants.requested_resolution = requested_resolution_;
   return wants;
 }
 
diff --git a/video/video_source_sink_controller.h b/video/video_source_sink_controller.h
index e2a7eb7..1bb6ef6 100644
--- a/video/video_source_sink_controller.h
+++ b/video/video_source_sink_controller.h
@@ -51,6 +51,8 @@
   bool rotation_applied() const;
   int resolution_alignment() const;
   const std::vector<rtc::VideoSinkWants::FrameSize>& resolutions() const;
+  bool active() const;
+  absl::optional<rtc::VideoSinkWants::FrameSize> requested_resolution() const;
 
   // Updates the settings stored internally. In order for these settings to be
   // applied to the sink, PushSourceSinkSettings() must subsequently be called.
@@ -61,6 +63,9 @@
   void SetRotationApplied(bool rotation_applied);
   void SetResolutionAlignment(int resolution_alignment);
   void SetResolutions(std::vector<rtc::VideoSinkWants::FrameSize> resolutions);
+  void SetActive(bool active);
+  void SetRequestedResolution(
+      absl::optional<rtc::VideoSinkWants::FrameSize> requested_resolution);
 
  private:
   rtc::VideoSinkWants CurrentSettingsToSinkWants() const
@@ -87,6 +92,9 @@
   int resolution_alignment_ RTC_GUARDED_BY(&sequence_checker_) = 1;
   std::vector<rtc::VideoSinkWants::FrameSize> resolutions_
       RTC_GUARDED_BY(&sequence_checker_);
+  bool active_ RTC_GUARDED_BY(&sequence_checker_) = true;
+  absl::optional<rtc::VideoSinkWants::FrameSize> requested_resolution_
+      RTC_GUARDED_BY(&sequence_checker_);
 };
 
 }  // namespace webrtc
diff --git a/video/video_source_sink_controller_unittest.cc b/video/video_source_sink_controller_unittest.cc
index 2db7b56..75cc52b 100644
--- a/video/video_source_sink_controller_unittest.cc
+++ b/video/video_source_sink_controller_unittest.cc
@@ -24,6 +24,7 @@
 
 namespace {
 
+using FrameSize = rtc::VideoSinkWants::FrameSize;
 constexpr int kIntUnconstrained = std::numeric_limits<int>::max();
 
 class MockVideoSinkWithVideoFrame : public rtc::VideoSinkInterface<VideoFrame> {
@@ -61,6 +62,7 @@
   EXPECT_FALSE(controller.pixels_per_frame_upper_limit().has_value());
   EXPECT_FALSE(controller.frame_rate_upper_limit().has_value());
   EXPECT_FALSE(controller.rotation_applied());
+  EXPECT_FALSE(controller.requested_resolution().has_value());
   EXPECT_EQ(controller.resolution_alignment(), 1);
 
   EXPECT_CALL(source, AddOrUpdateSink(_, _))
@@ -71,6 +73,7 @@
         EXPECT_EQ(wants.target_pixel_count, absl::nullopt);
         EXPECT_EQ(wants.max_framerate_fps, kIntUnconstrained);
         EXPECT_EQ(wants.resolution_alignment, 1);
+        EXPECT_FALSE(wants.requested_resolution.has_value());
       });
   controller.PushSourceSinkSettings();
 }
@@ -162,4 +165,35 @@
   VideoSourceSinkController controller(&sink, nullptr);
   controller.RequestRefreshFrame();
 }
+
+TEST(VideoSourceSinkControllerTest, RequestedResolutionPropagatesToWants) {
+  MockVideoSinkWithVideoFrame sink;
+  MockVideoSourceWithVideoFrame source;
+  VideoSourceSinkController controller(&sink, &source);
+  controller.SetRequestedResolution(FrameSize(640, 360));
+  EXPECT_TRUE(controller.requested_resolution().has_value());
+
+  EXPECT_CALL(source, AddOrUpdateSink(_, _))
+      .WillOnce([](rtc::VideoSinkInterface<VideoFrame>* sink,
+                   const rtc::VideoSinkWants& wants) {
+        EXPECT_EQ(*wants.requested_resolution, FrameSize(640, 360));
+      });
+  controller.PushSourceSinkSettings();
+}
+
+TEST(VideoSourceSinkControllerTest, ActivePropagatesToWants) {
+  MockVideoSinkWithVideoFrame sink;
+  MockVideoSourceWithVideoFrame source;
+  VideoSourceSinkController controller(&sink, &source);
+  controller.SetActive(true);
+  EXPECT_TRUE(controller.active());
+
+  EXPECT_CALL(source, AddOrUpdateSink(_, _))
+      .WillOnce([](rtc::VideoSinkInterface<VideoFrame>* sink,
+                   const rtc::VideoSinkWants& wants) {
+        EXPECT_TRUE(wants.is_active);
+      });
+  controller.PushSourceSinkSettings();
+}
+
 }  // namespace webrtc
diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc
index e320e43..1018df4 100644
--- a/video/video_stream_encoder.cc
+++ b/video/video_stream_encoder.cc
@@ -1130,10 +1130,30 @@
   RTC_DCHECK_LE(codec.startBitrate, 1000000);
   max_framerate_ = codec.maxFramerate;
 
-  // Inform source about max configured framerate.
+  // Inform source about max configured framerate,
+  // requested_resolution and which layers are active.
   int max_framerate = 0;
+  // Is any layer active.
+  bool active = false;
+  // The max requested_resolution.
+  absl::optional<rtc::VideoSinkWants::FrameSize> requested_resolution;
   for (const auto& stream : streams) {
     max_framerate = std::max(stream.max_framerate, max_framerate);
+    active |= stream.active;
+    // Note: we propagate the highest requested_resolution regardless
+    // if layer is active or not.
+    if (stream.requested_resolution) {
+      if (!requested_resolution) {
+        requested_resolution.emplace(stream.requested_resolution->width,
+                                     stream.requested_resolution->height);
+      } else {
+        requested_resolution.emplace(
+            std::max(stream.requested_resolution->width,
+                     requested_resolution->width),
+            std::max(stream.requested_resolution->height,
+                     requested_resolution->height));
+      }
+    }
   }
 
   // The resolutions that we're actually encoding with.
@@ -1146,20 +1166,28 @@
     encoder_resolutions.emplace_back(simulcastStream.width,
                                      simulcastStream.height);
   }
+
   worker_queue_->PostTask(SafeTask(
       task_safety_.flag(),
       [this, max_framerate, alignment,
-       encoder_resolutions = std::move(encoder_resolutions)]() {
+       encoder_resolutions = std::move(encoder_resolutions),
+       requested_resolution = std::move(requested_resolution), active]() {
         RTC_DCHECK_RUN_ON(worker_queue_);
         if (max_framerate !=
                 video_source_sink_controller_.frame_rate_upper_limit() ||
             alignment != video_source_sink_controller_.resolution_alignment() ||
             encoder_resolutions !=
-                video_source_sink_controller_.resolutions()) {
+                video_source_sink_controller_.resolutions() ||
+            (video_source_sink_controller_.requested_resolution() !=
+             requested_resolution) ||
+            (video_source_sink_controller_.active() != active)) {
           video_source_sink_controller_.SetFrameRateUpperLimit(max_framerate);
           video_source_sink_controller_.SetResolutionAlignment(alignment);
           video_source_sink_controller_.SetResolutions(
               std::move(encoder_resolutions));
+          video_source_sink_controller_.SetRequestedResolution(
+              requested_resolution);
+          video_source_sink_controller_.SetActive(active);
           video_source_sink_controller_.PushSourceSinkSettings();
         }
       }));