Reland "Revert "Reland "Allow sending to separate payload types for each simulcast index."""

This is a reland of commit Ife7d43471c85fdea9bd26cc982bce410c0d75527

In the previous implementation, there was an issue where switching codecs
with VideoEncoderSelector would cause a crash, so I have fixed it.
Specifically, if there are no changes to params.send_codecs, I now remove
all parameters related to mixed-codec simulcast.
This should ensure that when an unintended codec switch occurs,
the behavior remains the same as before.

Additionally, I have added a test to reproduce this crash issue.
I confirmed that the issue occurred in the previous implementation and
that it does not occur in the current implementation.

Original change's description:
> Revert "Reland "Allow sending to separate payload types for each simulcast index.""
>
> This reverts commit 49ac6b758cc3c28be2fc13028a829f016b453d39.
>
> Reason for revert: Break codec switch in singlecast.
>
> Original change's description:
> > Reland "Allow sending to separate payload types for each simulcast index."
> >
> > This is a reland of commit bcb19c00ba8ab1788ba3c08f28ee1b23e0cc77b9
> >
> > Original change's description:
> > > Allow sending to separate payload types for each simulcast index.
> > >
> > > This change is for mixed-codec simulcast.
> > >
> > > By obtaining the payload type via RtpConfig::GetStreamConfig(),
> > > the correct payload type can be retrieved regardless of whether
> > > RtpConfig::stream_configs is initialized or not.
> > >
> > > Bug: webrtc:362277533
> > > Change-Id: I6b2a1ae66356b20a832565ce6729c3ce9e73a161
> > > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/364760
> > > Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
> > > Commit-Queue: Florent Castelli <orphis@webrtc.org>
> > > Reviewed-by: Florent Castelli <orphis@webrtc.org>
> > > Cr-Commit-Position: refs/heads/main@{#43197}
> >
> > Bug: webrtc:362277533
> > Change-Id: Ia82c3390cceb9f68315c2fd9ba5114693669af32
> > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/374780
> > Commit-Queue: Henrik Boström <hbos@webrtc.org>
> > Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
> > Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
> > Cr-Commit-Position: refs/heads/main@{#43787}
>
> Bug: webrtc:362277533
> Change-Id: Ife7d43471c85fdea9bd26cc982bce410c0d75527
> Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/376040
> Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
> Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
> Reviewed-by: Jonas Oreland <jonaso@webrtc.org>
> Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
> Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
> Reviewed-by: Per Kjellander <perkj@webrtc.org>
> Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
> Reviewed-by: Evan Shrubsole <eshr@webrtc.org>
> Cr-Commit-Position: refs/heads/main@{#43830}

Bug: webrtc:362277533
Change-Id: I1772fa478a4fd4d5096d9bf727ed4de045861c4e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/378100
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#44007}
diff --git a/call/rtp_config.cc b/call/rtp_config.cc
index 453607f..115912c 100644
--- a/call/rtp_config.cc
+++ b/call/rtp_config.cc
@@ -239,4 +239,31 @@
   return std::nullopt;
 }
 
+RtpStreamConfig RtpConfig::GetStreamConfig(size_t index) const {
+  // GetStreamConfig function usually returns stream_configs[index], but if
+  // stream_configs is not initialized (i.e., index >= stream_configs.size()),
+  // it creates and returns an RtpStreamConfig using fields such as ssrcs, rids,
+  // payload_name, and payload_type from RtpConfig.
+  RTC_DCHECK_LT(index, ssrcs.size());
+  if (index < stream_configs.size()) {
+    return stream_configs[index];
+  }
+  RtpStreamConfig stream_config;
+  stream_config.ssrc = ssrcs[index];
+  if (index < rids.size()) {
+    stream_config.rid = rids[index];
+  }
+  stream_config.payload_name = payload_name;
+  stream_config.payload_type = payload_type;
+  stream_config.raw_payload = raw_payload;
+  if (!rtx.ssrcs.empty()) {
+    RTC_DCHECK_EQ(ssrcs.size(), rtx.ssrcs.size());
+    auto& stream_config_rtx = stream_config.rtx.emplace();
+    stream_config_rtx.ssrc = rtx.ssrcs[index];
+    stream_config_rtx.payload_type = rtx.payload_type;
+  }
+
+  return stream_config;
+}
+
 }  // namespace webrtc
diff --git a/call/rtp_config.h b/call/rtp_config.h
index 6b79e55..d77289f 100644
--- a/call/rtp_config.h
+++ b/call/rtp_config.h
@@ -193,6 +193,9 @@
   uint32_t GetMediaSsrcAssociatedWithRtxSsrc(uint32_t rtx_ssrc) const;
   uint32_t GetMediaSsrcAssociatedWithFlexfecSsrc(uint32_t flexfec_ssrc) const;
   std::optional<std::string> GetRidForSsrc(uint32_t ssrc) const;
+
+  // Returns send config for RTP stream by provided simulcast `index`.
+  RtpStreamConfig GetStreamConfig(size_t index) const;
 };
 }  // namespace webrtc
 #endif  // CALL_RTP_CONFIG_H_
