Add encoder switch request callback to VideoMediaSendChannel

Introduce a callback mechanism to intercept and handle encoder switch
requests within the video media engine. This makes it so that codec
switch/fallback requests first go through the the RtpTransceiver on the
signaling thread. That is consistent with other parameter related
changes and allows the transceiver to update the sender object(s) before
the codec change is applied.

Specifically, the changes include:
* Define EncoderSwitchRequestCallback and EncoderSwitchRequestAction
  in VideoMediaSendChannelInterface.
* Updating WebRtcVideoSendChannel to optionally use the callback
  instead of posting directly to the worker thread when an encoder
  switch is requested.
* Implementing GetEncoderSwitchRequestCallback in RtpTransceiver
  to ensure that when a switch occurs, cached RTP parameters on the
  signaling thread are cleared before the switch action is executed
  on the worker thread.
* Updating RtpSender to support clearing cached parameters via
  std::optional.
* Adding RTC_DCHECK to SafeTask and SafeInvocable to catch if
  the provided safety flag is invalid, early.

Bug: b/478050997
Change-Id: Id81a219a0f3dab6a167248c21513723504fa6e79
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/454700
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Sameer Vijaykar <samvi@google.com>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47148}
diff --git a/api/task_queue/pending_task_safety_flag.h b/api/task_queue/pending_task_safety_flag.h
index 875976e..a3c3cbd 100644
--- a/api/task_queue/pending_task_safety_flag.h
+++ b/api/task_queue/pending_task_safety_flag.h
@@ -19,6 +19,7 @@
 #include "api/scoped_refptr.h"
 #include "api/sequence_checker.h"
 #include "api/task_queue/task_queue_base.h"
+#include "rtc_base/checks.h"
 #include "rtc_base/system/no_unique_address.h"
 #include "rtc_base/system/rtc_export.h"
 
