Make nack history configurable.

This allows for `config_.rtp.nack.rtp_history_ms` to be modified
without deleting and recreating video receive streams.

Bug: webrtc:11993
Change-Id: I8ba132b22fe0e6de03d1c42fc38a570cbe138817
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/269301
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#37701}
diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h
index 998710d..451203b 100644
--- a/call/video_receive_stream.h
+++ b/call/video_receive_stream.h
@@ -298,6 +298,11 @@
   // thread.
   virtual void SetLossNotificationEnabled(bool enabled) = 0;
 
+  // Modify `rtp.nack.rtp_history_ms` post construction. Setting this value
+  // to 0 disables nack.
+  // Must be called on the packet delivery thread.
+  virtual void SetNackHistory(TimeDelta history) = 0;
+
  protected:
   virtual ~VideoReceiveStreamInterface() {}
 };
diff --git a/media/engine/fake_webrtc_call.h b/media/engine/fake_webrtc_call.h
index 9276a78..c807d31 100644
--- a/media/engine/fake_webrtc_call.h
+++ b/media/engine/fake_webrtc_call.h
@@ -293,6 +293,10 @@
     config_.rtp.lntf.enabled = enabled;
   }
 
+  void SetNackHistory(webrtc::TimeDelta history) override {
+    config_.rtp.nack.rtp_history_ms = history.ms();
+  }
+
   void Start() override;
   void Stop() override;
 
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 4cc18fb..c562abe 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -2991,26 +2991,32 @@
     stream_->SetLossNotificationEnabled(has_lntf);
   }
 
+  int new_history_ms = config_.rtp.nack.rtp_history_ms;
   const int rtp_history_ms = HasNack(codec.codec) ? kNackHistoryMs : 0;
   if (rtp_history_ms != config_.rtp.nack.rtp_history_ms) {
-    config_.rtp.nack.rtp_history_ms = rtp_history_ms;
-    recreate_needed = true;
+    new_history_ms = rtp_history_ms;
   }
 
   // The rtx-time parameter can be used to override the hardcoded default for
   // the NACK buffer length.
-  if (codec.rtx_time != -1 && config_.rtp.nack.rtp_history_ms != 0) {
-    config_.rtp.nack.rtp_history_ms = codec.rtx_time;
-    recreate_needed = true;
+  if (codec.rtx_time != -1 && new_history_ms != 0) {
+    new_history_ms = codec.rtx_time;
+  }
+
+  if (config_.rtp.nack.rtp_history_ms != new_history_ms) {
+    config_.rtp.nack.rtp_history_ms = new_history_ms;
+    stream_->SetNackHistory(webrtc::TimeDelta::Millis(new_history_ms));
   }
 
   const bool has_rtr = HasRrtr(codec.codec);
   if (has_rtr != config_.rtp.rtcp_xr.receiver_reference_time_report) {
+    // TODO(tommi): Look into if/when this happens in practice.
     config_.rtp.rtcp_xr.receiver_reference_time_report = has_rtr;
     recreate_needed = true;
   }
 
   if (codec.ulpfec.red_rtx_payload_type != -1) {
+    // TODO(tommi): Look into if/when this happens in practice.
     rtx_associated_payload_types[codec.ulpfec.red_rtx_payload_type] =
         codec.ulpfec.red_payload_type;
   }
@@ -3068,22 +3074,8 @@
 
   int nack_history_ms =
       nack_enabled ? rtx_time != -1 ? rtx_time : kNackHistoryMs : 0;
-  if (config_.rtp.nack.rtp_history_ms == nack_history_ms) {
-    RTC_LOG(LS_INFO)
-        << "Ignoring call to SetFeedbackParameters because parameters are "
-           "unchanged; nack="
-        << nack_enabled << ", rtx_time=" << rtx_time;
-    return;
-  }
-
-  RTC_LOG_F(LS_INFO) << "(recv) because of SetFeedbackParameters; nack="
-                     << nack_enabled << ". rtp_history_ms "
-                     << config_.rtp.nack.rtp_history_ms << "->"
-                     << nack_history_ms;
-
   config_.rtp.nack.rtp_history_ms = nack_history_ms;
-
-  RecreateReceiveStream();
+  stream_->SetNackHistory(webrtc::TimeDelta::Millis(nack_history_ms));
 }
 
 void WebRtcVideoChannel::WebRtcVideoReceiveStream::SetFlexFecPayload(
diff --git a/video/frame_buffer_proxy.cc b/video/frame_buffer_proxy.cc
index 9f79c92..f8901ae 100644
--- a/video/frame_buffer_proxy.cc
+++ b/video/frame_buffer_proxy.cc
@@ -173,6 +173,14 @@
     jitter_estimator_.UpdateRtt(TimeDelta::Millis(max_rtt_ms));
   }
 