diff --git a/call/rtp_video_sender.cc b/call/rtp_video_sender.cc
index a3ebc74..b51ac82 100644
--- a/call/rtp_video_sender.cc
+++ b/call/rtp_video_sender.cc
@@ -330,11 +330,13 @@
   return rtp_streams;
 }
 
-std::optional<VideoCodecType> GetVideoCodecType(const RtpConfig& config) {
-  if (config.raw_payload) {
+std::optional<VideoCodecType> GetVideoCodecType(const RtpConfig& config,
+                                                size_t simulcast_index) {
+  auto stream_config = config.GetStreamConfig(simulcast_index);
+  if (stream_config.raw_payload) {
     return std::nullopt;
   }
-  return PayloadStringToCodecType(config.payload_name);
+  return PayloadStringToCodecType(stream_config.payload_name);
 }
 bool TransportSeqNumExtensionConfigured(const RtpConfig& config) {
   return absl::c_any_of(config.extensions, [](const RtpExtension& ext) {
@@ -421,7 +423,6 @@
                                           crypto_options,
                                           std::move(frame_transformer))),
       rtp_config_(rtp_config),
-      codec_type_(GetVideoCodecType(rtp_config)),
       transport_(transport),
       independent_frame_ids_(
           !env.field_trials().IsDisabled(
@@ -470,12 +471,14 @@
   }
 
   bool fec_enabled = false;
-  for (const RtpStreamSender& stream : rtp_streams_) {
+  for (size_t i = 0; i < rtp_streams_.size(); i++) {
+    const RtpStreamSender& stream = rtp_streams_[i];
     // Simulcast has one module for each layer. Set the CNAME on all modules.
     stream.rtp_rtcp->SetCNAME(rtp_config_.c_name.c_str());
     stream.rtp_rtcp->SetMaxRtpPacketSize(rtp_config_.max_packet_size);
-    stream.rtp_rtcp->RegisterSendPayloadFrequency(rtp_config_.payload_type,
-                                                  kVideoPayloadTypeFrequency);
+    stream.rtp_rtcp->RegisterSendPayloadFrequency(
+        rtp_config_.GetStreamConfig(i).payload_type,
+        kVideoPayloadTypeFrequency);
     if (stream.fec_generator != nullptr) {
       fec_enabled = true;
     }
@@ -576,7 +579,7 @@
   // knowledge of the offset to a single place.
   if (!rtp_streams_[simulcast_index].rtp_rtcp->OnSendingRtpFrame(
           encoded_image.RtpTimestamp(), encoded_image.capture_time_ms_,
-          rtp_config_.payload_type,
+          rtp_config_.GetStreamConfig(simulcast_index).payload_type,
           encoded_image._frameType == VideoFrameType::kVideoFrameKey)) {
     // The payload router could be active but this module isn't sending.
     return Result(Result::ERROR_SEND_FAILED);
@@ -616,7 +619,9 @@
 
   bool send_result =
       rtp_streams_[simulcast_index].sender_video->SendEncodedImage(
-          rtp_config_.payload_type, codec_type_, rtp_timestamp, encoded_image,
+          rtp_config_.GetStreamConfig(simulcast_index).payload_type,
+          GetVideoCodecType(rtp_config_, simulcast_index), rtp_timestamp,
+          encoded_image,
           params_[simulcast_index].GetRtpVideoHeader(
               encoded_image, codec_specific_info, frame_id),
           expected_retransmission_time);
@@ -754,9 +759,12 @@
 
   // Configure RTX payload types.
   RTC_DCHECK_GE(rtp_config_.rtx.payload_type, 0);
-  for (const RtpStreamSender& stream : rtp_streams_) {
-    stream.rtp_rtcp->SetRtxSendPayloadType(rtp_config_.rtx.payload_type,
-                                           rtp_config_.payload_type);
+  for (size_t i = 0; i < rtp_streams_.size(); ++i) {
+    const RtpStreamSender& stream = rtp_streams_[i];
+    RtpStreamConfig stream_config = rtp_config_.GetStreamConfig(i);
+    RTC_DCHECK(stream_config.rtx);
+    stream.rtp_rtcp->SetRtxSendPayloadType(stream_config.rtx->payload_type,
+                                           stream_config.payload_type);
     stream.rtp_rtcp->SetRtxSendStatus(kRtxRetransmitted |
                                       kRtxRedundantPayloads);
   }
diff --git a/call/rtp_video_sender.h b/call/rtp_video_sender.h
index 65da26d..8965cfb 100644
--- a/call/rtp_video_sender.h
+++ b/call/rtp_video_sender.h
@@ -201,7 +201,6 @@
   const std::vector<webrtc_internal_rtp_video_sender::RtpStreamSender>
       rtp_streams_;
   const RtpConfig rtp_config_;
-  const std::optional<VideoCodecType> codec_type_;
   RtpTransportControllerSendInterface* const transport_;
 
   // When using the generic descriptor we want all simulcast streams to share
diff --git a/call/rtp_video_sender_unittest.cc b/call/rtp_video_sender_unittest.cc
index 936f293..8fc744f 100644
--- a/call/rtp_video_sender_unittest.cc
+++ b/call/rtp_video_sender_unittest.cc
@@ -84,6 +84,7 @@
 using ::testing::SizeIs;
 
 const int8_t kPayloadType = 96;
+const int8_t kPayloadType2 = 98;
 const uint32_t kSsrc1 = 12345;
 const uint32_t kSsrc2 = 23456;
 const uint32_t kRtxSsrc1 = 34567;
@@ -133,7 +134,8 @@
     Transport* transport,
     const std::vector<uint32_t>& ssrcs,
     const std::vector<uint32_t>& rtx_ssrcs,
-    int payload_type) {
+    int payload_type,
+    rtc::ArrayView<const int> payload_types) {
   VideoSendStream::Config config(transport);
   config.rtp.ssrcs = ssrcs;
   config.rtp.rtx.ssrcs = rtx_ssrcs;
@@ -145,6 +147,20 @@
   config.rtp.extensions.emplace_back(RtpDependencyDescriptorExtension::Uri(),
                                      kDependencyDescriptorExtensionId);
   config.rtp.extmap_allow_mixed = true;
+
+  if (!payload_types.empty()) {
+    RTC_CHECK_EQ(payload_types.size(), ssrcs.size());
+    for (size_t i = 0; i < ssrcs.size(); ++i) {
+      auto& stream_config = config.rtp.stream_configs.emplace_back();
+      stream_config.ssrc = ssrcs[i];
+      stream_config.payload_type = payload_types[i];
+      if (i < rtx_ssrcs.size()) {
+        auto& rtx = stream_config.rtx.emplace();
+        rtx.ssrc = rtx_ssrcs[i];
+        rtx.payload_type = payload_types[i] + 1;
+      }
+    }
+  }
   return config;
 }
 
@@ -157,6 +173,7 @@
       const std::map<uint32_t, RtpPayloadState>& suspended_payload_states,
       FrameCountObserver* frame_count_observer,
       rtc::scoped_refptr<FrameTransformerInterface> frame_transformer,
+      const std::vector<int>& payload_types,
       const FieldTrialsView* field_trials = nullptr)
       : time_controller_(Timestamp::Millis(1000000)),
         env_(CreateEnvironment(&field_trials_,
@@ -166,7 +183,8 @@
         config_(CreateVideoSendStreamConfig(&transport_,
                                             ssrcs,
                                             rtx_ssrcs,
-                                            payload_type)),
+                                            payload_type,
+                                            payload_types)),
         bitrate_config_(GetBitrateConfig()),
         transport_controller_(
             RtpTransportConfig{.env = env_, .bitrate_config = bitrate_config_}),
@@ -188,6 +206,22 @@
         std::make_unique<FecControllerDefault>(env_), nullptr, CryptoOptions{},
         frame_transformer);
   }
+  RtpVideoSenderTestFixture(
+      const std::vector<uint32_t>& ssrcs,
+      const std::vector<uint32_t>& rtx_ssrcs,
+      int payload_type,
+      const std::map<uint32_t, RtpPayloadState>& suspended_payload_states,
+      FrameCountObserver* frame_count_observer,
+      rtc::scoped_refptr<FrameTransformerInterface> frame_transformer,
+      const FieldTrialsView* field_trials = nullptr)
+      : RtpVideoSenderTestFixture(ssrcs,
+                                  rtx_ssrcs,
+                                  payload_type,
+                                  suspended_payload_states,
+                                  frame_count_observer,
+                                  frame_transformer,
+                                  /*payload_types=*/{},
+                                  field_trials) {}
 
   RtpVideoSenderTestFixture(
       const std::vector<uint32_t>& ssrcs,
@@ -202,6 +236,7 @@
                                   suspended_payload_states,
                                   frame_count_observer,
                                   /*frame_transformer=*/nullptr,
+                                  /*payload_types=*/{},
                                   field_trials) {}
 
   RtpVideoSenderTestFixture(
@@ -216,6 +251,7 @@
                                   suspended_payload_states,
                                   /*frame_count_observer=*/nullptr,
                                   /*frame_transformer=*/nullptr,
+                                  /*payload_types=*/{},
                                   field_trials) {}
 
   ~RtpVideoSenderTestFixture() { SetSending(false); }
@@ -953,6 +989,79 @@
   EXPECT_EQ(dd_s1.frame_number(), 1002);
 }
 
+TEST(RtpVideoSenderTest, MixedCodecSimulcastPayloadType) {
+  // When multiple payload types are set, verify that the payload type switches
+  // corresponding to the simulcast index.
+  RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2},
+                                 kPayloadType, {}, nullptr, nullptr,
+                                 {kPayloadType, kPayloadType2});
+  test.SetSending(true);
+
+  std::vector<uint16_t> rtp_sequence_numbers;
+  std::vector<RtpPacket> sent_packets;
+  EXPECT_CALL(test.transport(), SendRtp)
+      .Times(3)
+      .WillRepeatedly([&](rtc::ArrayView<const uint8_t> packet,
+                          const PacketOptions& options) -> bool {
+        RtpPacket& rtp_packet = sent_packets.emplace_back();
+        EXPECT_TRUE(rtp_packet.Parse(packet));
+        rtp_sequence_numbers.push_back(rtp_packet.SequenceNumber());
+        return true;
+      });
+
+  const uint8_t kPayload[1] = {'a'};
+  EncodedImage encoded_image;
+  encoded_image.SetEncodedData(
+      EncodedImageBuffer::Create(kPayload, sizeof(kPayload)));
+
+  CodecSpecificInfo codec_specific;
+  codec_specific.codecType = VideoCodecType::kVideoCodecVP8;
+
+  encoded_image.SetSimulcastIndex(0);
+  ASSERT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error,
+            EncodedImageCallback::Result::OK);
+  ASSERT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error,
+            EncodedImageCallback::Result::OK);
+  encoded_image.SetSimulcastIndex(1);
+  ASSERT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error,
+            EncodedImageCallback::Result::OK);
+
+  test.AdvanceTime(TimeDelta::Millis(33));
+  ASSERT_THAT(sent_packets, SizeIs(3));
+  EXPECT_EQ(sent_packets[0].PayloadType(), kPayloadType);
+  EXPECT_EQ(sent_packets[1].PayloadType(), kPayloadType);
+  EXPECT_EQ(sent_packets[2].PayloadType(), kPayloadType2);
+
+  // Verify that NACK is sent to the RTX payload type corresponding to the
+  // payload type.
+  rtcp::Nack nack1, nack2;
+  nack1.SetMediaSsrc(kSsrc1);
+  nack2.SetMediaSsrc(kSsrc2);
+  nack1.SetPacketIds({rtp_sequence_numbers[0], rtp_sequence_numbers[1]});
+  nack2.SetPacketIds({rtp_sequence_numbers[2]});
+  rtc::Buffer nack_buffer1 = nack1.Build();
+  rtc::Buffer nack_buffer2 = nack2.Build();
+
+  std::vector<RtpPacket> sent_rtx_packets;
+  EXPECT_CALL(test.transport(), SendRtp)
+      .Times(3)
+      .WillRepeatedly([&](rtc::ArrayView<const uint8_t> packet,
+                          const PacketOptions& options) {
+        RtpPacket& rtp_packet = sent_rtx_packets.emplace_back();
+        EXPECT_TRUE(rtp_packet.Parse(packet));
+        return true;
+      });
+  test.router()->DeliverRtcp(nack_buffer1.data(), nack_buffer1.size());
+  test.router()->DeliverRtcp(nack_buffer2.data(), nack_buffer2.size());
+
+  test.AdvanceTime(TimeDelta::Millis(33));
+
+  ASSERT_THAT(sent_rtx_packets, SizeIs(3));
+  EXPECT_EQ(sent_rtx_packets[0].PayloadType(), kPayloadType + 1);
+  EXPECT_EQ(sent_rtx_packets[1].PayloadType(), kPayloadType + 1);
+  EXPECT_EQ(sent_rtx_packets[2].PayloadType(), kPayloadType2 + 1);
+}
+
 TEST(RtpVideoSenderTest,
      SupportsDependencyDescriptorForVp8NotProvidedByEncoder) {
   constexpr uint8_t kPayload[1] = {'a'};
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 36a86ec..3a4be48 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -783,6 +783,46 @@
   return size_bytes.Get();
 }
 
