Update encoder settings periodically, not only on new bandwidth estimate

Also moved actual update call to encoder thread, and tweaked frame rate
estimate to be less noisy.

This is a re-upload of https://review.webrtc.org/47249004

BUG=chromium:476469
R=stefan@webrtc.org

Review URL: https://codereview.webrtc.org/1180623005.

Cr-Commit-Position: refs/heads/master@{#9417}
diff --git a/webrtc/modules/video_coding/main/source/media_optimization.cc b/webrtc/modules/video_coding/main/source/media_optimization.cc
index 524a7b2..4dbdf44 100644
--- a/webrtc/modules/video_coding/main/source/media_optimization.cc
+++ b/webrtc/modules/video_coding/main/source/media_optimization.cc
@@ -631,8 +631,9 @@
     }
   }
   if (num > 1) {
-    const int64_t diff = now - incoming_frame_times_[num - 1];
-    incoming_frame_rate_ = 1.0;
+    const int64_t diff =
+        incoming_frame_times_[0] - incoming_frame_times_[num - 1];
+    incoming_frame_rate_ = 0.0;  // No frame rate estimate available.
     if (diff > 0) {
       incoming_frame_rate_ = nr_of_frames * 1000.0f / static_cast<float>(diff);
     }
diff --git a/webrtc/modules/video_coding/main/source/media_optimization.h b/webrtc/modules/video_coding/main/source/media_optimization.h
index 3ab31f2..e0010db 100644
--- a/webrtc/modules/video_coding/main/source/media_optimization.h
+++ b/webrtc/modules/video_coding/main/source/media_optimization.h
@@ -79,6 +79,7 @@
   // Informs Media Optimization of encoded output.
   int32_t UpdateWithEncodedData(const EncodedImage& encoded_image);
 
+  // InputFrameRate 0 = no frame rate estimate available.
   uint32_t InputFrameRate();
   uint32_t SentFrameRate();
   uint32_t SentBitRate();
diff --git a/webrtc/modules/video_coding/main/source/video_coding_impl.h b/webrtc/modules/video_coding/main/source/video_coding_impl.h
index c8f8221..d738a7c 100644
--- a/webrtc/modules/video_coding/main/source/video_coding_impl.h
+++ b/webrtc/modules/video_coding/main/source/video_coding_impl.h
@@ -93,6 +93,7 @@
   int32_t SetChannelParameters(uint32_t target_bitrate,  // bits/s.
                                uint8_t lossRate,
                                int64_t rtt);
+  int32_t UpdateEncoderParameters();
 
   int32_t RegisterTransportCallback(VCMPacketizationCallback* transport);
   int32_t RegisterSendStatisticsCallback(VCMSendStatisticsCallback* sendStats);
@@ -132,6 +133,15 @@
 
   VCMQMSettingsCallback* const qm_settings_callback_;
   VCMProtectionCallback* protection_callback_;
+
+  rtc::CriticalSection params_lock_;
+  struct EncoderParameters {
+    uint32_t target_bitrate;
+    uint8_t loss_rate;
+    int64_t rtt;
+    uint32_t input_frame_rate;
+    bool updated;
+  } encoder_params_ GUARDED_BY(params_lock_);
 };
 
 class VideoReceiver {
diff --git a/webrtc/modules/video_coding/main/source/video_sender.cc b/webrtc/modules/video_coding/main/source/video_sender.cc
index 9b855da..fb04b0e8 100644
--- a/webrtc/modules/video_coding/main/source/video_sender.cc
+++ b/webrtc/modules/video_coding/main/source/video_sender.cc
@@ -42,6 +42,7 @@
       current_codec_(),
       qm_settings_callback_(qm_settings_callback),
       protection_callback_(nullptr) {
+  encoder_params_ = {0, 0, 0, 0, false};
   // Allow VideoSender to be created on one thread but used on another, post
   // construction. This is currently how this class is being used by at least
   // one external project (diffractor).
@@ -67,6 +68,14 @@
     }
   }
 
+  {
+    rtc::CritScope cs(&params_lock_);
+    // Force an encoder parameters update, so that incoming frame rate is
+    // updated even if bandwidth hasn't changed.
+    encoder_params_.input_frame_rate = _mediaOpt.InputFrameRate();
+    encoder_params_.updated = true;
+  }
+
   return returnValue;
 }
 
