gstreamer/tests/check/elements/rtptimerqueue.c
Havard Graff 63c7a9ae43 rtpjitterbuffer: don't send multiple instant RTX for the same packet
Due to us not properly acknowleding the time when the last RTX was sent
when scheduling a new one, it can easily happen that due to the packet
you are requesting have a PTS that is slightly old (but not too old when
adding the latency of the jitterbuffer), both its calculated second and
third (etc.) timeout could already have passed. This would lead to a burst
of RTX requests, which acts completely against its purpose, potentially
spending a lot more bandwidth than needed.

This has been properly reproduced in the test:
test_rtx_not_bursting_requests

The good news is that slightly re-thinking the logic concerning
re-requesting RTX, made it a lot simpler to understand, and allows us
to remove two members of the RtpTimer which no longer serves any purpose
due to the refactoring. If desirable the whole "delay" concept can actually
be removed completely from the timers, and simply just added to the timeout
by the caller of the API. But that can be a change for a another time.

The only external change (other than the improved behavior around bursting
RTX) is that the "delay" field now stricly represents the delay between
the PTS of the RTX-requested packet and the time it is requested on,
whereas before this calculation was more about the theoretical calculated
delay. This is visible in three other RTX-tests where the delay had
to be adjusted slightly. I am confident however that this change is
correct.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/789>
2020-10-28 01:22:24 +01:00

326 lines
10 KiB
C