@@ -165,6 +166,7 @@
 inline absl::AnyInvocable<void() &&> SafeTask(
     scoped_refptr<PendingTaskSafetyFlag> flag,
     absl::AnyInvocable<void() &&> task) {
+  RTC_DCHECK(flag);
   return [flag = std::move(flag), task = std::move(task)]() mutable {
     if (flag->alive()) {
       std::move(task)();
@@ -176,6 +178,7 @@
 inline absl::AnyInvocable<void()> SafeInvocable(
     scoped_refptr<PendingTaskSafetyFlag> flag,
     absl::AnyInvocable<void()> task) {
+  RTC_DCHECK(flag);
   return [flag = std::move(flag), task = std::move(task)]() mutable {
     if (flag->alive()) {
       task();
diff --git a/media/base/fake_media_engine.cc b/media/base/fake_media_engine.cc
index a406b2a..cad3c58 100644
--- a/media/base/fake_media_engine.cc
+++ b/media/base/fake_media_engine.cc
@@ -654,7 +654,9 @@
     const MediaConfig& /* config */,
     const VideoOptions& options,
     const CryptoOptions& /* crypto_options */,
-    VideoBitrateAllocatorFactory* /* video_bitrate_allocator_factory */) {
+    VideoBitrateAllocatorFactory* /* video_bitrate_allocator_factory */,
+    VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+    /* video_encoder_switch_request_callback */) {
   std::unique_ptr<FakeVideoMediaSendChannel> ch =
       std::make_unique<FakeVideoMediaSendChannel>(options,
                                                   call->network_thread());
diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h
index 6cd6e66..4e5e43a 100644
--- a/media/base/fake_media_engine.h
+++ b/media/base/fake_media_engine.h
@@ -886,7 +886,9 @@
       const MediaConfig& config,
       const VideoOptions& options,
       const CryptoOptions& crypto_options,
-      VideoBitrateAllocatorFactory* video_bitrate_allocator_factory) override;
+      VideoBitrateAllocatorFactory* video_bitrate_allocator_factory,
+      VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+          video_encoder_switch_request_callback = nullptr) override;
   std::unique_ptr<VideoMediaReceiveChannelInterface> CreateReceiveChannel(
       const Environment& env,
       Call* call,
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 93693ec..cdc1928 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -960,6 +960,10 @@
 
 class VideoMediaSendChannelInterface : public MediaSendChannelInterface {
  public:
+  using EncoderSwitchRequestAction = absl::AnyInvocable<void() &&>;
+  using EncoderSwitchRequestCallback =
+      absl::AnyInvocable<void(EncoderSwitchRequestAction)>;
+
   virtual bool SetSenderParameters(const VideoSenderParameters& params) = 0;
   // Starts or stops transmission (and potentially capture) of local video.
   virtual bool SetSend(bool send) = 0;
diff --git a/media/base/media_engine.cc b/media/base/media_engine.cc
index 52da64d..78bb62a 100644
--- a/media/base/media_engine.cc
+++ b/media/base/media_engine.cc
@@ -79,7 +79,7 @@
 
 std::vector<RtpExtension> GetDefaultEnabledRtpHeaderExtensions(
     const RtpHeaderExtensionQueryInterface& query_interface,
-    const webrtc::FieldTrialsView* field_trials) {
+    const FieldTrialsView* field_trials) {
   std::vector<RtpExtension> extensions;
   for (const auto& entry :
        query_interface.GetRtpHeaderExtensions(field_trials)) {
diff --git a/media/base/media_engine.h b/media/base/media_engine.h
index 0ac3277..c85db96 100644
--- a/media/base/media_engine.h
+++ b/media/base/media_engine.h
@@ -77,7 +77,7 @@
   // Returns a vector of RtpHeaderExtensionCapability, whose direction is
   // kStopped if the extension is stopped (not used) by default.
   virtual std::vector<RtpHeaderExtensionCapability> GetRtpHeaderExtensions(
-      const webrtc::FieldTrialsView* field_trials) const = 0;
+      const FieldTrialsView* field_trials) const = 0;
 };
 
 class VoiceEngineInterface : public RtpHeaderExtensionQueryInterface {
@@ -153,7 +153,9 @@
       const MediaConfig& config,
       const VideoOptions& options,
       const CryptoOptions& crypto_options,
-      VideoBitrateAllocatorFactory* video_bitrate_allocator_factory) = 0;
+      VideoBitrateAllocatorFactory* video_bitrate_allocator_factory,
+      VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+          video_encoder_switch_request_callback = nullptr) = 0;
 
   virtual std::unique_ptr<VideoMediaReceiveChannelInterface>
   CreateReceiveChannel(const Environment& env,
@@ -240,7 +242,7 @@
 // GetRtpHeaderExtensions() that are not kStopped.
 std::vector<RtpExtension> GetDefaultEnabledRtpHeaderExtensions(
     const RtpHeaderExtensionQueryInterface& query_interface,
-    const webrtc::FieldTrialsView* field_trials);
+    const FieldTrialsView* field_trials);
 
 }  //  namespace webrtc
 
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index bb77667..91f2c9b 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -829,10 +829,13 @@
     const MediaConfig& config,
     const VideoOptions& options,
     const CryptoOptions& crypto_options,
-    VideoBitrateAllocatorFactory* video_bitrate_allocator_factory) {
+    VideoBitrateAllocatorFactory* video_bitrate_allocator_factory,
+    VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+        video_encoder_switch_request_callback) {
   return std::make_unique<WebRtcVideoSendChannel>(
       env, call, config, options, crypto_options, encoder_factory_.get(),
-      video_bitrate_allocator_factory);
+      video_bitrate_allocator_factory,
+      std::move(video_encoder_switch_request_callback));
 }
 std::unique_ptr<VideoMediaReceiveChannelInterface>
 WebRtcVideoEngine::CreateReceiveChannel(const Environment& env,
@@ -920,7 +923,9 @@
     const VideoOptions& options,
     const CryptoOptions& crypto_options,
     VideoEncoderFactory* encoder_factory,
-    VideoBitrateAllocatorFactory* bitrate_allocator_factory)
+    VideoBitrateAllocatorFactory* bitrate_allocator_factory,
+    VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+        video_encoder_switch_request_callback)
     : MediaChannelUtil(call->network_thread(), config.enable_dscp),
       env_(env),
       worker_thread_(call->worker_thread()),
@@ -931,7 +936,9 @@
       bitrate_allocator_factory_(bitrate_allocator_factory),
       default_send_options_(options),
       last_send_stats_log_ms_(-1),
-      crypto_options_(crypto_options) {
+      crypto_options_(crypto_options),
+      encoder_switch_request_callback_(
+          std::move(video_encoder_switch_request_callback)) {
   RTC_DCHECK_RUN_ON(&thread_checker_);
 }
 
@@ -1242,14 +1249,21 @@
 void WebRtcVideoSendChannel::RequestEncoderSwitch(
     std::optional<SdpVideoFormat> format,
     bool allow_default_fallback) {
-  if (!worker_thread_->IsCurrent()) {
-    worker_thread_->PostTask(
-        SafeTask(task_safety_.flag(), [this, format, allow_default_fallback] {
-          RequestEncoderSwitch(std::move(format), allow_default_fallback);
-        }));
-    return;
+  auto task =
+      SafeTask(task_safety_.flag(), [this, format, allow_default_fallback] {
+        RTC_DCHECK_RUN_ON(&thread_checker_);
+        ApplyEncoderSwitch(std::move(format), allow_default_fallback);
+      });
+  if (encoder_switch_request_callback_) {
+    encoder_switch_request_callback_(std::move(task));
+  } else {
+    worker_thread_->PostTask(std::move(task));
   }
+}
 
+void WebRtcVideoSendChannel::ApplyEncoderSwitch(
+    std::optional<SdpVideoFormat> format,
+    bool allow_default_fallback) {
   RTC_DCHECK_RUN_ON(&thread_checker_);
   RTC_DCHECK(format.has_value() || allow_default_fallback);
 
@@ -1277,7 +1291,7 @@
                           << allow_default_fallback;
 
       if (allow_default_fallback) {
-        RequestEncoderSwitch(std::nullopt, true);
+        ApplyEncoderSwitch(std::nullopt, true);
       }
       return;
     }
diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h
index e6b4b23..b28211a 100644
--- a/media/engine/webrtc_video_engine.h
+++ b/media/engine/webrtc_video_engine.h
@@ -106,7 +106,9 @@
       const MediaConfig& config,
       const VideoOptions& options,
       const CryptoOptions& crypto_options,
-      VideoBitrateAllocatorFactory* video_bitrate_allocator_factory) override;
+      VideoBitrateAllocatorFactory* video_bitrate_allocator_factory,
+      VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+          video_encoder_switch_request_callback = nullptr) override;
   std::unique_ptr<VideoMediaReceiveChannelInterface> CreateReceiveChannel(
       const Environment& env,
       Call* call,
@@ -167,7 +169,9 @@
       const VideoOptions& options,
       const CryptoOptions& crypto_options,
       VideoEncoderFactory* encoder_factory,
-      VideoBitrateAllocatorFactory* bitrate_allocator_factory);
+      VideoBitrateAllocatorFactory* bitrate_allocator_factory,
+      VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+          video_encoder_switch_request_callback);
   ~WebRtcVideoSendChannel() override;
 
   MediaType media_type() const override { return MediaType::VIDEO; }