+webrtc::RTCError ResolveSendCodecs(
+    const VideoCodecSettings& current_codec,
+    const std::vector<VideoCodecSettings>& current_codecs,
+    const std::vector<webrtc::RtpEncodingParameters>& encodings,
+    const std::vector<VideoCodecSettings> negotiated_codecs,
+    std::vector<VideoCodecSettings>* resolved_codecs) {
+  RTC_DCHECK(resolved_codecs);
+  resolved_codecs->clear();
+  for (size_t i = 0; i < encodings.size(); i++) {
+    const std::optional<webrtc::RtpCodec>& requested_codec = encodings[i].codec;
+    std::optional<VideoCodecSettings> found_codec;
+    if (!requested_codec) {
+      found_codec = current_codec;
+    } else if (i < current_codecs.size()) {
+      const VideoCodecSettings& codec = current_codecs[i];
+      if (IsSameRtpCodecIgnoringLevel(codec.codec, *requested_codec)) {
+        found_codec = codec;
+      }
+    }
+    if (!found_codec) {
+      RTC_DCHECK(requested_codec);
+      auto matched_codec =
+          absl::c_find_if(negotiated_codecs, [&](auto negotiated_codec) {
+            return IsSameRtpCodecIgnoringLevel(negotiated_codec.codec,
+                                               *requested_codec);
+          });
+      if (matched_codec == negotiated_codecs.end()) {
+        return webrtc::RTCError(
+            webrtc::RTCErrorType::INVALID_MODIFICATION,
+            "Attempted to use an unsupported codec for layer " +
+                std::to_string(i));
+      }
+      found_codec = *matched_codec;
+    }
+    RTC_DCHECK(found_codec);
+    resolved_codecs->push_back(*found_codec);
+  }
+  return webrtc::RTCError::OK();
+}
+
 }  // namespace
 // --------------- WebRtcVideoEngine ---------------------------
 