/* GStreamer
*
* Copyright (C) 2019 Net Insight AB
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <gst/check/gstcheck.h>
#include "gst/rtpmanager/rtptimerqueue.h"
GST_START_TEST (test_timer_queue_set_timer)
{
RtpTimerQueue *queue = rtp_timer_queue_new ();
RtpTimer *timer10, *timer0;
rtp_timer_queue_set_timer (queue, RTP_TIMER_EXPECTED, 10,
1 * GST_SECOND, 2 * GST_SECOND, 5 * GST_SECOND, 0);
timer10 = rtp_timer_queue_find (queue, 10);
fail_unless (timer10);
fail_unless_equals_int (10, timer10->seqnum);
fail_unless_equals_int (RTP_TIMER_EXPECTED, timer10->type);
/* timer10->timeout = timerout + delay */
fail_unless_equals_uint64 (3 * GST_SECOND, timer10->timeout);
fail_unless_equals_uint64 (5 * GST_SECOND, timer10->duration);
fail_unless_equals_uint64 (1 * GST_SECOND, timer10->rtx_base);
fail_unless_equals_uint64 (GST_CLOCK_TIME_NONE, timer10->rtx_last);
fail_unless_equals_int (0, timer10->num_rtx_retry);
fail_unless_equals_int (0, timer10->num_rtx_received);
rtp_timer_queue_set_timer (queue, RTP_TIMER_LOST, 0,
0 * GST_SECOND, 2 * GST_SECOND, 0, 0);
timer0 = rtp_timer_queue_find (queue, 0);
fail_unless (timer0);
fail_unless_equals_int (0, timer0->seqnum);
fail_unless_equals_int (RTP_TIMER_LOST, timer0->type);
fail_unless_equals_uint64 (2 * GST_SECOND, timer0->timeout);
fail_unless_equals_uint64 (0, timer0->duration);
fail_unless_equals_uint64 (0, timer0->rtx_base);
fail_unless_equals_uint64 (GST_CLOCK_TIME_NONE, timer0->rtx_last);
fail_unless_equals_int (0, timer0->num_rtx_retry);
fail_unless_equals_int (0, timer0->num_rtx_received);
/* also check order while at it */
fail_unless (timer10->list.next == NULL);
fail_unless (timer10->list.prev == (GList *) timer0);
fail_unless (timer0->list.next == (GList *) timer10);
fail_unless (timer0->list.prev == NULL);
g_object_unref (queue);
}
GST_END_TEST;
GST_START_TEST (test_timer_queue_insert_head)
{
RtpTimerQueue *queue = rtp_timer_queue_new ();
RtpTimer *timer, *next, *prev;
rtp_timer_queue_set_deadline (queue, 1, -1, 0);
rtp_timer_queue_set_deadline (queue, 3, -1, 0);
rtp_timer_queue_set_deadline (queue, 2, -1, 0);
rtp_timer_queue_set_deadline (queue, 0, -1, 0);
timer = rtp_timer_queue_find (queue, 0);
fail_if (timer == NULL);
fail_unless_equals_int (0, timer->seqnum);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_unless (prev == NULL);
fail_if (next == NULL);
fail_unless_equals_int (1, next->seqnum);
timer = rtp_timer_queue_find (queue, 3);
fail_if (timer == NULL);
fail_unless_equals_int (3, timer->seqnum);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_if (prev == NULL);
fail_unless_equals_int (2, prev->seqnum);
fail_unless (next == NULL);
timer = rtp_timer_queue_find (queue, 2);
fail_if (timer == NULL);
fail_unless_equals_int (2, timer->seqnum);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_if (prev == NULL);
fail_if (next == NULL);
fail_unless_equals_int (1, prev->seqnum);
fail_unless_equals_int (3, next->seqnum);
timer = rtp_timer_queue_find (queue, 1);
fail_if (timer == NULL);
fail_unless_equals_int (1, timer->seqnum);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_if (prev == NULL);
fail_if (next == NULL);
fail_unless_equals_int (0, prev->seqnum);
fail_unless_equals_int (2, next->seqnum);
g_object_unref (queue);
}
GST_END_TEST;
GST_START_TEST (test_timer_queue_reschedule)
{
RtpTimerQueue *queue = rtp_timer_queue_new ();
RtpTimer *timer, *next, *prev;
rtp_timer_queue_set_deadline (queue, 3, 1 * GST_SECOND, 0);
rtp_timer_queue_set_deadline (queue, 1, 2 * GST_SECOND, 0);
rtp_timer_queue_set_deadline (queue, 2, 3 * GST_SECOND, 0);
rtp_timer_queue_set_deadline (queue, 0, 4 * GST_SECOND, 0);
timer = rtp_timer_queue_find (queue, 1);
fail_if (timer == NULL);
/* move to head, making sure seqnum order is respected */
rtp_timer_queue_set_deadline (queue, 1, 1 * GST_SECOND, 0);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_unless (prev == NULL);
fail_if (next == NULL);
fail_unless_equals_int (3, next->seqnum);
/* move head back */
rtp_timer_queue_set_deadline (queue, 1, 2 * GST_SECOND, 0);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_if (prev == NULL);
fail_if (next == NULL);
fail_unless_equals_int (3, prev->seqnum);
fail_unless_equals_int (2, next->seqnum);
/* move to tail */
timer = rtp_timer_queue_find (queue, 2);
fail_if (timer == NULL);
rtp_timer_queue_set_deadline (queue, 2, 4 * GST_SECOND, 0);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_if (prev == NULL);
fail_unless (next == NULL);
fail_unless_equals_int (0, prev->seqnum);
/* move tail back */
rtp_timer_queue_set_deadline (queue, 2, 3 * GST_SECOND, 0);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_if (prev == NULL);
fail_if (next == NULL);
fail_unless_equals_int (1, prev->seqnum);
fail_unless_equals_int (0, next->seqnum);
/* not moving toward head */
rtp_timer_queue_set_deadline (queue, 2, 2 * GST_SECOND, 0);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_if (prev == NULL);
fail_if (next == NULL);
fail_unless_equals_int (1, prev->seqnum);
fail_unless_equals_int (0, next->seqnum);
/* not moving toward tail */
rtp_timer_queue_set_deadline (queue, 2, 3 * GST_SECOND, 0);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_if (prev == NULL);
fail_if (next == NULL);
fail_unless_equals_int (1, prev->seqnum);
fail_unless_equals_int (0, next->seqnum);
/* inner move toward head */
rtp_timer_queue_set_deadline (queue, 2, GST_SECOND + GST_SECOND / 2, 0);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_if (prev == NULL);
fail_if (next == NULL);
fail_unless_equals_int (3, prev->seqnum);
fail_unless_equals_int (1, next->seqnum);
/* inner move toward tail */
rtp_timer_queue_set_deadline (queue, 2, 3 * GST_SECOND, 0);
next = (RtpTimer *) timer->list.next;
prev = (RtpTimer *) timer->list.prev;
fail_if (prev == NULL);
fail_if (next == NULL);
fail_unless_equals_int (1, prev->seqnum);
fail_unless_equals_int (0, next->seqnum);
g_object_unref (queue);
}
GST_END_TEST;
GST_START_TEST (test_timer_queue_pop_until)
{
RtpTimerQueue *queue = rtp_timer_queue_new ();
RtpTimer *timer;
rtp_timer_queue_set_deadline (queue, 2, 2 * GST_SECOND, 0);
rtp_timer_queue_set_deadline (queue, 1, 1 * GST_SECOND, 0);
rtp_timer_queue_set_deadline (queue, 0, -1, 0);
timer = rtp_timer_queue_pop_until (queue, 1 * GST_SECOND);
fail_if (timer == NULL);
fail_unless_equals_int (0, timer->seqnum);
rtp_timer_free (timer);
timer = rtp_timer_queue_pop_until (queue, 1 * GST_SECOND);
fail_if (timer == NULL);
fail_unless_equals_int (1, timer->seqnum);
rtp_timer_free (timer);
timer = rtp_timer_queue_pop_until (queue, 1 * GST_SECOND);
fail_unless (timer == NULL);
g_object_unref (queue);
}
GST_END_TEST;
GST_START_TEST (test_timer_queue_update_timer_seqnum)
{
RtpTimerQueue *queue = rtp_timer_queue_new ();
RtpTimer *timer;
rtp_timer_queue_set_deadline (queue, 2, 2 * GST_SECOND, 0);
timer = rtp_timer_queue_find (queue, 2);
fail_if (timer == NULL);
rtp_timer_queue_update_timer (queue, timer, 3, 3 * GST_SECOND, 0, 0, FALSE);
timer = rtp_timer_queue_find (queue, 2);
fail_unless (timer == NULL);
timer = rtp_timer_queue_find (queue, 3);
fail_if (timer == NULL);
fail_unless_equals_int (1, rtp_timer_queue_length (queue));
g_object_unref (queue);
}
GST_END_TEST;
GST_START_TEST (test_timer_queue_dup_timer)
{
RtpTimerQueue *queue = rtp_timer_queue_new ();
RtpTimer *timer;
rtp_timer_queue_set_deadline (queue, 2, 2 * GST_SECOND, 0);
timer = rtp_timer_queue_find (queue, 2);
fail_if (timer == NULL);
timer = rtp_timer_dup (timer);
timer->seqnum = 3;
rtp_timer_queue_insert (queue, timer);
fail_unless_equals_int (2, rtp_timer_queue_length (queue));
g_object_unref (queue);
}
GST_END_TEST;
GST_START_TEST (test_timer_queue_timer_offset)
{
RtpTimerQueue *queue = rtp_timer_queue_new ();
RtpTimer *timer;
rtp_timer_queue_set_timer (queue, RTP_TIMER_EXPECTED, 2, 2 * GST_SECOND,
GST_MSECOND, 0, GST_USECOND);
timer = rtp_timer_queue_find (queue, 2);
fail_if (timer == NULL);
fail_unless_equals_uint64 (2 * GST_SECOND + GST_MSECOND + GST_USECOND,
timer->timeout);
fail_unless_equals_int64 (GST_USECOND, timer->offset);
rtp_timer_queue_update_timer (queue, timer, 2, 3 * GST_SECOND,
2 * GST_MSECOND, 2 * GST_USECOND, FALSE);
fail_unless_equals_uint64 (3 * GST_SECOND + 2 * GST_MSECOND +
2 * GST_USECOND, timer->timeout);
fail_unless_equals_int64 (2 * GST_USECOND, timer->offset);
g_object_unref (queue);
}
GST_END_TEST;
static Suite *
rtptimerqueue_suite (void)
{
Suite *s = suite_create ("rtptimerqueue");
TCase *tc_chain = tcase_create ("general");
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_timer_queue_set_timer);
tcase_add_test (tc_chain, test_timer_queue_insert_head);
tcase_add_test (tc_chain, test_timer_queue_reschedule);
tcase_add_test (tc_chain, test_timer_queue_pop_until);
tcase_add_test (tc_chain, test_timer_queue_update_timer_seqnum);
tcase_add_test (tc_chain, test_timer_queue_dup_timer);
tcase_add_test (tc_chain, test_timer_queue_timer_offset);
return s;
}
GST_CHECK_MAIN (rtptimerqueue);