@@ -291,6 +295,10 @@
   bool ApplyChangedParams(const ChangedSenderParameters& changed_params);
   bool ValidateSendSsrcAvailability(const StreamParams& sp) const
       RTC_EXCLUSIVE_LOCKS_REQUIRED(thread_checker_);
+  // Executes the actual encoder switch operation.
+  void ApplyEncoderSwitch(std::optional<SdpVideoFormat> format,
+                          bool allow_default_fallback)
+      RTC_RUN_ON(thread_checker_);
 
   // Wrapper for the sender part.
   class WebRtcVideoSendStream {
@@ -476,6 +484,10 @@
   // Callback invoked whenever the sender parameters change.
   absl::AnyInvocable<void()> parameters_changed_callback_
       RTC_GUARDED_BY(&thread_checker_);
+
+  // Callback to intercept the encoder switch request.
+  VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+      encoder_switch_request_callback_;
 };
 
 class WebRtcVideoReceiveChannel : public MediaChannelUtil,
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index d6f9d38..fdd105d 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -115,6 +115,7 @@
 #include "rtc_base/experiments/min_video_bitrate_experiment.h"
 #include "rtc_base/numerics/safe_conversions.h"
 #include "rtc_base/socket.h"
+#include "rtc_base/thread.h"
 #include "test/create_test_environment.h"
 #include "test/create_test_field_trials.h"
 #include "test/fake_decoder.h"
@@ -835,6 +836,47 @@
 }
 #endif  // defined(RTC_ENABLE_VP9)
 