@@ -1295,8 +1335,11 @@
   if (changed_params.send_codec)
     send_codec() = changed_params.send_codec;
 
-  if (changed_params.send_codecs)
+  if (changed_params.send_codecs) {
     send_codecs_ = *changed_params.send_codecs;
+  } else {
+    send_codecs_.clear();
+  }
 
   if (changed_params.extmap_allow_mixed) {
     SetExtmapAllowMixed(*changed_params.extmap_allow_mixed);
@@ -1429,31 +1472,25 @@
         break;
     }
 
-    // Since we validate that all layers have the same value, we can just check
-    // the first layer.
-    // TODO: https://issues.webrtc.org/362277533 - Support mixed-codec simulcast
-    if (parameters.encodings[0].codec && send_codec_ &&
-        !IsSameRtpCodecIgnoringLevel(send_codec_->codec,
-                                     *parameters.encodings[0].codec)) {
-      RTC_LOG(LS_VERBOSE) << "Trying to change codec to "
-                          << parameters.encodings[0].codec->name;
-      // Ignore level when matching negotiated codecs against the requested
-      // codec.
-      auto matched_codec =
-          absl::c_find_if(negotiated_codecs_, [&](auto negotiated_codec) {
-            return IsSameRtpCodecIgnoringLevel(negotiated_codec.codec,
-                                               *parameters.encodings[0].codec);
-          });
-      if (matched_codec == negotiated_codecs_.end()) {
-        return webrtc::InvokeSetParametersCallback(
-            callback, webrtc::RTCError(
-                          webrtc::RTCErrorType::INVALID_MODIFICATION,
-                          "Attempted to use an unsupported codec for layer 0"));
+    if (send_codec_ &&
+        std::any_of(parameters.encodings.begin(), parameters.encodings.end(),
+                    [](const auto& e) { return e.codec; })) {
+      std::vector<VideoCodecSettings> send_codecs;
+      auto error =
+          ResolveSendCodecs(*send_codec_, send_codecs_, parameters.encodings,
+                            negotiated_codecs_, &send_codecs);
+      if (!error.ok()) {
+        return webrtc::InvokeSetParametersCallback(callback, error);
       }
 
-      ChangedSenderParameters params;
-      params.send_codec = *matched_codec;
-      ApplyChangedParams(params);
+      if (send_codecs_ != send_codecs) {
+        ChangedSenderParameters params;
+        if (!send_codecs.empty()) {
+          params.send_codec = send_codecs[0];
+        }
+        params.send_codecs = send_codecs;
+        ApplyChangedParams(params);
+      }
     }
 
     SetPreferredDscp(new_dscp);
@@ -1993,7 +2030,9 @@
   parameters_.codec_settings = codec_settings;
 
   // Settings for mixed-codec simulcast.
-  if (!codec_settings_list.empty()) {
+  if (codec_settings_list.empty()) {
+    parameters_.config.rtp.stream_configs.clear();
+  } else {
     if (parameters_.config.rtp.ssrcs.size() == codec_settings_list.size()) {
       parameters_.config.rtp.stream_configs.resize(
           parameters_.config.rtp.ssrcs.size());
@@ -2072,7 +2111,8 @@
   // Set codecs and options.
   if (params.send_codec) {
     SetCodec(*params.send_codec,
-             params.send_codecs.value_or(parameters_.codec_settings_list));
+             params.send_codecs.value_or(
+                 std::vector<cricket::VideoCodecSettings>()));
     recreate_stream = false;  // SetCodec has already recreated the stream.
   } else if (params.conference_mode && parameters_.codec_settings) {
     SetCodec(*parameters_.codec_settings, parameters_.codec_settings_list);
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc
index 746c387..9ed4a5c 100644
--- a/pc/peer_connection_integrationtest.cc
+++ b/pc/peer_connection_integrationtest.cc
@@ -4199,6 +4199,51 @@
   EXPECT_TRUE(ExpectNewFrames(media_expectations));
 }
 
+TEST_P(PeerConnectionIntegrationTest,
+       EndToEndRtpSenderVideoEncoderSelectorSwitchCodec) {
+  ASSERT_TRUE(
+      CreateOneDirectionalPeerConnectionWrappers(/*caller_to_callee=*/true));
+  ConnectFakeSignaling();
+  // Add one-directional video, from caller to callee.
+  rtc::scoped_refptr<VideoTrackInterface> caller_track =
+      caller()->CreateLocalVideoTrack();
+  auto sender = caller()->AddTrack(caller_track);
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.offer_to_receive_video = 0;
+  caller()->SetOfferAnswerOptions(options);
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_THAT(
+      WaitUntil([&] { return SignalingStateStable(); }, ::testing::IsTrue()),
+      IsRtcOk());
+  ASSERT_EQ(callee()->pc()->GetReceivers().size(), 1u);
+
+  std::unique_ptr<MockEncoderSelector> encoder_selector =
+      std::make_unique<MockEncoderSelector>();
+  std::optional<SdpVideoFormat> next_format;
+  EXPECT_CALL(*encoder_selector, OnCurrentEncoder)
+      .WillOnce(::testing::Invoke([&](const SdpVideoFormat& format) {
+        EXPECT_EQ(format.name, "VP8");
+        next_format = SdpVideoFormat::VP9Profile0();
+      }))
+      .WillOnce(::testing::Invoke([&](const SdpVideoFormat& format) {
+        EXPECT_EQ(format.name, "VP9");
+      }));
+  EXPECT_CALL(*encoder_selector, OnAvailableBitrate)
+      .WillRepeatedly(
+          ::testing::Invoke([&](const DataRate& rate) { return next_format; }));
+
+  sender->SetEncoderSelector(std::move(encoder_selector));
+
+  // Expect video to be received in one direction.
+  MediaExpectations media_expectations;
+  media_expectations.CallerExpectsNoVideo();
+  media_expectations.CalleeExpectsSomeVideo();
+
+  EXPECT_TRUE(ExpectNewFrames(media_expectations));
+
+  caller()->pc()->Close();
+}
+
 int NacksReceivedCount(PeerConnectionIntegrationWrapper& pc) {
   rtc::scoped_refptr<const RTCStatsReport> report = pc.NewGetStats();
   auto sender_stats = report->GetStatsOfType<RTCOutboundRtpStreamStats>();