@@ -216,27 +225,42 @@
 int32_t VideoSender::SetChannelParameters(uint32_t target_bitrate,
                                           uint8_t lossRate,
                                           int64_t rtt) {
-  // TODO(tommi,mflodman): This method is called on the network thread via the
-  // OnNetworkChanged event (ViEEncoder::OnNetworkChanged). Could we instead
-  // post the updated information to the encoding thread and not grab a lock
-  // here?  This effectively means that the network thread will be blocked for
-  // as much as frame encoding period.
+  uint32_t target_rate =
+      _mediaOpt.SetTargetRates(target_bitrate, lossRate, rtt,
+                               protection_callback_, qm_settings_callback_);
 
-  uint32_t target_rate = _mediaOpt.SetTargetRates(target_bitrate,
-                                                  lossRate,
-                                                  rtt,
-                                                  protection_callback_,
-                                                  qm_settings_callback_);
   uint32_t input_frame_rate = _mediaOpt.InputFrameRate();
 
+  rtc::CritScope cs(&params_lock_);
+  encoder_params_ =
+      EncoderParameters{target_rate, lossRate, rtt, input_frame_rate, true};
+
+  return VCM_OK;
+}
+
+int32_t VideoSender::UpdateEncoderParameters() {
+  EncoderParameters params;
+  {
+    rtc::CritScope cs(&params_lock_);
+    params = encoder_params_;
+    encoder_params_.updated = false;
+  }
+
+  if (!params.updated || params.target_bitrate == 0)
+    return VCM_OK;
+
   CriticalSectionScoped sendCs(_sendCritSect);
   int32_t ret = VCM_UNINITIALIZED;
   static_assert(VCM_UNINITIALIZED < 0, "VCM_UNINITIALIZED must be negative.");
 
+  if (params.input_frame_rate == 0) {
+    // No frame rate estimate available, use default.
+    params.input_frame_rate = current_codec_.maxFramerate;
+  }
   if (_encoder != nullptr) {
-    ret = _encoder->SetChannelParameters(lossRate, rtt);
+    ret = _encoder->SetChannelParameters(params.loss_rate, params.rtt);
     if (ret >= 0) {
-      ret = _encoder->SetRates(target_rate, input_frame_rate);
+      ret = _encoder->SetRates(params.target_bitrate, params.input_frame_rate);
     }
   }
   return ret;
@@ -300,6 +324,7 @@
 int32_t VideoSender::AddVideoFrame(const VideoFrame& videoFrame,
                                    const VideoContentMetrics* contentMetrics,
                                    const CodecSpecificInfo* codecSpecificInfo) {
+  UpdateEncoderParameters();
   CriticalSectionScoped cs(_sendCritSect);
   if (_encoder == nullptr) {
     return VCM_UNINITIALIZED;
diff --git a/webrtc/modules/video_coding/main/source/video_sender_unittest.cc b/webrtc/modules/video_coding/main/source/video_sender_unittest.cc
index fe1ea98..88cb7b3 100644
--- a/webrtc/modules/video_coding/main/source/video_sender_unittest.cc
+++ b/webrtc/modules/video_coding/main/source/video_sender_unittest.cc
@@ -314,6 +314,20 @@
   EXPECT_EQ(-1, sender_->IntraFrameRequest(-1));
 }
 
+TEST_F(TestVideoSenderWithMockEncoder, EncoderFramerateUpdatedViaProcess) {
+  sender_->SetChannelParameters(settings_.startBitrate, 0, 200);
+  const int64_t kRateStatsWindowMs = 2000;
+  const uint32_t kInputFps = 20;
+  int64_t start_time = clock_.TimeInMilliseconds();
+  while (clock_.TimeInMilliseconds() < start_time + kRateStatsWindowMs) {
+    AddFrame();
+    clock_.AdvanceTimeMilliseconds(1000 / kInputFps);
+  }
+  EXPECT_CALL(encoder_, SetRates(_, kInputFps)).Times(1).WillOnce(Return(0));
+  sender_->Process();
+  AddFrame();
+}
+
 class TestVideoSenderWithVp8 : public TestVideoSender {
  public:
   TestVideoSenderWithVp8()
@@ -354,15 +368,13 @@
       EXPECT_CALL(post_encode_callback_, Encoded(_, NULL, NULL))
           .WillOnce(Return(0));
       AddFrame();
-
       // SetChannelParameters needs to be called frequently to propagate
       // framerate from the media optimization into the encoder.
       // Note: SetChannelParameters fails if less than 2 frames are in the
       // buffer since it will fail to calculate the framerate.
       if (i != 0) {
-        EXPECT_EQ(VCM_OK,
-                  sender_->SetChannelParameters(
-                      available_bitrate_kbps_ * 1000, 0, 200));
+        EXPECT_EQ(VCM_OK, sender_->SetChannelParameters(
+                              available_bitrate_kbps_ * 1000, 0, 200));
       }
     }
   }