+TEST_F(WebRtcVideoEngineTest, EncoderSwitchRequestCallback) {
+  AddSupportedVideoCodecType("VP8");
+
+  bool callback_called = false;
+  auto callback =
+      [&callback_called, call = call_.get(), main_thread = Thread::Current()](
+          VideoMediaSendChannelInterface::EncoderSwitchRequestAction action) {
+        call->worker_thread()->PostTask([action = std::move(action),
+                                         main_thread,
+                                         &callback_called]() mutable {
+          std::move(action)();
+          main_thread->PostTask([&callback_called] { callback_called = true; });
+        });
+      };
+
+  std::unique_ptr<VideoMediaSendChannelInterface> send_channel =
+      engine_->CreateSendChannel(
+          env_, call_.get(), GetMediaConfig(), VideoOptions(), CryptoOptions(),
+          video_bitrate_allocator_factory_.get(), std::move(callback));
+
+  VideoSenderParameters parameters;
+  parameters.codecs.push_back(GetEngineCodec("VP8"));
+  EXPECT_TRUE(send_channel->SetSenderParameters(parameters));
+
+  send_channel->OnReadyToSend(true);
+  EXPECT_TRUE(send_channel->AddSendStream(StreamParams::CreateLegacy(kSsrc)));
+  EXPECT_TRUE(send_channel->SetSend(true));
+
+  auto* web_rtc_send_channel =
+      static_cast<WebRtcVideoSendChannel*>(send_channel.get());
+
+  std::unique_ptr<Thread> bg_thread = Thread::Create();
+  bg_thread->Start();
+  bg_thread->BlockingCall(
+      [&] { web_rtc_send_channel->RequestEncoderSwitch(std::nullopt, true); });
+
+  EXPECT_TRUE(time_controller_.Wait([&] { return callback_called; }));
+
+  EXPECT_TRUE(send_channel->RemoveSendStream(kSsrc));
+}
+
 TEST_F(WebRtcVideoEngineTest, PropagatesInputFrameTimestamp) {
   AddSupportedVideoCodecType("VP8");
   FakeCall* fake_call = new FakeCall(env_);
diff --git a/pc/rtp_sender.cc b/pc/rtp_sender.cc
index ca3b131..3a4e6b7 100644
--- a/pc/rtp_sender.cc
+++ b/pc/rtp_sender.cc
@@ -261,9 +261,15 @@
   });
 }
 
-void RtpSenderBase::SetCachedParameters(RtpParameters parameters) {
+void RtpSenderBase::SetCachedParameters(
+    std::optional<RtpParameters> parameters) {
   RTC_DCHECK_RUN_ON(signaling_thread_);
-  cached_parameters_ = std::move(parameters);
+  if (parameters.has_value()) {
+    cached_parameters_ = std::move(*parameters);
+  } else {
+    cached_parameters_.reset();
+    last_transaction_id_.reset();
+  }
 }
 
 void RtpSenderBase::SetMediaChannel(MediaSendChannelInterface* media_channel) {
@@ -383,10 +389,8 @@
           << "Cached: " << cached_filtered << "\n"
           << "Result: " << result;
     }
-    // TODO(b/478050997): Re-enable this check once the downstream issue is
-    // resolved.
-    // RTC_DCHECK(cached_filtered == result)
-    //    << "The cached value should have been equal (filtered)";
+    RTC_DCHECK(cached_filtered == result)
+        << "The cached value should have been equal (filtered)";
   }
 #endif
   return result;
diff --git a/pc/rtp_sender.h b/pc/rtp_sender.h
index dc9591b..848db55 100644
--- a/pc/rtp_sender.h
+++ b/pc/rtp_sender.h
@@ -90,7 +90,7 @@
   virtual RTCError SetParametersInternal(const RtpParameters& parameters,
                                          SetParametersCallback,
                                          bool blocking) = 0;
-  virtual void SetCachedParameters(RtpParameters parameters) = 0;
+  virtual void SetCachedParameters(std::optional<RtpParameters> parameters) = 0;
 
   // GetParameters and SetParameters will remove deactivated simulcast layers
   // and restore them on SetParameters. This is probably a Bad Idea, but we
@@ -155,7 +155,7 @@
   RTCError SetParametersInternal(const RtpParameters& parameters,
                                  SetParametersCallback callback = nullptr,
                                  bool blocking = true) override;
