mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 02:30:35 +00:00
4c482befbd
The Onvif Streaming Specification specifies that the NTP timestamps in the Onvif extension header indicaes the absolute UTC time associated with the access unit. But by using running time we can not achieve that, since a frame's running time depends on the played interval, whether a non-flushing is done, etc. Instead we have to use the stream time. https://bugzilla.gnome.org/show_bug.cgi?id=757688
786 lines
25 KiB
C
786 lines
25 KiB
C
/*
|
|
* onviftimestamp.c
|
|
*
|
|
* Copyright (C) 2014 Axis Communications AB
|
|
* Author: Guillaume Desmottes <guillaume.desmottes@collabora.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <gst/check/gstcheck.h>
|
|
#include <gst/rtp/gstrtpbuffer.h>
|
|
|
|
/* For ease of programming we use globals to keep refs for our floating
|
|
* src and sink pads we create; otherwise we always have to do get_pad,
|
|
* get_peer, and then remove references in every test function */
|
|
static GstElement *element;
|
|
static GstPad *mysrcpad;
|
|
static GstPad *mysinkpad;
|
|
/* These are global mainly because they are used from the setup/cleanup
|
|
* fixture functions */
|
|
static gulong myprobe;
|
|
static GList *mypushedevents;
|
|
static GList *myreceivedevents;
|
|
|
|
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/x-rtp")
|
|
);
|
|
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/x-rtp")
|
|
);
|
|
|
|
#define NTP_OFFSET ((guint64) 1245)
|
|
#define TIMESTAMP ((GstClockTime)42)
|
|
#define CSEQ 0x78
|
|
#define COMPARE TRUE
|
|
#define NO_COMPARE FALSE
|
|
|
|
static GstPadProbeReturn
|
|
event_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
|
|
{
|
|
GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
|
|
|
|
GST_INFO ("got %" GST_PTR_FORMAT, event);
|
|
myreceivedevents = g_list_append (myreceivedevents, gst_event_ref (event));
|
|
|
|
return GST_PAD_PROBE_OK;
|
|
}
|
|
|
|
static GstEvent *
|
|
create_ntp_offset_event (GstClockTime ntp_offset, gboolean discont)
|
|
{
|
|
GstStructure *structure;
|
|
|
|
structure = gst_structure_new ("GstNtpOffset", "ntp-offset", G_TYPE_UINT64,
|
|
ntp_offset, "discont", G_TYPE_BOOLEAN, discont, NULL);
|
|
|
|
return gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, structure);
|
|
}
|
|
|
|
static GstEvent *
|
|
create_event (GstEventType type)
|
|
{
|
|
GstEvent *event = NULL;
|
|
|
|
switch (type) {
|
|
case GST_EVENT_CUSTOM_DOWNSTREAM:
|
|
event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
|
|
gst_structure_new ("x-app/test", "test-field", G_TYPE_STRING,
|
|
"test-value", NULL));
|
|
break;
|
|
case GST_EVENT_CUSTOM_DOWNSTREAM_OOB:
|
|
event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB,
|
|
gst_structure_new ("x-app/test", "test-field", G_TYPE_STRING,
|
|
"test-value", NULL));
|
|
break;
|
|
case GST_EVENT_EOS:
|
|
event = gst_event_new_eos ();
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
return event;
|
|
}
|
|
|
|
static void
|
|
create_and_push_event (GstEventType type)
|
|
{
|
|
GstEvent *event = create_event (type);
|
|
|
|
mypushedevents = g_list_append (mypushedevents, event);
|
|
fail_unless (gst_pad_push_event (mysrcpad, event));
|
|
}
|
|
|
|
static void
|
|
check_and_clear_events (gint expected, gboolean compare)
|
|
{
|
|
GList *p;
|
|
GList *r;
|
|
|
|
/* verify that there's as many queued events as expected */
|
|
fail_unless_equals_int (g_list_length (myreceivedevents), expected);
|
|
|
|
if (compare) {
|
|
fail_unless_equals_int (expected, g_list_length (mypushedevents));
|
|
|
|
/* verify that the events are queued in the expected order */
|
|
r = myreceivedevents;
|
|
p = mypushedevents;
|
|
|
|
while (p != NULL) {
|
|
fail_unless_equals_pointer (p->data, r->data);
|
|
p = g_list_next (p);
|
|
r = g_list_next (r);
|
|
}
|
|
}
|
|
|
|
g_list_free_full (myreceivedevents, (GDestroyNotify)gst_event_unref);
|
|
myreceivedevents = NULL;
|
|
g_list_free (mypushedevents);
|
|
mypushedevents = NULL;
|
|
}
|
|
|
|
static void
|
|
setup (void)
|
|
{
|
|
element = gst_check_setup_element ("rtponviftimestamp");
|
|
|
|
mysinkpad = gst_check_setup_sink_pad (element, &sinktemplate);
|
|
gst_pad_set_active (mysinkpad, TRUE);
|
|
|
|
mysrcpad = gst_check_setup_src_pad (element, &srctemplate);
|
|
gst_pad_set_active (mysrcpad, TRUE);
|
|
}
|
|
|
|
static void
|
|
cleanup (void)
|
|
{
|
|
gst_check_drop_buffers ();
|
|
|
|
gst_pad_set_active (mysrcpad, FALSE);
|
|
gst_check_teardown_src_pad (element);
|
|
mysrcpad = NULL;
|
|
|
|
gst_pad_set_active (mysinkpad, FALSE);
|
|
gst_check_teardown_sink_pad (element);
|
|
mysinkpad = NULL;
|
|
|
|
gst_check_teardown_element (element);
|
|
element = NULL;
|
|
|
|
gst_check_drop_buffers ();
|
|
}
|
|
|
|
static void
|
|
setup_with_event (void)
|
|
{
|
|
setup ();
|
|
|
|
myprobe = gst_pad_add_probe (mysinkpad,
|
|
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, event_probe, NULL, NULL);
|
|
myreceivedevents = NULL;
|
|
mypushedevents = NULL;
|
|
}
|
|
|
|
static void
|
|
cleanup_with_event (void)
|
|
{
|
|
gst_pad_remove_probe (mysinkpad, myprobe);
|
|
myprobe = 0;
|
|
myreceivedevents = NULL;
|
|
mypushedevents = NULL;
|
|
|
|
cleanup ();
|
|
}
|
|
|
|
static void
|
|
check_buffer_equal (GstBuffer * buf, GstBuffer * expected)
|
|
{
|
|
GstMapInfo info_buf, info_expected;
|
|
|
|
fail_if (buf == NULL);
|
|
fail_if (expected == NULL);
|
|
|
|
fail_unless (gst_buffer_map (buf, &info_buf, GST_MAP_READ));
|
|
fail_unless (gst_buffer_map (expected, &info_expected, GST_MAP_READ));
|
|
|
|
GST_LOG ("buffer: size %" G_GSIZE_FORMAT, info_buf.size);
|
|
GST_LOG ("expected: size %" G_GSIZE_FORMAT, info_expected.size);
|
|
GST_MEMDUMP ("buffer", info_buf.data, info_buf.size);
|
|
GST_MEMDUMP ("expected", info_expected.data, info_expected.size);
|
|
|
|
fail_unless_equals_uint64 (info_buf.size, info_expected.size);
|
|
fail_unless_equals_int (memcmp (info_buf.data, info_expected.data,
|
|
info_buf.size), 0);
|
|
|
|
gst_buffer_unmap (buf, &info_buf);
|
|
gst_buffer_unmap (expected, &info_expected);
|
|
}
|
|
|
|
/* Create a RTP buffer without the extension */
|
|
static GstBuffer *
|
|
create_rtp_buffer (GstClockTime timestamp, gboolean clean_point)
|
|
{
|
|
GstBuffer *buffer_in;
|
|
GstRTPBuffer rtpbuffer_in = GST_RTP_BUFFER_INIT;
|
|
|
|
buffer_in = gst_rtp_buffer_new_allocate (0, 0, 0);
|
|
GST_BUFFER_PTS (buffer_in) = timestamp;
|
|
|
|
if (!clean_point)
|
|
GST_BUFFER_FLAG_SET (buffer_in, GST_BUFFER_FLAG_DELTA_UNIT);
|
|
|
|
fail_unless (gst_rtp_buffer_map (buffer_in, GST_MAP_READ, &rtpbuffer_in));
|
|
fail_if (gst_rtp_buffer_get_extension (&rtpbuffer_in));
|
|
gst_rtp_buffer_unmap (&rtpbuffer_in);
|
|
|
|
return buffer_in;
|
|
}
|
|
|
|
static guint64
|
|
convert_to_ntp (GstClockTime t)
|
|
{
|
|
guint64 ntptime;
|
|
|
|
/* convert to NTP time. upper 32 bits should contain the seconds
|
|
* and the lower 32 bits, the fractions of a second. */
|
|
ntptime = gst_util_uint64_scale (t, (G_GINT64_CONSTANT (1) << 32),
|
|
GST_SECOND);
|
|
|
|
return ntptime;
|
|
}
|
|
|
|
/* Create a copy of @buffer_in having the RTP extension */
|
|
static GstBuffer *
|
|
create_extension_buffer (GstBuffer * buffer_in, gboolean clean_point,
|
|
gboolean end_contiguous, gboolean discont, guint64 ntp_offset, guint8 cseq)
|
|
{
|
|
GstBuffer *buffer_out;
|
|
GstRTPBuffer rtpbuffer_out = GST_RTP_BUFFER_INIT;
|
|
guint8 *data;
|
|
guint8 flags = 0;
|
|
|
|
buffer_out = gst_buffer_copy (buffer_in);
|
|
|
|
fail_unless (gst_rtp_buffer_map (buffer_out, GST_MAP_READWRITE,
|
|
&rtpbuffer_out));
|
|
|
|
/* extension */
|
|
fail_unless (gst_rtp_buffer_set_extension_data (&rtpbuffer_out, 0xABAC, 3));
|
|
fail_unless (gst_rtp_buffer_get_extension (&rtpbuffer_out));
|
|
fail_unless (gst_rtp_buffer_get_extension_data (&rtpbuffer_out, NULL,
|
|
(gpointer) & data, NULL));
|
|
|
|
/* NTP timestamp */
|
|
GST_WRITE_UINT64_BE (data, convert_to_ntp (GST_BUFFER_PTS (buffer_in) +
|
|
ntp_offset));
|
|
|
|
/* C E D mbz */
|
|
if (clean_point)
|
|
flags |= (1 << 7);
|
|
if (end_contiguous)
|
|
flags |= (1 << 6);
|
|
if (discont)
|
|
flags |= (1 << 5);
|
|
|
|
GST_WRITE_UINT8 (data + 8, flags);
|
|
|
|
/* CSeq */
|
|
GST_WRITE_UINT8 (data + 9, cseq);
|
|
|
|
memset (data + 10, 0, 4);
|
|
|
|
gst_rtp_buffer_unmap (&rtpbuffer_out);
|
|
|
|
return buffer_out;
|
|
}
|
|
|
|
static void
|
|
do_one_buffer_test_apply (gboolean clean_point)
|
|
{
|
|
GstBuffer *buffer_in, *buffer_out;
|
|
|
|
g_object_set (element, "ntp-offset", NTP_OFFSET, "cseq", 0x12345678,
|
|
"set-e-bit", FALSE, NULL);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
|
|
buffer_in = create_rtp_buffer (TIMESTAMP, clean_point);
|
|
buffer_out = create_extension_buffer (buffer_in, clean_point, FALSE, FALSE,
|
|
NTP_OFFSET, CSEQ);
|
|
|
|
/* push initial events */
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
|
|
/* Push buffer */
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer_in), GST_FLOW_OK);
|
|
|
|
check_buffer_equal ((GstBuffer *) buffers->data, buffer_out);
|
|
gst_buffer_unref (buffer_out);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_NULL, GST_STATE_CHANGE_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
do_two_buffers_test_apply (gboolean end_contiguous)
|
|
{
|
|
GstBuffer *buffer_in, *buffer_out;
|
|
GList *node;
|
|
|
|
g_object_set (element, "ntp-offset", NTP_OFFSET, "cseq", 0x12345678,
|
|
"set-e-bit", TRUE, NULL);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
|
|
buffer_in = create_rtp_buffer (TIMESTAMP, FALSE);
|
|
buffer_out = create_extension_buffer (buffer_in, FALSE, end_contiguous,
|
|
FALSE, NTP_OFFSET, CSEQ);
|
|
|
|
/* push initial events */
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
|
|
/* Push buffer */
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer_in), GST_FLOW_OK);
|
|
|
|
/* The buffer hasn't been pushed it as the element is waiting for the next
|
|
* buffer. */
|
|
fail_unless_equals_int (g_list_length (buffers), 0);
|
|
|
|
/* push an ntp-offset event to trigger a discontinuty */
|
|
fail_unless (gst_pad_push_event (mysrcpad,
|
|
create_ntp_offset_event (NTP_OFFSET, end_contiguous)));
|
|
|
|
/* A second buffer is pushed */
|
|
buffer_in = create_rtp_buffer (TIMESTAMP + 1, FALSE);
|
|
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer_in), GST_FLOW_OK);
|
|
|
|
/* The first buffer has now been pushed out */
|
|
fail_unless_equals_int (g_list_length (buffers), 1);
|
|
|
|
node = g_list_last (buffers);
|
|
check_buffer_equal ((GstBuffer *) node->data, buffer_out);
|
|
gst_buffer_unref (buffer_out);
|
|
|
|
/* Push EOS */
|
|
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()));
|
|
|
|
/* The second buffer has been pushed out */
|
|
fail_unless_equals_int (g_list_length (buffers), 2);
|
|
|
|
/* Last buffer always has the 'E' flag */
|
|
buffer_out = create_extension_buffer (buffer_in, FALSE, TRUE, end_contiguous,
|
|
NTP_OFFSET, CSEQ);
|
|
node = g_list_last (buffers);
|
|
check_buffer_equal ((GstBuffer *) node->data, buffer_out);
|
|
gst_buffer_unref (buffer_out);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_NULL, GST_STATE_CHANGE_SUCCESS);
|
|
}
|
|
|
|
GST_START_TEST (test_apply_clean_point)
|
|
{
|
|
do_one_buffer_test_apply (TRUE);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_apply_no_e_bit)
|
|
{
|
|
do_two_buffers_test_apply (FALSE);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_apply_e_bit)
|
|
{
|
|
do_two_buffers_test_apply (TRUE);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_flushing)
|
|
{
|
|
GstBuffer *buffer;
|
|
|
|
/* set the e-bit, so the element use caching */
|
|
g_object_set (element, "set-e-bit", TRUE, NULL);
|
|
/* set the ntp-offset, since no one will provide a clock */
|
|
g_object_set (element, "ntp-offset", NTP_OFFSET, NULL);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
|
|
/* create and push the first buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
/* no buffers should have made it through */
|
|
fail_unless_equals_int (g_list_length (buffers), 0);
|
|
|
|
/* flush the element */
|
|
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_flush_start ()));
|
|
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_flush_stop (FALSE)));
|
|
|
|
/* resend events */
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
|
|
/* create and push a second buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP + 1, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
/* still no buffers should have made it through (the first one should have
|
|
* been dropped during flushing) */
|
|
fail_unless_equals_int (g_list_length (buffers), 0);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_NULL, GST_STATE_CHANGE_SUCCESS);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_reusable_element_no_e_bit)
|
|
{
|
|
GstBuffer *buffer;
|
|
|
|
/* set the ntp-offset, since no one will provide a clock */
|
|
g_object_set (element, "ntp-offset", NTP_OFFSET, NULL);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
|
|
/* create and push the first buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
/* create and push a second buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP + 1, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
/* create and push a third buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP + 2, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_NULL, GST_STATE_CHANGE_SUCCESS);
|
|
|
|
fail_unless_equals_int (g_list_length (buffers), 3);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
|
|
/* create and push the first buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
/* create and push a second buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP + 1, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
/* create and push a third buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP + 2, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_NULL, GST_STATE_CHANGE_SUCCESS);
|
|
|
|
fail_unless_equals_int (g_list_length (buffers), 6);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_reusable_element_e_bit)
|
|
{
|
|
GstBuffer *buffer;
|
|
|
|
/* set the e-bit, so the element use caching */
|
|
g_object_set (element, "set-e-bit", TRUE, NULL);
|
|
/* set the ntp-offset, since no one will provide a clock */
|
|
g_object_set (element, "ntp-offset", NTP_OFFSET, NULL);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
|
|
/* create and push the first buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
/* create and push a second buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP + 1, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
/* create and push a third buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP + 2, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_NULL, GST_STATE_CHANGE_SUCCESS);
|
|
|
|
fail_unless_equals_int (g_list_length (buffers), 2);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
|
|
/* create and push the first buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
/* create and push a second buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP + 1, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
/* create and push a third buffer */
|
|
buffer = create_rtp_buffer (TIMESTAMP + 2, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_NULL, GST_STATE_CHANGE_SUCCESS);
|
|
|
|
fail_unless_equals_int (g_list_length (buffers), 4);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_ntp_offset_event)
|
|
{
|
|
GstBuffer *buffer_in, *buffer1_out, *buffer2_out;
|
|
GList *node;
|
|
|
|
/* set the e-bit, so the element use caching */
|
|
g_object_set (element, "set-e-bit", TRUE, NULL);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
|
|
/* push an ntp-offset event */
|
|
fail_unless (gst_pad_push_event (mysrcpad,
|
|
create_ntp_offset_event (NTP_OFFSET, FALSE)));
|
|
|
|
/* create and push the first buffer */
|
|
buffer_in = create_rtp_buffer (TIMESTAMP, TRUE);
|
|
buffer1_out = create_extension_buffer (buffer_in, TRUE, TRUE, FALSE,
|
|
NTP_OFFSET, 0);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer_in), GST_FLOW_OK);
|
|
|
|
/* push a new ntp offset */
|
|
fail_unless (gst_pad_push_event (mysrcpad,
|
|
create_ntp_offset_event (2 * NTP_OFFSET, TRUE)));
|
|
|
|
/* create and push a second buffer (last) */
|
|
buffer_in = create_rtp_buffer (TIMESTAMP + 1, TRUE);
|
|
buffer2_out = create_extension_buffer (buffer_in, TRUE, TRUE, TRUE,
|
|
2 * NTP_OFFSET, 0);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer_in), GST_FLOW_OK);
|
|
|
|
/* the first buffer should have been pushed now */
|
|
fail_unless_equals_int (g_list_length (buffers), 1);
|
|
node = g_list_last (buffers);
|
|
check_buffer_equal ((GstBuffer *) node->data, buffer1_out);
|
|
gst_buffer_unref (buffer1_out);
|
|
|
|
/* push EOS */
|
|
fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ()));
|
|
|
|
/* the second buffer has now been pushed */
|
|
fail_unless_equals_int (g_list_length (buffers), 2);
|
|
node = g_list_last (buffers);
|
|
check_buffer_equal ((GstBuffer *) node->data, buffer2_out);
|
|
gst_buffer_unref (buffer2_out);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_NULL, GST_STATE_CHANGE_SUCCESS);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_serialized_events)
|
|
{
|
|
GstBuffer *buffer;
|
|
|
|
/* we want the e-bit set so that buffers are cached */
|
|
g_object_set (element, "set-e-bit", TRUE, NULL);
|
|
g_object_set (element, "ntp-offset", NTP_OFFSET, NULL);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
|
|
/* send intitial events (stream-start and segment) */
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
check_and_clear_events (2, NO_COMPARE);
|
|
|
|
/* events received while no buffer is cached should be forwarded */
|
|
create_and_push_event (GST_EVENT_CUSTOM_DOWNSTREAM);
|
|
check_and_clear_events (1, NO_COMPARE);
|
|
|
|
/* create and push the first buffer, which should be cached */
|
|
buffer = create_rtp_buffer (TIMESTAMP, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
fail_unless_equals_int (g_list_length (buffers), 0);
|
|
/* serialized events should be queued when there's a buffer cached */
|
|
create_and_push_event (GST_EVENT_CUSTOM_DOWNSTREAM);
|
|
fail_unless_equals_int (g_list_length (myreceivedevents), 0);
|
|
/* there's still a buffer cached... */
|
|
create_and_push_event (GST_EVENT_CUSTOM_DOWNSTREAM);
|
|
fail_unless_equals_int (g_list_length (myreceivedevents), 0);
|
|
|
|
/* receiving a new buffer should let the first through, along with the
|
|
* queued serialized events */
|
|
buffer = create_rtp_buffer (TIMESTAMP, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
fail_unless_equals_int (g_list_length (buffers), 1);
|
|
check_and_clear_events (2, COMPARE);
|
|
|
|
/* there's still a buffer cached, a new serialized event should be quueud */
|
|
create_and_push_event (GST_EVENT_CUSTOM_DOWNSTREAM);
|
|
fail_unless_equals_int (g_list_length (myreceivedevents), 0);
|
|
|
|
/* when receiving an EOS cached buffer and queued events should be forwarded */
|
|
create_and_push_event (GST_EVENT_EOS);
|
|
check_and_clear_events (2, COMPARE);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
GST_START_TEST (test_non_serialized_events)
|
|
{
|
|
GstEvent *event;
|
|
GstBuffer *buffer;
|
|
|
|
/* we want the e-bit set so that buffers are cached */
|
|
g_object_set (element, "set-e-bit", TRUE, NULL);
|
|
g_object_set (element, "ntp-offset", NTP_OFFSET, NULL);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
|
|
/* send intitial events (stream-start and segment) */
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
fail_unless_equals_int (g_list_length (myreceivedevents), 2);
|
|
check_and_clear_events (2, NO_COMPARE);
|
|
|
|
/* events received while no buffer is cached should be forwarded */
|
|
create_and_push_event (GST_EVENT_CUSTOM_DOWNSTREAM_OOB);
|
|
check_and_clear_events (1, COMPARE);
|
|
|
|
/* create and push the first buffer, which should be cached */
|
|
buffer = create_rtp_buffer (TIMESTAMP, TRUE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
fail_unless_equals_int (g_list_length (buffers), 0);
|
|
/* non-serialized events should be forwarded regardless of whether
|
|
* there is a cached buffer */
|
|
create_and_push_event (GST_EVENT_CUSTOM_DOWNSTREAM_OOB);
|
|
check_and_clear_events (1, COMPARE);
|
|
|
|
/* there's still a buffer cached, push a serialized event and make sure
|
|
* it's queued */
|
|
create_and_push_event (GST_EVENT_CUSTOM_DOWNSTREAM);
|
|
fail_unless_equals_int (g_list_length (myreceivedevents), 0);
|
|
/* non-serialized events should be forwarded regardless of whether there
|
|
* are serialized events queued, thus the g_list_prepend below */
|
|
event = create_event (GST_EVENT_CUSTOM_DOWNSTREAM_OOB);
|
|
mypushedevents = g_list_prepend (mypushedevents, event);
|
|
fail_unless (gst_pad_push_event (mysrcpad, event));
|
|
fail_unless_equals_int (g_list_length (myreceivedevents), 1);
|
|
|
|
/* when receiving an EOS cached buffer and queued events should be forwarded */
|
|
create_and_push_event (GST_EVENT_EOS);
|
|
fail_unless_equals_int (g_list_length (buffers), 1);
|
|
check_and_clear_events (3, COMPARE);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
static void
|
|
do_ntp_time (GstClockTime buffer_time, gint segment_start, gint segment_base)
|
|
{
|
|
GstSegment segment;
|
|
GstBuffer *buffer;
|
|
GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT;
|
|
guint8 *data;
|
|
guint64 expected_ntp_time;
|
|
guint64 timestamp;
|
|
|
|
/* create a segment that controls the behavior
|
|
* by changing segment.start and segment.base we affect the stream time and
|
|
* running time respectively */
|
|
gst_segment_init (&segment, GST_FORMAT_TIME);
|
|
segment.start = segment_start;
|
|
segment.base = segment_base;
|
|
gst_pad_push_event (mysrcpad, gst_event_new_segment (&segment));
|
|
|
|
expected_ntp_time = gst_segment_to_stream_time (&segment, GST_FORMAT_TIME,
|
|
buffer_time);
|
|
expected_ntp_time += NTP_OFFSET;
|
|
expected_ntp_time = gst_util_uint64_scale (expected_ntp_time,
|
|
(G_GINT64_CONSTANT (1) << 32), GST_SECOND);
|
|
|
|
buffer = create_rtp_buffer (buffer_time, FALSE);
|
|
fail_unless_equals_int (gst_pad_push (mysrcpad, buffer), GST_FLOW_OK);
|
|
fail_unless_equals_int (g_list_length (buffers), 1);
|
|
|
|
buffer = g_list_last (buffers)->data;
|
|
|
|
/* get the extension header */
|
|
fail_unless (gst_rtp_buffer_map (buffer, GST_MAP_READWRITE, &rtpbuffer));
|
|
fail_unless (gst_rtp_buffer_get_extension_data (&rtpbuffer, NULL,
|
|
(gpointer) & data, NULL));
|
|
|
|
/* ...and read the NTP timestamp and verify that it's the expected one */
|
|
timestamp = GST_READ_UINT64_BE (data);
|
|
fail_unless_equals_uint64 (timestamp, expected_ntp_time);
|
|
|
|
gst_rtp_buffer_unmap (&rtpbuffer);
|
|
gst_check_drop_buffers ();
|
|
}
|
|
|
|
GST_START_TEST (test_ntp_time)
|
|
{
|
|
/* we do not need buffer caching, so do not set the e-bit */
|
|
g_object_set (element, "set-e-bit", FALSE, NULL);
|
|
/* set an ntp offset suitable for testing */
|
|
g_object_set (element, "ntp-offset", NTP_OFFSET, NULL);
|
|
|
|
ASSERT_SET_STATE (element, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
|
|
|
|
/* push initial events */
|
|
gst_check_setup_events (mysrcpad, element, NULL, GST_FORMAT_TIME);
|
|
|
|
/* first test with a "clean" segment */
|
|
do_ntp_time (GST_MSECOND, 0, 0);
|
|
do_ntp_time (GST_SECOND + GST_MSECOND, 0, 0);
|
|
|
|
/* verify that changing the running time does not affect the ntp time stamps */
|
|
do_ntp_time (GST_MSECOND, 0, GST_SECOND);
|
|
do_ntp_time (GST_SECOND + GST_MSECOND, 0, GST_SECOND);
|
|
|
|
/* changing the segment.start affects the stream time, verify that the element
|
|
* handles it correctly */
|
|
do_ntp_time (GST_MSECOND, GST_MSECOND / 2, 0);
|
|
do_ntp_time (GST_SECOND + GST_MSECOND, GST_MSECOND / 2, 0);
|
|
|
|
/* and finally change both of them and verify that all's fine */
|
|
do_ntp_time (GST_MSECOND, GST_MSECOND / 2, GST_SECOND);
|
|
do_ntp_time (GST_SECOND + GST_MSECOND, GST_MSECOND / 2, GST_SECOND);
|
|
}
|
|
|
|
GST_END_TEST;
|
|
|
|
static Suite *
|
|
onviftimestamp_suite (void)
|
|
{
|
|
Suite *s = suite_create ("onviftimestamp");
|
|
TCase *tc_general, *tc_events;
|
|
|
|
tc_general = tcase_create ("general");
|
|
suite_add_tcase (s, tc_general);
|
|
tcase_add_checked_fixture (tc_general, setup, cleanup);
|
|
|
|
tcase_add_test (tc_general, test_apply_clean_point);
|
|
tcase_add_test (tc_general, test_apply_no_e_bit);
|
|
tcase_add_test (tc_general, test_apply_e_bit);
|
|
tcase_add_test (tc_general, test_flushing);
|
|
tcase_add_test (tc_general, test_reusable_element_no_e_bit);
|
|
tcase_add_test (tc_general, test_reusable_element_e_bit);
|
|
tcase_add_test (tc_general, test_ntp_offset_event);
|
|
tcase_add_test (tc_general, test_ntp_time);
|
|
|
|
tc_events = tcase_create ("events");
|
|
suite_add_tcase (s, tc_events);
|
|
tcase_add_checked_fixture (tc_events, setup_with_event, cleanup_with_event);
|
|
|
|
tcase_add_test (tc_events, test_serialized_events);
|
|
tcase_add_test (tc_events, test_non_serialized_events);
|
|
|
|
return s;
|
|
}
|
|
|
|
GST_CHECK_MAIN (onviftimestamp);
|