+  void SetMaxWaits(TimeDelta max_wait_for_keyframe,
+                   TimeDelta max_wait_for_frame) override {
+    RTC_DCHECK_RUN_ON(&worker_sequence_checker_);
+    timeout_tracker_.SetTimeouts(
+        {.max_wait_for_keyframe = max_wait_for_keyframe,
+         .max_wait_for_frame = max_wait_for_frame});
+  }
+
   void StartNextDecode(bool keyframe_required) override {
     if (!worker_queue_->IsCurrent()) {
       worker_queue_->PostTask(SafeTask(
diff --git a/video/frame_buffer_proxy.h b/video/frame_buffer_proxy.h
index 1d440ed..a616d49 100644
--- a/video/frame_buffer_proxy.h
+++ b/video/frame_buffer_proxy.h
@@ -59,6 +59,8 @@
       std::unique_ptr<EncodedFrame> frame) = 0;
   virtual void UpdateRtt(int64_t max_rtt_ms) = 0;
   virtual int Size() = 0;
+  virtual void SetMaxWaits(TimeDelta max_wait_for_keyframe,
+                           TimeDelta max_wait_for_frame) = 0;
 
   // Run on either the worker thread or the decode thread.
   virtual void StartNextDecode(bool keyframe_required) = 0;
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
index 2fe72d1..1f31c41 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
@@ -56,6 +56,8 @@
 constexpr int kPacketBufferStartSize = 512;
 constexpr int kPacketBufferMaxSize = 2048;
 
+constexpr int kMaxPacketAgeToNack = 450;
+
 int PacketBufferMaxSize(const FieldTrialsView& field_trials) {
   // The group here must be a positive power of 2, in which case that is used as
   // size. All other values shall result in the default value being used.
@@ -105,12 +107,12 @@
 std::unique_ptr<NackRequester> MaybeConstructNackModule(
     TaskQueueBase* current_queue,
     NackPeriodicProcessor* nack_periodic_processor,
-    const VideoReceiveStreamInterface::Config& config,
+    const NackConfig& nack,
     Clock* clock,
     NackSender* nack_sender,
     KeyFrameRequestSender* keyframe_request_sender,
     const FieldTrialsView& field_trials) {
-  if (config.rtp.nack.rtp_history_ms == 0)
+  if (nack.rtp_history_ms == 0)
     return nullptr;
 
   // TODO(bugs.webrtc.org/12420): pass rtp_history_ms to the nack module.
@@ -223,6 +225,7 @@
     rtc::scoped_refptr<FrameTransformerInterface> frame_transformer,
     const FieldTrialsView& field_trials)
     : field_trials_(field_trials),
+      worker_queue_(current_queue),
       clock_(clock),
       config_(*config),
       packet_router_(packet_router),
@@ -246,6 +249,7 @@
           rtcp_cname_callback,
           config_.rtp.rtcp_xr.receiver_reference_time_report,
           config_.rtp.local_ssrc)),
+      nack_periodic_processor_(nack_periodic_processor),
       complete_frame_callback_(complete_frame_callback),
       keyframe_request_method_(config_.rtp.keyframe_method),
       // TODO(bugs.webrtc.org/10336): Let `rtcp_feedback_buffer_` communicate
@@ -253,7 +257,7 @@
       rtcp_feedback_buffer_(this, this, this),
       nack_module_(MaybeConstructNackModule(current_queue,
                                             nack_periodic_processor,
-                                            config_,
+                                            config_.rtp.nack,
                                             clock_,
                                             &rtcp_feedback_buffer_,
                                             &rtcp_feedback_buffer_,
@@ -280,7 +284,6 @@
   rtp_rtcp_->SetRemoteSSRC(config_.rtp.remote_ssrc);
 
   if (config_.rtp.nack.rtp_history_ms > 0) {
-    static constexpr int kMaxPacketAgeToNack = 450;
     rtp_receive_statistics_->SetMaxReorderingThreshold(config_.rtp.remote_ssrc,
                                                        kMaxPacketAgeToNack);
   }
@@ -704,10 +707,6 @@
   return config_.rtp.ulpfec_payload_type != -1;
 }
 
-bool RtpVideoStreamReceiver2::IsRetransmissionsEnabled() const {
-  return config_.rtp.nack.rtp_history_ms > 0;
-}
-
 bool RtpVideoStreamReceiver2::IsDecryptable() const {
   RTC_DCHECK_RUN_ON(&worker_task_checker_);
   return frames_decryptable_;
@@ -919,7 +918,7 @@
 }
 
 void RtpVideoStreamReceiver2::UpdateRtt(int64_t max_rtt_ms) {
-  RTC_DCHECK_RUN_ON(&worker_task_checker_);
+  RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
   if (nack_module_)
     nack_module_->UpdateRtt(max_rtt_ms);
 }
@@ -952,6 +951,21 @@
   }
 }
 
+void RtpVideoStreamReceiver2::SetNackHistory(TimeDelta history) {
+  RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
+  if (history.ms() == 0) {
+    nack_module_.reset();
+  } else if (!nack_module_) {
+    nack_module_ = std::make_unique<NackRequester>(
+        worker_queue_, nack_periodic_processor_, clock_, &rtcp_feedback_buffer_,
+        &rtcp_feedback_buffer_, field_trials_);
+  }
+
+  rtp_receive_statistics_->SetMaxReorderingThreshold(
+      config_.rtp.remote_ssrc,
+      history.ms() > 0 ? kMaxPacketAgeToNack : kDefaultMaxReorderingThreshold);
+}
+
 absl::optional<int64_t> RtpVideoStreamReceiver2::LastReceivedPacketMs() const {
   RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
   if (last_received_rtp_system_time_) {
@@ -978,7 +992,7 @@
 
 void RtpVideoStreamReceiver2::ReceivePacket(const RtpPacketReceived& packet) {
   RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
-  RTC_DCHECK_RUN_ON(&worker_task_checker_);
+
   if (packet.payload_size() == 0) {
     // Padding or keep-alive packet.
     // TODO(nisse): Could drop empty packets earlier, but need to figure out how
diff --git a/video/rtp_video_stream_receiver2.h b/video/rtp_video_stream_receiver2.h
index dff3bfc..4fc11da 100644
--- a/video/rtp_video_stream_receiver2.h
+++ b/video/rtp_video_stream_receiver2.h
@@ -149,7 +149,6 @@
                             bool buffering_allowed) override;
 
   bool IsUlpfecEnabled() const;
-  bool IsRetransmissionsEnabled() const;
 
   // Returns true if a decryptor is attached and frames can be decrypted.
   // Updated by OnDecryptionStatusChangeCallback. Note this refers to Frame
@@ -198,6 +197,8 @@
   // thread.
   void SetLossNotificationEnabled(bool enabled);
 
+  void SetNackHistory(TimeDelta history);
+
   absl::optional<int64_t> LastReceivedPacketMs() const;
   absl::optional<int64_t> LastReceivedKeyframePacketMs() const;
 
@@ -305,6 +306,7 @@
       RTC_RUN_ON(packet_sequence_checker_);
 
   const FieldTrialsView& field_trials_;
+  TaskQueueBase* const worker_queue_;
   Clock* const clock_;
   // Ownership of this object lies with VideoReceiveStreamInterface, which owns
   // `this`.
@@ -337,11 +339,15 @@
 
   const std::unique_ptr<ModuleRtpRtcpImpl2> rtp_rtcp_;
 
+  NackPeriodicProcessor* const nack_periodic_processor_;
   OnCompleteFrameCallback* complete_frame_callback_;
   const KeyFrameReqMethod keyframe_request_method_;
 
   RtcpFeedbackBuffer rtcp_feedback_buffer_;
-  const std::unique_ptr<NackRequester> nack_module_;
+  // TODO(tommi): Consider absl::optional<NackRequester> instead of unique_ptr
+  // since nack is usually configured.
+  std::unique_ptr<NackRequester> nack_module_
+      RTC_GUARDED_BY(packet_sequence_checker_);
   std::unique_ptr<LossNotificationController> loss_notification_controller_
       RTC_GUARDED_BY(packet_sequence_checker_);
 
diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc
index f84b981..ec40b41 100644
--- a/video/video_receive_stream2.cc
+++ b/video/video_receive_stream2.cc
@@ -182,17 +182,12 @@
 
 }  // namespace
 
-TimeDelta DetermineMaxWaitForFrame(
-    const VideoReceiveStreamInterface::Config& config,
-    bool is_keyframe) {
+TimeDelta DetermineMaxWaitForFrame(TimeDelta rtp_history, bool is_keyframe) {
   // A (arbitrary) conversion factor between the remotely signalled NACK buffer
   // time (if not present defaults to 1000ms) and the maximum time we wait for a
   // remote frame. Chosen to not change existing defaults when using not
   // rtx-time.
   const int conversion_factor = 3;
-  const TimeDelta rtp_history =
-      TimeDelta::Millis(config.rtp.nack.rtp_history_ms);
-
   if (rtp_history > TimeDelta::Zero() &&
       conversion_factor * rtp_history < kMaxWaitForFrame) {
     return is_keyframe ? rtp_history : conversion_factor * rtp_history;
@@ -238,8 +233,12 @@
                                  std::move(config_.frame_transformer),
                                  call->trials()),
       rtp_stream_sync_(call->worker_thread(), this),
-      max_wait_for_keyframe_(DetermineMaxWaitForFrame(config_, true)),
-      max_wait_for_frame_(DetermineMaxWaitForFrame(config_, false)),
+      max_wait_for_keyframe_(DetermineMaxWaitForFrame(
+          TimeDelta::Millis(config_.rtp.nack.rtp_history_ms),
+          true)),
+      max_wait_for_frame_(DetermineMaxWaitForFrame(
+          TimeDelta::Millis(config_.rtp.nack.rtp_history_ms),
+          false)),
       maximum_pre_stream_decoders_("max", kDefaultMaximumPreStreamDecoders),
       decode_sync_(decode_sync),
       decode_queue_(task_queue_factory_->CreateTaskQueue(
@@ -355,8 +354,7 @@
   const bool protected_by_fec = config_.rtp.protected_by_flexfec ||
                                 rtp_video_stream_receiver_.IsUlpfecEnabled();
 
-  if (rtp_video_stream_receiver_.IsRetransmissionsEnabled() &&
-      protected_by_fec) {
+  if (config_.rtp.nack.rtp_history_ms > 0 && protected_by_fec) {
     frame_buffer_->SetProtectionMode(kProtectionNackFEC);
   }
 
@@ -524,6 +522,36 @@
   rtp_video_stream_receiver_.SetLossNotificationEnabled(enabled);
 }
 
+void VideoReceiveStream2::SetNackHistory(TimeDelta history) {
+  RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
+  RTC_DCHECK_GE(history.ms(), 0);
+
+  if (config_.rtp.nack.rtp_history_ms == history.ms())
+    return;
+
+  // TODO(tommi): Stop using the config struct for the internal state.
+  const_cast<int&>(config_.rtp.nack.rtp_history_ms) = history.ms();
+
+  const bool protected_by_fec = config_.rtp.protected_by_flexfec ||
+                                rtp_video_stream_receiver_.IsUlpfecEnabled();
+
+  frame_buffer_->SetProtectionMode(history.ms() > 0 && protected_by_fec
+                                       ? kProtectionNackFEC
+                                       : kProtectionNack);
+
+  rtp_video_stream_receiver_.SetNackHistory(history);
+  TimeDelta max_wait_for_keyframe = DetermineMaxWaitForFrame(history, true);
+  TimeDelta max_wait_for_frame = DetermineMaxWaitForFrame(history, false);
+
+  decode_queue_.PostTask([this, max_wait_for_keyframe, max_wait_for_frame]() {
+    RTC_DCHECK_RUN_ON(&decode_queue_);
+    max_wait_for_keyframe_ = max_wait_for_keyframe;
+    max_wait_for_frame_ = max_wait_for_frame;
+  });
+
+  frame_buffer_->SetMaxWaits(max_wait_for_keyframe, max_wait_for_frame);
+}
+
 void VideoReceiveStream2::CreateAndRegisterExternalDecoder(
     const Decoder& decoder) {
   TRACE_EVENT0("webrtc",
@@ -765,6 +793,7 @@
 }
 
 TimeDelta VideoReceiveStream2::GetMaxWait() const {
+  RTC_DCHECK_RUN_ON(&decode_queue_);
   return keyframe_required_ ? max_wait_for_keyframe_ : max_wait_for_frame_;
 }
 
@@ -780,10 +809,11 @@
   RTC_DCHECK_RUN_ON(&decode_queue_);
   Timestamp now = clock_->CurrentTime();
   // TODO(bugs.webrtc.org/11993): PostTask to the network thread.
-  call_->worker_thread()->PostTask(
-      SafeTask(task_safety_.flag(), [this, wait_time, now] {
+  call_->worker_thread()->PostTask(SafeTask(
+      task_safety_.flag(),
+      [this, wait_time, now, max_wait_for_keyframe = max_wait_for_keyframe_] {
         RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
-        HandleFrameBufferTimeout(now, wait_time);
+        HandleFrameBufferTimeout(now, wait_time, max_wait_for_keyframe);
 
         decode_queue_.PostTask([this] {
           RTC_DCHECK_RUN_ON(&decode_queue_);
@@ -849,15 +879,16 @@
     call_->worker_thread()->PostTask(SafeTask(
         task_safety_.flag(),
         [this, now, received_frame_is_keyframe, force_request_key_frame,
-         decoded_frame_picture_id, keyframe_request_is_due]() {
+         decoded_frame_picture_id, keyframe_request_is_due,
+         max_wait_for_keyframe = max_wait_for_keyframe_]() {
           RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
 
           if (decoded_frame_picture_id != -1)
             rtp_video_stream_receiver_.FrameDecoded(decoded_frame_picture_id);
 
-          HandleKeyFrameGeneration(received_frame_is_keyframe, now,
-                                   force_request_key_frame,
-                                   keyframe_request_is_due);
+          HandleKeyFrameGeneration(
+              received_frame_is_keyframe, now, force_request_key_frame,
+              keyframe_request_is_due, max_wait_for_keyframe);
         }));
   }
 }
@@ -926,7 +957,8 @@
     bool received_frame_is_keyframe,
     Timestamp now,
     bool always_request_key_frame,
-    bool keyframe_request_is_due) {
+    bool keyframe_request_is_due,
+    TimeDelta max_wait_for_keyframe) {
   RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
   bool request_key_frame = always_request_key_frame;
 
@@ -935,7 +967,7 @@
     if (received_frame_is_keyframe) {
       keyframe_generation_requested_ = false;
     } else if (keyframe_request_is_due) {
-      if (!IsReceivingKeyFrame(now)) {
+      if (!IsReceivingKeyFrame(now, max_wait_for_keyframe)) {
         request_key_frame = true;
       }
     } else {
@@ -951,9 +983,12 @@
   }
 }
 
-void VideoReceiveStream2::HandleFrameBufferTimeout(Timestamp now,
-                                                   TimeDelta wait) {
+void VideoReceiveStream2::HandleFrameBufferTimeout(
+    Timestamp now,
+    TimeDelta wait,
+    TimeDelta max_wait_for_keyframe) {
   RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
+
   absl::optional<int64_t> last_packet_ms =
       rtp_video_stream_receiver_.LastReceivedPacketMs();
 
@@ -966,7 +1001,7 @@
   if (!stream_is_active)
     stats_proxy_.OnStreamInactive();
 
-  if (stream_is_active && !IsReceivingKeyFrame(now) &&
+  if (stream_is_active && !IsReceivingKeyFrame(now, max_wait_for_keyframe) &&
       (!config_.crypto_options.sframe.require_frame_encryption ||
        rtp_video_stream_receiver_.IsDecryptable())) {
     RTC_LOG(LS_WARNING) << "No decodable frame in " << wait
@@ -975,16 +1010,18 @@
   }
 }
 
-bool VideoReceiveStream2::IsReceivingKeyFrame(Timestamp now) const {
+bool VideoReceiveStream2::IsReceivingKeyFrame(
+    Timestamp now,
+    TimeDelta max_wait_for_keyframe) const {
   RTC_DCHECK_RUN_ON(&packet_sequence_checker_);
   absl::optional<int64_t> last_keyframe_packet_ms =
       rtp_video_stream_receiver_.LastReceivedKeyframePacketMs();
 
   // If we recently have been receiving packets belonging to a keyframe then
   // we assume a keyframe is currently being received.
-  bool receiving_keyframe = last_keyframe_packet_ms &&
-                            now - Timestamp::Millis(*last_keyframe_packet_ms) <
-                                max_wait_for_keyframe_;
+  bool receiving_keyframe =
+      last_keyframe_packet_ms &&
+      now - Timestamp::Millis(*last_keyframe_packet_ms) < max_wait_for_keyframe;
   return receiving_keyframe;
 }
 
diff --git a/video/video_receive_stream2.h b/video/video_receive_stream2.h
index 79bd6ca..686e3af 100644
--- a/video/video_receive_stream2.h
+++ b/video/video_receive_stream2.h
@@ -148,6 +148,7 @@
   void SetRtcpMode(RtcpMode mode) override;
   void SetFlexFecProtection(RtpPacketSinkInterface* flexfec_sink) override;
   void SetLossNotificationEnabled(bool enabled) override;
+  void SetNackHistory(TimeDelta history) override;
 
   webrtc::VideoReceiveStreamInterface::Stats GetStats() const override;
 
@@ -195,7 +196,9 @@
   TimeDelta GetMaxWait() const RTC_RUN_ON(decode_queue_);
   void HandleEncodedFrame(std::unique_ptr<EncodedFrame> frame)
       RTC_RUN_ON(decode_queue_);
-  void HandleFrameBufferTimeout(Timestamp now, TimeDelta wait)
+  void HandleFrameBufferTimeout(Timestamp now,
+                                TimeDelta wait,
+                                TimeDelta max_wait_for_keyframe)
       RTC_RUN_ON(packet_sequence_checker_);
   void UpdatePlayoutDelays() const
       RTC_EXCLUSIVE_LOCKS_REQUIRED(worker_sequence_checker_);
@@ -203,9 +206,11 @@
   void HandleKeyFrameGeneration(bool received_frame_is_keyframe,
                                 Timestamp now,
                                 bool always_request_key_frame,
-                                bool keyframe_request_is_due)
+                                bool keyframe_request_is_due,
+                                TimeDelta max_wait_for_keyframe)
       RTC_RUN_ON(packet_sequence_checker_);
-  bool IsReceivingKeyFrame(Timestamp timestamp) const
+  bool IsReceivingKeyFrame(Timestamp timestamp,
+                           TimeDelta max_wait_for_keyframe) const
       RTC_RUN_ON(packet_sequence_checker_);
   int DecodeAndMaybeDispatchEncodedFrame(std::unique_ptr<EncodedFrame> frame)
       RTC_RUN_ON(decode_queue_);
@@ -274,8 +279,8 @@
       RTC_GUARDED_BY(worker_sequence_checker_);
 
   // Keyframe request intervals are configurable through field trials.
-  const TimeDelta max_wait_for_keyframe_;
-  const TimeDelta max_wait_for_frame_;
+  TimeDelta max_wait_for_keyframe_ RTC_GUARDED_BY(decode_queue_);
+  TimeDelta max_wait_for_frame_ RTC_GUARDED_BY(decode_queue_);
 
   // All of them tries to change current min_playout_delay on `timing_` but
   // source of the change request is different in each case. Among them the
diff --git a/video/video_receive_stream_timeout_tracker.cc b/video/video_receive_stream_timeout_tracker.cc
index 32e8bf8..0409f26 100644
--- a/video/video_receive_stream_timeout_tracker.cc
+++ b/video/video_receive_stream_timeout_tracker.cc
@@ -40,6 +40,7 @@
 }
 
 void VideoReceiveStreamTimeoutTracker::Start(bool waiting_for_keyframe) {
+  RTC_DCHECK_RUN_ON(bookkeeping_queue_);
   RTC_DCHECK(!timeout_task_.Running());
   waiting_for_keyframe_ = waiting_for_keyframe;
   TimeDelta timeout_delay = TimeoutForNextFrame();
@@ -55,6 +56,7 @@
 }
 
 void VideoReceiveStreamTimeoutTracker::SetWaitingForKeyframe() {
+  RTC_DCHECK_RUN_ON(bookkeeping_queue_);
   waiting_for_keyframe_ = true;
   TimeDelta timeout_delay = TimeoutForNextFrame();
   if (clock_->CurrentTime() + timeout_delay < timeout_) {
@@ -64,6 +66,7 @@
 }
 
 void VideoReceiveStreamTimeoutTracker::OnEncodedFrameReleased() {
+  RTC_DCHECK_RUN_ON(bookkeeping_queue_);
   // If we were waiting for a keyframe, then it has just been released.
   waiting_for_keyframe_ = false;
   last_frame_ = clock_->CurrentTime();
@@ -71,6 +74,7 @@
 }
 
 TimeDelta VideoReceiveStreamTimeoutTracker::HandleTimeoutTask() {
+  RTC_DCHECK_RUN_ON(bookkeeping_queue_);
   Timestamp now = clock_->CurrentTime();
   // `timeout_` is hit and we have timed out. Schedule the next timeout at
   // the timeout delay.
@@ -86,4 +90,9 @@
   return timeout_ - now;
 }
 
+void VideoReceiveStreamTimeoutTracker::SetTimeouts(Timeouts timeouts) {
+  RTC_DCHECK_RUN_ON(bookkeeping_queue_);
+  timeouts_ = timeouts;
+}
+
 }  // namespace webrtc
diff --git a/video/video_receive_stream_timeout_tracker.h b/video/video_receive_stream_timeout_tracker.h
index c5a1c73..c15aa70 100644
--- a/video/video_receive_stream_timeout_tracker.h
+++ b/video/video_receive_stream_timeout_tracker.h
@@ -46,8 +46,10 @@
   void OnEncodedFrameReleased();
   TimeDelta TimeUntilTimeout() const;
 
+  void SetTimeouts(Timeouts timeouts);
+
  private:
-  TimeDelta TimeoutForNextFrame() const {
+  TimeDelta TimeoutForNextFrame() const RTC_RUN_ON(bookkeeping_queue_) {
     return waiting_for_keyframe_ ? timeouts_.max_wait_for_keyframe
                                  : timeouts_.max_wait_for_frame;
   }
@@ -55,7 +57,7 @@
 
   Clock* const clock_;
   TaskQueueBase* const bookkeeping_queue_;
-  const Timeouts timeouts_;
+  Timeouts timeouts_ RTC_GUARDED_BY(bookkeeping_queue_);
   const TimeoutCallback timeout_cb_;
   RepeatingTaskHandle timeout_task_;