tests: Refactor RTP basepayloading test into pay/depay parts
authorSebastian Rasmussen <sebrn@axis.com>
Thu, 30 Jan 2014 23:06:18 +0000 (00:06 +0100)
committerWim Taymans <wtaymans@redhat.com>
Mon, 24 Feb 2014 11:12:18 +0000 (12:12 +0100)
Fixes https://bugzilla.gnome.org/show_bug.cgi?id=723328

tests/check/Makefile.am
tests/check/libs/.gitignore
tests/check/libs/rtp-basepayloading.c [deleted file]
tests/check/libs/rtpbasedepayload.c [new file with mode: 0644]
tests/check/libs/rtpbasepayload.c [new file with mode: 0644]

index d7ca820..1d4b7ef 100644 (file)
@@ -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)
 
index cc7566a..ab9f68a 100644 (file)
@@ -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 (file)
index c3c6c94..0000000
+++ /dev/null
@@ -1,412 +0,0 @@
-/* GStreamer RTP base payloader unit tests
- * Copyright (C) 2013 Sebastian Rasmussen <sebras@hotmail.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General
- * Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include <gst/gst.h>
-#include <gst/check/gstcheck.h>
-#include <gst-libs/gst/rtp/gstrtpbuffer.h>
-#include <gst-libs/gst/rtp/gstrtpbasepayload.h>
-#include <gst-libs/gst/rtp/gstrtpbasedepayload.h>
-
-/* 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", &timestamp));
-
-  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 (file)
index 0000000..71a1d85
--- /dev/null
@@ -0,0 +1,1247 @@
+/* GStreamer RTP base depayloader unit tests
+ * Copyright (C) 2014 Sebastian Rasmussen <sebras@hotmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General
+ * Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <gst/gst.h>
+#include <gst/check/gstcheck.h>
+#include <gst-libs/gst/rtp/gstrtpbuffer.h>
+#include <gst-libs/gst/rtp/gstrtpbasedepayload.h>
+
+#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, &timestamp, &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, &timestamp, &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 (file)
index 0000000..021345d
--- /dev/null
@@ -0,0 +1,2073 @@
+/* GStreamer RTP base payloader unit tests
+ * Copyright (C) 2014  Sebastian Rasmussen <sebras@hotmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General
+ * Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <gst/gst.h>
+#include <gst/check/gstcheck.h>
+#include <gst-libs/gst/rtp/gstrtpbuffer.h>
+#include <gst-libs/gst/rtp/gstrtpbasepayload.h>
+
+#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, &timestamp, &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, &timestamp, &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", &timestamp, NULL);
+  fail_unless_equals_int (timestamp, 0);
+
+  push_buffer (state,
+      "pts", 1 * GST_SECOND,
+      NULL);
+  g_object_get (state->element, "timestamp", &timestamp, 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)