From ba9e8f0797ac26e1c46764a3dd73f9c2fe0143c2 Mon Sep 17 00:00:00 2001 From: Sebastian Rasmussen Date: Fri, 31 Jan 2014 00:06:18 +0100 Subject: [PATCH] tests: Refactor RTP basepayloading test into pay/depay parts Fixes https://bugzilla.gnome.org/show_bug.cgi?id=723328 --- tests/check/Makefile.am | 14 +- tests/check/libs/.gitignore | 3 +- tests/check/libs/rtp-basepayloading.c | 412 ------- tests/check/libs/rtpbasedepayload.c | 1247 ++++++++++++++++++++ tests/check/libs/rtpbasepayload.c | 2073 +++++++++++++++++++++++++++++++++ 5 files changed, 3333 insertions(+), 416 deletions(-) delete mode 100644 tests/check/libs/rtp-basepayloading.c create mode 100644 tests/check/libs/rtpbasedepayload.c create mode 100644 tests/check/libs/rtpbasepayload.c diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index d7ca820..1d4b7ef 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -195,7 +195,8 @@ check_PROGRAMS = \ libs/pbutils \ libs/profile \ libs/rtp \ - libs/rtp-basepayloading \ + libs/rtpbasedepayload \ + libs/rtpbasepayload \ libs/rtsp \ libs/rtspconnection \ libs/sdp \ @@ -363,10 +364,17 @@ libs_rtp_LDADD = \ $(top_builddir)/gst-libs/gst/rtp/libgstrtp-@GST_API_VERSION@.la \ $(GST_BASE_LIBS) $(LDADD) -libs_rtp_basepayloading_CFLAGS = \ +libs_rtpbasepayload_CFLAGS = \ $(GST_PLUGINS_BASE_CFLAGS) \ $(AM_CFLAGS) -libs_rtp_basepayloading_LDADD = \ +libs_rtpbasepayload_LDADD = \ + $(top_builddir)/gst-libs/gst/rtp/libgstrtp-@GST_API_VERSION@.la \ + $(GST_BASE_LIBS) $(LDADD) + +libs_rtpbasedepayload_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(AM_CFLAGS) +libs_rtpbasedepayload_LDADD = \ $(top_builddir)/gst-libs/gst/rtp/libgstrtp-@GST_API_VERSION@.la \ $(GST_BASE_LIBS) $(LDADD) diff --git a/tests/check/libs/.gitignore b/tests/check/libs/.gitignore index cc7566a..ab9f68a 100644 --- a/tests/check/libs/.gitignore +++ b/tests/check/libs/.gitignore @@ -12,7 +12,8 @@ navigation pbutils profile rtp -rtp-basepayloading +rtpbasedepayload +rtpbasepayload rtsp rtspconnection sdp diff --git a/tests/check/libs/rtp-basepayloading.c b/tests/check/libs/rtp-basepayloading.c deleted file mode 100644 index c3c6c94..0000000 --- a/tests/check/libs/rtp-basepayloading.c +++ /dev/null @@ -1,412 +0,0 @@ -/* GStreamer RTP base payloader unit tests - * Copyright (C) 2013 Sebastian Rasmussen - * - * 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 -#include -#include -#include -#include - -/* GstRtpDummyPay */ - -#define GST_TYPE_RTP_DUMMY_PAY \ - (gst_rtp_dummy_pay_get_type()) -#define GST_RTP_DUMMY_PAY(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DUMMY_PAY,GstRtpDummyPay)) -#define GST_RTP_DUMMY_PAY_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DUMMY_PAY,GstRtpDummyPayClass)) -#define GST_IS_RTP_DUMMY_PAY(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DUMMY_PAY)) -#define GST_IS_RTP_DUMMY_PAY_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DUMMY_PAY)) - -typedef struct _GstRtpDummyPay GstRtpDummyPay; -typedef struct _GstRtpDummyPayClass GstRtpDummyPayClass; - -struct _GstRtpDummyPay -{ - GstRTPBasePayload payload; -}; - -struct _GstRtpDummyPayClass -{ - GstRTPBasePayloadClass parent_class; -}; - -GType gst_rtp_dummy_pay_get_type (void); - -G_DEFINE_TYPE (GstRtpDummyPay, gst_rtp_dummy_pay, GST_TYPE_RTP_BASE_PAYLOAD); - -static GstFlowReturn gst_rtp_dummy_pay_handle_buffer (GstRTPBasePayload * pay, - GstBuffer * buffer); - -static GstStaticPadTemplate gst_rtp_dummy_pay_sink_template = -GST_STATIC_PAD_TEMPLATE ("sink", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS_ANY); - -static GstStaticPadTemplate gst_rtp_dummy_pay_src_template = -GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS_ANY); - -static void -gst_rtp_dummy_pay_class_init (GstRtpDummyPayClass * klass) -{ - GstElementClass *gstelement_class; - GstRTPBasePayloadClass *gstrtpbasepayload_class; - - gstelement_class = GST_ELEMENT_CLASS (klass); - gstrtpbasepayload_class = GST_RTP_BASE_PAYLOAD_CLASS (klass); - - gst_element_class_add_pad_template (gstelement_class, - gst_static_pad_template_get (&gst_rtp_dummy_pay_sink_template)); - gst_element_class_add_pad_template (gstelement_class, - gst_static_pad_template_get (&gst_rtp_dummy_pay_src_template)); - - gstrtpbasepayload_class->handle_buffer = gst_rtp_dummy_pay_handle_buffer; -} - -static void -gst_rtp_dummy_pay_init (GstRtpDummyPay * pay) -{ - gst_rtp_base_payload_set_options (GST_RTP_BASE_PAYLOAD (pay), "application", - TRUE, "dummy", 42); -} - -static GstRtpDummyPay * -rtp_dummy_pay_new (void) -{ - return g_object_new (GST_TYPE_RTP_DUMMY_PAY, NULL); -} - -static GstFlowReturn -gst_rtp_dummy_pay_handle_buffer (GstRTPBasePayload * pay, GstBuffer * buffer) -{ - GstBuffer *paybuffer; - - if (!gst_pad_has_current_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (pay))) { - if (!gst_rtp_base_payload_set_outcaps (GST_RTP_BASE_PAYLOAD (pay), NULL)) { - gst_buffer_unref (buffer); - return GST_FLOW_NOT_NEGOTIATED; - } - } - - paybuffer = gst_rtp_buffer_new_allocate (0, 0, 0); - GST_BUFFER_PTS (paybuffer) = GST_BUFFER_PTS (buffer); - GST_BUFFER_OFFSET (paybuffer) = GST_BUFFER_OFFSET (buffer); - gst_buffer_append (paybuffer, buffer); - - return gst_rtp_base_payload_push (pay, paybuffer); -} - -/* GstRtpDummyDepay */ - -#define GST_TYPE_RTP_DUMMY_DEPAY \ - (gst_rtp_dummy_depay_get_type()) -#define GST_RTP_DUMMY_DEPAY(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DUMMY_DEPAY,GstRtpDummyDepay)) -#define GST_RTP_DUMMY_DEPAY_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DUMMY_DEPAY,GstRtpDummyDepayClass)) -#define GST_IS_RTP_DUMMY_DEPAY(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DUMMY_DEPAY)) -#define GST_IS_RTP_DUMMY_DEPAY_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DUMMY_DEPAY)) - -typedef struct _GstRtpDummyDepay GstRtpDummyDepay; -typedef struct _GstRtpDummyDepayClass GstRtpDummyDepayClass; - -struct _GstRtpDummyDepay -{ - GstRTPBaseDepayload depayload; -}; - -struct _GstRtpDummyDepayClass -{ - GstRTPBaseDepayloadClass parent_class; -}; - -GType gst_rtp_dummy_depay_get_type (void); - -G_DEFINE_TYPE (GstRtpDummyDepay, gst_rtp_dummy_depay, - GST_TYPE_RTP_BASE_DEPAYLOAD); - -static GstBuffer *gst_rtp_dummy_depay_process (GstRTPBaseDepayload * depayload, - GstBuffer * buf); - -static GstStaticPadTemplate gst_rtp_dummy_depay_sink_template = -GST_STATIC_PAD_TEMPLATE ("sink", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS_ANY); - -static GstStaticPadTemplate gst_rtp_dummy_depay_src_template = -GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS_ANY); - -static void -gst_rtp_dummy_depay_class_init (GstRtpDummyDepayClass * klass) -{ - GstElementClass *gstelement_class; - GstRTPBaseDepayloadClass *gstrtpbasedepayload_class; - - gstelement_class = GST_ELEMENT_CLASS (klass); - gstrtpbasedepayload_class = GST_RTP_BASE_DEPAYLOAD_CLASS (klass); - - gst_element_class_add_pad_template (gstelement_class, - gst_static_pad_template_get (&gst_rtp_dummy_depay_sink_template)); - gst_element_class_add_pad_template (gstelement_class, - gst_static_pad_template_get (&gst_rtp_dummy_depay_src_template)); - - gstrtpbasedepayload_class->process = gst_rtp_dummy_depay_process; -} - -static void -gst_rtp_dummy_depay_init (GstRtpDummyDepay * depay) -{ -} - -static GstRtpDummyDepay * -rtp_dummy_depay_new (void) -{ - return g_object_new (GST_TYPE_RTP_DUMMY_DEPAY, NULL); -} - -static GstBuffer * -gst_rtp_dummy_depay_process (GstRTPBaseDepayload * depayload, GstBuffer * buf) -{ - GstRTPBuffer rtp = { NULL }; - GstBuffer *outbuf; - - gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp); - outbuf = gst_rtp_buffer_get_payload_buffer (&rtp); - gst_rtp_buffer_unmap (&rtp); - - return outbuf; -} - -/* Tests */ - -static gboolean -bus_callback (GstBus * bus, GstMessage * message, gpointer user_data) -{ - GMainLoop *mainloop = (GMainLoop *) user_data; - gboolean result; - - switch (GST_MESSAGE_TYPE (message)) { - case GST_MESSAGE_ERROR: - { - GError *err; - gchar *debug; - gchar *element_name; - - element_name = message->src ? gst_object_get_name (message->src) : NULL; - gst_message_parse_error (message, &err, &debug); - fail ("Error from element %s: %s\n%s", GST_STR_NULL (element_name), - err->message, debug ? debug : ""); - g_error_free (err); - g_free (debug); - g_free (element_name); - - g_main_loop_quit (mainloop); - result = FALSE; - } - break; - - case GST_MESSAGE_EOS: - g_main_loop_quit (mainloop); - result = FALSE; - break; - - default: - result = TRUE; - break; - } - - return result; -} - -static void -push_buffer (GstElement * src, GstClockTime timestamp) -{ - GstBuffer *buf; - GstFlowReturn ret; - - GST_LOG ("pushing buffer %" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp)); - - buf = gst_buffer_new_allocate (NULL, 0, NULL); - GST_BUFFER_PTS (buf) = timestamp; - g_signal_emit_by_name (src, "push-buffer", buf, &ret); - fail_unless_equals_int (ret, GST_FLOW_OK); - gst_buffer_unref (buf); -} - -static void -await_buffer (GstElement * sink, GstClockTime timestamp) -{ - GstSample *sample = NULL; - GstBuffer *buf; - - GST_LOG ("awaiting buffer %" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp)); - - g_signal_emit_by_name (sink, "pull-sample", &sample); - fail_if (sample == NULL); - buf = gst_sample_get_buffer (sample); - GST_LOG ("got buffer %" GST_TIME_FORMAT, - GST_TIME_ARGS (GST_BUFFER_PTS (buf))); - fail_unless_equals_int (GST_BUFFER_PTS (buf), timestamp); - gst_sample_unref (sample); -} - -static void -push_eos (GstElement * src) -{ - GstFlowReturn ret; - - GST_LOG ("pushing EOS"); - - g_signal_emit_by_name (src, "end-of-stream", &ret); - fail_unless_equals_int (ret, GST_FLOW_OK); -} - -static void -await_eos (GstElement * sink) -{ - GstSample *sample = NULL; - gboolean eos; - - GST_LOG ("awaiting EOS"); - - g_signal_emit_by_name (sink, "pull-sample", &sample); - fail_if (sample != NULL); - g_object_get (sink, "eos", &eos, NULL); - fail_unless (eos); -} - -GST_START_TEST (rtp_base_test) -{ - GstElement *pipeline, *src, *pay, *depay, *sink; - GMainLoop *mainloop; - GstBus *bus; - GstCaps *caps; - GstStructure *stats; - guint clock_rate; - guint seqnum; - guint timestamp; - GObjectClass *klass; - - pipeline = gst_pipeline_new (NULL); - fail_unless (GST_IS_PIPELINE (pipeline)); - - src = gst_element_factory_make ("appsrc", NULL); - fail_unless (src != NULL); - - pay = GST_ELEMENT (rtp_dummy_pay_new ()); - fail_unless (GST_IS_RTP_DUMMY_PAY (pay)); - - depay = GST_ELEMENT (rtp_dummy_depay_new ()); - fail_unless (GST_IS_RTP_DUMMY_DEPAY (depay)); - - sink = gst_element_factory_make ("appsink", NULL); - - caps = gst_caps_from_string ("application/x-raw"); - g_object_set (src, "do-timestamp", TRUE, "caps", caps, "format", - GST_FORMAT_TIME, NULL); - gst_caps_unref (caps); - - g_object_set (pay, "seqnum-offset", 0, "timestamp-offset", 0, NULL); - - g_object_set (sink, "sync", FALSE, "emit-signals", TRUE, NULL); - - gst_bin_add_many (GST_BIN (pipeline), src, pay, depay, sink, NULL); - - fail_unless (gst_element_link (src, pay)); - fail_unless (gst_element_link (pay, depay)); - fail_unless (gst_element_link (depay, sink)); - - mainloop = g_main_loop_new (NULL, FALSE); - fail_unless (mainloop != NULL); - - bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); - gst_bus_add_watch (bus, bus_callback, (gpointer) mainloop); - gst_object_unref (bus); - - gst_element_set_state (pipeline, GST_STATE_PLAYING); - - fail_unless (gst_element_seek (src, 1.0, GST_FORMAT_TIME, - GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, - GST_CLOCK_TIME_NONE)); - - push_buffer (src, 0 * GST_SECOND); - await_buffer (sink, 0 * GST_SECOND); - - push_buffer (src, 1 * GST_SECOND); - await_buffer (sink, 1 * GST_SECOND); - - klass = G_OBJECT_GET_CLASS (pay); - fail_unless (g_object_class_find_property (klass, "stats") != NULL); - - g_object_get (pay, "stats", &stats, NULL); - - fail_unless (gst_structure_has_field (stats, "clock-rate")); - fail_unless (gst_structure_has_field (stats, "seqnum")); - fail_unless (gst_structure_has_field (stats, "timestamp")); - - fail_unless (gst_structure_get_uint (stats, "clock-rate", &clock_rate)); - fail_unless (gst_structure_get_uint (stats, "seqnum", &seqnum)); - fail_unless (gst_structure_get_uint (stats, "timestamp", ×tamp)); - - fail_unless_equals_int (clock_rate, 42); - fail_unless_equals_int (seqnum, 1); - fail_unless_equals_int (timestamp, 42); - - push_eos (src); - await_eos (sink); - - g_main_loop_run (mainloop); - - gst_element_set_state (pipeline, GST_STATE_NULL); - - g_main_loop_unref (mainloop); - - gst_object_unref (pipeline); -} - -GST_END_TEST; - -static Suite * -rtp_basepayloading_suite (void) -{ - Suite *s = suite_create ("rtp_base_payloading_test"); - TCase *tc_chain = tcase_create (""); - - tcase_set_timeout (tc_chain, 60); - - suite_add_tcase (s, tc_chain); - tcase_add_test (tc_chain, rtp_base_test); - - return s; -} - -GST_CHECK_MAIN (rtp_basepayloading) diff --git a/tests/check/libs/rtpbasedepayload.c b/tests/check/libs/rtpbasedepayload.c new file mode 100644 index 0000000..71a1d85 --- /dev/null +++ b/tests/check/libs/rtpbasedepayload.c @@ -0,0 +1,1247 @@ +/* GStreamer RTP base depayloader unit tests + * Copyright (C) 2014 Sebastian Rasmussen + * + * 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 +#include +#include +#include + +#define DEFAULT_CLOCK_RATE (42) + +/* GstRtpDummyDepay */ + +#define GST_TYPE_RTP_DUMMY_DEPAY \ + (gst_rtp_dummy_depay_get_type()) +#define GST_RTP_DUMMY_DEPAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DUMMY_DEPAY,GstRtpDummyDepay)) +#define GST_RTP_DUMMY_DEPAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DUMMY_DEPAY,GstRtpDummyDepayClass)) +#define GST_IS_RTP_DUMMY_DEPAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DUMMY_DEPAY)) +#define GST_IS_RTP_DUMMY_DEPAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DUMMY_DEPAY)) + +typedef struct _GstRtpDummyDepay GstRtpDummyDepay; +typedef struct _GstRtpDummyDepayClass GstRtpDummyDepayClass; + +struct _GstRtpDummyDepay +{ + GstRTPBaseDepayload depayload; + guint64 rtptime; +}; + +struct _GstRtpDummyDepayClass +{ + GstRTPBaseDepayloadClass parent_class; +}; + +GType gst_rtp_dummy_depay_get_type (void); + +G_DEFINE_TYPE (GstRtpDummyDepay, gst_rtp_dummy_depay, + GST_TYPE_RTP_BASE_DEPAYLOAD); + +static GstBuffer *gst_rtp_dummy_depay_process (GstRTPBaseDepayload * depayload, + GstBuffer * buf); +static gboolean gst_rtp_dummy_depay_set_caps (GstRTPBaseDepayload *filter, + GstCaps *caps); + +static GstStaticPadTemplate gst_rtp_dummy_depay_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate gst_rtp_dummy_depay_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static void +gst_rtp_dummy_depay_class_init (GstRtpDummyDepayClass * klass) +{ + GstElementClass *gstelement_class; + GstRTPBaseDepayloadClass *gstrtpbasedepayload_class; + + gstelement_class = GST_ELEMENT_CLASS (klass); + gstrtpbasedepayload_class = GST_RTP_BASE_DEPAYLOAD_CLASS (klass); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_rtp_dummy_depay_sink_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_rtp_dummy_depay_src_template)); + + gstrtpbasedepayload_class->process = gst_rtp_dummy_depay_process; + gstrtpbasedepayload_class->set_caps = gst_rtp_dummy_depay_set_caps; +} + +static void +gst_rtp_dummy_depay_init (GstRtpDummyDepay * depay) +{ + depay->rtptime = 0; +} + +static GstRtpDummyDepay * +rtp_dummy_depay_new (void) +{ + return g_object_new (GST_TYPE_RTP_DUMMY_DEPAY, NULL); +} + +static GstBuffer * +gst_rtp_dummy_depay_process (GstRTPBaseDepayload * depayload, GstBuffer * buf) +{ + GstRTPBuffer rtp = { NULL }; + GstBuffer *outbuf; + guint32 rtptime; + guint i; + + GST_LOG ("depayloading buffer pts=%" GST_TIME_FORMAT " offset=%" + G_GUINT64_FORMAT " memories=%d", GST_TIME_ARGS (GST_BUFFER_PTS(buf)), + GST_BUFFER_OFFSET(buf), gst_buffer_n_memory (buf)); + + for (i = 0; i < gst_buffer_n_memory (buf); i++) { + GstMemory *mem = gst_buffer_get_memory (buf, 0); + gsize size, offset, maxsize; + size = gst_memory_get_sizes (mem, &offset, &maxsize); + GST_LOG ("\tsize=%zd offset=%zd maxsize=%zd", size, offset, maxsize); + gst_memory_unref (mem); + } + + gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp); + outbuf = gst_rtp_buffer_get_payload_buffer (&rtp); + rtptime = gst_rtp_buffer_get_timestamp (&rtp); + gst_rtp_buffer_unmap (&rtp); + + GST_BUFFER_PTS (outbuf) = GST_BUFFER_PTS (buf); + GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET (buf); + + GST_LOG ("depayloaded buffer pts=%" GST_TIME_FORMAT " offset=%" + G_GUINT64_FORMAT " rtptime=%" G_GUINT32_FORMAT " memories=%d", + GST_TIME_ARGS (GST_BUFFER_PTS(outbuf)), + GST_BUFFER_OFFSET(outbuf), rtptime, gst_buffer_n_memory (buf)); + + for (i = 0; i < gst_buffer_n_memory (buf); i++) { + GstMemory *mem = gst_buffer_get_memory (buf, 0); + gsize size, offset, maxsize; + size = gst_memory_get_sizes (mem, &offset, &maxsize); + GST_LOG ("\tsize=%zd offset=%zd maxsize=%zd", size, offset, maxsize); + gst_memory_unref (mem); + } + + return outbuf; +} + +static gboolean +gst_rtp_dummy_depay_set_caps (GstRTPBaseDepayload *filter, GstCaps *caps) +{ + GstEvent *event; + event = gst_event_new_caps (caps); + gst_pad_push_event (filter->srcpad, event); + return TRUE; +} + +/* Helper functions and global state */ + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +typedef struct State State; + +struct State { + GstElement *element; + GstPad *sinkpad; + GstPad *srcpad; +}; + +static GList *events; + +static gboolean +event_func (GstPad * pad, GstObject * noparent, GstEvent * event) +{ + events = g_list_append (events, gst_event_ref (event)); + return gst_pad_event_default (pad, noparent, event); +} + +static void drop_events (void) +{ + while (events != NULL) { + gst_event_unref (GST_EVENT (events->data)); + events = g_list_delete_link (events, events); + } +} + +static void validate_events_received (guint received) +{ + fail_unless_equals_int (g_list_length (events), received); +} + +static void validate_event (guint index, const gchar *name, + const gchar *field, ...) +{ + GstEvent *event; + va_list var_args; + + fail_if (index >= g_list_length (events)); + event = GST_EVENT (g_list_nth_data (events, index)); + fail_if (event == NULL); + + GST_TRACE ("%" GST_PTR_FORMAT, event); + + fail_unless_equals_string (GST_EVENT_TYPE_NAME (event), name); + + va_start (var_args, field); + while (field) { + if (!g_strcmp0 (field, "timestamp")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + GstClockTime timestamp, duration; + gst_event_parse_gap (event, ×tamp, &duration); + fail_unless_equals_uint64 (timestamp, expected); + } else if (!g_strcmp0 (field, "duration")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + GstClockTime timestamp, duration; + gst_event_parse_gap (event, ×tamp, &duration); + fail_unless_equals_uint64 (duration, expected); + } else if (!g_strcmp0 (field, "time")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + fail_unless_equals_uint64 (segment->time, expected); + } else if (!g_strcmp0 (field, "start")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + fail_unless_equals_uint64 (segment->start, expected); + } else if (!g_strcmp0 (field, "stop")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + fail_unless_equals_uint64 (segment->stop, expected); + } else if (!g_strcmp0 (field, "applied-rate")) { + gdouble expected = va_arg (var_args, gdouble); + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + fail_unless_equals_uint64 (segment->applied_rate, expected); + } else if (!g_strcmp0 (field, "rate")) { + gdouble expected = va_arg (var_args, gdouble); + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + fail_unless_equals_uint64 (segment->rate, expected); + } else if (!g_strcmp0 (field, "media-type")) { + const gchar *expected = va_arg (var_args, const gchar *); + GstCaps *caps; + const gchar *media_type; + gst_event_parse_caps (event, &caps); + media_type = gst_structure_get_name (gst_caps_get_structure (caps, 0)); + fail_unless_equals_string (media_type, expected); + } else if (!g_strcmp0 (field, "npt-start")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + GstCaps *caps; + GstClockTime start; + gst_event_parse_caps (event, &caps); + fail_unless (gst_structure_get_clock_time ( + gst_caps_get_structure (caps, 0), "npt-start", &start)); + fail_unless_equals_uint64 (start, expected); + } else if (!g_strcmp0 (field, "npt-stop")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + GstCaps *caps; + GstClockTime stop; + gst_event_parse_caps (event, &caps); + fail_unless (gst_structure_get_clock_time ( + gst_caps_get_structure (caps, 0), "npt-stop", &stop)); + fail_unless_equals_uint64 (stop, expected); + } else if (!g_strcmp0 (field, "play-speed")) { + gdouble expected = va_arg (var_args, gdouble); + GstCaps *caps; + gdouble speed; + gst_event_parse_caps (event, &caps); + fail_unless (gst_structure_get_double ( + gst_caps_get_structure (caps, 0), "play-speed", &speed)); + fail_unless (speed == expected); + } else if (!g_strcmp0 (field, "play-scale")) { + gdouble expected = va_arg (var_args, gdouble); + GstCaps *caps; + gdouble scale; + gst_event_parse_caps (event, &caps); + fail_unless (gst_structure_get_double ( + gst_caps_get_structure (caps, 0), "play-scale", &scale)); + fail_unless (scale == expected); + } else { + fail ("test cannot validate unknown event field '%s'", field); + } + field = va_arg (var_args, const gchar *); + } + va_end (var_args); +} + +#define push_rtp_buffer(state, field, ...) \ + push_rtp_buffer_full ((state), GST_FLOW_OK, (field), __VA_ARGS__) +#define push_rtp_buffer_fails(state, error, field, ...) \ + push_rtp_buffer_full ((state), (error), (field), __VA_ARGS__) + +static void push_rtp_buffer_full (State *state, GstFlowReturn expected, + const gchar *field, ...) +{ + GstBuffer *buf = gst_rtp_buffer_new_allocate (0, 0, 0); + GstRTPBuffer rtp = { NULL }; + gboolean mapped = FALSE; + va_list var_args; + + va_start (var_args, field); + while (field) { + if (!g_strcmp0 (field, "pts")) { + GstClockTime pts = va_arg (var_args, GstClockTime); + GST_BUFFER_PTS (buf) = pts; + } else if (!g_strcmp0 (field, "offset")) { + guint64 offset = va_arg (var_args, guint64); + GST_BUFFER_OFFSET (buf) = offset; + } else if (!g_strcmp0 (field, "discont")) { + gboolean discont = va_arg (var_args, gboolean); + if (discont) { + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + } else { + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); + } + } else { + if (!mapped) { + gst_rtp_buffer_map (buf, GST_MAP_WRITE, &rtp); + mapped = TRUE; + } + if (!g_strcmp0 (field, "rtptime")) { + guint32 rtptime = va_arg (var_args, guint32); + gst_rtp_buffer_set_timestamp (&rtp, rtptime); + } else if (!g_strcmp0 (field, "payload-type")) { + guint payload_type = va_arg (var_args, guint); + gst_rtp_buffer_set_payload_type (&rtp, payload_type); + } else if (!g_strcmp0 (field, "seq")) { + guint seq = va_arg (var_args, guint); + gst_rtp_buffer_set_seq (&rtp, seq); + } else if (!g_strcmp0 (field, "ssrc")) { + guint32 ssrc = va_arg (var_args, guint32); + gst_rtp_buffer_set_ssrc (&rtp, ssrc); + } else { + fail ("test cannot set unknown buffer field '%s'", field); + } + } + field = va_arg (var_args, const gchar *); + } + va_end (var_args); + + if (mapped) { + gst_rtp_buffer_unmap (&rtp); + } + + fail_unless_equals_int (gst_pad_push (state->srcpad, buf), expected); +} + +#define push_buffer(state, field, ...) \ + push_buffer_full ((state), GST_FLOW_OK, (field), __VA_ARGS__) + +static void push_buffer_full (State *state, GstFlowReturn expected, + const gchar *field, ...) +{ + GstBuffer *buf = gst_buffer_new_allocate (0, 0, 0); + va_list var_args; + + va_start (var_args, field); + while (field) { + if (!g_strcmp0 (field, "pts")) { + GstClockTime pts = va_arg (var_args, GstClockTime); + GST_BUFFER_PTS (buf) = pts; + } else if (!g_strcmp0 (field, "offset")) { + guint64 offset = va_arg (var_args, guint64); + GST_BUFFER_OFFSET (buf) = offset; + } else if (!g_strcmp0 (field, "discont")) { + gboolean discont = va_arg (var_args, gboolean); + if (discont) { + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + } else { + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); + } + } else { + fail ("test cannot set unknown buffer field '%s'", field); + } + field = va_arg (var_args, const gchar *); + } + va_end (var_args); + + fail_unless_equals_int (gst_pad_push (state->srcpad, buf), expected); +} + +static void validate_buffers_received (guint received) +{ + fail_unless_equals_int (g_list_length (buffers), received); +} + +static void validate_buffer (guint index, const gchar *field, ...) +{ + GstBuffer *buf; + va_list var_args; + + fail_if (index >= g_list_length (buffers)); + buf = GST_BUFFER (g_list_nth_data (buffers, (index))); + fail_if (buf == NULL); + + GST_TRACE ("%" GST_PTR_FORMAT, buf); + + va_start (var_args, field); + while (field) { + if (!g_strcmp0 (field, "pts")) { + GstClockTime pts = va_arg (var_args, GstClockTime); + fail_unless_equals_uint64 (GST_BUFFER_PTS (buf), pts); + } else if (!g_strcmp0 (field, "offset")) { + guint64 offset = va_arg (var_args, guint64); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET(buf), offset); + } else if (!g_strcmp0 (field, "discont")) { + gboolean discont = va_arg (var_args, gboolean); + if (discont) { + fail_unless (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT)); + } else { + fail_if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT)); + } + } else { + fail ("test cannot validate unknown buffer field '%s'", field); + } + field = va_arg (var_args, const gchar *); + } + va_end (var_args); +} + +static State *create_depayloader (const gchar *caps_str, + const gchar *property, ...) +{ + va_list var_args; + GstCaps *caps; + State *state; + + state = g_new0 (State, 1); + + state->element = GST_ELEMENT (rtp_dummy_depay_new ()); + fail_unless (GST_IS_RTP_DUMMY_DEPAY (state->element)); + + va_start (var_args, property); + g_object_set_valist (G_OBJECT (state->element), property, var_args); + va_end (var_args); + + state->srcpad = gst_check_setup_src_pad (state->element, &srctemplate); + state->sinkpad = gst_check_setup_sink_pad (state->element, &sinktemplate); + + fail_unless (gst_pad_set_active (state->srcpad, TRUE)); + fail_unless (gst_pad_set_active (state->sinkpad, TRUE)); + + if (caps_str) { + caps = gst_caps_from_string (caps_str); + } else { + caps = NULL; + } + gst_check_setup_events (state->srcpad, state->element, caps, GST_FORMAT_TIME); + if (caps) { + gst_caps_unref (caps); + } + + gst_pad_set_chain_function (state->sinkpad, gst_check_chain_func); + gst_pad_set_event_function (state->sinkpad, event_func); + + return state; +} + +static void set_state (State *state, GstState new_state) +{ + fail_unless_equals_int (gst_element_set_state (state->element, new_state), + GST_STATE_CHANGE_SUCCESS); +} + +static void packet_lost (State *state, GstClockTime timestamp, + GstClockTime duration) +{ + GstEvent *event; + guint seqnum = 0x4243; + gboolean late = TRUE; + guint retries = 42; + + event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, + gst_structure_new ("GstRTPPacketLost", + "seqnum", G_TYPE_UINT, seqnum, + "timestamp", G_TYPE_UINT64, timestamp, + "duration", G_TYPE_UINT64, duration, + "late", G_TYPE_BOOLEAN, late, + "retry", G_TYPE_UINT, retries, + NULL));; + + fail_unless (gst_pad_push_event (state->srcpad, event)); +} + +static void reconfigure_caps (State *state, const gchar *caps_str) +{ + GstCaps *newcaps; + GstEvent *event; + newcaps = gst_caps_from_string (caps_str); + event = gst_event_new_caps (newcaps); + gst_caps_unref (newcaps); + fail_unless (gst_pad_push_event (state->srcpad, event)); +} + +static void flush_pipeline (State *state) +{ + GstEvent *event; + GstSegment segment; + event = gst_event_new_flush_start (); + fail_unless (gst_pad_push_event (state->srcpad, event)); + event = gst_event_new_flush_stop (TRUE); + fail_unless (gst_pad_push_event (state->srcpad, event)); + gst_segment_init (&segment, GST_FORMAT_TIME); + event = gst_event_new_segment (&segment); + fail_unless (gst_pad_push_event (state->srcpad, event)); +} + +static void destroy_depayloader (State *state) +{ + gst_check_teardown_sink_pad (state->element); + gst_check_teardown_src_pad (state->element); + + gst_check_drop_buffers (); + drop_events (); + + g_object_unref (state->element); + + g_free (state); +} + +/* Tests */ + +/* send two RTP packets having sequential sequence numbers and timestamps + * differing by DEFAULT_CLOCK_RATE. the depayloader first pushes the normal + * stream-start, caps and segment events downstream before processing each RTP + * packet and pushing a corresponding buffer. PTS will be carried over from the + * RTP packets by the payloader to the buffers. because the sequence numbers are + * sequential then GST_BUFFER_FLAG_DISCONT will not be set for either buffer. + */ +GST_START_TEST (rtp_base_depayload_buffer_test) +{ + State *state; + + state = create_depayloader ("application/x-rtp", NULL); + + set_state (state, GST_STATE_PLAYING); + + push_rtp_buffer (state, + "pts", 0 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234), + "seq", 0x4242, + NULL); + + push_rtp_buffer (state, + "pts", 1 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234) + 1 * DEFAULT_CLOCK_RATE, + "seq", 0x4242 + 1, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_events_received (3); + + validate_event (0, "stream-start", + NULL); + + validate_event (1, "caps", + "media-type", "application/x-rtp", + NULL); + + validate_event (2, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST + +/* the intent with this test is to provide the depayloader with a buffer that + * does not contain an RTP header. this makes it impossible for the depayloader + * to depayload the incoming RTP packet, yet the stream-start and caps events + * will still be pushed. + */ +GST_START_TEST (rtp_base_depayload_invalid_rtp_packet_test) +{ + State *state; + + state = create_depayloader ("application/x-rtp", NULL); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + "offset", GST_BUFFER_OFFSET_NONE, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (0); + + validate_events_received (2); + + validate_event (0, "stream-start", + NULL); + + validate_event (1, "caps", + "media-type", "application/x-rtp", + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST + +/* validate what happens when a depayloader is provided with two RTP packets + * sent after each other that do not have sequential sequence numbers. in this + * case the depayloader should be able to depayload both first and the second + * buffer, but the second buffer will have GST_BUFFER_FLAG_DISCONT set to + * indicate that the was a discontinuity in the stream. the initial events are + * pushed prior to the buffers arriving so they should be unaffected by the gap + * in sequence numbers. + */ +GST_START_TEST (rtp_base_depayload_with_gap_test) +{ + State *state; + + state = create_depayloader ("application/x-rtp", NULL); + + set_state (state, GST_STATE_PLAYING); + + push_rtp_buffer (state, + "pts", 0 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x43214321), + "seq", 0x4242, + NULL); + + push_rtp_buffer (state, + "pts", 1 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x43214321) + 1 * DEFAULT_CLOCK_RATE, + "seq", 0x4242 + 2, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "discont", TRUE, + NULL); + + validate_events_received (3); + + validate_event (0, "stream-start", + NULL); + + validate_event (1, "caps", + "media-type", "application/x-rtp", + NULL); + + validate_event (2, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST + +/* two RTP packets are pushed in this test, and while the sequence numbers are + * sequential they are reversed. the expectation is that the depayloader will be + * able to depayload the first RTP packet, but once the second RTP packet + * arrives it will be discarded because it arrived too late. the initial events + * should be unaffected by the reversed buffers. + */ +GST_START_TEST (rtp_base_depayload_reversed_test) +{ + State *state; + + state = create_depayloader ("application/x-rtp", NULL); + + set_state (state, GST_STATE_PLAYING); + + push_rtp_buffer (state, + "pts", 0 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x43214321), + "seq", 0x4242, + NULL); + + push_rtp_buffer (state, + "pts", 1 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x43214321) + 1 * DEFAULT_CLOCK_RATE, + "seq", 0x4242 - 1, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (1); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_events_received (3); + + validate_event (0, "stream-start", + NULL); + + validate_event (1, "caps", + "media-type", "application/x-rtp", + NULL); + + validate_event (2, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST + +/* the intent of this test is to push two RTP packets that have reverse sequence + * numbers that differ significantly. the depayloader will consider RTP packets + * where the sequence numbers differ by more than 1000 to indicate that the + * source of the RTP packets has been restarted. therefore it will let both + * depayloaded buffers through, but the latter buffer marked + * GST_BUFFER_FLAG_DISCONT to indicate the discontinuity in the stream. the + * initial events should be unaffected by the reversed buffers. + */ +GST_START_TEST (rtp_base_depayload_old_reversed_test) +{ + State *state; + + state = create_depayloader ("application/x-rtp", NULL); + + set_state (state, GST_STATE_PLAYING); + + push_rtp_buffer (state, + "pts", 0 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x43214321), + "seq", 0x4242, + NULL); + + push_rtp_buffer (state, + "pts", 1 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x43214321) + 1 * DEFAULT_CLOCK_RATE, + "seq", 0x4242 - 1000, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "discont", TRUE, + NULL); + + validate_events_received (3); + + validate_event (0, "stream-start", + NULL); + + validate_event (1, "caps", + "media-type", "application/x-rtp", + NULL); + + validate_event (2, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST + +/* a depayloader that has not received any caps event will not be able to + * process any incoming RTP packet. instead pushing an RTP packet should result + * in the expected error. + */ +GST_START_TEST (rtp_base_depayload_without_negotiation_test) +{ + State *state; + + state = create_depayloader (NULL, NULL); + + set_state (state, GST_STATE_PLAYING); + + push_rtp_buffer_fails (state, GST_FLOW_NOT_NEGOTIATED, + "pts", 0 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234), + "seq", 0x4242, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (0); + + validate_events_received (1); + + validate_event (0, "stream-start", + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST + +/* a depayloader that receives the downstream event GstRTPPacketLost should + * respond by emitting a gap event with the corresponding timestamp and + * duration. the initial events are unaffected, but are succeeded by the added + * gap event. + */ +GST_START_TEST (rtp_base_depayload_packet_lost_test) +{ + State *state; + + state = create_depayloader ("application/x-rtp", NULL); + + set_state (state, GST_STATE_PLAYING); + + push_rtp_buffer (state, + "pts", 0 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234), + "seq", 0x4242, + NULL); + + packet_lost (state, 1 * GST_SECOND, GST_SECOND); + + push_rtp_buffer (state, + "pts", 2 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234) + 2 * DEFAULT_CLOCK_RATE, + "seq", 0x4242 + 2, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_buffer (1, + "pts", 2 * GST_SECOND, + "discont", TRUE, + NULL); + + validate_events_received (4); + + validate_event (0, "stream-start", + NULL); + + validate_event (1, "caps", + "media-type", "application/x-rtp", + NULL); + + validate_event (2, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + NULL); + + validate_event (3, "gap", + "timestamp", 1 * GST_SECOND, + "duration", GST_SECOND, + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST + +/* a depayloader that receives identical caps events simply ignores the latter + * events without propagating them downstream. + */ +GST_START_TEST (rtp_base_depayload_repeated_caps_test) +{ + State *state; + + state = create_depayloader ("application/x-rtp", NULL); + + set_state (state, GST_STATE_PLAYING); + + push_rtp_buffer (state, + "pts", 0 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234), + "seq", 0x4242, + NULL); + + reconfigure_caps (state, "application/x-rtp"); + + push_rtp_buffer (state, + "pts", 1 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234) + 1 * DEFAULT_CLOCK_RATE, + "seq", 0x4242 + 1, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_events_received (3); + + validate_event (0, "stream-start", + NULL); + + validate_event (1, "caps", + "media-type", "application/x-rtp", + NULL); + + validate_event (2, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST +/* when a depayloader receives new caps events with npt-start and npt-stop times + * it should save these timestamps as they should affect the next segment event + * being pushed by the depayloader. a new segment event is not pushed by the + * depayloader until a flush_stop event and a succeeding segment event are + * received. of course the intial event are unaffected, as is the incoming caps + * event. + */ +GST_START_TEST (rtp_base_depayload_npt_test) +{ + State *state; + + state = create_depayloader ("application/x-rtp", NULL); + + set_state (state, GST_STATE_PLAYING); + + push_rtp_buffer (state, + "pts", 0 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234), + "seq", 0x4242, + NULL); + + reconfigure_caps (state, + "application/x-rtp, npt-start=(guint64)1234, npt-stop=(guint64)4321"); + + flush_pipeline (state); + + push_rtp_buffer (state, + "pts", 1 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234) + 1 * DEFAULT_CLOCK_RATE, + "seq", 0x4242 + 1, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_events_received (7); + + validate_event (0, "stream-start", + NULL); + + validate_event (1, "caps", + "media-type", "application/x-rtp", + NULL); + + validate_event (2, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + NULL); + + validate_event (3, "caps", + "media-type", "application/x-rtp", + "npt-start", G_GUINT64_CONSTANT (1234), + "npt-stop", G_GUINT64_CONSTANT (4321), + NULL); + + validate_event (4, "flush-start", + NULL); + + validate_event (5, "flush-stop", + NULL); + + validate_event (6, "segment", + "time", G_GUINT64_CONSTANT (1234), + "start", G_GUINT64_CONSTANT (0), + "stop", G_GUINT64_CONSTANT (4321 - 1234), + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST + +/* when a depayloader receives a new caps event with play-scale it should save + * this rate as it should affect the next segment event being pushed by the + * depayloader. a new segment event is not pushed by the depayloader until a + * flush_stop event and a succeeding segment event are received. of course the + * intial event are unaffected, as is the incoming caps event. + */ +GST_START_TEST (rtp_base_depayload_play_scale_test) +{ + State *state; + + state = create_depayloader ("application/x-rtp", NULL); + + set_state (state, GST_STATE_PLAYING); + + push_rtp_buffer (state, + "pts", 0 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234), + "seq", 0x4242, + NULL); + + reconfigure_caps (state, + "application/x-rtp, play-scale=(double)2.0"); + + flush_pipeline (state); + + push_rtp_buffer (state, + "pts", 1 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234) + 1 * DEFAULT_CLOCK_RATE, + "seq", 0x4242 + 1, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_events_received (7); + + validate_event (0, "stream-start", + NULL); + + validate_event (1, "caps", + "media-type", "application/x-rtp", + NULL); + + validate_event (2, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + NULL); + + validate_event (3, "caps", + "media-type", "application/x-rtp", + "play-scale", 2.0, + NULL); + + validate_event (4, "flush-start", + NULL); + + validate_event (5, "flush-stop", + NULL); + + validate_event (6, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + "rate", 1.0, + "applied-rate", 2.0, + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST + +/* when a depayloader receives a new caps event with play-speed it should save + * this rate as it should affect the next segment event being pushed by the + * depayloader. a new segment event is not pushed by the depayloader until a + * flush_stop event and a succeeding segment event are received. of course the + * intial event are unaffected, as is the incoming caps event. + */ +GST_START_TEST (rtp_base_depayload_play_speed_test) +{ + State *state; + + state = create_depayloader ("application/x-rtp", NULL); + + set_state (state, GST_STATE_PLAYING); + + push_rtp_buffer (state, + "pts", 0 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234), + "seq", 0x4242, + NULL); + + reconfigure_caps (state, + "application/x-rtp, play-speed=(double)2.0"); + + flush_pipeline (state); + + push_rtp_buffer (state, + "pts", 1 * GST_SECOND, + "rtptime", G_GUINT64_CONSTANT (0x1234) + 1 * DEFAULT_CLOCK_RATE, + "seq", 0x4242 + 1, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "discont", FALSE, + NULL); + + validate_events_received (7); + + validate_event (0, "stream-start", + NULL); + + validate_event (1, "caps", + "media-type", "application/x-rtp", + NULL); + + validate_event (2, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + NULL); + + validate_event (3, "caps", + "media-type", "application/x-rtp", + "play-speed", 2.0, + NULL); + + validate_event (4, "flush-start", + NULL); + + validate_event (5, "flush-stop", + NULL); + + validate_event (6, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + "rate", 2.0, + "applied-rate", 1.0, + NULL); + + destroy_depayloader (state); +} + +GST_END_TEST + +static Suite * +rtp_basepayloading_suite (void) +{ + Suite *s = suite_create ("rtp_base_depayloading_test"); + TCase *tc_chain = tcase_create ("depayloading tests"); + + tcase_set_timeout (tc_chain, 60); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, rtp_base_depayload_buffer_test); + + tcase_add_test (tc_chain, rtp_base_depayload_invalid_rtp_packet_test); + tcase_add_test (tc_chain, rtp_base_depayload_with_gap_test); + tcase_add_test (tc_chain, rtp_base_depayload_reversed_test); + tcase_add_test (tc_chain, rtp_base_depayload_old_reversed_test); + + tcase_add_test (tc_chain, rtp_base_depayload_without_negotiation_test); + + tcase_add_test (tc_chain, rtp_base_depayload_packet_lost_test); + + tcase_add_test (tc_chain, rtp_base_depayload_repeated_caps_test); + tcase_add_test (tc_chain, rtp_base_depayload_npt_test); + tcase_add_test (tc_chain, rtp_base_depayload_play_scale_test); + tcase_add_test (tc_chain, rtp_base_depayload_play_speed_test); + + return s; +} + +GST_CHECK_MAIN (rtp_basepayloading) diff --git a/tests/check/libs/rtpbasepayload.c b/tests/check/libs/rtpbasepayload.c new file mode 100644 index 0000000..021345d --- /dev/null +++ b/tests/check/libs/rtpbasepayload.c @@ -0,0 +1,2073 @@ +/* GStreamer RTP base payloader unit tests + * Copyright (C) 2014 Sebastian Rasmussen + * + * 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 +#include +#include +#include + +#define DEFAULT_CLOCK_RATE (42) +#define BUFFER_BEFORE_LIST (10) + +/* GstRtpDummyPay */ + +#define GST_TYPE_RTP_DUMMY_PAY \ + (gst_rtp_dummy_pay_get_type()) +#define GST_RTP_DUMMY_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DUMMY_PAY,GstRtpDummyPay)) +#define GST_RTP_DUMMY_PAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DUMMY_PAY,GstRtpDummyPayClass)) +#define GST_IS_RTP_DUMMY_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DUMMY_PAY)) +#define GST_IS_RTP_DUMMY_PAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DUMMY_PAY)) + +typedef struct _GstRtpDummyPay GstRtpDummyPay; +typedef struct _GstRtpDummyPayClass GstRtpDummyPayClass; + +struct _GstRtpDummyPay +{ + GstRTPBasePayload payload; +}; + +struct _GstRtpDummyPayClass +{ + GstRTPBasePayloadClass parent_class; +}; + +GType gst_rtp_dummy_pay_get_type (void); + +G_DEFINE_TYPE (GstRtpDummyPay, gst_rtp_dummy_pay, GST_TYPE_RTP_BASE_PAYLOAD); + +static GstFlowReturn gst_rtp_dummy_pay_handle_buffer (GstRTPBasePayload * pay, + GstBuffer * buffer); + +static GstStaticPadTemplate gst_rtp_dummy_pay_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate gst_rtp_dummy_pay_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static void +gst_rtp_dummy_pay_class_init (GstRtpDummyPayClass * klass) +{ + GstElementClass *gstelement_class; + GstRTPBasePayloadClass *gstrtpbasepayload_class; + + gstelement_class = GST_ELEMENT_CLASS (klass); + gstrtpbasepayload_class = GST_RTP_BASE_PAYLOAD_CLASS (klass); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_rtp_dummy_pay_sink_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_rtp_dummy_pay_src_template)); + + gstrtpbasepayload_class->handle_buffer = gst_rtp_dummy_pay_handle_buffer; +} + +static void +gst_rtp_dummy_pay_init (GstRtpDummyPay * pay) +{ + gst_rtp_base_payload_set_options (GST_RTP_BASE_PAYLOAD (pay), "application", + TRUE, "dummy", DEFAULT_CLOCK_RATE); +} + +static GstRtpDummyPay * +rtp_dummy_pay_new (void) +{ + return g_object_new (GST_TYPE_RTP_DUMMY_PAY, NULL); +} + +static GstFlowReturn +gst_rtp_dummy_pay_handle_buffer (GstRTPBasePayload * pay, GstBuffer * buffer) +{ + GstBuffer *paybuffer; + + GST_LOG ("payloading buffer pts=%" GST_TIME_FORMAT " offset=%" + G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_PTS (buffer)), + GST_BUFFER_OFFSET(buffer)); + + if (!gst_pad_has_current_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (pay))) { + if (!gst_rtp_base_payload_set_outcaps (GST_RTP_BASE_PAYLOAD (pay), + "custom-caps", G_TYPE_UINT, DEFAULT_CLOCK_RATE, NULL)) { + gst_buffer_unref (buffer); + return GST_FLOW_NOT_NEGOTIATED; + } + } + + paybuffer = gst_rtp_buffer_new_allocate (0, 0, 0); + + GST_BUFFER_PTS (paybuffer) = GST_BUFFER_PTS (buffer); + GST_BUFFER_OFFSET (paybuffer) = GST_BUFFER_OFFSET (buffer); + + gst_buffer_append (paybuffer, buffer); + + GST_LOG ("payloaded buffer pts=%" GST_TIME_FORMAT " offset=%" + G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_PTS (paybuffer)), + GST_BUFFER_OFFSET(paybuffer)); + + if (GST_BUFFER_PTS (paybuffer) < BUFFER_BEFORE_LIST) { + return gst_rtp_base_payload_push (pay, paybuffer); + } else { + GstBufferList *list = gst_buffer_list_new (); + gst_buffer_list_add (list, paybuffer); + return gst_rtp_base_payload_push_list (pay, list); + } +} + +/* Helper functions and global state */ + +static GstStaticPadTemplate srctmpl = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate sinktmpl = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate special_sinktmpl = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/x-rtp, payload=(int)98, ssrc=(uint)24, " + "timestamp-offset=(uint)212, seqnum-offset=(uint)2424")); + +typedef struct State State; + +struct State { + GstElement *element; + GstPad *sinkpad; + GstPad *srcpad; +}; + +static GList *events; + +static gboolean +event_func (GstPad * pad, GstObject * noparent, GstEvent * event) +{ + events = g_list_append (events, gst_event_ref (event)); + return gst_pad_event_default (pad, noparent, event); +} + +static void drop_events (void) +{ + while (events != NULL) { + gst_event_unref (GST_EVENT (events->data)); + events = g_list_delete_link (events, events); + } +} + +static void validate_events_received (guint received) +{ + fail_unless_equals_int (g_list_length (events), received); +} + +static void validate_event (guint index, const gchar *name, + const gchar *field, ...) +{ + GstEvent *event; + va_list var_args; + + fail_if (index >= g_list_length (events)); + event = GST_EVENT (g_list_nth_data (events, index)); + fail_if (event == NULL); + + GST_TRACE ("%" GST_PTR_FORMAT, event); + + fail_unless_equals_string (GST_EVENT_TYPE_NAME (event), name); + + va_start (var_args, field); + while (field) { + if (!g_strcmp0 (field, "timestamp")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + GstClockTime timestamp, duration; + gst_event_parse_gap (event, ×tamp, &duration); + fail_unless_equals_uint64 (timestamp, expected); + } else if (!g_strcmp0 (field, "duration")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + GstClockTime timestamp, duration; + gst_event_parse_gap (event, ×tamp, &duration); + fail_unless_equals_uint64 (duration, expected); + } else if (!g_strcmp0 (field, "time")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + fail_unless_equals_uint64 (segment->time, expected); + } else if (!g_strcmp0 (field, "start")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + fail_unless_equals_uint64 (segment->start, expected); + } else if (!g_strcmp0 (field, "stop")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + fail_unless_equals_uint64 (segment->stop, expected); + } else if (!g_strcmp0 (field, "applied-rate")) { + gdouble expected = va_arg (var_args, gdouble); + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + fail_unless_equals_uint64 (segment->applied_rate, expected); + } else if (!g_strcmp0 (field, "rate")) { + gdouble expected = va_arg (var_args, gdouble); + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + fail_unless_equals_uint64 (segment->rate, expected); + } else if (!g_strcmp0 (field, "media-type")) { + const gchar *expected = va_arg (var_args, const gchar *); + GstCaps *caps; + const gchar *media_type; + gst_event_parse_caps (event, &caps); + media_type = gst_structure_get_name (gst_caps_get_structure (caps, 0)); + fail_unless_equals_string (media_type, expected); + } else if (!g_strcmp0 (field, "npt-start")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + GstCaps *caps; + GstClockTime start; + gst_event_parse_caps (event, &caps); + fail_unless (gst_structure_get_clock_time ( + gst_caps_get_structure (caps, 0), "npt-start", &start)); + fail_unless_equals_uint64 (start, expected); + } else if (!g_strcmp0 (field, "npt-stop")) { + GstClockTime expected = va_arg (var_args, GstClockTime); + GstCaps *caps; + GstClockTime stop; + gst_event_parse_caps (event, &caps); + fail_unless (gst_structure_get_clock_time ( + gst_caps_get_structure (caps, 0), "npt-stop", &stop)); + fail_unless_equals_uint64 (stop, expected); + } else if (!g_strcmp0 (field, "play-speed")) { + gdouble expected = va_arg (var_args, gdouble); + GstCaps *caps; + gdouble speed; + gst_event_parse_caps (event, &caps); + fail_unless (gst_structure_get_double ( + gst_caps_get_structure (caps, 0), "play-speed", &speed)); + fail_unless (speed == expected); + } else if (!g_strcmp0 (field, "play-scale")) { + gdouble expected = va_arg (var_args, gdouble); + GstCaps *caps; + gdouble scale; + gst_event_parse_caps (event, &caps); + fail_unless (gst_structure_get_double ( + gst_caps_get_structure (caps, 0), "play-scale", &scale)); + fail_unless (scale == expected); + } else if (!g_strcmp0 (field, "ssrc")) { + guint expected = va_arg (var_args, guint); + GstCaps *caps; + guint ssrc; + gst_event_parse_caps (event, &caps); + fail_unless (gst_structure_get_uint ( + gst_caps_get_structure (caps, 0), "ssrc", &ssrc)); + fail_unless_equals_int (ssrc, expected); + } else { + fail ("test cannot validate unknown event field '%s'", field); + } + field = va_arg (var_args, const gchar *); + } + va_end (var_args); +} + +static void validate_normal_start_events (uint index) +{ + validate_event (index, "stream-start", + NULL); + + validate_event (index + 1, "caps", + "media-type", "application/x-rtp", + NULL); + + validate_event (index + 2, "segment", + "time", G_GUINT64_CONSTANT (0), + "start", G_GUINT64_CONSTANT (0), + "stop", G_MAXUINT64, + NULL); +} + +#define push_buffer(state, field, ...) \ + push_buffer_full ((state), GST_FLOW_OK, (field), __VA_ARGS__) +#define push_buffer_fails(state, field, ...) \ + push_buffer_full ((state), GST_FLOW_FLUSHING, (field), __VA_ARGS__) + +static void push_buffer_full (State *state, GstFlowReturn expected, + const gchar *field, ...) +{ + GstBuffer *buf = gst_rtp_buffer_new_allocate (0, 0, 0); + GstRTPBuffer rtp = { NULL }; + gboolean mapped = FALSE; + va_list var_args; + + va_start (var_args, field); + while (field) { + if (!g_strcmp0 (field, "pts")) { + GstClockTime pts = va_arg (var_args, GstClockTime); + GST_BUFFER_PTS (buf) = pts; + } else if (!g_strcmp0 (field, "offset")) { + guint64 offset = va_arg (var_args, guint64); + GST_BUFFER_OFFSET (buf) = offset; + } else if (!g_strcmp0 (field, "discont")) { + gboolean discont = va_arg (var_args, gboolean); + if (discont) { + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + } else { + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); + } + } else { + if (!mapped) { + gst_rtp_buffer_map (buf, GST_MAP_WRITE, &rtp); + mapped = TRUE; + } + if (!g_strcmp0 (field, "rtptime")) { + guint32 rtptime = va_arg (var_args, guint32); + gst_rtp_buffer_set_timestamp (&rtp, rtptime); + } else if (!g_strcmp0 (field, "payload-type")) { + guint payload_type = va_arg (var_args, guint); + gst_rtp_buffer_set_payload_type (&rtp, payload_type); + } else if (!g_strcmp0 (field, "seq")) { + guint seq = va_arg (var_args, guint); + gst_rtp_buffer_set_seq (&rtp, seq); + } else if (!g_strcmp0 (field, "ssrc")) { + guint32 ssrc = va_arg (var_args, guint32); + gst_rtp_buffer_set_ssrc (&rtp, ssrc); + } else { + fail ("test cannot set unknown buffer field '%s'", field); + } + } + field = va_arg (var_args, const gchar *); + } + va_end (var_args); + + if (mapped) { + gst_rtp_buffer_unmap (&rtp); + } + + fail_unless_equals_int (gst_pad_push (state->srcpad, buf), expected); +} + +static void push_buffer_list (State *state, const gchar *field, ...) +{ + GstBuffer *buf = gst_rtp_buffer_new_allocate (0, 0, 0); + GstRTPBuffer rtp = { NULL }; + gboolean mapped = FALSE; + GstBufferList *list; + va_list var_args; + + va_start (var_args, field); + while (field) { + if (!g_strcmp0 (field, "pts")) { + GstClockTime pts = va_arg (var_args, GstClockTime); + GST_BUFFER_PTS (buf) = pts; + } else if (!g_strcmp0 (field, "offset")) { + guint64 offset = va_arg (var_args, guint64); + GST_BUFFER_OFFSET (buf) = offset; + } else if (!g_strcmp0 (field, "discont")) { + gboolean discont = va_arg (var_args, gboolean); + if (discont) { + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + } else { + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT); + } + } else { + if (!mapped) { + gst_rtp_buffer_map (buf, GST_MAP_WRITE, &rtp); + mapped = TRUE; + } + if (!g_strcmp0 (field, "rtptime")) { + guint32 rtptime = va_arg (var_args, guint32); + gst_rtp_buffer_set_timestamp (&rtp, rtptime); + } else if (!g_strcmp0 (field, "payload-type")) { + guint payload_type = va_arg (var_args, guint); + gst_rtp_buffer_set_payload_type (&rtp, payload_type); + } else if (!g_strcmp0 (field, "seq")) { + guint seq = va_arg (var_args, guint); + gst_rtp_buffer_set_seq (&rtp, seq); + } else if (!g_strcmp0 (field, "ssrc")) { + guint32 ssrc = va_arg (var_args, guint32); + gst_rtp_buffer_set_ssrc (&rtp, ssrc); + } else { + fail ("test cannot set unknown buffer field '%s'", field); + } + } + field = va_arg (var_args, const gchar *); + } + va_end (var_args); + + if (mapped) { + gst_rtp_buffer_unmap (&rtp); + } + + list = gst_buffer_list_new (); + gst_buffer_list_add (list, buf); + fail_unless_equals_int (gst_pad_push_list (state->srcpad, list), GST_FLOW_OK); +} + +static void validate_buffers_received (guint received_buffers) +{ + fail_unless_equals_int (g_list_length (buffers), received_buffers); +} + +static void validate_buffer (guint index, const gchar *field, ...) +{ + GstBuffer *buf; + GstRTPBuffer rtp = { NULL }; + gboolean mapped = FALSE; + va_list var_args; + + fail_if (index >= g_list_length (buffers)); + buf = GST_BUFFER (g_list_nth_data (buffers, index)); + fail_if (buf == NULL); + + GST_TRACE ("%" GST_PTR_FORMAT, buf); + + va_start (var_args, field); + while (field) { + if (!g_strcmp0 (field, "pts")) { + GstClockTime pts = va_arg (var_args, GstClockTime); + fail_unless_equals_uint64 (GST_BUFFER_PTS (buf), pts); + } else if (!g_strcmp0 (field, "offset")) { + guint64 offset = va_arg (var_args, guint64); + fail_unless_equals_uint64 (GST_BUFFER_OFFSET(buf), offset); + } else if (!g_strcmp0 (field, "discont")) { + gboolean discont = va_arg (var_args, gboolean); + if (discont) { + fail_unless (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT)); + } else { + fail_if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT)); + } + } else { + if (!mapped) { + gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp); + mapped = TRUE; + } + if (!g_strcmp0 (field, "rtptime")) { + guint32 rtptime = va_arg (var_args, guint32); + fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp), rtptime); + } else if (!g_strcmp0 (field, "payload-type")) { + guint pt = va_arg (var_args, guint); + fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), pt); + } else if (!g_strcmp0 (field, "seq")) { + guint seq = va_arg (var_args, guint); + fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp), seq); + } else if (!g_strcmp0 (field, "ssrc")) { + guint32 ssrc = va_arg (var_args, guint32); + fail_unless_equals_int (gst_rtp_buffer_get_ssrc (&rtp), ssrc); + } else { + fail ("test cannot validate unknown buffer field '%s'", field); + } + } + field = va_arg (var_args, const gchar *); + } + va_end (var_args); + + if (mapped) { + gst_rtp_buffer_unmap (&rtp); + } +} + +static void get_buffer_field (guint index, const gchar *field, ...) +{ + GstBuffer *buf; + GstRTPBuffer rtp = { NULL }; + gboolean mapped = FALSE; + va_list var_args; + + fail_if (index >= g_list_length (buffers)); + buf = GST_BUFFER (g_list_nth_data (buffers, (index))); + fail_if (buf == NULL); + + va_start (var_args, field); + while (field) { + if (!g_strcmp0 (field, "pts")) { + GstClockTime *pts = va_arg (var_args, GstClockTime *); + *pts = GST_BUFFER_PTS (buf); + } else if (!g_strcmp0 (field, "offset")) { + guint64 *offset = va_arg (var_args, guint64 *); + *offset = GST_BUFFER_OFFSET (buf); + } else if (!g_strcmp0 (field, "discont")) { + gboolean *discont = va_arg (var_args, gboolean*); + *discont = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT); + } else { + if (!mapped) { + gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp); + mapped = TRUE; + } + if (!g_strcmp0 (field, "rtptime")) { + guint32 *rtptime = va_arg (var_args, guint32 *); + *rtptime = gst_rtp_buffer_get_timestamp (&rtp); + } else if (!g_strcmp0 (field, "payload-type")) { + guint *pt = va_arg (var_args, guint *); + *pt = gst_rtp_buffer_get_payload_type (&rtp); + } else if (!g_strcmp0 (field, "seq")) { + guint16 *seq = va_arg (var_args, guint16 *); + *seq = gst_rtp_buffer_get_seq (&rtp); + } else if (!g_strcmp0 (field, "ssrc")) { + guint32 *ssrc = va_arg (var_args, guint32 *); + *ssrc = gst_rtp_buffer_get_ssrc (&rtp); + } else { + fail ("test retrieve validate unknown buffer field '%s'", field); + } + } + field = va_arg (var_args, const gchar *); + } + va_end (var_args); + + if (mapped) + gst_rtp_buffer_unmap (&rtp); +} + +static State *create_payloader (const gchar *caps_str, + GstStaticPadTemplate *sinktmpl, const gchar *property, ...) +{ + va_list var_args; + GstCaps *caps; + State *state; + + state = g_new0 (State, 1); + + state->element = GST_ELEMENT (rtp_dummy_pay_new ()); + fail_unless (GST_IS_RTP_DUMMY_PAY (state->element)); + + va_start (var_args, property); + g_object_set_valist (G_OBJECT (state->element), property, var_args); + va_end (var_args); + + state->srcpad = gst_check_setup_src_pad (state->element, &srctmpl); + state->sinkpad = gst_check_setup_sink_pad (state->element, sinktmpl); + + fail_unless (gst_pad_set_active (state->srcpad, TRUE)); + fail_unless (gst_pad_set_active (state->sinkpad, TRUE)); + + caps = gst_caps_from_string (caps_str); + gst_check_setup_events (state->srcpad, state->element, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + gst_pad_set_chain_function (state->sinkpad, gst_check_chain_func); + gst_pad_set_event_function (state->sinkpad, event_func); + + return state; +} + +static void set_state (State *state, GstState new_state) +{ + fail_unless_equals_int (gst_element_set_state (state->element, new_state), + GST_STATE_CHANGE_SUCCESS); +} + +static void validate_would_not_be_filled (State *state, guint size, + GstClockTime duration) +{ + GstRTPBasePayload *basepay; + basepay = GST_RTP_BASE_PAYLOAD (state->element); + fail_if (gst_rtp_base_payload_is_filled (basepay, size, duration)); +} + +static void validate_would_be_filled (State *state, guint size, + GstClockTime duration) +{ + GstRTPBasePayload *basepay; + basepay = GST_RTP_BASE_PAYLOAD (state->element); + fail_unless (gst_rtp_base_payload_is_filled (basepay, size, duration)); +} + +static void ssrc_collision (State *state, guint ssrc, + gboolean have_new_ssrc, guint new_ssrc) +{ + GstStructure *s; + GstEvent *event; + if (have_new_ssrc) { + s = gst_structure_new ("GstRTPCollision", + "ssrc", G_TYPE_UINT, ssrc, + "suggested-ssrc", G_TYPE_UINT, new_ssrc, + NULL); + } else { + s = gst_structure_new ("GstRTPCollision", + "ssrc", G_TYPE_UINT, ssrc, + NULL); + } + event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s); + fail_unless (gst_pad_push_event (state->sinkpad, event)); +} + +static void reconfigure (State *state) +{ + GstEvent *event; + event = gst_event_new_reconfigure (); + fail_unless (gst_pad_push_event (state->sinkpad, event)); +} + +static void validate_stats (State *state, guint clock_rate, + GstClockTime running_time, guint16 seq, guint32 rtptime) +{ + GstStructure *stats; + + g_object_get (state->element, "stats", &stats, NULL); + + fail_unless_equals_int ( + g_value_get_uint (gst_structure_get_value (stats, "clock-rate")), + clock_rate); + fail_unless_equals_uint64 ( + g_value_get_uint64 (gst_structure_get_value (stats, "running-time")), + running_time); + fail_unless_equals_int ( + g_value_get_uint (gst_structure_get_value (stats, "seqnum")), + seq); + fail_unless_equals_int ( + g_value_get_uint (gst_structure_get_value (stats, "timestamp")), + rtptime); + + gst_structure_free (stats); +} + +static void destroy_payloader (State *state) +{ + gst_check_teardown_sink_pad (state->element); + gst_check_teardown_src_pad (state->element); + + gst_check_drop_buffers (); + drop_events (); + + g_object_unref (state->element); + + g_free (state); +} + +/* Tests */ + +/* push two buffers to the payloader which should successfully payload them + * into RTP packets. the first packet will have a random rtptime and sequence + * number, but the last packet should have an rtptime incremented by + * DEFAULT_CLOCK_RATE and a sequence number incremented by one becuase the + * packets are sequential. besides the two payloaded RTP packets there should + * be the three events initial events: stream-start, caps and segment. + */ +GST_START_TEST (rtp_base_payload_buffer_test) +{ + State *state; + guint32 rtptime; + guint16 seq; + + state = create_payloader ("application/x-rtp", &sinktmpl, + "perfect-rtptime", FALSE, + NULL); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + + push_buffer (state, + "pts", 1 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + NULL); + get_buffer_field (0, + "rtptime", &rtptime, + "seq", &seq, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "rtptime", rtptime + 1 * DEFAULT_CLOCK_RATE, + "seq", seq + 1, + NULL); + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* push single buffers in buffer lists to the payloader to be payloaded into + * RTP packets. the dummy payloader will start pushing buffer lists itself + * after BUFFER_BEFORE_LIST payloaded RTP packets. any RTP packets included in + * buffer lists should have rtptime and sequence numbers incrementting in the + * same way as for separate RTP packets. + */ +GST_START_TEST (rtp_base_payload_buffer_list_test) +{ + State *state; + guint32 rtptime; + guint16 seq; + guint i; + + state = create_payloader ("application/x-rtp", &sinktmpl, NULL); + + set_state (state, GST_STATE_PLAYING); + + for (i = 0; i < BUFFER_BEFORE_LIST + 1; i++) { + push_buffer_list (state, + "pts", i * GST_SECOND, + NULL); + } + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (11); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + NULL); + get_buffer_field (0, + "rtptime", &rtptime, + "seq", &seq, + NULL); + + for (i = 1; i < BUFFER_BEFORE_LIST + 1; i++) { + validate_buffer (i, + "pts", i * GST_SECOND, + "rtptime", rtptime + i * DEFAULT_CLOCK_RATE, + "seq", seq + i, + NULL); + } + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* push two buffers. because the payloader is using non-perfect rtptime the + * second buffer will be timestamped with the default clock and ignore any + * offset set on the buffers being payloaded. + */ +GST_START_TEST (rtp_base_payload_normal_rtptime_test) +{ + guint32 rtptime; + State *state; + + state = create_payloader ("application/x-rtp", &sinktmpl, + "perfect-rtptime", FALSE, + NULL); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + "offset", GST_BUFFER_OFFSET_NONE, + NULL); + + push_buffer (state, + "pts", 1 * GST_SECOND, + "offset", GST_BUFFER_OFFSET_NONE, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "offset", GST_BUFFER_OFFSET_NONE, + NULL); + get_buffer_field (0, + "rtptime", &rtptime, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "offset", GST_BUFFER_OFFSET_NONE, + "rtptime", rtptime + DEFAULT_CLOCK_RATE, + NULL); + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* push two buffers. because the payloader is using perfect rtptime the + * second buffer will be timestamped with a timestamp incremented with the + * difference in offset between the first and second buffer. the pts will be + * ignored for any buffer after the first buffer. + */ +GST_START_TEST (rtp_base_payload_perfect_rtptime_test) +{ + guint32 rtptime; + State *state; + + state = create_payloader ("application/x-rtp", &sinktmpl, + "perfect-rtptime", TRUE, + NULL); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + "offset", 0, + NULL); + + push_buffer (state, + "pts", GST_CLOCK_TIME_NONE, + "offset", 21, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "offset", 0, + NULL); + get_buffer_field (0, + "rtptime", &rtptime, + NULL); + + validate_buffer (1, + "pts", GST_CLOCK_TIME_NONE, + "offset", 21, + "rtptime", rtptime + 21, + NULL); + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* validate that a payloader will re-use the last used timestamp when a buffer + * is using perfect rtptime and both the pushed buffers timestamp and the offset + * is NONE. the payloader is configuered to start with a specific timestamp. + * then a buffer is sent with a valid timestamp but without any offset. the + * payloded RTP packet is expected to use the specific timestamp. next another + * buffer is pushed with a normal timestamp set to illustrate that the payloaded + * RTP packet will have an increased timestamp. finally a buffer without any + * timestamp or offset is pushed. in this case the payloaded RTP packet is + * expected to have the same timestamp as the previously payloaded RTP packet. + */ +GST_START_TEST (rtp_base_payload_no_pts_no_offset_test) +{ + State *state; + + state = create_payloader ("application/x-rtp", &sinktmpl, + "timestamp-offset", 0x42, + NULL); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + "offset", GST_BUFFER_OFFSET_NONE, + NULL); + + push_buffer (state, + "pts", 1 * GST_SECOND, + "offset", GST_BUFFER_OFFSET_NONE, + NULL); + + push_buffer (state, + "pts", GST_CLOCK_TIME_NONE, + "offset", GST_BUFFER_OFFSET_NONE, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (3); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "offset", GST_BUFFER_OFFSET_NONE, + "rtptime", 0x42, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "offset", GST_BUFFER_OFFSET_NONE, + "rtptime", 0x42 + 1 * DEFAULT_CLOCK_RATE, + NULL); + + validate_buffer (2, + "pts", GST_CLOCK_TIME_NONE, + "offset", GST_BUFFER_OFFSET_NONE, + "rtptime", 0x42 + 1 * DEFAULT_CLOCK_RATE, + NULL); + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* validate that a downstream element with caps on its sink pad can effectively + * configure the payloader's payload-type, ssrc, timestamp-offset and + * seqnum-offset properties and therefore also affect the payloaded RTP packets. + * this is done by connecting to a sink pad with template caps setting the + * relevant fields and then pushing a buffer and making sure that the payloaded + * RTP packet has the expected properties. + */ +GST_START_TEST (rtp_base_payload_downstream_caps_test) +{ + State *state; + + state = create_payloader ("application/x-rtp", &special_sinktmpl, NULL); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (1); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "seq", 2424, + "payload-type", 98, + "ssrc", 24, + "rtptime", 212, + NULL); + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* when a payloader receives a GstRTPCollision upstream event it should try to + * switch to a new ssrc for the next payloaded RTP packets. GstRTPCollision can + * supply a suggested new ssrc. if a suggested new ssrc is supplied then the + * payloaded is supposed to use this new ssrc, otherwise it should generate a + * new random ssrc which is not identical to the one that collided. + * + * this is tested by first setting the ssrc to a specific value and pushing a + * buffer. the payloaded RTP packet is validate to have the set ssrc. then a + * GstRTPCollision event is generated to instruct the payloader that the + * previously set ssrc collided. this event suggests a new ssrc and it is + * verified that a pushed buffer results in a payloaded RTP packet that actually + * uses this new ssrc. finally a new GstRTPCollision event is generated to + * indicate another ssrc collision. this time the event does not suggest a new + * ssrc. the payloaded RTP packet is then expected to have a new random ssrc + * different from the collided one. + */ +GST_START_TEST (rtp_base_payload_ssrc_collision_test) +{ + State *state; + guint32 ssrc; + + state = create_payloader ("application/x-rtp", &sinktmpl, + NULL); + + g_object_set (state->element, "ssrc", 0x4242, NULL); + g_object_get (state->element, "ssrc", &ssrc, NULL); + fail_unless_equals_int (ssrc, 0x4242); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + + ssrc_collision (state, 0x4242, TRUE, 0x4343); + + push_buffer (state, + "pts", 1 * GST_SECOND, + NULL); + + ssrc_collision (state, 0x4343, FALSE, 0); + + push_buffer (state, + "pts", 2 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (3); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "ssrc", 0x4242, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "ssrc", 0x4343, + NULL); + + validate_buffer (2, + "pts", 2 * GST_SECOND, + NULL); + get_buffer_field (2, + "ssrc", &ssrc, + NULL); + fail_if (ssrc == 0x4343); + + validate_events_received (5); + + validate_normal_start_events (0); + + validate_event (3, "caps", + "media-type", "application/x-rtp", + "ssrc", 0x4343, + NULL); + + validate_event (4, "caps", + "media-type", "application/x-rtp", + "ssrc", ssrc, + NULL); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* validate that an upstream event different from GstRTPCollision is succesfully + * forwarded to upstream elements. in this test a caps reconfiguration event is + * pushed upstream to validate the behaviour. + */ +GST_START_TEST (rtp_base_payload_reconfigure_test) +{ + State *state; + + state = create_payloader ("application/x-rtp", &sinktmpl, NULL); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + + reconfigure (state); + + push_buffer (state, + "pts", 1 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + NULL); + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* validate that changing the mtu actually affects whether buffers are + * considered to be filled. first detect the default mtu and check that having + * buffers slightly less or equal to the size will not be considered to be + * filled, and that going over this size will be filling the buffers. then + * change the mtu slightly and validate that the boundary actually changed. + * lastly try the boundary values and make sure that they work as expected. + */ +GST_START_TEST (rtp_base_payload_property_mtu_test) +{ + State *state; + guint mtu, check; + + state = create_payloader ("application/x-rtp", &sinktmpl, NULL); + + g_object_get (state->element, "mtu", &mtu, NULL); + validate_would_not_be_filled (state, mtu - 1, GST_CLOCK_TIME_NONE); + validate_would_not_be_filled (state, mtu, GST_CLOCK_TIME_NONE); + validate_would_be_filled (state, mtu + 1, GST_CLOCK_TIME_NONE); + + g_object_set (state->element, "mtu", mtu - 1, NULL); + g_object_get (state->element, "mtu", &check, NULL); + fail_unless_equals_int (check, mtu - 1); + validate_would_not_be_filled (state, mtu - 1, GST_CLOCK_TIME_NONE); + validate_would_be_filled (state, mtu, GST_CLOCK_TIME_NONE); + validate_would_be_filled (state, mtu + 1, GST_CLOCK_TIME_NONE); + + g_object_set (state->element, "mtu", 28, NULL); + g_object_get (state->element, "mtu", &check, NULL); + fail_unless_equals_int (check, 28); + validate_would_not_be_filled (state, 28, GST_CLOCK_TIME_NONE); + validate_would_be_filled (state, 29, GST_CLOCK_TIME_NONE); + + g_object_set (state->element, "mtu", G_MAXUINT, NULL); + g_object_get (state->element, "mtu", &check, NULL); + fail_unless_equals_int (check, G_MAXUINT); + validate_would_not_be_filled (state, G_MAXUINT - 1, GST_CLOCK_TIME_NONE); + validate_would_not_be_filled (state, G_MAXUINT, GST_CLOCK_TIME_NONE); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* validate that changing the payload-type will actually affect the + * payload-type of the payloaded RTP packets. first get the default, then send + * a buffer with this payload-type. increment the payload-type and send another + * buffer. then test the boundary values for the payload-type and make sure + * that these are all carried over to the payloaded RTP packets. + */ +GST_START_TEST (rtp_base_payload_property_pt_test) +{ + State *state; + guint payload_type, check; + + state = create_payloader ("application/x-rtp", &sinktmpl, NULL); + + set_state (state, GST_STATE_PLAYING); + + g_object_get (state->element, "pt", &payload_type, NULL); + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + + g_object_set (state->element, "pt", payload_type + 1, NULL); + g_object_get (state->element, "pt", &check, NULL); + fail_unless_equals_int (check, payload_type + 1); + push_buffer (state, + "pts", 1 * GST_SECOND, + NULL); + + g_object_set (state->element, "pt", 0, NULL); + g_object_get (state->element, "pt", &check, NULL); + fail_unless_equals_int (check, 0); + push_buffer (state, + "pts", 2 * GST_SECOND, + NULL); + + g_object_set (state->element, "pt", 0x7f, NULL); + g_object_get (state->element, "pt", &check, NULL); + fail_unless_equals_int (check, 0x7f); + push_buffer (state, + "pts", 3 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (4); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "payload-type", payload_type, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "payload-type", payload_type + 1, + NULL); + + validate_buffer (2, + "pts", 2 * GST_SECOND, + "payload-type", 0, + NULL); + + validate_buffer (3, + "pts", 3 * GST_SECOND, + "payload-type", 0x7f, + NULL); + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* validate that changing the ssrc will actually affect the ssrc of the + * payloaded RTP packets. first get the current ssrc which should indicate + * random ssrcs. send two buffers and expect their ssrcs to be random but + * identical. since setting the ssrc will only take effect when the pipeline + * goes READY->PAUSED, bring the pipeline to NULL state, set the ssrc to a given + * value and make sure that this is carried over to the payloaded RTP packets. + * the last step is to test the boundary values. + */ +GST_START_TEST (rtp_base_payload_property_ssrc_test) +{ + State *state; + guint32 ssrc; + + state = create_payloader ("application/x-rtp", &sinktmpl, NULL); + + set_state (state, GST_STATE_PLAYING); + + g_object_get (state->element, "ssrc", &ssrc, NULL); + fail_unless_equals_int (ssrc, -1); + + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + + push_buffer (state, + "pts", 1 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + g_object_set (state->element, "ssrc", 0x4242, NULL); + g_object_get (state->element, "ssrc", &ssrc, NULL); + fail_unless_equals_int (ssrc, 0x4242); + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 2 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + g_object_set (state->element, "ssrc", 0, NULL); + g_object_get (state->element, "ssrc", &ssrc, NULL); + fail_unless_equals_int (ssrc, 0); + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 3 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + g_object_set (state->element, "ssrc", G_MAXUINT32, NULL); + g_object_get (state->element, "ssrc", &ssrc, NULL); + fail_unless_equals_int (ssrc, G_MAXUINT32); + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 4 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (5); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + NULL); + get_buffer_field (0, + "ssrc", &ssrc, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "ssrc", ssrc, + NULL); + + validate_buffer (2, + "pts", 2 * GST_SECOND, + "ssrc", 0x4242, + NULL); + + validate_buffer (3, + "pts", 3 * GST_SECOND, + "ssrc", 0, + NULL); + + validate_buffer (4, + "pts", 4 * GST_SECOND, + "ssrc", G_MAXUINT32, + NULL); + + validate_events_received (12); + + validate_normal_start_events (0); + + validate_normal_start_events (3); + + validate_normal_start_events (6); + + validate_normal_start_events (9); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* validate that changing the timestamp-offset will actually effect the rtptime + * of the payloaded RTP packets. unfortunately setting the timestamp-offset + * property will only take effect when the payloader goes from READY to PAUSED. + * so the test starts by making sure that the default timestamp-offset indicates + * random timestamps. then a buffer is pushed which is expected to be payloaded + * as an RTP packet with a random timestamp. then the timestamp-offset is + * modified without changing the state of the pipeline. therefore the next + * buffer pushed is expected to result in an RTP packet with a timestamp equal + * to the previous RTP packet incremented by DEFAULT_CLOCK_RATE. next the + * pipeline is brought to NULL state and the timestamp-offset is set to a + * specific value, the pipeline is then brought back to PLAYING state and the + * two buffers pushed are expected to result in payloaded RTP packets that have + * timestamps based on the set timestamp-offset incremented by multiples of + * DEFAULT_CLOCK_RATE. next the boundary values of the timestamp-offset are + * tested. again the pipeline state needs to be modified and buffers are pushed + * and the resulting payloaded RTP packets' timestamps are validated. note that + * the maximum timestamp-offset value will wrap around for the very last + * payloaded RTP packet. + */ +GST_START_TEST (rtp_base_payload_property_timestamp_offset_test) +{ + guint32 rtptime; + guint32 offset; + State *state; + + state = create_payloader ("application/x-rtp", &sinktmpl, NULL); + + set_state (state, GST_STATE_PLAYING); + + g_object_get (state->element, "timestamp-offset", &offset, NULL); + fail_unless_equals_int (offset, -1); + + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + + g_object_set (state->element, "timestamp-offset", 0x42, NULL); + g_object_get (state->element, "timestamp-offset", &offset, NULL); + fail_unless_equals_int (offset, 0x42); + push_buffer (state, + "pts", 1 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + g_object_set (state->element, "timestamp-offset", 0x4242, NULL); + g_object_get (state->element, "timestamp-offset", &offset, NULL); + fail_unless_equals_int (offset, 0x4242); + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 2 * GST_SECOND, + NULL); + + push_buffer (state, + "pts", 3 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + g_object_set (state->element, "timestamp-offset", 0, NULL); + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 4 * GST_SECOND, + NULL); + + push_buffer (state, + "pts", 5 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + g_object_set (state->element, "timestamp-offset", G_MAXUINT32, NULL); + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 6 * GST_SECOND, + NULL); + + push_buffer (state, + "pts", 7 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (8); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + NULL); + get_buffer_field (0, + "rtptime", &rtptime, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "rtptime", rtptime + 1 * DEFAULT_CLOCK_RATE, + NULL); + + validate_buffer (2, + "pts", 2 * GST_SECOND, + "rtptime", 0x4242 + 2 * DEFAULT_CLOCK_RATE, + NULL); + + validate_buffer (3, + "pts", 3 * GST_SECOND, + "rtptime", 0x4242 + 3 * DEFAULT_CLOCK_RATE, + NULL); + + validate_buffer (4, + "pts", 4 * GST_SECOND, + "rtptime", 0 + 4 * DEFAULT_CLOCK_RATE, + NULL); + + validate_buffer (5, + "pts", 5 * GST_SECOND, + "rtptime", 0 + 5 * DEFAULT_CLOCK_RATE, + NULL); + + validate_buffer (6, + "pts", 6 * GST_SECOND, + "rtptime", G_MAXUINT32 + 6 * DEFAULT_CLOCK_RATE, + NULL); + + validate_buffer (7, + "pts", 7 * GST_SECOND, + "rtptime", 7 * DEFAULT_CLOCK_RATE - 1, + NULL); + + validate_events_received (12); + + validate_normal_start_events (0); + + validate_normal_start_events (3); + + validate_normal_start_events (6); + + validate_normal_start_events (9); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* as for timestamp-offset above setting the seqnum-offset property of a + * payloader will only take effect when the payloader goes from READY to PAUSED + * state. this test starts by validating that seqnum-offset indicates random + * sequence numbers and that the random sequence numbers increment by one for + * each payloaded RTP packet. also it is verified that setting seqnum-offset + * without bringing the pipeline to READY will not affect the payloaded RTP + * packets' sequence numbers. next the pipeline is brought to NULL state, + * seqnum-offset is set to a specific value before bringing the pipeline back to + * PLAYING state. the next two buffers pushed are expected to resulting in + * payloaded RTP packets that start with sequence numbers relating to the set + * seqnum-offset value, and that again increment by one for each packet. finally + * the boundary values of seqnum-offset are tested. this means bringing the + * pipeline to NULL state, setting the seqnum-offset and bringing the pipeline + * back to PLAYING state. note that for the very last payloded RTP packet the + * sequence number will have wrapped around because the previous packet is + * expected to have the maximum sequence number value. + */ +GST_START_TEST (rtp_base_payload_property_seqnum_offset_test) +{ + State *state; + guint16 seq; + gint offset; + + state = create_payloader ("application/x-rtp", &sinktmpl, NULL); + + set_state (state, GST_STATE_PLAYING); + + g_object_get (state->element, "seqnum-offset", &offset, NULL); + fail_unless_equals_int (offset, -1); + + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + + g_object_set (state->element, "seqnum-offset", 0x42, NULL); + g_object_get (state->element, "seqnum-offset", &offset, NULL); + fail_unless_equals_int (offset, 0x42); + push_buffer (state, + "pts", 1 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + g_object_set (state->element, "seqnum-offset", 0x4242, NULL); + g_object_get (state->element, "seqnum-offset", &offset, NULL); + fail_unless_equals_int (offset, 0x4242); + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 2 * GST_SECOND, + NULL); + + push_buffer (state, + "pts", 3 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + g_object_set (state->element, "seqnum-offset", -1, NULL); + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 4 * GST_SECOND, + NULL); + + push_buffer (state, + "pts", 5 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + g_object_set (state->element, "seqnum-offset", G_MAXUINT16, NULL); + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 6 * GST_SECOND, + NULL); + + push_buffer (state, + "pts", 7 * GST_SECOND, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (8); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + NULL); + get_buffer_field (0, + "seq", &seq, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "seq", seq + 1, + NULL); + + validate_buffer (2, + "pts", 2 * GST_SECOND, + "seq", 0x4242, + NULL); + + validate_buffer (3, + "pts", 3 * GST_SECOND, + "seq", 0x4242 + 1, + NULL); + + validate_buffer (4, + "pts", 4 * GST_SECOND, + NULL); + get_buffer_field (4, + "seq", &seq, + NULL); + + validate_buffer (5, + "pts", 5 * GST_SECOND, + "seq", seq + 1, + NULL); + + validate_buffer (6, + "pts", 6 * GST_SECOND, + "seq", G_MAXUINT16, + NULL); + + validate_buffer (7, + "pts", 7 * GST_SECOND, + "seq", 0, + NULL); + + validate_events_received (12); + + validate_normal_start_events (0); + + validate_normal_start_events (3); + + validate_normal_start_events (6); + + validate_normal_start_events (9); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* a payloader's max-ptime property is linked to its MTU property. whenever a + * packet is larger than MTU or has a duration longer than max-ptime it will be + * considered to be full. so this test first validates that the default value of + * max-ptime is unspecified. then it retrieves the MTU and validates that a + * packet of size MTU will not be considered full even if the duration is at its + * maximum value. however incrementing the size to exceed the MTU will result in + * the packet being full. next max-ptime is set to a value and it is verified + * that only if both the size and duration are below the allowed values then the + * packet will be considered not to be full, otherwise it will be reported as + * being full. finally the boundary values of the property are tested in a + * similar fashion. + */ +GST_START_TEST (rtp_base_payload_property_max_ptime_test) +{ + gint64 max_ptime; + State *state; + guint mtu; + + state = create_payloader ("application/x-rtp", &sinktmpl, NULL); + + g_object_get (state->element, "max-ptime", &max_ptime, NULL); + fail_unless_equals_int64 (max_ptime, -1); + g_object_get (state->element, "mtu", &mtu, NULL); + validate_would_not_be_filled (state, mtu, G_MAXINT64 - 1); + validate_would_be_filled (state, mtu + 1, G_MAXINT64 - 1); + + g_object_set (state->element, "max-ptime", GST_SECOND, NULL); + g_object_get (state->element, "max-ptime", &max_ptime, NULL); + fail_unless_equals_int64 (max_ptime, GST_SECOND); + validate_would_not_be_filled (state, mtu, GST_SECOND - 1); + validate_would_be_filled (state, mtu, GST_SECOND); + validate_would_be_filled (state, mtu + 1, GST_SECOND - 1); + validate_would_be_filled (state, mtu + 1, GST_SECOND); + + g_object_set (state->element, "max-ptime", G_GUINT64_CONSTANT (-1), NULL); + g_object_get (state->element, "max-ptime", &max_ptime, NULL); + fail_unless_equals_int64 (max_ptime, G_GUINT64_CONSTANT (-1)); + validate_would_not_be_filled (state, mtu, G_MAXINT64 - 1); + validate_would_be_filled (state, mtu + 1, G_MAXINT64 - 1); + + g_object_set (state->element, "max-ptime", G_MAXINT64, NULL); + g_object_get (state->element, "max-ptime", &max_ptime, NULL); + fail_unless_equals_int64 (max_ptime, G_MAXINT64); + validate_would_be_filled (state, mtu, G_MAXINT64); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* a basepayloader has a min-ptime property with an allowed range, the property + * itself is never checked by the payloader but is meant to be used by + * inheriting classes. therefore this test only validates that setting the + * property will mean that retrieveing the property results in the value + * previously being set. first the default value is validated, then a new + * specific value, before finally testing the boundary values. + */ +GST_START_TEST (rtp_base_payload_property_min_ptime_test) +{ + State *state; + guint64 reference, min_ptime; + + state = create_payloader ("application/x-rtp", &sinktmpl, NULL); + + g_object_get (state->element, "min-ptime", &reference, NULL); + fail_unless_equals_int (reference, 0); + + g_object_set (state->element, "min-ptime", reference + 1, NULL); + g_object_get (state->element, "min-ptime", &min_ptime, NULL); + fail_unless_equals_int (min_ptime, reference + 1); + + g_object_set (state->element, "min-ptime", G_GUINT64_CONSTANT (0), NULL); + g_object_get (state->element, "min-ptime", &min_ptime, NULL); + fail_unless_equals_int (min_ptime, 0); + + g_object_set (state->element, "min-ptime", G_MAXINT64, NULL); + g_object_get (state->element, "min-ptime", &min_ptime, NULL); + fail_unless_equals_int64 (min_ptime, G_MAXINT64); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* paylaoders have a timestamp property that reflects the timestamp of the last + * payloaded RTP packet. in this test the timestamp-offset is set to a specific + * value so that when the first buffer is pushed its timestamp can be predicted + * and thus that the timestamp property also has this value. (if + * timestamp-offset was not set the timestamp would be random). another buffer + * is then pushed and its timestamp is expected to increment by + * DEFAULT_CLOCK_RATE. + */ +GST_START_TEST (rtp_base_payload_property_timestamp_test) +{ + State *state; + guint32 timestamp; + + state = create_payloader ("application/x-rtp", &sinktmpl, + "timestamp-offset", 0, + NULL); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + g_object_get (state->element, "timestamp", ×tamp, NULL); + fail_unless_equals_int (timestamp, 0); + + push_buffer (state, + "pts", 1 * GST_SECOND, + NULL); + g_object_get (state->element, "timestamp", ×tamp, NULL); + fail_unless_equals_int (timestamp, DEFAULT_CLOCK_RATE); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "rtptime", 0, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "rtptime", DEFAULT_CLOCK_RATE, + NULL); + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* basepayloaders have a seqnum property that is supposed to contain the + * sequence number of the last payloaded RTP packet. so therefore this test + * initializes the seqnum-offset property to a know value and pushes a buffer. + * the payloaded RTP packet is expected to have a sequence number equal to the + * set seqnum-offset, as is the seqnum property. next another buffer is pushed + * and then both the payloaded RTP packet and the seqnum property value are + * expected to increment by one compared to the previous packet. + */ +GST_START_TEST (rtp_base_payload_property_seqnum_test) +{ + State *state; + guint seq; + + state = create_payloader ("application/x-rtp", &sinktmpl, + "seqnum-offset", 0, NULL); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + g_object_get (state->element, "seqnum", &seq, NULL); + fail_unless_equals_int (seq, 0); + + push_buffer (state, + "pts", 1 * GST_SECOND, + NULL); + g_object_get (state->element, "seqnum", &seq, NULL); + fail_unless_equals_int (seq, 1); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (2); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "seq", 0, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "seq", 1, + NULL); + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* basepayloader has a perfect-rtptime property when it is set to FALSE + * the timestamps of payloaded RTP packets will determined by initial + * timestamp-offset (usually random) as well as the clock-rate. when + * perfect-rtptime is set to TRUE the timestamps of payloaded RTP packets are + * instead determined by the timestamp of the first packet and then the + * difference in offset of the input buffers. + * + * to verify that this test starts by setting the timestamp-offset to a specific + * value to prevent random timestamps of the RTP packets. next perfect-rtptime + * is set to FALSE. the two buffers pushed will result in two payloaded RTP + * packets whose timestamps differ based on the current clock-rate + * DEFAULT_CLOCK_RATE. the next step is to set perfect-rtptime to TRUE. the two + * buffers that are pushed will result in two payloaded RTP packets. the first + * of these RTP packets has a timestamp that relates to the previous packet and + * the difference in offset between the middle two input buffers. the latter of + * the two RTP packets has a timestamp that instead relates to the offset of the + * last two input buffers. + */ +GST_START_TEST (rtp_base_payload_property_perfect_rtptime_test) +{ + State *state; + guint32 timestamp_base = 0; + gboolean perfect; + + state = create_payloader ("application/x-rtp", &sinktmpl, + "timestamp-offset", timestamp_base, + NULL); + + set_state (state, GST_STATE_PLAYING); + + g_object_set (state->element, "perfect-rtptime", FALSE, NULL); + g_object_get (state->element, "perfect-rtptime", &perfect, NULL); + fail_unless (!perfect); + + push_buffer (state, + "pts", 0 * GST_SECOND, + "offset", 0, + NULL); + + push_buffer (state, + "pts", 1 * GST_SECOND, + "offset", 17, + NULL); + + g_object_set (state->element, "perfect-rtptime", TRUE, NULL); + g_object_get (state->element, "perfect-rtptime", &perfect, NULL); + fail_unless (perfect); + + push_buffer (state, + "pts", 2 * GST_SECOND, + "offset", 31, + NULL); + + push_buffer (state, + "pts", 3 * GST_SECOND, + "offset", 67, + NULL); + + set_state (state, GST_STATE_NULL); + + validate_buffers_received (4); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + "offset", 0, + "rtptime", timestamp_base, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + "offset", 17, + "rtptime", timestamp_base + 1 * DEFAULT_CLOCK_RATE, + NULL); + + validate_buffer (2, + "pts", 2 * GST_SECOND, + "offset", 31, + "rtptime", timestamp_base + 1 * DEFAULT_CLOCK_RATE + (31 - 17), + NULL); + + validate_buffer (3, + "pts", 3 * GST_SECOND, + "offset", 67, + "rtptime", timestamp_base + 1 * DEFAULT_CLOCK_RATE + (67 - 17), + NULL); + + validate_events_received (3); + + validate_normal_start_events (0); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* basepayloaders have a ptime-multiple property but its value does not affect + * any payloaded RTP packets as this is supposed to be done by inherited + * classes. therefore this test only validates the default value of the + * property, makes sure that a set value actually sticks and that the boundary + * values are indeed allowed to be set. + */ +GST_START_TEST (rtp_base_payload_property_ptime_multiple_test) +{ + State *state; + gint64 multiple; + + state = create_payloader ("application/x-rtp", &sinktmpl, NULL); + + g_object_get (state->element, "ptime-multiple", &multiple, NULL); + fail_unless_equals_int64 (multiple, 0); + + g_object_set (state->element, "ptime-multiple", 42, NULL); + g_object_get (state->element, "ptime-multiple", &multiple, NULL); + fail_unless_equals_int64 (multiple, 42); + + g_object_set (state->element, "ptime-multiple", 0, NULL); + g_object_get (state->element, "ptime-multiple", &multiple, NULL); + fail_unless_equals_int64 (multiple, 0); + + g_object_set (state->element, "ptime-multiple", G_MAXINT64, NULL); + g_object_get (state->element, "ptime-multiple", &multiple, NULL); + fail_unless_equals_int64 (multiple, G_MAXINT64); + + destroy_payloader (state); +} + +GST_END_TEST; + +/* basepayloaders have a property called stats that is used to atomically + * retrieve several values (clock-rate, running-time, seqnum and timestamp) that + * relate to the stream and its current progress. this test is meant to test + * retrieval of these values. + * + * first of all perfect-rtptime is set to TRUE, next the the test starts out by + * setting seqnum-offset and timestamp-offset to known values to prevent that + * sequence numbers and timestamps of payloaded RTP packets are random. next the + * stats property is retrieved. the clock-rate must be at the default + * DEFAULT_CLOCK_RATE, while running-time must be equal to the first buffers + * PTS. the sequence number should be equal to the initialized value of + * seqnum-offset and the timestamp should be equal to the initialized value of + * timestamp-offset. after pushing a second buffer the stats property is + * validate again. this time running-time, seqnum and timestamp should have + * advanced as expected. next the pipeline is brought to NULL state to be able + * to change the perfect-rtptime property to FALSE before going back to PLAYING + * state. this is done to validate that the stats values reflect normal + * timestamp updates that are not based on input buffer offsets as expected. + * lastly two buffers are pushed and the stats property retrieved after each + * time. here it is expected that the sequence numbers values are restarted at + * the inital value while the timestamps and running-time reflect the input + * buffers. + */ +GST_START_TEST (rtp_base_payload_property_stats_test) +{ + State *state; + + state = create_payloader ("application/x-rtp", &sinktmpl, + "perfect-rtptime", TRUE, + "seqnum-offset", 0, + "timestamp-offset", 0, + NULL); + + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 0 * GST_SECOND, + NULL); + validate_stats (state, + DEFAULT_CLOCK_RATE, + 0 * GST_SECOND, + 0, + 0 * DEFAULT_CLOCK_RATE); + + push_buffer (state, + "pts", 1 * GST_SECOND, + NULL); + validate_stats (state, + DEFAULT_CLOCK_RATE, + 1 * DEFAULT_CLOCK_RATE, + 1, + 1 * DEFAULT_CLOCK_RATE); + + set_state (state, GST_STATE_NULL); + g_object_set (state->element, "perfect-rtptime", FALSE, NULL); + set_state (state, GST_STATE_PLAYING); + + push_buffer (state, + "pts", 2 * GST_SECOND, + NULL); + validate_stats (state, + DEFAULT_CLOCK_RATE, + 2 * GST_SECOND, + 0, + 2 * DEFAULT_CLOCK_RATE); + + push_buffer (state, + "pts", 3 * GST_SECOND, + NULL); + validate_stats (state, + DEFAULT_CLOCK_RATE, + 3 * GST_SECOND, + 1, + 3 * DEFAULT_CLOCK_RATE); + set_state (state, GST_STATE_NULL); + + validate_buffers_received (4); + + validate_buffer (0, + "pts", 0 * GST_SECOND, + NULL); + + validate_buffer (1, + "pts", 1 * GST_SECOND, + NULL); + + validate_buffer (2, + "pts", 2 * GST_SECOND, + NULL); + + validate_buffer (3, + "pts", 3 * GST_SECOND, + NULL); + + validate_events_received (6); + + validate_normal_start_events (0); + + validate_normal_start_events (3); + + destroy_payloader (state); +} + +GST_END_TEST; + +static Suite * +rtp_basepayloading_suite (void) +{ + Suite *s = suite_create ("rtp_base_payloading_test"); + TCase *tc_chain = tcase_create ("payloading tests"); + + tcase_set_timeout (tc_chain, 60); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, rtp_base_payload_buffer_test); + tcase_add_test (tc_chain, rtp_base_payload_buffer_list_test); + + tcase_add_test (tc_chain, rtp_base_payload_normal_rtptime_test); + tcase_add_test (tc_chain, rtp_base_payload_perfect_rtptime_test); + tcase_add_test (tc_chain, rtp_base_payload_no_pts_no_offset_test); + + tcase_add_test (tc_chain, rtp_base_payload_downstream_caps_test); + + tcase_add_test (tc_chain, rtp_base_payload_ssrc_collision_test); + tcase_add_test (tc_chain, rtp_base_payload_reconfigure_test); + + tcase_add_test (tc_chain, rtp_base_payload_property_mtu_test); + tcase_add_test (tc_chain, rtp_base_payload_property_pt_test); + tcase_add_test (tc_chain, rtp_base_payload_property_ssrc_test); + tcase_add_test (tc_chain, rtp_base_payload_property_timestamp_offset_test); + tcase_add_test (tc_chain, rtp_base_payload_property_seqnum_offset_test); + tcase_add_test (tc_chain, rtp_base_payload_property_max_ptime_test); + tcase_add_test (tc_chain, rtp_base_payload_property_min_ptime_test); + tcase_add_test (tc_chain, rtp_base_payload_property_timestamp_test); + tcase_add_test (tc_chain, rtp_base_payload_property_seqnum_test); + tcase_add_test (tc_chain, rtp_base_payload_property_perfect_rtptime_test); + tcase_add_test (tc_chain, rtp_base_payload_property_ptime_multiple_test); + tcase_add_test (tc_chain, rtp_base_payload_property_stats_test); + + return s; +} + +GST_CHECK_MAIN (rtp_basepayloading) -- 2.7.4