-  void SetCachedParameters(RtpParameters parameters) override;
+  void SetCachedParameters(std::optional<RtpParameters> parameters) override;
   RTCError CheckSetParameters(const RtpParameters& parameters);
   RtpParameters GetParametersInternalWithAllLayers() const override;
   RTCError SetParametersInternalWithAllLayers(
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index 0701dca..a36eab4 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -231,7 +231,9 @@
     const AudioOptions& audio_options,
     const VideoOptions& video_options,
     const CryptoOptions& crypto_options,
-    VideoBitrateAllocatorFactory* video_bitrate_allocator_factory) {
+    VideoBitrateAllocatorFactory* video_bitrate_allocator_factory,
+    VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+        video_encoder_switch_request_callback = nullptr) {
   if (media_type == MediaType::AUDIO) {
     return {media_engine->voice().CreateSendChannel(
                 env, call, media_config, audio_options, crypto_options),
@@ -240,7 +242,8 @@
   }
   return {media_engine->video().CreateSendChannel(
               env, call, media_config, video_options, crypto_options,
-              video_bitrate_allocator_factory),
+              video_bitrate_allocator_factory,
+              std::move(video_encoder_switch_request_callback)),
           media_engine->video().CreateReceiveChannel(
               env, call, media_config, video_options, crypto_options)};
 }
@@ -286,8 +289,11 @@
       thread_(context->signaling_thread()),
       unified_plan_(false),
       media_type_(media_type),
+      signaling_thread_safety_(PendingTaskSafetyFlag::CreateAttachedToTaskQueue(
+          /*alive=*/false,
+          context->signaling_thread())),
       network_thread_safety_(PendingTaskSafetyFlag::CreateAttachedToTaskQueue(
-          true,
+          /*alive=*/true,
           context->network_thread())),
       context_(context),
       codec_lookup_helper_(codec_lookup_helper),
@@ -310,8 +316,11 @@
       thread_(context->signaling_thread()),
       unified_plan_(true),
       media_type_(sender->media_type()),
+      signaling_thread_safety_(PendingTaskSafetyFlag::CreateAttachedToTaskQueue(
+          /*alive=*/false,
+          context->signaling_thread())),
       network_thread_safety_(PendingTaskSafetyFlag::CreateAttachedToTaskQueue(
-          true,
+          /*alive=*/true,
           context->network_thread())),
       context_(context),
       codec_lookup_helper_(codec_lookup_helper),
@@ -365,8 +374,11 @@
       thread_(context->signaling_thread()),
       unified_plan_(true),
       media_type_(media_type),
+      signaling_thread_safety_(PendingTaskSafetyFlag::CreateAttachedToTaskQueue(
+          /*alive=*/false,
+          context->signaling_thread())),
       network_thread_safety_(PendingTaskSafetyFlag::CreateAttachedToTaskQueue(
-          true,
+          /*alive=*/true,
           context->network_thread())),
       media_engine_ref_(nullptr),
       context_(context),
