/* * onviftimestamp.c * * Copyright (C) 2014 Axis Communications AB * Author: Guillaume Desmottes * * 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 . */ #include #include /* 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, gboolean first_buffer) { 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 (first_buffer) flags |= (1 << 5); 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, TRUE); /* 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, TRUE); /* 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, FALSE); 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, TRUE))); /* 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, TRUE); 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, FALSE); 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);