Fix bug in dynamic pacer causing slightly inaccurate pacing rate.

When new packets are enqueued after a dead-period where media debt is
zero, that time slice should not be used to reduce the debt for the
new packet.

Bug: webrtc:10809
Change-Id: Ifb960548e6aa349b79f37743cbfed78a5c937a13
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/234081
Reviewed-by: Philip Eliasson <philipel@webrtc.org>
Commit-Queue: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35143}
diff --git a/modules/pacing/pacing_controller.cc b/modules/pacing/pacing_controller.cc
index 27cfb92..896daa0 100644
--- a/modules/pacing/pacing_controller.cc
+++ b/modules/pacing/pacing_controller.cc
@@ -297,10 +297,21 @@
 
   Timestamp now = CurrentTime();
 
-  if (mode_ == ProcessMode::kDynamic && packet_queue_.Empty() &&
-      NextSendTime() <= now) {
-    TimeDelta elapsed_time = UpdateTimeAndGetElapsed(now);
+  if (mode_ == ProcessMode::kDynamic && packet_queue_.Empty()) {
+    // If queue is empty, we need to "fast-forward" the last process time,
+    // so that we don't use passed time as budget for sending the first new
+    // packet.
+    Timestamp target_process_time = now;
+    Timestamp next_send_time = NextSendTime();
+    if (next_send_time.IsFinite()) {
+      // There was already a valid planned send time, such as a keep-alive.
+      // Use that as last process time only if it's prior to now.
+      target_process_time = std::min(now, next_send_time);
+    }
+
+    TimeDelta elapsed_time = UpdateTimeAndGetElapsed(target_process_time);
     UpdateBudgetWithElapsedTime(elapsed_time);
+    last_process_time_ = target_process_time;
   }
   packet_queue_.Push(priority, now, packet_counter_++, std::move(packet));
 }
diff --git a/modules/pacing/pacing_controller_unittest.cc b/modules/pacing/pacing_controller_unittest.cc
index a953d5b..d1f751e 100644
--- a/modules/pacing/pacing_controller_unittest.cc
+++ b/modules/pacing/pacing_controller_unittest.cc
@@ -252,8 +252,7 @@
     Send(type, ssrc, sequence_number, capture_time_ms, size);
     EXPECT_CALL(callback_,
                 SendPacket(ssrc, sequence_number, capture_time_ms,
-                           type == RtpPacketMediaType::kRetransmission, false))
-        .Times(1);
+                           type == RtpPacketMediaType::kRetransmission, false));
   }
 
   std::unique_ptr<RtpPacketToSend> BuildRtpPacket(RtpPacketMediaType type) {
@@ -2135,6 +2134,39 @@
   AdvanceTimeAndProcess();
 }
 
+TEST_P(PacingControllerTest, GapInPacingDoesntAccumulateBudget) {
+  if (PeriodicProcess()) {
+    // This test checks behavior when not using interval budget.
+    return;
+  }
+
+  const uint32_t kSsrc = 12345;
+  uint16_t sequence_number = 1234;
+  const DataSize kPackeSize = DataSize::Bytes(250);
+  const TimeDelta kPacketSendTime = TimeDelta::Millis(15);
+
+  pacer_->SetPacingRates(kPackeSize / kPacketSendTime,
+                         /*padding_rate=*/DataRate::Zero());
+
+  // Send an initial packet.
+  SendAndExpectPacket(RtpPacketMediaType::kVideo, kSsrc, sequence_number++,
+                      clock_.TimeInMilliseconds(), kPackeSize.bytes());
+  pacer_->ProcessPackets();
+  ::testing::Mock::VerifyAndClearExpectations(&callback_);
+
+  // Advance time kPacketSendTime past where the media debt should be 0.
+  clock_.AdvanceTime(2 * kPacketSendTime);
+
+  // Enqueue two new packets. Expect only one to be sent one ProcessPackets().
+  Send(RtpPacketMediaType::kVideo, kSsrc, sequence_number + 1,
+       clock_.TimeInMilliseconds(), kPackeSize.bytes());
+  Send(RtpPacketMediaType::kVideo, kSsrc, sequence_number + 2,
+       clock_.TimeInMilliseconds(), kPackeSize.bytes());
+  EXPECT_CALL(callback_, SendPacket(kSsrc, sequence_number + 1,
+                                    clock_.TimeInMilliseconds(), false, false));
+  pacer_->ProcessPackets();
+}
+
 INSTANTIATE_TEST_SUITE_P(
     WithAndWithoutIntervalBudget,
     PacingControllerTest,