@@ -393,7 +405,8 @@
     RTC_DCHECK_RUN_ON(this->context()->worker_thread());
     auto channels = CreateMediaContentChannels(
         media_type_, env_, media_engine(), call, media_config, audio_options,
-        video_options, crypto_options, video_bitrate_allocator_factory);
+        video_options, crypto_options, video_bitrate_allocator_factory,
+        GetEncoderSwitchRequestCallback());
     owned_send_channel_ = std::move(channels.first);
     owned_receive_channel_ = std::move(channels.second);
     senders_.push_back(CreateSender(media_type_, env_, context_, legacy_stats_,
@@ -444,6 +457,15 @@
 
   mid_ = mid;
 
+  if (!signaling_thread_safety_) {
+    // This code path is hit during rollback.
+    signaling_thread_safety_ = PendingTaskSafetyFlag::Create();
+  } else {
+    // Newly constructed.
+    RTC_DCHECK(!signaling_thread_safety_->alive());
+    signaling_thread_safety_->SetAlive();
+  }
+
   std::unique_ptr<ChannelInterface> new_channel;
   // TODO(bugs.webrtc.org/11992): CreateVideoChannel internally switches to
   // the worker thread. We shouldn't be using the `call_ptr_` hack here but
@@ -471,7 +493,7 @@
       auto channels = CreateMediaContentChannels(
           media_type(), env_, media_engine(), call_ptr, media_config,
           audio_options, video_options, crypto_options,
-          video_bitrate_allocator_factory);
+          video_bitrate_allocator_factory, GetEncoderSwitchRequestCallback());
       media_send_channel = std::move(channels.first);
       media_receive_channel = std::move(channels.second);
       SetMediaChannels(media_send_channel.get(), media_receive_channel.get());
@@ -513,7 +535,6 @@
 
   RTC_DCHECK_EQ(media_type(), channel->media_type());
   RTC_DCHECK(mid_ || channel->mid().empty());
-  signaling_thread_safety_ = PendingTaskSafetyFlag::Create();
   channel_ = std::move(channel);
   transport_name_ = std::nullopt;
 
@@ -578,7 +599,7 @@
   // combine these into one function to avoid an ordering mistake?
 
   if (!channel_) {
-    RTC_DCHECK(!signaling_thread_safety_);
+    RTC_DCHECK(!signaling_thread_safety_ || !signaling_thread_safety_->alive());
     return nullptr;
   }
 
@@ -598,7 +619,7 @@
 absl::AnyInvocable<void() &&> RtpTransceiver::GetDeleteChannelWorkerTask(
     bool stop_senders) {
   RTC_DCHECK_RUN_ON(thread_);
-  RTC_DCHECK(signaling_thread_safety_ == nullptr)
+  RTC_DCHECK(!signaling_thread_safety_ || !signaling_thread_safety_->alive())
       << "GetClearChannelNetworkTask() must be called first";
 
   if (!channel_) {
@@ -668,6 +689,33 @@
     receiver->internal()->SetMediaChannel(receive);
   }
 }
+VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+RtpTransceiver::GetEncoderSwitchRequestCallback() {
+  if (media_type() != MediaType::VIDEO) {
+    return nullptr;
+  }
+  RTC_DCHECK(signaling_thread_safety_);
+  // Return a task that first clears the sender parameter cache on the signaling
+  // thread and then posts a task to apply the codec switch changes to the
+  // parameters on the worker.
+  return
+      [this, signaling_thread = context_->signaling_thread(),
+       worker_thread = context_->worker_thread(),
+       signaling_safety = signaling_thread_safety_](
+          VideoMediaSendChannelInterface::EncoderSwitchRequestAction action) {
+        // Called on the encoder task queue.
+        signaling_thread->PostTask(SafeTask(
+            signaling_safety,
+            [this, worker_thread, action = std::move(action)]() mutable {
+              for (const auto& sender : senders_) {
+                sender->internal()->SetCachedParameters(std::nullopt);
+              }
+              worker_thread->PostTask([action = std::move(action)]() mutable {
+                std::move(action)();
+              });
+            }));
+      };
+}
 
 // RTC_RUN_ON(context()->worker_thread());
 void RtpTransceiver::ClearMediaChannelReferences() {
diff --git a/pc/rtp_transceiver.h b/pc/rtp_transceiver.h
index cad5276..81e1855 100644
--- a/pc/rtp_transceiver.h
+++ b/pc/rtp_transceiver.h
@@ -423,6 +423,9 @@
       RTC_RUN_ON(context()->worker_thread());
   void ClearMediaChannelReferences() RTC_RUN_ON(context()->worker_thread());
 
+  VideoMediaSendChannelInterface::EncoderSwitchRequestCallback
+  GetEncoderSwitchRequestCallback();
+
   RTCError UpdateCodecPreferencesCaches(
       const std::vector<RtpCodecCapability>& codecs);
   // Helper function for handling extensions during O/A
@@ -438,8 +441,7 @@
   TaskQueueBase* const thread_;
   const bool unified_plan_;
   const MediaType media_type_;
-  scoped_refptr<PendingTaskSafetyFlag> signaling_thread_safety_
-      RTC_GUARDED_BY(thread_);
+  scoped_refptr<PendingTaskSafetyFlag> signaling_thread_safety_;
   scoped_refptr<PendingTaskSafetyFlag> network_thread_safety_;
   std::vector<scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>>
       senders_;
diff --git a/pc/test/mock_rtp_sender_internal.h b/pc/test/mock_rtp_sender_internal.h
index 6d14a12..eeba6fa 100644
--- a/pc/test/mock_rtp_sender_internal.h
+++ b/pc/test/mock_rtp_sender_internal.h
@@ -13,6 +13,7 @@
 
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -60,7 +61,10 @@
               set_transport,
               (webrtc::scoped_refptr<DtlsTransportInterface>),
               (override));
-  MOCK_METHOD(void, SetCachedParameters, (RtpParameters), (override));
+  MOCK_METHOD(void,
+              SetCachedParameters,
+              (std::optional<RtpParameters>),
+              (override));
   MOCK_METHOD(RtpParameters, GetParameters, (), (const, override));
   MOCK_METHOD(RtpParameters,
               GetParametersInternal,