Commit fb9c75db authored by Havard Graff's avatar Havard Graff Committed by Sebastian Dröge
Browse files

rtpjitterbuffer: fix lost-event using dts instead of pts

The lost-event was using a different time-domain (dts) than the outgoing
buffers (pts). Given certain network-conditions these two would become
sufficiently different and the lost-event contained timestamp/duration
that was really wrong. As an example GstAudioDecoder could produce
a stream that jumps back and forth in time after receiving a lost-event.

The previous behavior calculated the pts (based on the rtptime) inside the
rtp_jitter_buffer_insert function, but now this functionality has been
refactored into a new function rtp_jitter_buffer_calculate_pts that is
called much earlier in the _chain function to make pts available to
various calculations that wrongly used dts previously
(like the lost-event).

There are however two calculations where using dts is the right thing to
do: calculating the receive-jitter and the rtx-round-trip-time, where the
arrival time of the buffer from the network is the right metric
(and is what dts in fact is today).

The patch also adds two tests regarding B-frames or the
“rtptime-going-backwards”-scenario, as there were some concerns that this
patch might break this behavior (which the tests shows it does not).
parent bea35f97
This diff is collapsed.
......@@ -688,92 +688,16 @@ queue_do_insert (RTPJitterBuffer * jbuf, GList * list, GList * item)
queue->length++;
}
/**
* rtp_jitter_buffer_insert:
* @jbuf: an #RTPJitterBuffer
* @item: an #RTPJitterBufferItem to insert
* @head: TRUE when the head element changed.
* @percent: the buffering percent after insertion
* @base_time: base time of the pipeline
*
* Inserts @item into the packet queue of @jbuf. The sequence number of the
* packet will be used to sort the packets. This function takes ownerhip of
* @buf when the function returns %TRUE.
*
* When @head is %TRUE, the new packet was added at the head of the queue and
* will be available with the next call to rtp_jitter_buffer_pop() and
* rtp_jitter_buffer_peek().
*
* Returns: %FALSE if a packet with the same number already existed.
*/
gboolean
rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item,
gboolean * head, gint * percent, GstClockTime base_time)
GstClockTime
rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
guint32 rtptime, GstClockTime base_time)
{
GList *list, *event = NULL;
guint32 rtptime;
guint64 ext_rtptime;
guint16 seqnum;
GstClockTime gstrtptime, dts;
GstClockTime gstrtptime, pts;
GstClock *media_clock, *pipeline_clock;
guint64 media_clock_offset;
gboolean rfc7273_mode;
g_return_val_if_fail (jbuf != NULL, FALSE);
g_return_val_if_fail (item != NULL, FALSE);
list = jbuf->packets->tail;
/* no seqnum, simply append then */
if (item->seqnum == -1)
goto append;
seqnum = item->seqnum;
/* loop the list to skip strictly larger seqnum buffers */
for (; list; list = g_list_previous (list)) {
guint16 qseq;
gint gap;
RTPJitterBufferItem *qitem = (RTPJitterBufferItem *) list;
if (qitem->seqnum == -1) {
/* keep a pointer to the first consecutive event if not already
* set. we will insert the packet after the event if we can't find
* a packet with lower sequence number before the event. */
if (event == NULL)
event = list;
continue;
}
qseq = qitem->seqnum;
/* compare the new seqnum to the one in the buffer */
gap = gst_rtp_buffer_compare_seqnum (seqnum, qseq);
/* we hit a packet with the same seqnum, notify a duplicate */
if (G_UNLIKELY (gap == 0))
goto duplicate;
/* seqnum > qseq, we can stop looking */
if (G_LIKELY (gap < 0))
break;
/* if we've found a packet with greater sequence number, cleanup the
* event pointer as the packet will be inserted before the event */
event = NULL;
}
/* if event is set it means that packets before the event had smaller
* sequence number, so we will insert our packet after the event */
if (event)
list = event;
dts = item->dts;
if (item->rtptime == -1)
goto append;
rtptime = item->rtptime;
/* rtp time jumps are checked for during skew calculation, but bypassed
* in other mode, so mind those here and reset jb if needed.
* Only reset if valid input time, which is likely for UDP input
......@@ -797,8 +721,7 @@ rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item,
/* Return the last time if we got the same RTP timestamp again */
ext_rtptime = gst_rtp_buffer_ext_timestamp (&jbuf->ext_rtptime, rtptime);
if (jbuf->last_rtptime != -1 && ext_rtptime == jbuf->last_rtptime) {
item->pts = jbuf->prev_out_time;
goto append;
return jbuf->prev_out_time;
}
/* keep track of the last extended rtptime */
......@@ -906,12 +829,12 @@ rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item,
external, rate_num, rate_denom);
if (rtpsystime > base_time)
item->pts = rtpsystime - base_time;
pts = rtpsystime - base_time;
else
item->pts = 0;
pts = 0;
GST_DEBUG ("RFC7273 clock time %" GST_TIME_FORMAT ", out %" GST_TIME_FORMAT,
GST_TIME_ARGS (rtpsystime), GST_TIME_ARGS (item->pts));
GST_TIME_ARGS (rtpsystime), GST_TIME_ARGS (pts));
} else if (rfc7273_mode && (jbuf->mode == RTP_JITTER_BUFFER_MODE_SLAVE
|| jbuf->mode == RTP_JITTER_BUFFER_MODE_SYNCED)
&& media_clock_offset != -1 && jbuf->rfc7273_sync) {
......@@ -960,12 +883,12 @@ rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item,
/* All this assumes that the pipeline has enough additional
* latency to cover for the network delay */
if (rtpsystime > base_time)
item->pts = rtpsystime - base_time;
pts = rtpsystime - base_time;
else
item->pts = 0;
pts = 0;
GST_DEBUG ("RFC7273 clock time %" GST_TIME_FORMAT ", out %" GST_TIME_FORMAT,
GST_TIME_ARGS (rtpsystime), GST_TIME_ARGS (item->pts));
GST_TIME_ARGS (rtpsystime), GST_TIME_ARGS (pts));
} else {
/* If we used the RFC7273 clock before and not anymore,
* we need to resync it later again */
......@@ -973,39 +896,40 @@ rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item,
/* do skew calculation by measuring the difference between rtptime and the
* receive dts, this function will return the skew corrected rtptime. */
item->pts = calculate_skew (jbuf, ext_rtptime, gstrtptime, dts);
pts = calculate_skew (jbuf, ext_rtptime, gstrtptime, dts);
}
/* check if timestamps are not going backwards, we can only check this if we
* have a previous out time and a previous send_diff */
if (G_LIKELY (item->pts != -1 && jbuf->prev_out_time != -1
if (G_LIKELY (pts != -1 && jbuf->prev_out_time != -1
&& jbuf->prev_send_diff != -1)) {
/* now check for backwards timestamps */
if (G_UNLIKELY (
/* if the server timestamps went up and the out_time backwards */
(gstrtptime - jbuf->base_rtptime > jbuf->prev_send_diff
&& item->pts < jbuf->prev_out_time) ||
&& pts < jbuf->prev_out_time) ||
/* if the server timestamps went backwards and the out_time forwards */
(gstrtptime - jbuf->base_rtptime < jbuf->prev_send_diff
&& item->pts > jbuf->prev_out_time) ||
&& pts > jbuf->prev_out_time) ||
/* if the server timestamps did not change */
gstrtptime - jbuf->base_rtptime == jbuf->prev_send_diff)) {
GST_DEBUG ("backwards timestamps, using previous time");
item->pts = jbuf->prev_out_time;
pts = jbuf->prev_out_time;
}
}
if (dts != -1 && item->pts + jbuf->delay < dts) {
if (dts != -1 && pts + jbuf->delay < dts) {
/* if we are going to produce a timestamp that is later than the input
* timestamp, we need to reset the jitterbuffer. Likely the server paused
* temporarily */
GST_DEBUG ("out %" GST_TIME_FORMAT " + %" G_GUINT64_FORMAT " < time %"
GST_TIME_FORMAT ", reset jitterbuffer", GST_TIME_ARGS (item->pts),
GST_TIME_FORMAT ", reset jitterbuffer", GST_TIME_ARGS (pts),
jbuf->delay, GST_TIME_ARGS (dts));
rtp_jitter_buffer_resync (jbuf, dts, gstrtptime, ext_rtptime, TRUE);
item->pts = dts;
pts = dts;
}
jbuf->prev_out_time = item->pts;
jbuf->prev_out_time = pts;
jbuf->prev_send_diff = gstrtptime - jbuf->base_rtptime;
if (media_clock)
......@@ -1013,6 +937,83 @@ rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item,
if (pipeline_clock)
gst_object_unref (pipeline_clock);
return pts;
}
/**
* rtp_jitter_buffer_insert:
* @jbuf: an #RTPJitterBuffer
* @item: an #RTPJitterBufferItem to insert
* @head: TRUE when the head element changed.
* @percent: the buffering percent after insertion
*
* Inserts @item into the packet queue of @jbuf. The sequence number of the
* packet will be used to sort the packets. This function takes ownerhip of
* @buf when the function returns %TRUE.
*
* When @head is %TRUE, the new packet was added at the head of the queue and
* will be available with the next call to rtp_jitter_buffer_pop() and
* rtp_jitter_buffer_peek().
*
* Returns: %FALSE if a packet with the same number already existed.
*/
gboolean
rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item,
gboolean * head, gint * percent)
{
GList *list, *event = NULL;
guint16 seqnum;
g_return_val_if_fail (jbuf != NULL, FALSE);
g_return_val_if_fail (item != NULL, FALSE);
list = jbuf->packets->tail;
/* no seqnum, simply append then */
if (item->seqnum == -1)
goto append;
seqnum = item->seqnum;
/* loop the list to skip strictly larger seqnum buffers */
for (; list; list = g_list_previous (list)) {
guint16 qseq;
gint gap;
RTPJitterBufferItem *qitem = (RTPJitterBufferItem *) list;
if (qitem->seqnum == -1) {
/* keep a pointer to the first consecutive event if not already
* set. we will insert the packet after the event if we can't find
* a packet with lower sequence number before the event. */
if (event == NULL)
event = list;
continue;
}
qseq = qitem->seqnum;
/* compare the new seqnum to the one in the buffer */
gap = gst_rtp_buffer_compare_seqnum (seqnum, qseq);
/* we hit a packet with the same seqnum, notify a duplicate */
if (G_UNLIKELY (gap == 0))
goto duplicate;
/* seqnum > qseq, we can stop looking */
if (G_LIKELY (gap < 0))
break;
/* if we've found a packet with greater sequence number, cleanup the
* event pointer as the packet will be inserted before the event */
event = NULL;
}
/* if event is set it means that packets before the event had smaller
* sequence number, so we will insert our packet after the event */
if (event)
list = event;
append:
queue_do_insert (jbuf, list, (GList *) item);
......
......@@ -168,8 +168,7 @@ void rtp_jitter_buffer_reset_skew (RTPJitterBuffer *jbuf)
gboolean rtp_jitter_buffer_insert (RTPJitterBuffer *jbuf,
RTPJitterBufferItem *item,
gboolean *head, gint *percent,
GstClockTime base_time);
gboolean *head, gint *percent);
void rtp_jitter_buffer_disable_buffering (RTPJitterBuffer *jbuf, gboolean disabled);
......@@ -190,4 +189,7 @@ void rtp_jitter_buffer_get_sync (RTPJitterBuffer *jbuf,
guint64 *timestamp, guint32 *clock_rate,
guint64 *last_rtptime);
GstClockTime rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
guint32 rtptime, GstClockTime base_time);
#endif /* __RTP_JITTER_BUFFER_H__ */
......@@ -860,6 +860,148 @@ GST_START_TEST (test_num_late_when_considered_lost_arrives)
GST_END_TEST;
GST_START_TEST (test_lost_event_uses_pts)
{
GstHarness *h = gst_harness_new ("rtpjitterbuffer");
GstEvent *out_event;
GstClockTime now;
gint jb_latency_ms = 100;
gint i;
gst_harness_set_src_caps (h, generate_caps ());
g_object_set (h->element, "do-lost", TRUE, "latency", jb_latency_ms, NULL);
/* push the first buffer through */
fail_unless_equals_int (GST_FLOW_OK,
gst_harness_push (h, generate_test_buffer (0)));
fail_unless (gst_harness_crank_single_clock_wait (h));
gst_buffer_unref (gst_harness_pull (h));
/* push some buffers arriving in perfect time! */
for (i = 1; i <= 2; i++) {
fail_unless_equals_int (GST_FLOW_OK,
gst_harness_push (h, generate_test_buffer (i)));
gst_buffer_unref (gst_harness_pull (h));
}
/* hop over 1 packets (seqnum 3) and make another one (gap of 1), but due to
network delays, this packets is also grossly late */
i = 4;
/* advance the clock to the latest possible time buffer 4 could arrive */
now = i * PCMU_BUF_DURATION + jb_latency_ms * GST_MSECOND;
gst_harness_set_time (h, now);
gst_harness_push (h, generate_test_buffer_full (now, FALSE, i, i * PCMU_RTP_TS_DURATION));
/* drop GstEventStreamStart & GstEventCaps & GstEventSegment */
for (int i = 0; i < 3; i++)
gst_event_unref (gst_harness_pull_event (h));
/* we should now have received a packet-lost-event for buffer 3 */
out_event = gst_harness_pull_event (h);
verify_lost_event (out_event, 3, 3 * PCMU_BUF_DURATION, PCMU_BUF_DURATION);
/* and pull out packet 4 */
gst_buffer_unref (gst_harness_pull (h));
fail_unless (verify_jb_stats (h->element,
gst_structure_new ("application/x-rtp-jitterbuffer-stats",
"num-pushed", G_TYPE_UINT64, (guint64) 4,
"num-lost", G_TYPE_UINT64, (guint64) 1, NULL)));
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_lost_event_with_backwards_rtptime)
{
GstHarness *h = gst_harness_new ("rtpjitterbuffer");
GstEvent *out_event;
gint jb_latency_ms = 100;
gint i;
gst_harness_set_src_caps (h, generate_caps ());
g_object_set (h->element, "do-lost", TRUE, "latency", jb_latency_ms, NULL);
/* push the first buffer through */
fail_unless_equals_int (GST_FLOW_OK,
gst_harness_push (h, generate_test_buffer (0)));
fail_unless (gst_harness_crank_single_clock_wait (h));
gst_buffer_unref (gst_harness_pull (h));
/* push some buffers arriving in perfect time! */
for (i = 1; i <= 2; i++) {
fail_unless_equals_int (GST_FLOW_OK,
gst_harness_push (h, generate_test_buffer (i)));
gst_buffer_unref (gst_harness_pull (h));
}
/*
For video using B-frames, an expected sequence
could be like this:
(I = I-frame, P = P-frame, B = B-frame)
___ ___ ___ ___ ___
... | 3 | | 4 | | 5 | | 6 | | 7 |
––– ––– ––– ––– –––
rtptime: 3(I) 5(P) 5(P) 4(B) 6(P)
arrival(dts): 3 5 5 5 6
Notice here that packet 6 (the B frame) make
the rtptime go backwards.
But we get this:
___ ___ _ _ ___ ___
... | 3 | | 4 | | | | 6 | | 7 |
––– ––– - - ––– –––
rtptime: 3(I) 5(P) 4(B) 6(P)
arrival(dts): 3 5 5 6
*/
/* seqnum 3 */
fail_unless_equals_int (GST_FLOW_OK,
gst_harness_push (h, generate_test_buffer (3)));
gst_buffer_unref (gst_harness_pull (h));
/* seqnum 4, arriving at time 5 with rtptime 5 */
gst_harness_push (h, generate_test_buffer_full (
5 * PCMU_BUF_DURATION, FALSE, 4, 5 * PCMU_RTP_TS_DURATION));
gst_buffer_unref (gst_harness_pull (h));
/* seqnum 6, arriving at time 5 with rtptime 4,
making a gap for missing seqnum 5 */
gst_harness_push (h, generate_test_buffer_full (
5 * PCMU_BUF_DURATION, FALSE, 6, 4 * PCMU_RTP_TS_DURATION));
/* seqnum 7, arriving at time 6 with rtptime 6 */
gst_harness_push (h, generate_test_buffer_full (
6 * PCMU_BUF_DURATION, FALSE, 7, 6 * PCMU_RTP_TS_DURATION));
/* drop GstEventStreamStart & GstEventCaps & GstEventSegment */
for (int i = 0; i < 3; i++)
gst_event_unref (gst_harness_pull_event (h));
/* we should now have received a packet-lost-event for seqnum 5,
with time 5 and 0 duration */
gst_harness_crank_single_clock_wait (h);
out_event = gst_harness_pull_event (h);
verify_lost_event (out_event, 5, 5 * PCMU_BUF_DURATION, 0);
/* and pull out 6 and 7 */
gst_buffer_unref (gst_harness_pull (h));
gst_buffer_unref (gst_harness_pull (h));
fail_unless (verify_jb_stats (h->element,
gst_structure_new ("application/x-rtp-jitterbuffer-stats",
"num-pushed", G_TYPE_UINT64, (guint64) 7,
"num-lost", G_TYPE_UINT64, (guint64) 1, NULL)));
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_all_packets_are_timestamped_zero)
{
GstHarness *h = gst_harness_new ("rtpjitterbuffer");
......@@ -1228,110 +1370,6 @@ GST_START_TEST (test_rtx_two_missing)
GST_END_TEST;
GST_START_TEST (text_rtx_two_missing_early)
{
GstHarness *h = gst_harness_new ("rtpjitterbuffer");
GstTestClock *testclock;
gint latency_ms = 30;
GstClockTime last_rtx_request, now;
testclock = gst_harness_get_testclock (h);
gst_harness_set_src_caps (h, generate_caps ());
g_object_set (h->element, "do-retransmission", TRUE, "latency", latency_ms,
NULL);
for (gint i = 0; i <= latency_ms / PCMU_BUF_MS; i++) {
gst_test_clock_set_time (testclock, i * PCMU_BUF_DURATION);
fail_unless_equals_int (GST_FLOW_OK,
gst_harness_push (h, generate_test_buffer (i)));
gst_harness_wait_for_clock_id_waits (h, 1, 60);
}
gst_harness_crank_single_clock_wait (h);
fail_unless_equals_int64 (latency_ms * GST_MSECOND,
gst_clock_get_time (GST_CLOCK (testclock)));
for (gint i = 0; i <= latency_ms / PCMU_BUF_MS; i++)
gst_buffer_unref (gst_harness_pull (h));
/* drop reconfigure event */
gst_event_unref (gst_harness_pull_upstream_event (h));
/*
The expected sequence of buffers is this:
___ ___ ___ ___ ___
| 0 | | 1 | | 2 | | 3 | | 4 |
––– ––– ––– ––– –––
0ms 20ms 40ms 60ms 80ms
But instead we get this:
___ ___ _ _ _ _ ___
| 0 | | 1 | | | | | | 4 |
––– ––– – – – – –––
0ms 20ms 41ms
*/
now = 41 * GST_MSECOND;
gst_harness_set_time (h, now);
fail_unless_equals_int (GST_FLOW_OK,
gst_harness_push (h,
generate_test_buffer_full (now, TRUE, 4, 4 * PCMU_RTP_TS_DURATION)));
/*
With the now calculated packet-spacing of (41-20) / 3 = 7,
giving us these expected times:
___ ___ ___ ___ ___
| 0 | | 1 | | 2 | | 3 | | 4 |
––– ––– ––– ––– –––
0ms 20ms 27ms 34ms 41ms
For RTX, the inital RTX-timeouts for the missing buffers are
the expected arrival time + half the packet-spacing time, like this:
___ ___
| 2 | | 3 |
––– –––
50ms 70ms
But since we have re-calculated the estimated arrival-time
of these buffers, we have to adjust the RTX timeout as well,
and we use the original delay (packet-spacing / 2) = 10ms,
and add it on:
___ ___
| 2 | | 3 |
––– –––
37ms 44ms
Also note that the first RTX request is now scheduled for a
time that is prior to NOW (37ms < 41ms), so it will be sent straight
away without us needing to "crank" the timer-thread
*/
/* The RTX request for packet 2 has timestamp 27ms and delay 10ms */
verify_rtx_event (gst_harness_pull_upstream_event (h),
2, 27 * GST_MSECOND, 10, PCMU_BUF_DURATION);
/* and is sent immediately after packet 4 arrives (41ms) */
last_rtx_request = gst_clock_get_time (GST_CLOCK (testclock));
fail_unless_equals_int64 (last_rtx_request, now);
/* crank the timer thread */
gst_harness_crank_single_clock_wait (h);
/* The RTX request for packet 3 has timestamp 34ms and delay 10ms */
verify_rtx_event (gst_harness_pull_upstream_event (h),
3, 34 * GST_MSECOND, 10, PCMU_BUF_DURATION);
/* and is sent at 44ms */
last_rtx_request = gst_clock_get_time (GST_CLOCK (testclock));
fail_unless_equals_int64 (last_rtx_request, 44 * GST_MSECOND);
gst_object_unref (testclock);
gst_harness_teardown (h);
}
GST_END_TEST;
GST_START_TEST (test_rtx_packet_delay)
{
GstHarness *h = gst_harness_new ("rtpjitterbuffer");
......@@ -1365,9 +1403,10 @@ GST_START_TEST (test_rtx_packet_delay)
* the estimate for 2 could be refined now to 20ms. also packet 2, 3 and 4
* are exceeding the max allowed reorder distance and should request a
* retransmission right away */
gst_harness_set_time (h, 1 * PCMU_BUF_DURATION);
fail_unless_equals_int (GST_FLOW_OK,
gst_harness_push (h, generate_test_buffer_full (20 * GST_MSECOND, TRUE, 8,
8 * PCMU_RTP_TS_DURATION)));
gst_harness_push (h, generate_test_buffer_full (1 * PCMU_BUF_DURATION, TRUE, 8,
1 * PCMU_RTP_TS_DURATION)));
/* drop reconfigure event */
gst_event_unref (gst_harness_pull_upstream_event (h));
......@@ -1377,23 +1416,23 @@ GST_START_TEST (test_rtx_packet_delay)
/* we should now receive retransmission requests for 2 -> 5 */
out_event = gst_harness_pull_upstream_event (h);
verify_rtx_event (out_event, 2, 20 * GST_MSECOND, 17, PCMU_BUF_DURATION);
verify_rtx_event (out_event, 2, 20 * GST_MSECOND, 10, PCMU_BUF_DURATION);
for (i = 3; i < 5; i++) {
GST_DEBUG ("popping %d", i);
out_event = gst_harness_pull_upstream_event (h);
verify_rtx_event (out_event, i, 20 * GST_MSECOND, 17, PCMU_BUF_DURATION);
verify_rtx_event (out_event, i, 20 * GST_MSECOND, 10, PCMU_BUF_DURATION);
}
fail_unless_equals_int (0, gst_harness_upstream_events_in_queue (h));
/* push 9, this should immediately request retransmission of 5 */
fail_unless_equals_int (GST_FLOW_OK,
gst_harness_push (h, generate_test_buffer_full (20 * GST_MSECOND, TRUE, 9,
9 * PCMU_RTP_TS_DURATION)));
gst_harness_push (h, generate_test_buffer_full (1 * PCMU_BUF_DURATION, TRUE, 9,
1 * PCMU_RTP_TS_DURATION)));
/* we should now receive retransmission requests for 5 */
out_event = gst_harness_pull_upstream_event (h);
verify_rtx_event (out_event, 5, 20 * GST_MSECOND, 17, PCMU_BUF_DURATION);
verify_rtx_event (out_event, 5, 20 * GST_MSECOND, 10, PCMU_BUF_DURATION);
/* wait for timeout for rtx 6 -> 7 */
gst_test_clock_set_time_and_process (testclock, 60 * GST_MSECOND);
......@@ -1401,7 +1440,7 @@ GST_START_TEST (test_rtx_packet_delay)
for (i = 6; i < 8; i++) {
GST_DEBUG ("popping %d", i);
out_event = gst_harness_pull_upstream_event (h);
verify_rtx_event (out_event, i, 20 * GST_MSECOND, 17, PCMU_BUF_DURATION);
verify_rtx_event (out_event, i, 20 * GST_MSECOND, 10, PCMU_BUF_DURATION);
}
/* churn through 7 sync_times until the new buffer gets pushed out */
......@@ -2137,6 +2176,83 @@ GST_START_TEST (test_rtx_same_delay_and_retry_timeout)
GST_END_TEST;
GST_START_TEST (test_rtx_with_backwards_rtptime)
{
GstHarness *h = gst_harness_new ("rtpjitterbuffer");
GstEvent *event;
gint jb_latency_ms = 40;
gint i;
gst_harness_set_src_caps (h, generate_caps ());
g_object_set (h->element,
"do-retransmission", TRUE,
"latency", jb_latency_ms,
NULL);