rtpjitterbuffer: clean up and improve missing packets handling
authorHavard Graff <havard@pexip.com>
Tue, 2 Jun 2020 17:38:33 +0000 (19:38 +0200)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Sat, 24 Apr 2021 13:53:58 +0000 (13:53 +0000)
* Try to make variable and function names more clear.
* Add plenty of comments describing the logic step-by-step.
* Improve the logging around this, making the logs easier to read and
  understand when debugging these issues.

* Revise the logic of packets that are actually beyond saving in doing
  the following:
1. Do an optimistic estimation of which packets can still arrive.
2. Based on this, find which packets (and duration) are now hopelessly
   lost.
3. Issue an immediate lost-event for the hopelessly lost and then add
   lost/rtx timers for the ones we still hope to save, meaning that if
   they are to arrive, they will not be discarded.

* Revise the use of rtx-delay:
  Earlier the rtx-delay would vary, depending on the pts of the latest
  packet and the estimated pts of the packet it being issued a RTX for,
  but now that we aim to estimate the PTS of the missing packet accurately,
  the RTX delay should remain the same for all packets.
  Meaning: If the packet have a PTS of X, the delay in asked for a RTX
  for this packet is always a constant X + delay, not a variable one.

* Finally ensure that the chaotic "check-for-stall" tests uses timestamps
  that starts from 0 to make them easier to debug.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/952>

gst/rtpmanager/gstrtpjitterbuffer.c
tests/check/elements/rtpjitterbuffer.c

index b360831..eec1f9e 100644 (file)
@@ -930,7 +930,7 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
    * GstRtpJitterBuffer::on-npt-stop:
    * @buffer: the object which received the signal
    *
-   * Signal that the jitterbufer has pushed the RTP packet that corresponds to
+   * Signal that the jitterbuffer has pushed the RTP packet that corresponds to
    * the npt-stop position.
    */
   gst_rtp_jitter_buffer_signals[SIGNAL_ON_NPT_STOP] =
@@ -2254,7 +2254,7 @@ get_rtx_delay (GstRtpJitterBufferPrivate * priv)
  * had for this packet.
  */
 static void
-update_timers (GstRtpJitterBuffer * jitterbuffer, guint16 seqnum,
+update_rtx_timers (GstRtpJitterBuffer * jitterbuffer, guint16 seqnum,
     GstClockTime dts, GstClockTime pts, gboolean do_next_seqnum,
     gboolean is_rtx, RtpTimer * timer)
 {
@@ -2299,7 +2299,7 @@ update_timers (GstRtpJitterBuffer * jitterbuffer, guint16 seqnum,
   }
 
   do_next_seqnum = do_next_seqnum && priv->packet_spacing > 0
-      && priv->do_retransmission && priv->rtx_next_seqnum;
+      && priv->rtx_next_seqnum;
 
   if (timer && timer->type != RTP_TIMER_DEADLINE) {
     if (timer->num_rtx_retry > 0) {
@@ -2326,27 +2326,27 @@ update_timers (GstRtpJitterBuffer * jitterbuffer, guint16 seqnum,
   }
 
   if (do_next_seqnum && pts != GST_CLOCK_TIME_NONE) {
-    GstClockTime expected, delay;
+    GstClockTime next_expected_pts, delay;
 
     /* calculate expected arrival time of the next seqnum */
-    expected = pts + priv->packet_spacing;
+    next_expected_pts = pts + priv->packet_spacing;
 
     delay = get_rtx_delay (priv);
 
     /* and update/install timer for next seqnum */
-    GST_DEBUG_OBJECT (jitterbuffer, "Add RTX timer #%d, expected %"
-        GST_TIME_FORMAT ", delay %" GST_TIME_FORMAT ", packet-spacing %"
+    GST_DEBUG_OBJECT (jitterbuffer, "Add RTX timer #%d, next_expected_pts %"
+        GST_TIME_FORMAT ", delay %" GST_TIME_FORMAT ", est packet duration %"
         GST_TIME_FORMAT ", jitter %" GST_TIME_FORMAT, priv->next_in_seqnum,
-        GST_TIME_ARGS (expected), GST_TIME_ARGS (delay),
+        GST_TIME_ARGS (next_expected_pts), GST_TIME_ARGS (delay),
         GST_TIME_ARGS (priv->packet_spacing), GST_TIME_ARGS (priv->avg_jitter));
 
     if (timer && !is_stats_timer) {
       timer->type = RTP_TIMER_EXPECTED;
       rtp_timer_queue_update_timer (priv->timers, timer, priv->next_in_seqnum,
-          expected, delay, 0, TRUE);
+          next_expected_pts, delay, 0, TRUE);
     } else {
       rtp_timer_queue_set_expected (priv->timers, priv->next_in_seqnum,
-          expected, delay, priv->packet_spacing);
+          next_expected_pts, delay, priv->packet_spacing);
     }
   } else if (timer && timer->type != RTP_TIMER_DEADLINE && !is_stats_timer) {
     /* if we had a timer, remove it, we don't know when to expect the next
@@ -2443,130 +2443,180 @@ insert_lost_event (GstRtpJitterBuffer * jitterbuffer,
 }
 
 static void
-calculate_expected (GstRtpJitterBuffer * jitterbuffer, guint32 expected,
-    guint16 seqnum, GstClockTime pts, gint gap)
+gst_rtp_jitter_buffer_handle_missing_packets (GstRtpJitterBuffer * jitterbuffer,
+    guint32 missing_seqnum, guint16 current_seqnum, GstClockTime pts, gint gap,
+    GstClockTime now)
 {
   GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  GstClockTime duration, expected_pts;
+  GstClockTime est_pkt_duration, est_pts;
   gboolean equidistant = priv->equidistant > 0;
   GstClockTime last_in_pts = priv->last_in_pts;
+  GstClockTimeDiff offset = timeout_offset (jitterbuffer);
+  GstClockTime rtx_delay = get_rtx_delay (priv);
+  guint16 remaining_gap;
+  GstClockTimeDiff remaining_duration;
+  GstClockTimeDiff remainder_duration;
+  guint i;
 
   GST_DEBUG_OBJECT (jitterbuffer,
-      "pts %" GST_TIME_FORMAT ", last %" GST_TIME_FORMAT,
-      GST_TIME_ARGS (pts), GST_TIME_ARGS (last_in_pts));
-
-  if (pts == GST_CLOCK_TIME_NONE) {
-    GST_WARNING_OBJECT (jitterbuffer, "Have no PTS");
-    return;
-  }
+      "Missing packets: (#%u->#%u), gap %d, pts %" GST_TIME_FORMAT
+      ", last-pts %" GST_TIME_FORMAT,
+      missing_seqnum, current_seqnum - 1, gap, GST_TIME_ARGS (pts),
+      GST_TIME_ARGS (last_in_pts));
 
   if (equidistant) {
-    GstClockTime total_duration;
+    GstClockTimeDiff total_duration;
+    gboolean too_late;
+
     /* the total duration spanned by the missing packets */
-    if (pts >= last_in_pts)
-      total_duration = pts - last_in_pts;
-    else
-      total_duration = 0;
+    total_duration = MAX (0, GST_CLOCK_DIFF (last_in_pts, pts));
 
     /* interpolate between the current time and the last time based on
      * number of packets we are missing, this is the estimated duration
      * for the missing packet based on equidistant packet spacing. */
-    duration = total_duration / (gap + 1);
+    est_pkt_duration = total_duration / (gap + 1);
+
+    /* if we have valid packet-spacing, use that */
+    if (total_duration > 0 && priv->packet_spacing) {
+      est_pkt_duration = priv->packet_spacing;
+    }
+
+    est_pts = last_in_pts + est_pkt_duration;
+    GST_DEBUG_OBJECT (jitterbuffer, "estimated missing packet pts %"
+        GST_TIME_FORMAT " and duration %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (est_pts), GST_TIME_ARGS (est_pkt_duration));
 
-    GST_DEBUG_OBJECT (jitterbuffer, "duration %" GST_TIME_FORMAT,
-        GST_TIME_ARGS (duration));
+    /* a packet is considered too late if our estimated pts plus all
+       applicable offsets are in the past */
+    too_late = now > (est_pts + offset);
 
-    if (total_duration > priv->latency_ns) {
-      GstClockTime gap_time;
+    /* Here we optimistically try to save any packets that could potentially
+       be saved by making sure we create lost/rtx timers for them, and for
+       the rest that could not possibly be saved, we create a "multi-lost"
+       event immediately containing the missing duration and sequence numbers */
+    if (too_late) {
       guint lost_packets;
+      GstClockTime lost_duration;
+      GstClockTimeDiff gap_time;
+      guint saveable_packets;
+      GstClockTime saveable_duration;
 
-      if (duration > 0) {
-        GstClockTime gap_dur = gap * duration;
-        if (gap_dur > priv->latency_ns)
-          gap_time = gap_dur - priv->latency_ns;
-        else
-          gap_time = 0;
-        lost_packets = gap_time / duration;
-      } else {
-        gap_time = total_duration - priv->latency_ns;
-        lost_packets = gap;
-      }
+      /* gap time represents the total duration of all missing packets */
+      gap_time = MAX (0, GST_CLOCK_DIFF (est_pts, pts));
 
-      /* too many lost packets, some of the missing packets are already
-       * too late and we can generate lost packet events for them. */
-      GST_INFO_OBJECT (jitterbuffer,
-          "lost packets (%d, #%d->#%d) duration too large %" GST_TIME_FORMAT
-          " > %" GST_TIME_FORMAT ", consider %u lost (%" GST_TIME_FORMAT ")",
-          gap, expected, seqnum - 1, GST_TIME_ARGS (total_duration),
-          GST_TIME_ARGS (priv->latency_ns), lost_packets,
-          GST_TIME_ARGS (gap_time));
+      /* based on the estimated packet duration, we
+         can figure out how many packets we could possibly save */
+      saveable_packets = offset / est_pkt_duration;
+      /* and say that the amount of lost packet is the sequence-number
+         gap minus these saveable packets, but at least 1 */
+      lost_packets = MAX (1, (gint) gap - (gint) saveable_packets);
+
+      /* now we know how many packets we can actually save */
+      saveable_packets = gap - lost_packets;
+
+      /* we convert that to time */
+      saveable_duration = saveable_packets * est_pkt_duration;
+
+      /* and we now have the duration we need to fill */
+      lost_duration = GST_CLOCK_DIFF (saveable_duration, gap_time);
 
       /* this multi-lost-packet event will be inserted directly into the packet-queue
          for immediate processing */
       if (lost_packets > 0) {
         RtpTimer *timer;
-        GstClockTime timestamp =
-            apply_offset (jitterbuffer, last_in_pts + duration);
-        insert_lost_event (jitterbuffer, expected, lost_packets, timestamp,
-            gap_time, 0);
+        GstClockTime timestamp = apply_offset (jitterbuffer, est_pts);
+
+        GST_INFO_OBJECT (jitterbuffer, "lost event for %d packet(s) (#%d->#%d) "
+            "for duration %" GST_TIME_FORMAT, lost_packets, missing_seqnum,
+            missing_seqnum + lost_packets - 1, GST_TIME_ARGS (lost_duration));
+
+        insert_lost_event (jitterbuffer, missing_seqnum, lost_packets,
+            timestamp, lost_duration, 0);
 
-        timer = rtp_timer_queue_find (priv->timers, expected);
-        if (timer && timer->type == RTP_TIMER_EXPECTED) {
+        timer = rtp_timer_queue_find (priv->timers, missing_seqnum);
+        if (timer && timer->type != RTP_TIMER_DEADLINE) {
           if (timer->queued)
             rtp_timer_queue_unschedule (priv->timers, timer);
           GST_DEBUG_OBJECT (jitterbuffer, "removing timer for seqnum #%u",
-              expected);
+              missing_seqnum);
           rtp_timer_free (timer);
         }
 
-        expected += lost_packets;
-        last_in_pts += gap_time;
+        missing_seqnum += lost_packets;
+        est_pts += lost_duration;
       }
     }
 
-    expected_pts = last_in_pts + duration;
   } else {
     /* If we cannot assume equidistant packet spacing, the only thing we now
      * for sure is that the missing packets have expected pts not later than
      * the last received pts. */
-    duration = 0;
-    expected_pts = pts;
+    est_pkt_duration = 0;
+    est_pts = pts;
   }
 
-  if (priv->do_retransmission) {
-    RtpTimer *timer = rtp_timer_queue_find (priv->timers, expected);
-    GstClockTime rtx_delay = get_rtx_delay (priv);
-
-    /* if we had a timer for the first missing packet, update it. */
-    if (timer && timer->type == RTP_TIMER_EXPECTED) {
-      GstClockTime timeout = timer->timeout;
-      GstClockTime delay = MAX (rtx_delay, pts - expected_pts);
-
-      timer->duration = duration;
-      if (timeout > (expected_pts + delay) && timer->num_rtx_retry == 0) {
-        rtp_timer_queue_update_timer (priv->timers, timer, timer->seqnum,
-            expected_pts, delay, 0, TRUE);
+  /* Figure out how many more packets we are missing. */
+  remaining_gap = current_seqnum - missing_seqnum;
+  /* and how much time these packets represent */
+  remaining_duration = MAX (0, GST_CLOCK_DIFF (est_pts, pts));
+  /* Given the calculated packet-duration (packet spacing when equidistant),
+     the remainder is what we are left with after subtracting the ideal time
+     for the gap */
+  remainder_duration =
+      MAX (0, GST_CLOCK_DIFF (est_pkt_duration * remaining_gap,
+          remaining_duration));
+
+  GST_DEBUG_OBJECT (jitterbuffer, "remaining gap of %u, with "
+      "duration %" GST_TIME_FORMAT " gives remainder duration %"
+      GST_STIME_FORMAT, remaining_gap, GST_TIME_ARGS (remaining_duration),
+      GST_STIME_ARGS (remainder_duration));
+
+  for (i = 0; i < remaining_gap; i++) {
+    GstClockTime duration = est_pkt_duration;
+    /* we add the remainder on the first packet */
+    if (i == 0)
+      duration += remainder_duration;
+
+    /* clip duration to what is actually left */
+    remaining_duration = MAX (0, GST_CLOCK_DIFF (est_pts, pts));
+    duration = MIN (duration, remaining_duration);
+
+    if (priv->do_retransmission) {
+      RtpTimer *timer = rtp_timer_queue_find (priv->timers, missing_seqnum);
+
+      /* if we had a timer for the missing packet, update it. */
+      if (timer && timer->type == RTP_TIMER_EXPECTED) {
+        timer->duration = duration;
+        if (timer->timeout > (est_pts + rtx_delay) && timer->num_rtx_retry == 0) {
+          rtp_timer_queue_update_timer (priv->timers, timer, timer->seqnum,
+              est_pts, rtx_delay, 0, TRUE);
+          GST_DEBUG_OBJECT (jitterbuffer, "Update RTX timer(s) #%u, "
+              "pts %" GST_TIME_FORMAT ", delay %" GST_TIME_FORMAT
+              ", duration %" GST_TIME_FORMAT,
+              missing_seqnum, GST_TIME_ARGS (est_pts),
+              GST_TIME_ARGS (rtx_delay), GST_TIME_ARGS (duration));
+        }
+      } else {
+        GST_DEBUG_OBJECT (jitterbuffer, "Add RTX timer(s) #%u, "
+            "pts %" GST_TIME_FORMAT ", delay %" GST_TIME_FORMAT
+            ", duration %" GST_TIME_FORMAT,
+            missing_seqnum, GST_TIME_ARGS (est_pts),
+            GST_TIME_ARGS (rtx_delay), GST_TIME_ARGS (duration));
+        rtp_timer_queue_set_expected (priv->timers, missing_seqnum, est_pts,
+            rtx_delay, duration);
       }
-      expected++;
-      expected_pts += duration;
+    } else {
+      GST_INFO_OBJECT (jitterbuffer,
+          "Add Lost timer for #%u, pts %" GST_TIME_FORMAT
+          ", duration %" GST_TIME_FORMAT ", offset %" GST_STIME_FORMAT,
+          missing_seqnum, GST_TIME_ARGS (est_pts),
+          GST_TIME_ARGS (duration), GST_STIME_ARGS (offset));
+      rtp_timer_queue_set_lost (priv->timers, missing_seqnum, est_pts,
+          duration, offset);
     }
 
-    while (gst_rtp_buffer_compare_seqnum (expected, seqnum) > 0) {
-      /* minimum delay the expected-timer has "waited" is the elapsed time
-       * since expected arrival of the missing packet */
-      GstClockTime delay = MAX (rtx_delay, pts - expected_pts);
-      rtp_timer_queue_set_expected (priv->timers, expected, expected_pts,
-          delay, duration);
-      expected_pts += duration;
-      expected++;
-    }
-  } else {
-    while (gst_rtp_buffer_compare_seqnum (expected, seqnum) > 0) {
-      rtp_timer_queue_set_lost (priv->timers, expected, expected_pts,
-          duration, timeout_offset (jitterbuffer));
-      expected_pts += duration;
-      expected++;
-    }
+    missing_seqnum++;
+    est_pts += duration;
   }
 }
 
@@ -2856,6 +2906,7 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
   guint16 seqnum;
   guint32 expected, rtptime;
   GstFlowReturn ret = GST_FLOW_OK;
+  GstClockTime now;
   GstClockTime dts, pts;
   guint64 latency_ts;
   gboolean head;
@@ -2884,6 +2935,7 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
   gst_rtp_buffer_unmap (&rtp);
 
   is_rtx = GST_BUFFER_IS_RETRANSMISSION (buffer);
+  now = get_current_running_time (jitterbuffer);
 
   /* make sure we have PTS and DTS set */
   pts = GST_BUFFER_PTS (buffer);
@@ -2896,7 +2948,7 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
   if (dts == -1) {
     /* If we have no DTS here, i.e. no capture time, get one from the
      * clock now to have something to calculate with in the future. */
-    dts = get_current_running_time (jitterbuffer);
+    dts = now;
     pts = dts;
 
     /* Remember that we estimated the DTS if we are running already
@@ -3095,7 +3147,8 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
       if (gap > 0) {
         GST_DEBUG_OBJECT (jitterbuffer, "%d missing packets", gap);
         /* fill in the gap with EXPECTED timers */
-        calculate_expected (jitterbuffer, expected, seqnum, pts, gap);
+        gst_rtp_jitter_buffer_handle_missing_packets (jitterbuffer, expected,
+            seqnum, pts, gap, now);
         do_next_seqnum = TRUE;
       } else {
         GST_DEBUG_OBJECT (jitterbuffer, "old packet received");
@@ -3211,8 +3264,10 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
   if (gst_rtp_jitter_buffer_fast_start (jitterbuffer))
     head = TRUE;
 
-  /* update timers */
-  update_timers (jitterbuffer, seqnum, dts, pts, do_next_seqnum, is_rtx, timer);
+  /* update rtx timers */
+  if (priv->do_retransmission)
+    update_rtx_timers (jitterbuffer, seqnum, dts, pts, do_next_seqnum, is_rtx,
+        timer);
 
   /* we had an unhandled SR, handle it now */
   if (priv->last_sr)
@@ -3814,7 +3869,7 @@ do_expected_timeout (GstRtpJitterBuffer * jitterbuffer, RtpTimer * timer,
   GstClockTimeDiff offset = 0;
   GstClockTime timeout;
 
-  GST_DEBUG_OBJECT (jitterbuffer, "expected %d didn't arrive, now %"
+  GST_DEBUG_OBJECT (jitterbuffer, "expected #%d didn't arrive, now %"
       GST_TIME_FORMAT, timer->seqnum, GST_TIME_ARGS (now));
 
   rtx_retry_timeout = get_rtx_retry_timeout (priv);
@@ -3867,19 +3922,20 @@ do_expected_timeout (GstRtpJitterBuffer * jitterbuffer, RtpTimer * timer,
    */
   timeout = timer->rtx_last + rtx_retry_timeout;
   GST_DEBUG_OBJECT (jitterbuffer,
-      "timer #%i new timeout %" GST_TIME_FORMAT ", rtx retry timeout%"
+      "timer #%i new timeout %" GST_TIME_FORMAT ", rtx retry timeout %"
       GST_TIME_FORMAT ", num_retry %u", timer->seqnum, GST_TIME_ARGS (timeout),
       GST_TIME_ARGS (rtx_retry_timeout), timer->num_rtx_retry);
   if ((priv->rtx_max_retries != -1
           && timer->num_rtx_retry >= priv->rtx_max_retries)
       || (timeout > timer->rtx_base + rtx_retry_period)) {
-    GST_DEBUG_OBJECT (jitterbuffer, "reschedule #%i as LOST timer",
-        timer->seqnum);
     /* too many retransmission request, we now convert the timer
      * to a lost timer, leave the num_rtx_retry as it is for stats */
     timer->type = RTP_TIMER_LOST;
     timeout = timer->rtx_base;
     offset = timeout_offset (jitterbuffer);
+    GST_DEBUG_OBJECT (jitterbuffer, "reschedule #%i as LOST timer for %"
+        GST_TIME_FORMAT, timer->seqnum,
+        GST_TIME_ARGS (timer->rtx_base + offset));
   }
   rtp_timer_queue_update_timer (priv->timers, timer, timer->seqnum,
       timeout, 0, offset, FALSE);
index 6097ef1..af9a03a 100644 (file)
@@ -532,7 +532,9 @@ static void
 push_test_buffer_now (GstHarness * h, guint seqnum, guint32 rtptime,
     gboolean rtx)
 {
-  GstClockTime now = gst_clock_get_time (GST_ELEMENT_CLOCK (h->element));
+  GstClockTime now =
+      gst_clock_get_time (GST_ELEMENT_CLOCK (h->element)) -
+      h->element->base_time;
   GstBuffer *buf = generate_test_buffer_full (now, seqnum, rtptime);
   if (rtx)
     GST_BUFFER_FLAG_SET (buf, GST_RTP_BUFFER_FLAG_RETRANSMISSION);
@@ -906,14 +908,18 @@ GST_START_TEST (test_late_packets_still_makes_lost_events)
           generate_test_buffer_full (now,
               seqnum, seqnum * TEST_RTP_TS_DURATION)));
 
-  /* we should now receive packet-lost-events for the gap
-   * FIXME: The timeout and duration here are a bit crap...
-   */
-  verify_lost_event (h, next_seqnum, 3400 * GST_MSECOND, 6500 * GST_MSECOND);
-  verify_lost_event (h, next_seqnum + 1,
-      9900 * GST_MSECOND, 3300 * GST_MSECOND);
+  /* We get one "huge" lost-event accounting for all the missing time */
+  verify_lost_event (h, next_seqnum, 120 * GST_MSECOND, 9860 * GST_MSECOND);
+
+  /* and the next packet is optimistically expected to be the one
+     just prior to our current packet, so we time that out with a crank */
+  gst_harness_crank_single_clock_wait (h);
 
-  /* verify that packet @seqnum made it through! */
+  /* and we verify that indeed this lost event was thought to should
+     have arrived 20ms prior to the packet that actually arrived */
+  verify_lost_event (h, next_seqnum + 1, 9980 * GST_MSECOND, 20 * GST_MSECOND);
+
+  /* and finally verify that the super-late packet made it through! */
   out_buf = gst_harness_pull (h);
   fail_unless (GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_DISCONT));
   fail_unless_equals_int (seqnum, get_rtp_seq_num (out_buf));
@@ -1248,12 +1254,101 @@ GST_START_TEST (test_loss_equidistant_spacing_with_parameter_packets)
   gst_harness_push (h, generate_test_buffer_full (frame * TEST_BUF_DURATION,
           seq + 1, frame * TEST_RTP_TS_DURATION));
 
-  /* Check that the lost event has been generated assuming equidistant
-   * spacing. */
-  verify_lost_event (h, seq,
-      frame * TEST_BUF_DURATION - TEST_BUF_DURATION / 2, TEST_BUF_DURATION / 2);
+  /* given that the last known PTS (pkt#12) was 200ms and this last PTS (pkt#14) was 220ms,
+     and our current packet-spacing is 20ms, the lost-event gets a problem here:
+     If we use our packet-spacing, the last pushed packet (#12) should have
+     a duration of 20ms, meaning we would expect the missing packet (#13) to
+     have a PTS of 220ms. However, packet #14 comes in at 220ms, so what is
+     the best estimation for the missing packet here?
+
+     Given that we want to estimate the most optimistic PTS in order to give
+     the packet as many chances as possible to arrive, we end up with a PTS
+     of 220ms and a duration of 0, since that will be the most optimistic
+     placement given that it has to be before pkt #14.
+   */
+
+  /* timeout the lost-event */
+  gst_harness_crank_single_clock_wait (h);
+  verify_lost_event (h, seq, frame * TEST_BUF_DURATION, 0);
+
+  gst_buffer_unref (gst_harness_pull (h));
+
+  gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+
+typedef struct
+{
+  guint gap;
+  GstClockTime duration[3];
+} ThreeLostPackets;
+
+ThreeLostPackets no_fractional_lost_event_durations_input[] = {
+  {5, {60 * GST_MSECOND, 20 * GST_MSECOND, 20 * GST_MSECOND}},
+  {4, {40 * GST_MSECOND, 20 * GST_MSECOND, 20 * GST_MSECOND}},
+  {3, {20 * GST_MSECOND, 20 * GST_MSECOND, 20 * GST_MSECOND}},
+  {2, {20 * GST_MSECOND, 20 * GST_MSECOND, 0 * GST_MSECOND}},
+  {1, {20 * GST_MSECOND, 0 * GST_MSECOND, 0 * GST_MSECOND}},
+  {0, {0 * GST_MSECOND, 0 * GST_MSECOND, 0 * GST_MSECOND}},
+};
+
+/* This test looks after that fact that when we have equidistant
+   packetspacing, we try and keep that spacing for the lost events,
+   so we operate in "whole" packets.
+*/
+GST_START_TEST (test_no_fractional_lost_event_durations)
+{
+  ThreeLostPackets *ctx = &no_fractional_lost_event_durations_input[__i__];
+
+  GstHarness *h = gst_harness_new ("rtpjitterbuffer");
+  GstClockTime now;
+  guint latency_ms = 100;
+  guint16 seqnum, gap_seqnum;
+  GstClockTime pts;
+  GstClockTime duration;
+
+  g_object_set (h->element, "do-lost", TRUE, NULL);
+  seqnum = construct_deterministic_initial_state (h, latency_ms);
+  gap_seqnum = seqnum + ctx->gap;
+
+  now = gap_seqnum * TEST_BUF_DURATION;
+  gst_harness_set_time (h, now);
+  fail_unless_equals_int (GST_FLOW_OK, gst_harness_push (h,
+          generate_test_buffer_full (now,
+              seqnum + 3, gap_seqnum * TEST_RTP_TS_DURATION)));
+
+  pts = seqnum * TEST_BUF_DURATION;
+  now = gst_clock_get_time (GST_ELEMENT_CLOCK (h->element));
+  /* check if the lost-event has expired, if not
+     crank to move the time ahead */
+  if (pts + latency_ms * GST_MSECOND > now)
+    gst_harness_crank_single_clock_wait (h);
+  duration = ctx->duration[0];
+  verify_lost_event (h, seqnum, pts, duration);
+
+  seqnum++;
+  pts += duration;
+  duration = ctx->duration[1];
+  now = gst_clock_get_time (GST_ELEMENT_CLOCK (h->element));
+  if (pts + latency_ms * GST_MSECOND > now)
+    gst_harness_crank_single_clock_wait (h);
+  verify_lost_event (h, seqnum, pts, duration);
+
+  seqnum++;
+  pts += duration;
+  duration = ctx->duration[2];
+  now = gst_clock_get_time (GST_ELEMENT_CLOCK (h->element));
+  if (pts + latency_ms * GST_MSECOND > now)
+    gst_harness_crank_single_clock_wait (h);
+  verify_lost_event (h, seqnum, pts, duration);
 
+  /* followed by the buffer */
   gst_buffer_unref (gst_harness_pull (h));
+  /* verify that we have pulled out all waiting buffers and events */
+  fail_unless_equals_int (0, gst_harness_buffers_in_queue (h));
+  fail_unless_equals_int (0, gst_harness_events_in_queue (h));
 
   gst_harness_teardown (h);
 }
@@ -1706,8 +1801,7 @@ GST_START_TEST (test_rtx_duplicate_packet_updates_rtx_stats)
   gint latency_ms = 100;
   gint next_seqnum;
   GstClockTime now, rtx_request_6, rtx_request_7;
-  gint rtx_delay_ms_0 = TEST_BUF_MS / 2;
-  gint rtx_delay_ms_1 = TEST_BUF_MS;
+  gint rtx_delay_ms = TEST_BUF_MS / 2;
   gint i;
 
   g_object_set (h->element, "do-retransmission", TRUE, NULL);
@@ -1721,17 +1815,17 @@ GST_START_TEST (test_rtx_duplicate_packet_updates_rtx_stats)
   /* Wait for NACKs on 6 and 7 */
   gst_harness_crank_single_clock_wait (h);
   verify_rtx_event (h, 6, 6 * TEST_BUF_DURATION,
-      rtx_delay_ms_0, TEST_BUF_DURATION);
+      rtx_delay_ms, TEST_BUF_DURATION);
   rtx_request_6 = gst_clock_get_time (GST_ELEMENT_CLOCK (h->element));
   fail_unless_equals_int64 (rtx_request_6,
-      6 * TEST_BUF_DURATION + rtx_delay_ms_0 * GST_MSECOND);
+      6 * TEST_BUF_DURATION + rtx_delay_ms * GST_MSECOND);
 
   gst_harness_crank_single_clock_wait (h);
   verify_rtx_event (h,
-      7, 7 * TEST_BUF_DURATION, rtx_delay_ms_1, TEST_BUF_DURATION);
+      7, 7 * TEST_BUF_DURATION, rtx_delay_ms, TEST_BUF_DURATION);
   rtx_request_7 = gst_clock_get_time (GST_ELEMENT_CLOCK (h->element));
   fail_unless_equals_int64 (rtx_request_7,
-      7 * TEST_BUF_DURATION + rtx_delay_ms_1 * GST_MSECOND);
+      7 * TEST_BUF_DURATION + rtx_delay_ms * GST_MSECOND);
 
   /* Original packet 7 arrives */
   now = 161 * GST_MSECOND;
@@ -3093,6 +3187,8 @@ check_for_stall (GstHarness * h, BufferArrayCtx * bufs, guint num_bufs)
   GArray *array;
 
   gst_harness_use_systemclock (h);
+  gst_element_set_base_time (h->element,
+      gst_clock_get_time (GST_ELEMENT_CLOCK (h->element)));
   gst_harness_set_src_caps (h, generate_caps ());
 
   g_object_get (h->element, "latency", &latency_ms, NULL);
@@ -3218,6 +3314,9 @@ rtpjitterbuffer_suite (void)
   tcase_add_test (tc_chain, test_reorder_of_non_equidistant_packets);
   tcase_add_test (tc_chain,
       test_loss_equidistant_spacing_with_parameter_packets);
+  tcase_add_loop_test (tc_chain, test_no_fractional_lost_event_durations, 0,
+      G_N_ELEMENTS (no_fractional_lost_event_durations_input));
+
 
   tcase_add_test (tc_chain, test_rtx_expected_next);
   tcase_add_test (tc_chain, test_rtx_not_bursting_requests);