rtpmanager: Update codes based on 1.18.4 01/248401/4
authorSangchul Lee <sc11.lee@samsung.com>
Wed, 25 Nov 2020 07:28:04 +0000 (16:28 +0900)
committerSangchul Lee <sc11.lee@samsung.com>
Thu, 22 Apr 2021 08:14:21 +0000 (08:14 +0000)
Use #ifndef/#endif directives with TIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK
to avoid build breaks from the gstreamer core not updated.
It can be removed when the gtreamer core is updated to 1.18 or higher version.

Change-Id: I8bb3b97922e3e1cb5231f246e4f54a781fe6ba3e
Signed-off-by: Sangchul Lee <sc11.lee@samsung.com>
30 files changed:
gst/rtpmanager/Makefile.am
gst/rtpmanager/gstrtpbin.c
gst/rtpmanager/gstrtpbin.h
gst/rtpmanager/gstrtpdtmfmux.c
gst/rtpmanager/gstrtpfunnel.c
gst/rtpmanager/gstrtpjitterbuffer.c
gst/rtpmanager/gstrtpmux.c
gst/rtpmanager/gstrtpmux.h
gst/rtpmanager/gstrtpptdemux.c
gst/rtpmanager/gstrtpptdemux.h
gst/rtpmanager/gstrtprtxqueue.c
gst/rtpmanager/gstrtprtxreceive.c
gst/rtpmanager/gstrtprtxsend.c
gst/rtpmanager/gstrtprtxsend.h
gst/rtpmanager/gstrtpsession.c
gst/rtpmanager/gstrtpssrcdemux.c
gst/rtpmanager/gstrtpssrcdemux.h
gst/rtpmanager/meson.build
gst/rtpmanager/rtpjitterbuffer.c
gst/rtpmanager/rtpjitterbuffer.h
gst/rtpmanager/rtpsession.c
gst/rtpmanager/rtpsession.h
gst/rtpmanager/rtpsource.c
gst/rtpmanager/rtpstats.c
gst/rtpmanager/rtpstats.h
gst/rtpmanager/rtptimerqueue.c [new file with mode: 0644]
gst/rtpmanager/rtptimerqueue.h [new file with mode: 0644]
gst/rtpmanager/rtptwcc.c [new file with mode: 0644]
gst/rtpmanager/rtptwcc.h [new file with mode: 0644]
packaging/gst-plugins-good.spec

index 0defa28..82e2293 100644 (file)
@@ -15,7 +15,9 @@ libgstrtpmanager_la_SOURCES = gstrtpmanager.c \
                              rtpsource.c      \
                              rtpstats.c      \
                              gstrtpsession.c \
-                             gstrtpfunnel.c
+                             gstrtpfunnel.c \
+                             rtptimerqueue.c \
+                             rtptwcc.c
 
 noinst_HEADERS = gstrtpbin.h \
                 gstrtpdtmfmux.h \
@@ -31,7 +33,9 @@ noinst_HEADERS = gstrtpbin.h \
                 rtpsource.h  \
                 rtpstats.h  \
                 gstrtpsession.h \
-                gstrtpfunnel.h
+                gstrtpfunnel.h \
+                rtptimerqueue.h \
+                rtptwcc.h
 
 
 libgstrtpmanager_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) \
index 0583481..4dacde9 100644 (file)
@@ -1,24 +1,25 @@
- /* GStreamer
 * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.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.
 */
+/* GStreamer
+ * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.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.
+ */
 
 /**
  * SECTION:element-rtpbin
+ * @title: rtpbin
  * @see_also: rtpjitterbuffer, rtpsession, rtpptdemux, rtpssrcdemux
  *
  * RTP bin combines the functions of #GstRtpSession, #GstRtpSsrcDemux,
@@ -50,7 +51,7 @@
  * To use #GstRtpBin as a sender, request a send_rtp_sink_\%u pad, which will
  * automatically create a send_rtp_src_\%u pad. If the session number is not provided,
  * the pad from the lowest available session will be returned. The session manager will modify the
- * SSRC in the RTP packets to its own SSRC and wil forward the packets on the
+ * SSRC in the RTP packets to its own SSRC and will forward the packets on the
  * send_rtp_src_\%u pad after updating its internal state.
  *
  * The session manager needs the clock-rate of the payload types it is handling
  * An AUX receiver has 1 src_\%u pad that much match the sessionid in the signal
  * and 1 or more sink_\%u pads. A session will be made for each sink_\%u pad
  * when the corresponding recv_rtp_sink_\%u pad is requested on #GstRtpBin.
+ * The #GstRtpBin::request-jitterbuffer signal can be used to provide a custom
+ * element to perform arrival time smoothing, reordering and optionally packet
+ * loss detection and retransmission requests.
+ *
+ * ## Example pipelines
  *
- * <refsect2>
- * <title>Example pipelines</title>
  * |[
  * gst-launch-1.0 udpsrc port=5000 caps="application/x-rtp, ..." ! .recv_rtp_sink_0 \
  *     rtpbin ! rtptheoradepay ! theoradec ! xvimagesink
  * synchronisation.
  * Send RTCP reports for session 0 on port 5005 and RTCP reports for session 1
  * on port 5007.
- * </refsect2>
+ *
  */
 
 #ifdef HAVE_CONFIG_H
@@ -274,6 +278,8 @@ enum
   SIGNAL_REQUEST_FEC_DECODER,
   SIGNAL_REQUEST_FEC_ENCODER,
 
+  SIGNAL_REQUEST_JITTERBUFFER,
+
   SIGNAL_NEW_JITTERBUFFER,
   SIGNAL_NEW_STORAGE,
 
@@ -389,10 +395,12 @@ complete_session_receiver (GstRtpBin * rtpbin, GstRtpBinSession * session,
     guint sessid);
 static GstPad *complete_session_rtcp (GstRtpBin * rtpbin,
     GstRtpBinSession * session, guint sessid);
+static GstElement *session_request_element (GstRtpBinSession * session,
+    guint signal);
 
 /* Manages the RTP stream for one SSRC.
  *
- * We pipe the stream (comming from the SSRC demuxer) into a jitterbuffer.
+ * We pipe the stream (coming from the SSRC demuxer) into a jitterbuffer.
  * If we see an SDES RTCP packet that links multiple SSRCs together based on a
  * common CNAME, we create a GstRtpBinClient structure to group the SSRCs
  * together (see below).
@@ -701,6 +709,7 @@ create_session (GstRtpBin * rtpbin, gint id)
 
   /* configure SDES items */
   GST_OBJECT_LOCK (rtpbin);
+  g_object_set (demux, "max-streams", rtpbin->max_streams, NULL);
   g_object_set (session, "sdes", rtpbin->sdes, "rtp-profile",
       rtpbin->rtp_profile, "rtcp-sync-send-time", rtpbin->rtcp_sync_send_time,
       NULL);
@@ -858,6 +867,7 @@ free_session (GstRtpBinSession * sess, GstRtpBin * bin)
 
   g_slist_foreach (sess->elements, (GFunc) remove_bin_element, bin);
   g_slist_free (sess->elements);
+  sess->elements = NULL;
 
   g_slist_foreach (sess->streams, (GFunc) free_stream, bin);
   g_slist_free (sess->streams);
@@ -890,7 +900,7 @@ get_pt_map (GstRtpBinSession * session, guint pt)
 
   bin = session->bin;
 
-  GST_DEBUG ("emiting signal for pt %u in session %u", pt, session->id);
+  GST_DEBUG ("emitting signal for pt %u in session %u", pt, session->id);
 
   /* not in cache, send signal to request caps */
   g_value_init (&args[0], GST_TYPE_ELEMENT);
@@ -998,7 +1008,8 @@ gst_rtp_bin_clear_pt_map (GstRtpBin * bin)
       GstRtpBinStream *stream = (GstRtpBinStream *) streams->data;
 
       GST_DEBUG_OBJECT (bin, "clearing stream %p", stream);
-      g_signal_emit_by_name (stream->buffer, "clear-pt-map", NULL);
+      if (g_signal_lookup ("clear-pt-map", G_OBJECT_TYPE (stream->buffer)) != 0)
+        g_signal_emit_by_name (stream->buffer, "clear-pt-map", NULL);
       if (stream->demux)
         g_signal_emit_by_name (stream->demux, "clear-pt-map", NULL);
     }
@@ -1097,6 +1108,12 @@ gst_rtp_bin_request_decoder (GstRtpBin * bin, guint session_id)
   return NULL;
 }
 
+static GstElement *
+gst_rtp_bin_request_jitterbuffer (GstRtpBin * bin, guint session_id)
+{
+  return gst_element_factory_make ("rtpjitterbuffer", NULL);
+}
+
 static void
 gst_rtp_bin_propagate_property_to_jitterbuffer (GstRtpBin * bin,
     const gchar * name, const GValue * value)
@@ -1110,8 +1127,14 @@ gst_rtp_bin_propagate_property_to_jitterbuffer (GstRtpBin * bin,
     GST_RTP_SESSION_LOCK (session);
     for (streams = session->streams; streams; streams = g_slist_next (streams)) {
       GstRtpBinStream *stream = (GstRtpBinStream *) streams->data;
+      GObjectClass *jb_class;
 
-      g_object_set_property (G_OBJECT (stream->buffer), name, value);
+      jb_class = G_OBJECT_GET_CLASS (G_OBJECT (stream->buffer));
+      if (g_object_class_find_property (jb_class, name))
+        g_object_set_property (G_OBJECT (stream->buffer), name, value);
+      else
+        GST_WARNING_OBJECT (bin,
+            "Stream jitterbuffer does not expose property %s", name);
     }
     GST_RTP_SESSION_UNLOCK (session);
   }
@@ -1201,11 +1224,8 @@ get_current_times (GstRtpBin * bin, GstClockTime * running_time,
       switch (bin->ntp_time_source) {
         case GST_RTP_NTP_TIME_SOURCE_NTP:
         case GST_RTP_NTP_TIME_SOURCE_UNIX:{
-          GTimeVal current;
-
           /* get current NTP time */
-          g_get_current_time (&current);
-          ntpns = GST_TIMEVAL_TO_TIME (current);
+          ntpns = g_get_real_time () * GST_USECOND;
 
           /* add constant to convert from 1970 based time to 1900 based time */
           if (bin->ntp_time_source == GST_RTP_NTP_TIME_SOURCE_NTP)
@@ -1243,6 +1263,15 @@ stream_set_ts_offset (GstRtpBin * bin, GstRtpBinStream * stream,
     gboolean allow_positive_ts_offset)
 {
   gint64 prev_ts_offset;
+  GObjectClass *jb_class;
+
+  jb_class = G_OBJECT_GET_CLASS (G_OBJECT (stream->buffer));
+
+  if (!g_object_class_find_property (jb_class, "ts-offset")) {
+    GST_LOG_OBJECT (bin,
+        "stream's jitterbuffer does not expose ts-offset property");
+    return;
+  }
 
   g_object_get (stream->buffer, "ts-offset", &prev_ts_offset, NULL);
 
@@ -1391,7 +1420,7 @@ gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream, guint8 len,
     /* For NTP sync we need to first get a snapshot of running_time and NTP
      * time. We know at what running_time we play a certain RTP time, we also
      * calculated when we would play the RTP time in the SR packet. Now we need
-     * to know how the running_time and the NTP time relate to eachother. */
+     * to know how the running_time and the NTP time relate to each other. */
     get_current_times (bin, &local_running_time, &local_ntpnstime);
 
     /* see how far away the NTP time is. This is the difference between the
@@ -1432,9 +1461,9 @@ gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream, guint8 len,
     /* calculate the min of all deltas, ignoring streams that did not yet have a
      * valid rt_delta because we did not yet receive an SR packet for those
      * streams.
-     * We calculate the mininum because we would like to only apply positive
+     * We calculate the minimum because we would like to only apply positive
      * offsets to streams, delaying their playback instead of trying to speed up
-     * other streams (which might be imposible when we have to create negative
+     * other streams (which might be impossible when we have to create negative
      * latencies).
      * The stream that has the smallest diff is selected as the reference stream,
      * all other streams will have a positive offset to this difference. */
@@ -1447,7 +1476,7 @@ gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream, guint8 len,
       guint64 ext_base;
 
       use_rtp = TRUE;
-      /* signed version for convienience */
+      /* signed version for convenience */
       clock_base = base_rtptime;
       /* deal with possible wrap-around */
       ext_base = base_rtptime;
@@ -1704,13 +1733,15 @@ create_stream (GstRtpBinSession * session, guint32 ssrc)
   GstRtpBinStream *stream;
   GstRtpBin *rtpbin;
   GstState target;
+  GObjectClass *jb_class;
 
   rtpbin = session->bin;
 
   if (g_slist_length (session->streams) >= rtpbin->max_streams)
     goto max_streams;
 
-  if (!(buffer = gst_element_factory_make ("rtpjitterbuffer", NULL)))
+  if (!(buffer =
+          session_request_element (session, SIGNAL_REQUEST_JITTERBUFFER)))
     goto no_jitterbuffer;
 
   if (!rtpbin->ignore_pt) {
@@ -1724,11 +1755,12 @@ create_stream (GstRtpBinSession * session, guint32 ssrc)
       goto no_queue2;
   }
 #endif
+
   stream = g_new0 (GstRtpBinStream, 1);
   stream->ssrc = ssrc;
   stream->bin = rtpbin;
   stream->session = session;
-  stream->buffer = buffer;
+  stream->buffer = gst_object_ref (buffer);
   stream->demux = demux;
 
   stream->have_sync = FALSE;
@@ -1741,28 +1773,44 @@ create_stream (GstRtpBinSession * session, guint32 ssrc)
   stream->clock_base = -100 * GST_SECOND;
   session->streams = g_slist_prepend (session->streams, stream);
 
-  /* provide clock_rate to the jitterbuffer when needed */
-  stream->buffer_ptreq_sig = g_signal_connect (buffer, "request-pt-map",
-      (GCallback) pt_map_requested, session);
-  stream->buffer_ntpstop_sig = g_signal_connect (buffer, "on-npt-stop",
-      (GCallback) on_npt_stop, stream);
+  jb_class = G_OBJECT_GET_CLASS (G_OBJECT (buffer));
+
+  if (g_signal_lookup ("request-pt-map", G_OBJECT_TYPE (buffer)) != 0) {
+    /* provide clock_rate to the jitterbuffer when needed */
+    stream->buffer_ptreq_sig = g_signal_connect (buffer, "request-pt-map",
+        (GCallback) pt_map_requested, session);
+  }
+  if (g_signal_lookup ("on-npt-stop", G_OBJECT_TYPE (buffer)) != 0) {
+    stream->buffer_ntpstop_sig = g_signal_connect (buffer, "on-npt-stop",
+        (GCallback) on_npt_stop, stream);
+  }
 
   g_object_set_data (G_OBJECT (buffer), "GstRTPBin.session", session);
   g_object_set_data (G_OBJECT (buffer), "GstRTPBin.stream", stream);
 
   /* configure latency and packet lost */
   g_object_set (buffer, "latency", rtpbin->latency_ms, NULL);
-  g_object_set (buffer, "drop-on-latency", rtpbin->drop_on_latency, NULL);
-  g_object_set (buffer, "do-lost", rtpbin->do_lost, NULL);
-  g_object_set (buffer, "mode", rtpbin->buffer_mode, NULL);
-  g_object_set (buffer, "do-retransmission", rtpbin->do_retransmission, NULL);
-  g_object_set (buffer, "max-rtcp-rtp-time-diff",
-      rtpbin->max_rtcp_rtp_time_diff, NULL);
-  g_object_set (buffer, "max-dropout-time", rtpbin->max_dropout_time,
-      "max-misorder-time", rtpbin->max_misorder_time, NULL);
-  g_object_set (buffer, "rfc7273-sync", rtpbin->rfc7273_sync, NULL);
-  g_object_set (buffer, "max-ts-offset-adjustment",
-      rtpbin->max_ts_offset_adjustment, NULL);
+
+  if (g_object_class_find_property (jb_class, "drop-on-latency"))
+    g_object_set (buffer, "drop-on-latency", rtpbin->drop_on_latency, NULL);
+  if (g_object_class_find_property (jb_class, "do-lost"))
+    g_object_set (buffer, "do-lost", rtpbin->do_lost, NULL);
+  if (g_object_class_find_property (jb_class, "mode"))
+    g_object_set (buffer, "mode", rtpbin->buffer_mode, NULL);
+  if (g_object_class_find_property (jb_class, "do-retransmission"))
+    g_object_set (buffer, "do-retransmission", rtpbin->do_retransmission, NULL);
+  if (g_object_class_find_property (jb_class, "max-rtcp-rtp-time-diff"))
+    g_object_set (buffer, "max-rtcp-rtp-time-diff",
+        rtpbin->max_rtcp_rtp_time_diff, NULL);
+  if (g_object_class_find_property (jb_class, "max-dropout-time"))
+    g_object_set (buffer, "max-dropout-time", rtpbin->max_dropout_time, NULL);
+  if (g_object_class_find_property (jb_class, "max-misorder-time"))
+    g_object_set (buffer, "max-misorder-time", rtpbin->max_misorder_time, NULL);
+  if (g_object_class_find_property (jb_class, "rfc7273-sync"))
+    g_object_set (buffer, "rfc7273-sync", rtpbin->rfc7273_sync, NULL);
+  if (g_object_class_find_property (jb_class, "max-ts-offset-adjustment"))
+    g_object_set (buffer, "max-ts-offset-adjustment",
+        rtpbin->max_ts_offset_adjustment, NULL);
 
 #ifdef TIZEN_FEATURE_RTSP_MODIFICATION
   /* configure queue2 to use live buffering */
@@ -1772,9 +1820,6 @@ create_stream (GstRtpBinSession * session, guint32 ssrc)
     g_object_set (queue2, "buffer-mode", GST_BUFFERING_LIVE, NULL);
   }
 #endif
-  /* need to sink the jitterbufer or otherwise signal handlers from bindings will
-   * take ownership of it and we don't own it anymore */
-  gst_object_ref_sink (buffer);
   g_signal_emit (rtpbin, gst_rtp_bin_signals[SIGNAL_NEW_JITTERBUFFER], 0,
       buffer, session->id, ssrc);
 
@@ -1786,12 +1831,6 @@ create_stream (GstRtpBinSession * session, guint32 ssrc)
     gst_bin_add (GST_BIN_CAST (rtpbin), queue2);
 #endif
 
-  gst_bin_add (GST_BIN_CAST (rtpbin), buffer);
-
-  /* unref the jitterbuffer again, the bin has a reference now and
-   * we don't need it anymore */
-  gst_object_unref (buffer);
-
   /* link stuff */
 #ifdef TIZEN_FEATURE_RTSP_MODIFICATION
   if (queue2) {
@@ -1814,9 +1853,12 @@ create_stream (GstRtpBinSession * session, guint32 ssrc)
   if (rtpbin->buffering) {
     guint64 last_out;
 
-    GST_INFO_OBJECT (rtpbin,
-        "bin is buffering, set jitterbuffer as not active");
-    g_signal_emit_by_name (buffer, "set-active", FALSE, (gint64) 0, &last_out);
+    if (g_signal_lookup ("set-active", G_OBJECT_TYPE (buffer)) != 0) {
+      GST_INFO_OBJECT (rtpbin,
+          "bin is buffering, set jitterbuffer as not active");
+      g_signal_emit_by_name (buffer, "set-active", FALSE, (gint64) 0,
+          &last_out);
+    }
   }
 
 
@@ -1840,7 +1882,7 @@ create_stream (GstRtpBinSession * session, guint32 ssrc)
   /* ERRORS */
 max_streams:
   {
-    GST_WARNING_OBJECT (rtpbin, "stream exeeds maximum (%d)",
+    GST_WARNING_OBJECT (rtpbin, "stream exceeds maximum (%d)",
         rtpbin->max_streams);
     return NULL;
   }
@@ -1870,33 +1912,37 @@ no_queue2:
 static void
 free_stream (GstRtpBinStream * stream, GstRtpBin * bin)
 {
+  GstRtpBinSession *sess = stream->session;
   GSList *clients, *next_client;
 
   GST_DEBUG_OBJECT (bin, "freeing stream %p", stream);
 
-  if (stream->demux) {
-    g_signal_handler_disconnect (stream->demux, stream->demux_newpad_sig);
-    g_signal_handler_disconnect (stream->demux, stream->demux_ptreq_sig);
-    g_signal_handler_disconnect (stream->demux, stream->demux_ptchange_sig);
-  }
-  g_signal_handler_disconnect (stream->buffer, stream->buffer_handlesync_sig);
-  g_signal_handler_disconnect (stream->buffer, stream->buffer_ptreq_sig);
-  g_signal_handler_disconnect (stream->buffer, stream->buffer_ntpstop_sig);
-
+  gst_element_set_locked_state (stream->buffer, TRUE);
   if (stream->demux)
     gst_element_set_locked_state (stream->demux, TRUE);
-  gst_element_set_locked_state (stream->buffer, TRUE);
 
+  gst_element_set_state (stream->buffer, GST_STATE_NULL);
   if (stream->demux)
     gst_element_set_state (stream->demux, GST_STATE_NULL);
-  gst_element_set_state (stream->buffer, GST_STATE_NULL);
 
-  /* now remove this signal, we need this while going to NULL because it to
-   * do some cleanups */
-  if (stream->demux)
+  if (stream->demux) {
+    g_signal_handler_disconnect (stream->demux, stream->demux_newpad_sig);
+    g_signal_handler_disconnect (stream->demux, stream->demux_ptreq_sig);
+    g_signal_handler_disconnect (stream->demux, stream->demux_ptchange_sig);
     g_signal_handler_disconnect (stream->demux, stream->demux_padremoved_sig);
+  }
+
+  if (stream->buffer_handlesync_sig)
+    g_signal_handler_disconnect (stream->buffer, stream->buffer_handlesync_sig);
+  if (stream->buffer_ptreq_sig)
+    g_signal_handler_disconnect (stream->buffer, stream->buffer_ptreq_sig);
+  if (stream->buffer_ntpstop_sig)
+    g_signal_handler_disconnect (stream->buffer, stream->buffer_ntpstop_sig);
+
+  sess->elements = g_slist_remove (sess->elements, stream->buffer);
+  remove_bin_element (stream->buffer, bin);
+  gst_object_unref (stream->buffer);
 
-  gst_bin_remove (GST_BIN_CAST (bin), stream->buffer);
   if (stream->demux)
     gst_bin_remove (GST_BIN_CAST (bin), stream->demux);
 
@@ -2016,8 +2062,8 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_REQUEST_PT_MAP] =
       g_signal_new ("request-pt-map", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, request_pt_map),
-      _gst_caps_accumulator, NULL, g_cclosure_marshal_generic, GST_TYPE_CAPS,
-      2, G_TYPE_UINT, G_TYPE_UINT);
+      _gst_caps_accumulator, NULL, NULL, GST_TYPE_CAPS, 2, G_TYPE_UINT,
+      G_TYPE_UINT);
 
     /**
    * GstRtpBin::payload-type-change:
@@ -2030,8 +2076,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_PAYLOAD_TYPE_CHANGE] =
       g_signal_new ("payload-type-change", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, payload_type_change),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
 
   /**
    * GstRtpBin::clear-pt-map:
@@ -2043,8 +2088,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_CLEAR_PT_MAP] =
       g_signal_new ("clear-pt-map", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRtpBinClass,
-          clear_pt_map), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
-      0, G_TYPE_NONE);
+          clear_pt_map), NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE);
 
   /**
    * GstRtpBin::reset-sync:
@@ -2056,8 +2100,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_RESET_SYNC] =
       g_signal_new ("reset-sync", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRtpBinClass,
-          reset_sync), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
-      0, G_TYPE_NONE);
+          reset_sync), NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE);
 
   /**
    * GstRtpBin::get-session:
@@ -2071,8 +2114,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_GET_SESSION] =
       g_signal_new ("get-session", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRtpBinClass,
-          get_session), NULL, NULL, g_cclosure_marshal_generic,
-      GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
+          get_session), NULL, NULL, NULL, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
 
   /**
    * GstRtpBin::get-internal-session:
@@ -2084,38 +2126,40 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_GET_INTERNAL_SESSION] =
       g_signal_new ("get-internal-session", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRtpBinClass,
-          get_internal_session), NULL, NULL, g_cclosure_marshal_generic,
-      RTP_TYPE_SESSION, 1, G_TYPE_UINT);
+          get_internal_session), NULL, NULL, NULL, RTP_TYPE_SESSION, 1,
+      G_TYPE_UINT);
 
   /**
    * GstRtpBin::get-internal-storage:
    * @rtpbin: the object which received the signal
    * @id: the session id
    *
-   * Request the internal RTPStorage object as #GObject in session @id.
+   * Request the internal RTPStorage object as #GObject in session @id. This
+   * is the internal storage used by the RTPStorage element, which is used to
+   * keep a backlog of received RTP packets for the session @id.
    *
    * Since: 1.14
    */
   gst_rtp_bin_signals[SIGNAL_GET_INTERNAL_STORAGE] =
       g_signal_new ("get-internal-storage", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRtpBinClass,
-          get_internal_storage), NULL, NULL, g_cclosure_marshal_generic,
-      G_TYPE_OBJECT, 1, G_TYPE_UINT);
+          get_internal_storage), NULL, NULL, NULL, G_TYPE_OBJECT, 1,
+      G_TYPE_UINT);
 
   /**
    * GstRtpBin::get-storage:
    * @rtpbin: the object which received the signal
    * @id: the session id
    *
-   * Request the RTPStorage element as #GObject in session @id.
+   * Request the RTPStorage element as #GObject in session @id. This element
+   * is used to keep a backlog of received RTP packets for the session @id.
    *
    * Since: 1.16
    */
   gst_rtp_bin_signals[SIGNAL_GET_STORAGE] =
       g_signal_new ("get-storage", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRtpBinClass,
-          get_storage), NULL, NULL, g_cclosure_marshal_generic,
-      GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
+          get_storage), NULL, NULL, NULL, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
 
   /**
    * GstRtpBin::on-new-ssrc:
@@ -2128,8 +2172,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_NEW_SSRC] =
       g_signal_new ("on-new-ssrc", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_new_ssrc),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
   /**
    * GstRtpBin::on-ssrc-collision:
    * @rtpbin: the object which received the signal
@@ -2141,8 +2184,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_SSRC_COLLISION] =
       g_signal_new ("on-ssrc-collision", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_ssrc_collision),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
   /**
    * GstRtpBin::on-ssrc-validated:
    * @rtpbin: the object which received the signal
@@ -2154,8 +2196,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_SSRC_VALIDATED] =
       g_signal_new ("on-ssrc-validated", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_ssrc_validated),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
   /**
    * GstRtpBin::on-ssrc-active:
    * @rtpbin: the object which received the signal
@@ -2167,8 +2208,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_SSRC_ACTIVE] =
       g_signal_new ("on-ssrc-active", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_ssrc_active),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
   /**
    * GstRtpBin::on-ssrc-sdes:
    * @rtpbin: the object which received the signal
@@ -2180,8 +2220,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_SSRC_SDES] =
       g_signal_new ("on-ssrc-sdes", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_ssrc_sdes),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
 
   /**
    * GstRtpBin::on-bye-ssrc:
@@ -2194,8 +2233,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_BYE_SSRC] =
       g_signal_new ("on-bye-ssrc", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_bye_ssrc),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
   /**
    * GstRtpBin::on-bye-timeout:
    * @rtpbin: the object which received the signal
@@ -2207,8 +2245,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_BYE_TIMEOUT] =
       g_signal_new ("on-bye-timeout", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_bye_timeout),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
   /**
    * GstRtpBin::on-timeout:
    * @rtpbin: the object which received the signal
@@ -2220,8 +2257,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_TIMEOUT] =
       g_signal_new ("on-timeout", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_timeout),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
   /**
    * GstRtpBin::on-sender-timeout:
    * @rtpbin: the object which received the signal
@@ -2233,8 +2269,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_SENDER_TIMEOUT] =
       g_signal_new ("on-sender-timeout", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_sender_timeout),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
 
   /**
    * GstRtpBin::on-npt-stop:
@@ -2247,8 +2282,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_NPT_STOP] =
       g_signal_new ("on-npt-stop", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_npt_stop),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
 
   /**
    * GstRtpBin::request-rtp-encoder:
@@ -2265,8 +2299,8 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_REQUEST_RTP_ENCODER] =
       g_signal_new ("request-rtp-encoder", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
-          request_rtp_encoder), _gst_element_accumulator, NULL,
-      g_cclosure_marshal_generic, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
+          request_rtp_encoder), _gst_element_accumulator, NULL, NULL,
+      GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
 
   /**
    * GstRtpBin::request-rtp-decoder:
@@ -2284,7 +2318,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
       g_signal_new ("request-rtp-decoder", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
           request_rtp_decoder), _gst_element_accumulator, NULL,
-      g_cclosure_marshal_generic, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
+      NULL, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
 
   /**
    * GstRtpBin::request-rtcp-encoder:
@@ -2301,8 +2335,8 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_REQUEST_RTCP_ENCODER] =
       g_signal_new ("request-rtcp-encoder", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
-          request_rtcp_encoder), _gst_element_accumulator, NULL,
-      g_cclosure_marshal_generic, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
+          request_rtcp_encoder), _gst_element_accumulator, NULL, NULL,
+      GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
 
   /**
    * GstRtpBin::request-rtcp-decoder:
@@ -2319,7 +2353,35 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_REQUEST_RTCP_DECODER] =
       g_signal_new ("request-rtcp-decoder", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
-          request_rtcp_decoder), _gst_element_accumulator, NULL,
+          request_rtcp_decoder), _gst_element_accumulator, NULL, NULL,
+      GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
+
+  /**
+   * GstRtpBin::request-jitterbuffer:
+   * @rtpbin: the object which received the signal
+   * @session: the session
+   *
+   * Request a jitterbuffer element for the given @session.
+   *
+   * If no handler is connected, the default jitterbuffer will be used.
+   *
+   * Note: The provided element is expected to conform to the API exposed
+   * by the standard #GstRtpJitterBuffer. Runtime checks will be made to
+   * determine whether it exposes properties and signals before attempting
+   * to set, call or connect to them, and some functionalities of #GstRtpBin
+   * may not be available when that is not the case.
+   *
+   * This should be considered experimental API, as the standard jitterbuffer
+   * API is susceptible to change, provided elements will have to update their
+   * custom jitterbuffer's API to match the API of #GstRtpJitterBuffer if and
+   * when it changes.
+   *
+   * Since: 1.18
+   */
+  gst_rtp_bin_signals[SIGNAL_REQUEST_JITTERBUFFER] =
+      g_signal_new ("request-jitterbuffer", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
+          request_jitterbuffer), _gst_element_accumulator, NULL,
       g_cclosure_marshal_generic, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
 
   /**
@@ -2337,7 +2399,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_NEW_JITTERBUFFER] =
       g_signal_new ("new-jitterbuffer", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
-          new_jitterbuffer), NULL, NULL, g_cclosure_marshal_generic,
+          new_jitterbuffer), NULL, NULL, NULL,
       G_TYPE_NONE, 3, GST_TYPE_ELEMENT, G_TYPE_UINT, G_TYPE_UINT);
 
   /**
@@ -2354,7 +2416,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_NEW_STORAGE] =
       g_signal_new ("new-storage", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
-          new_storage), NULL, NULL, g_cclosure_marshal_generic,
+          new_storage), NULL, NULL, NULL,
       G_TYPE_NONE, 2, GST_TYPE_ELEMENT, G_TYPE_UINT);
 
   /**
@@ -2372,8 +2434,8 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_REQUEST_AUX_SENDER] =
       g_signal_new ("request-aux-sender", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
-          request_aux_sender), _gst_element_accumulator, NULL,
-      g_cclosure_marshal_generic, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
+          request_aux_sender), _gst_element_accumulator, NULL, NULL,
+      GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
 
   /**
    * GstRtpBin::request-aux-receiver:
@@ -2390,8 +2452,8 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_REQUEST_AUX_RECEIVER] =
       g_signal_new ("request-aux-receiver", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
-          request_aux_receiver), _gst_element_accumulator, NULL,
-      g_cclosure_marshal_generic, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
+          request_aux_receiver), _gst_element_accumulator, NULL, NULL,
+      GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
 
   /**
    * GstRtpBin::request-fec-decoder:
@@ -2408,8 +2470,8 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_REQUEST_FEC_DECODER] =
       g_signal_new ("request-fec-decoder", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
-          request_fec_decoder), _gst_element_accumulator, NULL,
-      g_cclosure_marshal_generic, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
+          request_fec_decoder), _gst_element_accumulator, NULL, NULL,
+      GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
 
   /**
    * GstRtpBin::request-fec-encoder:
@@ -2426,8 +2488,8 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_REQUEST_FEC_ENCODER] =
       g_signal_new ("request-fec-encoder", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
-          request_fec_encoder), _gst_element_accumulator, NULL,
-      g_cclosure_marshal_generic, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
+          request_fec_encoder), _gst_element_accumulator, NULL, NULL,
+      GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
 
   /**
    * GstRtpBin::on-new-sender-ssrc:
@@ -2442,8 +2504,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_NEW_SENDER_SSRC] =
       g_signal_new ("on-new-sender-ssrc", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass, on_new_sender_ssrc),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
   /**
    * GstRtpBin::on-sender-ssrc-active:
    * @rtpbin: the object which received the signal
@@ -2457,13 +2518,18 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   gst_rtp_bin_signals[SIGNAL_ON_SENDER_SSRC_ACTIVE] =
       g_signal_new ("on-sender-ssrc-active", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
-          on_sender_ssrc_active), NULL, NULL, g_cclosure_marshal_generic,
+          on_sender_ssrc_active), NULL, NULL, NULL,
       G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
 
   g_object_class_install_property (gobject_class, PROP_SDES,
       g_param_spec_boxed ("sdes", "SDES",
           "The SDES items of this session",
-          GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+          GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
+#ifndef TIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK
+          | GST_PARAM_DOC_SHOW_DEFAULT));
+#else
+          ));
+#endif
 
   g_object_class_install_property (gobject_class, PROP_DO_LOST,
       g_param_spec_boolean ("do-lost", "Do Lost",
@@ -2541,7 +2607,7 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
    *
    * Enables RTP retransmission on all streams. To control retransmission on
    * a per-SSRC basis, connect to the #GstRtpBin::new-jitterbuffer signal and
-   * set the #GstRtpJitterBuffer::do-retransmission property on the
+   * set the #GstRtpJitterBuffer:do-retransmission property on the
    * #GstRtpJitterBuffer object instead.
    */
   g_object_class_install_property (gobject_class, PROP_DO_RETRANSMISSION,
@@ -2640,14 +2706,6 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
           "changed to 0 (no limit)", 0, G_MAXINT64, DEFAULT_MAX_TS_OFFSET,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
-#ifdef TIZEN_FEATURE_RTSP_MODIFICATION
-  g_object_class_install_property (gobject_class, PROP_USE_RTSP_BUFFERING,
-      g_param_spec_boolean ("use-rtsp-buffering", "Use RTSP buffering",
-          "Use RTSP buffering in RTP_JITTER_BUFFER_MODE_SLAVE buffer mode",
-          DEFAULT_RTSP_USE_BUFFERING,
-          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-#endif
-
   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_rtp_bin_change_state);
   gstelement_class->request_new_pad =
       GST_DEBUG_FUNCPTR (gst_rtp_bin_request_new_pad);
@@ -2688,8 +2746,14 @@ gst_rtp_bin_class_init (GstRtpBinClass * klass)
   klass->request_rtp_decoder = GST_DEBUG_FUNCPTR (gst_rtp_bin_request_decoder);
   klass->request_rtcp_encoder = GST_DEBUG_FUNCPTR (gst_rtp_bin_request_encoder);
   klass->request_rtcp_decoder = GST_DEBUG_FUNCPTR (gst_rtp_bin_request_decoder);
+  klass->request_jitterbuffer =
+      GST_DEBUG_FUNCPTR (gst_rtp_bin_request_jitterbuffer);
 
   GST_DEBUG_CATEGORY_INIT (gst_rtp_bin_debug, "rtpbin", 0, "RTP bin");
+
+#ifndef TIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK
+  gst_type_mark_as_plugin_api (GST_RTP_BIN_RTCP_SYNC_TYPE, 0);
+#endif
 }
 
 static void
@@ -3155,6 +3219,7 @@ gst_rtp_bin_handle_message (GstBin * bin, GstMessage * message)
             for (streams = session->streams; streams;
                 streams = g_slist_next (streams)) {
               GstRtpBinStream *stream = (GstRtpBinStream *) streams->data;
+
 #ifdef TIZEN_FEATURE_RTSP_MODIFICATION
               if (rtpbin->use_rtsp_buffering &&
                   rtpbin->buffer_mode == RTP_JITTER_BUFFER_MODE_SLAVE) {
@@ -3201,6 +3266,7 @@ gst_rtp_bin_handle_message (GstBin * bin, GstMessage * message)
 #else
               GST_DEBUG_OBJECT (bin, "stream %p percent %d", stream,
                   stream->percent);
+
 #endif
               /* find min percent */
               if (min_percent > stream->percent)
@@ -3214,6 +3280,7 @@ gst_rtp_bin_handle_message (GstBin * bin, GstMessage * message)
           GST_RTP_SESSION_UNLOCK (session);
         }
         GST_DEBUG_OBJECT (bin, "min percent %d", min_percent);
+
 #ifdef TIZEN_FEATURE_RTSP_MODIFICATION
         if (!(rtpbin->use_rtsp_buffering &&
             rtpbin->buffer_mode == RTP_JITTER_BUFFER_MODE_SLAVE)) {
@@ -3306,10 +3373,12 @@ gst_rtp_bin_handle_message (GstBin * bin, GstMessage * message)
                 streams = g_slist_next (streams)) {
               GstRtpBinStream *stream = (GstRtpBinStream *) streams->data;
               GstElement *element = stream->buffer;
-              guint64 last_out;
+              guint64 last_out = -1;
 
-              g_signal_emit_by_name (element, "set-active", active, offset,
-                  &last_out);
+              if (g_signal_lookup ("set-active", G_OBJECT_TYPE (element)) != 0) {
+                g_signal_emit_by_name (element, "set-active", active, offset,
+                    &last_out);
+              }
 
               if (!active) {
                 g_object_get (element, "percent", &stream->percent, NULL);
@@ -3521,7 +3590,7 @@ fec_decoder_link_failed:
   }
 }
 
-/* a new pad (SSRC) was created in @session. This signal is emited from the
+/* a new pad (SSRC) was created in @session. This signal is emitted from the
  * payload demuxer. */
 static void
 new_payload_found (GstElement * element, guint pt, GstPad * pad,
@@ -3616,7 +3685,7 @@ static void
 payload_type_change (GstElement * element, guint pt, GstRtpBinSession * session)
 {
   GST_DEBUG_OBJECT (session->bin,
-      "emiting signal for pt type changed to %u in session %u", pt,
+      "emitting signal for pt type changed to %u in session %u", pt,
       session->id);
 
   g_signal_emit (session->bin, gst_rtp_bin_signals[SIGNAL_PAYLOAD_TYPE_CHANGE],
@@ -3689,19 +3758,23 @@ new_ssrc_pad_found (GstElement * element, guint ssrc, GstPad * pad,
   gst_object_unref (sinkpad);
   gst_object_unref (srcpad);
 
-  GST_DEBUG_OBJECT (rtpbin, "linking jitterbuffer RTCP");
-  padname = g_strdup_printf ("rtcp_src_%u", ssrc);
-  srcpad = gst_element_get_static_pad (element, padname);
-  g_free (padname);
   sinkpad = gst_element_get_request_pad (stream->buffer, "sink_rtcp");
-  gst_pad_link_full (srcpad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
-  gst_object_unref (sinkpad);
-  gst_object_unref (srcpad);
+  if (sinkpad) {
+    GST_DEBUG_OBJECT (rtpbin, "linking jitterbuffer RTCP");
+    padname = g_strdup_printf ("rtcp_src_%u", ssrc);
+    srcpad = gst_element_get_static_pad (element, padname);
+    g_free (padname);
+    gst_pad_link_full (srcpad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
+    gst_object_unref (sinkpad);
+    gst_object_unref (srcpad);
+  }
 
-  /* connect to the RTCP sync signal from the jitterbuffer */
-  GST_DEBUG_OBJECT (rtpbin, "connecting sync signal");
-  stream->buffer_handlesync_sig = g_signal_connect (stream->buffer,
-      "handle-sync", (GCallback) gst_rtp_bin_handle_sync, stream);
+  if (g_signal_lookup ("handle-sync", G_OBJECT_TYPE (stream->buffer)) != 0) {
+    /* connect to the RTCP sync signal from the jitterbuffer */
+    GST_DEBUG_OBJECT (rtpbin, "connecting sync signal");
+    stream->buffer_handlesync_sig = g_signal_connect (stream->buffer,
+        "handle-sync", (GCallback) gst_rtp_bin_handle_sync, stream);
+  }
 
   if (stream->demux) {
     /* connect to the new-pad signal of the payload demuxer, this will expose the
@@ -3975,7 +4048,8 @@ create_recv_rtp (GstRtpBin * rtpbin, GstPadTemplate * templ, const gchar * name)
   /* ERRORS */
 no_name:
   {
-    g_warning ("rtpbin: invalid name given");
+    g_warning ("rtpbin: cannot find session id for pad: %s",
+        GST_STR_NULL (name));
     return NULL;
   }
 create_error:
@@ -4150,7 +4224,8 @@ create_recv_rtcp (GstRtpBin * rtpbin, GstPadTemplate * templ,
   /* ERRORS */
 no_name:
   {
-    g_warning ("rtpbin: invalid name given");
+    g_warning ("rtpbin: cannot find session id for pad: %s",
+        GST_STR_NULL (name));
     return NULL;
   }
 create_error:
@@ -4479,7 +4554,8 @@ create_send_rtp (GstRtpBin * rtpbin, GstPadTemplate * templ, const gchar * name)
   /* ERRORS */
 no_name:
   {
-    g_warning ("rtpbin: invalid name given");
+    g_warning ("rtpbin: cannot find session id for pad: %s",
+        GST_STR_NULL (name));
     return NULL;
   }
 create_error:
@@ -4633,7 +4709,8 @@ create_send_rtcp (GstRtpBin * rtpbin, GstPadTemplate * templ,
   /* ERRORS */
 no_name:
   {
-    g_warning ("rtpbin: invalid name given");
+    g_warning ("rtpbin: cannot find session id for pad: %s",
+        GST_STR_NULL (name));
     return NULL;
   }
 create_error:
@@ -4682,7 +4759,7 @@ remove_rtcp (GstRtpBin * rtpbin, GstRtpBinSession * session)
 }
 
 /* If the requested name is NULL we should create a name with
- * the session number assuming we want the lowest posible session
+ * the session number assuming we want the lowest possible session
  * with a free pad like the template */
 static gchar *
 gst_rtp_bin_get_free_pad_name (GstElement * element, GstPadTemplate * templ)
index 0151b94..949565a 100644 (file)
@@ -137,6 +137,8 @@ struct _GstRtpBinClass {
   GstElement* (*request_fec_encoder)  (GstRtpBin *rtpbin, guint session);
   GstElement* (*request_fec_decoder)  (GstRtpBin *rtpbin, guint session);
 
+  GstElement* (*request_jitterbuffer) (GstRtpBin *rtpbin, guint session);
+
   void     (*on_new_sender_ssrc)      (GstRtpBin *rtpbin, guint session, guint32 ssrc);
   void     (*on_sender_ssrc_active)   (GstRtpBin *rtpbin, guint session, guint32 ssrc);
 };
index cc6d747..cff68e5 100644 (file)
@@ -27,6 +27,7 @@
 
 /**
  * SECTION:element-rtpdtmfmux
+ * @title: rtpdtmfmux
  * @see_also: rtpdtmfsrc, dtmfsrc, rtpmux
  *
  * The RTP "DTMF" Muxer muxes multiple RTP streams into a valid RTP
index 155875b..7638b34 100644 (file)
  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
  * Boston, MA 02110-1301, USA.
  */
+
+ /**
+ * SECTION:element-rtpfunnel
+ * @title: rtpfunnel
+ * @see_also: rtpbasepaylaoder, rtpsession
+ *
+ * RTP funnel is basically like a normal funnel with a few added
+ * functionalities to support bundling.
+ *
+ * Bundle is the concept of sending multiple streams in a single RTP session.
+ * These can be both audio and video streams, and several of both.
+ * One of the advantages with bundling is that you can get away with fewer
+ * ports for sending and receiving media. Also the RTCP traffic gets more
+ * compact if you can report on multiple streams in a single sender/receiver
+ * report.
+ *
+ * One of the reasons for a specialized RTP funnel is that some messages
+ * coming upstream want to find their way back to the right stream,
+ * and a normal funnel can't know which of its sinkpads it should send
+ * these messages to. The RTP funnel achieves this by keeping track of the
+ * SSRC of each stream on its sinkpad, and then uses the fact that upstream
+ * events are tagged inside rtpbin with the appropriate SSRC, so that upon
+ * receiving such an event, the RTP funnel can do a simple lookup for the
+ * right pad to forward the event to.
+ *
+ * A good example here is the KeyUnit event. If several video encoders are
+ * being bundled together using the RTP funnel, and one of the decoders on
+ * the receiving side asks for a KeyUnit, typically a RTCP PLI message will
+ * be sent from the receiver to the sender, and this will be transformed into
+ * a GstForceKeyUnit event inside GstRTPSession, and sent upstream. The
+ * RTP funnel can than make sure that this event hits the right encoder based
+ * on the SSRC embedded in the event.
+ *
+ * Another feature of the RTP funnel is that it will mux together TWCC
+ * (Transport-Wide Congestion Control) sequence-numbers. The point being that
+ * it should increment "transport-wide", meaning potentially several
+ * bundled streams. Note that not *all* streams being bundled needs to be
+ * affected by this. As an example Google WebRTC will use bundle with audio
+ * and video, but will only use TWCC sequence-numbers for the video-stream(s).
+ *
+ */
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
+#include <gst/rtp/gstrtpbuffer.h>
+
 #include "gstrtpfunnel.h"
 
 GST_DEBUG_CATEGORY_STATIC (gst_rtp_funnel_debug);
 #define GST_CAT_DEFAULT gst_rtp_funnel_debug
 
+#define TWCC_EXTMAP_STR "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
+
+/**************** GstRTPFunnelPad ****************/
+
 struct _GstRtpFunnelPadClass
 {
   GstPadClass class;
@@ -38,30 +86,31 @@ struct _GstRtpFunnelPad
 {
   GstPad pad;
   guint32 ssrc;
+  gboolean has_twcc;
 };
 
-enum
-{
-  PROP_0,
-  PROP_COMMON_TS_OFFSET,
-};
-
-#define DEFAULT_COMMON_TS_OFFSET -1
-
 G_DEFINE_TYPE (GstRtpFunnelPad, gst_rtp_funnel_pad, GST_TYPE_PAD);
 
 static void
-gst_rtp_funnel_pad_class_init (GstRtpFunnelPadClass * klass)
+gst_rtp_funnel_pad_class_init (G_GNUC_UNUSED GstRtpFunnelPadClass * klass)
 {
-  (void) klass;
 }
 
 static void
-gst_rtp_funnel_pad_init (GstRtpFunnelPad * pad)
+gst_rtp_funnel_pad_init (G_GNUC_UNUSED GstRtpFunnelPad * pad)
 {
-  (void) pad;
 }
 
+/**************** GstRTPFunnel ****************/
+
+enum
+{
+  PROP_0,
+  PROP_COMMON_TS_OFFSET,
+};
+
+#define DEFAULT_COMMON_TS_OFFSET -1
+
 struct _GstRtpFunnelClass
 {
   GstElementClass class;
@@ -72,12 +121,16 @@ struct _GstRtpFunnel
   GstElement element;
 
   GstPad *srcpad;
-  GstCaps *srccaps;
+  GstCaps *srccaps;             /* protected by OBJECT_LOCK */
   gboolean send_sticky_events;
-  GHashTable *ssrc_to_pad;
+  GHashTable *ssrc_to_pad;      /* protected by OBJECT_LOCK */
   /* The last pad data was chained on */
   GstPad *current_pad;
 
+  guint8 twcc_ext_id;           /* the negotiated twcc extmap id */
+  guint16 twcc_seqnum;          /* our internal twcc seqnum */
+  guint twcc_pads;              /* numer of sinkpads with negotiated twcc */
+
   /* properties */
   gint common_ts_offset;
 };
@@ -102,7 +155,8 @@ static void
 gst_rtp_funnel_send_sticky (GstRtpFunnel * funnel, GstPad * pad)
 {
   GstEvent *stream_start;
-  GstEvent *caps;
+  GstCaps *caps;
+  GstEvent *caps_ev;
 
   if (!funnel->send_sticky_events)
     goto done;
@@ -113,8 +167,15 @@ gst_rtp_funnel_send_sticky (GstRtpFunnel * funnel, GstPad * pad)
     goto done;
   }
 
-  caps = gst_event_new_caps (funnel->srccaps);
-  if (caps && !gst_pad_push_event (funnel->srcpad, caps)) {
+  /* We modify these caps in our sink pad event handlers, so make sure to
+   * send a copy downstream so that we can keep our internal caps writable */
+  GST_OBJECT_LOCK (funnel);
+  caps = gst_caps_copy (funnel->srccaps);
+  GST_OBJECT_UNLOCK (funnel);
+
+  caps_ev = gst_event_new_caps (caps);
+  gst_caps_unref (caps);
+  if (caps_ev && !gst_pad_push_event (funnel->srcpad, caps_ev)) {
     GST_ERROR_OBJECT (funnel, "Could not push caps");
     goto done;
   }
@@ -146,6 +207,43 @@ done:
   return;
 }
 
+static void
+gst_rtp_funnel_set_twcc_seqnum (GstRtpFunnel * funnel,
+    GstPad * pad, GstBuffer ** buf)
+{
+  GstRtpFunnelPad *fpad = GST_RTP_FUNNEL_PAD_CAST (pad);
+  GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
+
+  if (!funnel->twcc_ext_id || !fpad->has_twcc)
+    return;
+
+  *buf = gst_buffer_make_writable (*buf);
+
+  if (gst_rtp_buffer_map (*buf, GST_MAP_READWRITE, &rtp)) {
+    gpointer data;
+
+    /* if there already is a twcc-seqnum inside the packet */
+    if (gst_rtp_buffer_get_extension_onebyte_header (&rtp, funnel->twcc_ext_id,
+            0, &data, NULL)) {
+
+      /* with only one pad, we read the twcc-seqnum instead of writing it */
+      if (funnel->twcc_pads == 1) {
+        funnel->twcc_seqnum = GST_READ_UINT16_BE (data);
+      } else {
+        GST_WRITE_UINT16_BE (data, funnel->twcc_seqnum);
+      }
+    } else {
+      guint16 seq_be;
+      GST_WRITE_UINT16_BE (&seq_be, funnel->twcc_seqnum);
+      gst_rtp_buffer_add_extension_onebyte_header (&rtp, funnel->twcc_ext_id,
+          &seq_be, 2);
+    }
+  }
+  gst_rtp_buffer_unmap (&rtp);
+
+  funnel->twcc_seqnum++;
+}
+
 static GstFlowReturn
 gst_rtp_funnel_sink_chain_object (GstPad * pad, GstRtpFunnel * funnel,
     gboolean is_list, GstMiniObject * obj)
@@ -159,11 +257,13 @@ gst_rtp_funnel_sink_chain_object (GstPad * pad, GstRtpFunnel * funnel,
   gst_rtp_funnel_send_sticky (funnel, pad);
   gst_rtp_funnel_forward_segment (funnel, pad);
 
-  if (is_list)
+  if (is_list) {
     res = gst_pad_push_list (funnel->srcpad, GST_BUFFER_LIST_CAST (obj));
-  else
-    res = gst_pad_push (funnel->srcpad, GST_BUFFER_CAST (obj));
-
+  } else {
+    GstBuffer *buf = GST_BUFFER_CAST (obj);
+    gst_rtp_funnel_set_twcc_seqnum (funnel, pad, &buf);
+    res = gst_pad_push (funnel->srcpad, buf);
+  }
   GST_PAD_STREAM_UNLOCK (funnel->srcpad);
 
   return res;
@@ -188,10 +288,59 @@ gst_rtp_funnel_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
       GST_MINI_OBJECT_CAST (buffer));
 }
 
+static void
+gst_rtp_funnel_set_twcc_ext_id (GstRtpFunnel * funnel, guint8 twcc_ext_id)
+{
+  gchar *name;
+
+  if (funnel->twcc_ext_id == twcc_ext_id)
+    return;
+
+  name = g_strdup_printf ("extmap-%u", twcc_ext_id);
+
+  GST_OBJECT_LOCK (funnel);
+  gst_caps_set_simple (funnel->srccaps, name, G_TYPE_STRING, TWCC_EXTMAP_STR,
+      NULL);
+  GST_OBJECT_UNLOCK (funnel);
+
+  g_free (name);
+
+  /* make sure we update the sticky with the new caps */
+  funnel->send_sticky_events = TRUE;
+
+  GST_INFO_OBJECT (funnel, "Setting twcc-ext-id to %u", twcc_ext_id);
+  funnel->twcc_ext_id = twcc_ext_id;
+}
+
+static guint8
+_get_extmap_id_for_attribute (const GstStructure * s, const gchar * ext_name)
+{
+  guint i;
+  guint8 extmap_id = 0;
+  guint n_fields = gst_structure_n_fields (s);
+
+  for (i = 0; i < n_fields; i++) {
+    const gchar *field_name = gst_structure_nth_field_name (s, i);
+    if (g_str_has_prefix (field_name, "extmap-")) {
+      const gchar *str = gst_structure_get_string (s, field_name);
+      if (str && g_strcmp0 (str, ext_name) == 0) {
+        gint64 id = g_ascii_strtoll (field_name + 7, NULL, 10);
+        if (id > 0 && id < 15) {
+          extmap_id = id;
+          break;
+        }
+      }
+    }
+  }
+  return extmap_id;
+}
+
 static gboolean
 gst_rtp_funnel_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
 {
   GstRtpFunnel *funnel = GST_RTP_FUNNEL_CAST (parent);
+  GstRtpFunnelPad *fpad = GST_RTP_FUNNEL_PAD_CAST (pad);
+
   gboolean forward = TRUE;
   gboolean ret = TRUE;
 
@@ -207,23 +356,32 @@ gst_rtp_funnel_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
       GstCaps *caps;
       GstStructure *s;
       guint ssrc;
+      guint8 ext_id;
+
       gst_event_parse_caps (event, &caps);
 
+      GST_OBJECT_LOCK (funnel);
       if (!gst_caps_can_intersect (funnel->srccaps, caps)) {
         GST_ERROR_OBJECT (funnel, "Can't intersect with caps %" GST_PTR_FORMAT,
             caps);
         g_assert_not_reached ();
       }
+      GST_OBJECT_UNLOCK (funnel);
 
       s = gst_caps_get_structure (caps, 0);
       if (gst_structure_get_uint (s, "ssrc", &ssrc)) {
-        GstRtpFunnelPad *fpad = GST_RTP_FUNNEL_PAD_CAST (pad);
         fpad->ssrc = ssrc;
         GST_DEBUG_OBJECT (pad, "Got ssrc: %u", ssrc);
         GST_OBJECT_LOCK (funnel);
         g_hash_table_insert (funnel->ssrc_to_pad, GUINT_TO_POINTER (ssrc), pad);
         GST_OBJECT_UNLOCK (funnel);
       }
+      ext_id = _get_extmap_id_for_attribute (s, TWCC_EXTMAP_STR);
+      if (ext_id > 0) {
+        fpad->has_twcc = TRUE;
+        funnel->twcc_pads++;
+        gst_rtp_funnel_set_twcc_ext_id (funnel, ext_id);
+      }
 
       forward = FALSE;
       break;
@@ -245,8 +403,7 @@ static gboolean
 gst_rtp_funnel_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
 {
   GstRtpFunnel *funnel = GST_RTP_FUNNEL_CAST (parent);
-  gboolean res = FALSE;
-  (void) funnel;
+  gboolean res = TRUE;
 
   switch (GST_QUERY_TYPE (query)) {
     case GST_QUERY_CAPS:
@@ -256,12 +413,14 @@ gst_rtp_funnel_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
 
       gst_query_parse_caps (query, &filter_caps);
 
+      GST_OBJECT_LOCK (funnel);
       if (filter_caps) {
         new_caps = gst_caps_intersect_full (funnel->srccaps, filter_caps,
             GST_CAPS_INTERSECT_FIRST);
       } else {
         new_caps = gst_caps_copy (funnel->srccaps);
       }
+      GST_OBJECT_UNLOCK (funnel);
 
       if (funnel->common_ts_offset >= 0)
         gst_caps_set_simple (new_caps, "timestamp-offset", G_TYPE_UINT,
@@ -271,7 +430,25 @@ gst_rtp_funnel_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
       GST_DEBUG_OBJECT (pad, "Answering caps-query with caps: %"
           GST_PTR_FORMAT, new_caps);
       gst_caps_unref (new_caps);
-      res = TRUE;
+      break;
+    }
+    case GST_QUERY_ACCEPT_CAPS:
+    {
+      GstCaps *caps;
+      gboolean result;
+
+      gst_query_parse_accept_caps (query, &caps);
+
+      GST_OBJECT_LOCK (funnel);
+      result = gst_caps_is_subset (caps, funnel->srccaps);
+      if (!result) {
+        GST_ERROR_OBJECT (pad,
+            "caps: %" GST_PTR_FORMAT " were not compatible with: %"
+            GST_PTR_FORMAT, caps, funnel->srccaps);
+      }
+      GST_OBJECT_UNLOCK (funnel);
+
+      gst_query_set_accept_caps_result (query, result);
       break;
     }
     default:
index e8b636f..9608957 100644 (file)
@@ -30,6 +30,7 @@
 
 /**
  * SECTION:element-rtpjitterbuffer
+ * @title: rtpjitterbuffer
  *
  * This element reorders and removes duplicate RTP packets as they are received
  * from a network source.
  *
  * This element will automatically be used inside rtpbin.
  *
- * <refsect2>
- * <title>Example pipelines</title>
+ * ## Example pipelines
  * |[
  * gst-launch-1.0 rtspsrc location=rtsp://192.168.1.133:8554/mpeg1or2AudioVideoTest ! rtpjitterbuffer ! rtpmpvdepay ! mpeg2dec ! xvimagesink
  * ]| Connect to a streaming server and decode the MPEG video. The jitterbuffer is
  * inserted into the pipeline to smooth out network jitter and to reorder the
  * out-of-order RTP packets.
- * </refsect2>
+ *
  */
 
 #ifdef HAVE_CONFIG_H
 #include "gstrtpjitterbuffer.h"
 #include "rtpjitterbuffer.h"
 #include "rtpstats.h"
+#include "rtptimerqueue.h"
 
 #include <gst/glib-compat-private.h>
 
@@ -133,6 +134,8 @@ enum
 #define DEFAULT_TS_OFFSET           0
 #define DEFAULT_MAX_TS_OFFSET_ADJUSTMENT 0
 #define DEFAULT_DO_LOST             FALSE
+#define DEFAULT_POST_DROP_MESSAGES  FALSE
+#define DEFAULT_DROP_MESSAGES_INTERVAL_MS   200
 #define DEFAULT_MODE                RTP_JITTER_BUFFER_MODE_SLAVE
 #define DEFAULT_PERCENT             0
 #define DEFAULT_DO_RETRANSMISSION   FALSE
@@ -163,6 +166,8 @@ enum
   PROP_TS_OFFSET,
   PROP_MAX_TS_OFFSET_ADJUSTMENT,
   PROP_DO_LOST,
+  PROP_POST_DROP_MESSAGES,
+  PROP_DROP_MESSAGES_INTERVAL,
   PROP_MODE,
   PROP_PERCENT,
   PROP_DO_RETRANSMISSION,
@@ -264,11 +269,17 @@ enum
 #define GST_BUFFER_IS_RETRANSMISSION(buffer) \
   GST_BUFFER_FLAG_IS_SET (buffer, GST_RTP_BUFFER_FLAG_RETRANSMISSION)
 
-typedef struct TimerQueue
+#if !GLIB_CHECK_VERSION(2, 60, 0)
+#define g_queue_clear_full queue_clear_full
+static void
+queue_clear_full (GQueue * queue, GDestroyNotify free_func)
 {
-  GQueue *timers;
-  GHashTable *hashtable;
-} TimerQueue;
+  gpointer data;
+
+  while ((data = g_queue_pop_head (queue)) != NULL)
+    free_func (data);
+}
+#endif
 
 struct _GstRtpJitterBufferPrivate
 {
@@ -277,9 +288,9 @@ struct _GstRtpJitterBufferPrivate
 
   RTPJitterBuffer *jbuf;
   GMutex jbuf_lock;
-  gboolean waiting_queue;
+  guint waiting_queue;
   GCond jbuf_queue;
-  gboolean waiting_timer;
+  guint waiting_timer;
   GCond jbuf_timer;
   gboolean waiting_event;
   GCond jbuf_event;
@@ -302,6 +313,8 @@ struct _GstRtpJitterBufferPrivate
   gint64 ts_offset;
   guint64 max_ts_offset_adjustment;
   gboolean do_lost;
+  gboolean post_drop_messages;
+  guint drop_messages_interval_ms;
   gboolean do_retransmission;
   gboolean rtx_next_seqnum;
   gint rtx_delay;
@@ -338,8 +351,10 @@ struct _GstRtpJitterBufferPrivate
   GstClockTime last_in_pts;
   guint32 next_in_seqnum;
 
-  GArray *timers;
-  TimerQueue *rtx_stats_timers;
+  /* "normal" timers */
+  RtpTimerQueue *timers;
+  /* timers used for RTX statistics backlog */
+  RtpTimerQueue *rtx_stats_timers;
 
   /* start and stop ranges */
   GstClockTime npt_start;
@@ -391,31 +406,18 @@ struct _GstRtpJitterBufferPrivate
   GstClockTime last_pts;
   guint64 last_rtptime;
   GstClockTime avg_jitter;
-};
 
+  /* for dropped packet messages */
+  GstClockTime last_drop_msg_timestamp;
+  /* accumulators; reset every time a drop message is posted */
+  guint num_too_late;
+  guint num_drop_on_latency;
+};
 typedef enum
 {
-  TIMER_TYPE_EXPECTED,
-  TIMER_TYPE_LOST,
-  TIMER_TYPE_DEADLINE,
-  TIMER_TYPE_EOS
-} TimerType;
-
-typedef struct
-{
-  guint idx;
-  guint16 seqnum;
-  guint num;
-  TimerType type;
-  GstClockTime timeout;
-  GstClockTime duration;
-  GstClockTime rtx_base;
-  GstClockTime rtx_delay;
-  GstClockTime rtx_retry;
-  GstClockTime rtx_last;
-  guint num_rtx_retry;
-  guint num_rtx_received;
-} TimerData;
+  REASON_TOO_LATE,
+  REASON_DROP_ON_LATENCY
+} DropMessageReason;
 
 static GstStaticPadTemplate gst_rtp_jitter_buffer_sink_template =
 GST_STATIC_PAD_TEMPLATE ("sink",
@@ -508,7 +510,6 @@ gst_rtp_jitter_buffer_set_active (GstRtpJitterBuffer * jitterbuffer,
 static void do_handle_sync (GstRtpJitterBuffer * jitterbuffer);
 
 static void unschedule_current_timer (GstRtpJitterBuffer * jitterbuffer);
-static void remove_all_timers (GstRtpJitterBuffer * jitterbuffer);
 
 static void wait_next_timeout (GstRtpJitterBuffer * jitterbuffer);
 
@@ -516,10 +517,10 @@ static GstStructure *gst_rtp_jitter_buffer_create_stats (GstRtpJitterBuffer *
     jitterbuffer);
 
 static void update_rtx_stats (GstRtpJitterBuffer * jitterbuffer,
-    TimerData * timer, GstClockTime dts, gboolean success);
+    const RtpTimer * timer, GstClockTime dts, gboolean success);
 
-static TimerQueue *timer_queue_new (void);
-static void timer_queue_free (TimerQueue * queue);
+static GstClockTime get_current_running_time (GstRtpJitterBuffer *
+    jitterbuffer);
 
 static void
 gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
@@ -593,6 +594,47 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
+   * GstRtpJitterBuffer:post-drop-messages:
+   *
+   * Post custom messages to the bus when a packet is dropped by the
+   * jitterbuffer due to arriving too late, being already considered lost,
+   * or being dropped due to the drop-on-latency property being enabled.
+   * Message is of type GST_MESSAGE_ELEMENT and contains a GstStructure named
+   * "drop-msg" with the following fields:
+   *
+   * * #guint   `seqnum`: Seqnum of dropped packet.
+   * * #guint64 `timestamp`: PTS timestamp of dropped packet.
+   * * #GString `reason`: Reason for dropping the packet.
+   * * #guint   `num-too-late`: Number of packets arriving too late since
+   *    last drop message.
+   * * #guint   `num-drop-on-latency`: Number of packets dropped due to the
+   *    drop-on-latency property since last drop message.
+   *
+   * Since: 1.18
+   */
+  g_object_class_install_property (gobject_class, PROP_POST_DROP_MESSAGES,
+      g_param_spec_boolean ("post-drop-messages", "Post drop messages",
+          "Post a custom message to the bus when a packet is dropped by the jitterbuffer",
+          DEFAULT_POST_DROP_MESSAGES,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GstRtpJitterBuffer:drop-messages-interval:
+   *
+   * Minimal time in milliseconds between posting dropped packet messages, if enabled
+   * by setting property by setting #GstRtpJitterBuffer:post-drop-messages to %TRUE.
+   * If interval is set to 0, every dropped packet will result in a drop message being posted.
+   *
+   * Since: 1.18
+   */
+  g_object_class_install_property (gobject_class, PROP_DROP_MESSAGES_INTERVAL,
+      g_param_spec_uint ("drop-messages-interval",
+          "Drop message interval",
+          "Minimal time between posting dropped packet messages", 0,
+          G_MAXUINT, DEFAULT_DROP_MESSAGES_INTERVAL_MS,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  /**
    * GstRtpJitterBuffer:mode:
    *
    * Control the buffering and timestamping mode used by the jitterbuffer.
@@ -689,7 +731,7 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
           -1, G_MAXINT, DEFAULT_RTX_DELAY_REORDER,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
   /**
-   * GstRtpJitterBuffer::rtx-retry-timeout:
+   * GstRtpJitterBuffer:rtx-retry-timeout:
    *
    * When no packet has been received after sending a retransmission event
    * for this time, retry sending a retransmission event.
@@ -705,7 +747,7 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
           "ms (-1 automatic)", -1, G_MAXINT, DEFAULT_RTX_RETRY_TIMEOUT,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
   /**
-   * GstRtpJitterBuffer::rtx-min-retry-timeout:
+   * GstRtpJitterBuffer:rtx-min-retry-timeout:
    *
    * The minimum amount of time between retry timeouts. When
    * GstRtpJitterBuffer::rtx-retry-timeout is -1, this value ensures a
@@ -767,7 +809,7 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
           "(-1 automatic)", -1, G_MAXINT, DEFAULT_RTX_DEADLINE,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 /**
-   * GstRtpJitterBuffer::rtx-stats-timeout:
+   * GstRtpJitterBuffer:rtx-stats-timeout:
    *
    * The time to wait for a retransmitted packet after it has been
    * considered lost in order to collect RTX statistics.
@@ -798,64 +840,15 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
    * Various jitterbuffer statistics. This property returns a GstStructure
    * with name application/x-rtp-jitterbuffer-stats with the following fields:
    *
-   * <itemizedlist>
-   * <listitem>
-   *   <para>
-   *   #guint64
-   *   <classname>&quot;num-pushed&quot;</classname>:
-   *   the number of packets pushed out.
-   *   </para>
-   * </listitem>
-   * <listitem>
-   *   <para>
-   *   #guint64
-   *   <classname>&quot;num-lost&quot;</classname>:
-   *   the number of packets considered lost.
-   *   </para>
-   * </listitem>
-   * <listitem>
-   *   <para>
-   *   #guint64
-   *   <classname>&quot;num-late&quot;</classname>:
-   *   the number of packets arriving too late.
-   *   </para>
-   * </listitem>
-   * <listitem>
-   *   <para>
-   *   #guint64
-   *   <classname>&quot;num-duplicates&quot;</classname>:
-   *   the number of duplicate packets.
-   *   </para>
-   * </listitem>
-   * <listitem>
-   *   <para>
-   *   #guint64
-   *   <classname>&quot;rtx-count&quot;</classname>:
-   *   the number of retransmissions requested.
-   *   </para>
-   * </listitem>
-   * <listitem>
-   *   <para>
-   *   #guint64
-   *   <classname>&quot;rtx-success-count&quot;</classname>:
-   *   the number of successful retransmissions.
-   *   </para>
-   * </listitem>
-   * <listitem>
-   *   <para>
-   *   #gdouble
-   *   <classname>&quot;rtx-per-packet&quot;</classname>:
-   *   average number of RTX per packet.
-   *   </para>
-   * </listitem>
-   * <listitem>
-   *   <para>
-   *   #guint64
-   *   <classname>&quot;rtx-rtt&quot;</classname>:
-   *   average round trip time per RTX.
-   *   </para>
-   * </listitem>
-   * </itemizedlist>
+   * * #guint64 `num-pushed`: the number of packets pushed out.
+   * * #guint64 `num-lost`: the number of packets considered lost.
+   * * #guint64 `num-late`: the number of packets arriving too late.
+   * * #guint64 `num-duplicates`: the number of duplicate packets.
+   * * #guint64 `avg-jitter`: the average jitter in nanoseconds.
+   * * #guint64 `rtx-count`: the number of retransmissions requested.
+   * * #guint64 `rtx-success-count`: the number of successful retransmissions.
+   * * #gdouble `rtx-per-packet`: average number of RTX per packet.
+   * * #guint64 `rtx-rtt`: average round trip time per RTX.
    *
    * Since: 1.4
    */
@@ -913,8 +906,7 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
   gst_rtp_jitter_buffer_signals[SIGNAL_REQUEST_PT_MAP] =
       g_signal_new ("request-pt-map", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpJitterBufferClass,
-          request_pt_map), NULL, NULL, g_cclosure_marshal_generic,
-      GST_TYPE_CAPS, 1, G_TYPE_UINT);
+          request_pt_map), NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);
   /**
    * GstRtpJitterBuffer::handle-sync:
    * @buffer: the object which received the signal
@@ -925,7 +917,7 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
   gst_rtp_jitter_buffer_signals[SIGNAL_HANDLE_SYNC] =
       g_signal_new ("handle-sync", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpJitterBufferClass,
-          handle_sync), NULL, NULL, g_cclosure_marshal_VOID__BOXED,
+          handle_sync), NULL, NULL, NULL,
       G_TYPE_NONE, 1, GST_TYPE_STRUCTURE | G_SIGNAL_TYPE_STATIC_SCOPE);
 
   /**
@@ -938,8 +930,7 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
   gst_rtp_jitter_buffer_signals[SIGNAL_ON_NPT_STOP] =
       g_signal_new ("on-npt-stop", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpJitterBufferClass,
-          on_npt_stop), NULL, NULL, g_cclosure_marshal_VOID__VOID,
-      G_TYPE_NONE, 0, G_TYPE_NONE);
+          on_npt_stop), NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE);
 
   /**
    * GstRtpJitterBuffer::clear-pt-map:
@@ -952,7 +943,7 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
       g_signal_new ("clear-pt-map", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
       G_STRUCT_OFFSET (GstRtpJitterBufferClass, clear_pt_map), NULL, NULL,
-      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE);
+      NULL, G_TYPE_NONE, 0, G_TYPE_NONE);
 
   /**
    * GstRtpJitterBuffer::set-active:
@@ -967,8 +958,7 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
       g_signal_new ("set-active", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
       G_STRUCT_OFFSET (GstRtpJitterBufferClass, set_active), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_UINT64, 2, G_TYPE_BOOLEAN,
-      G_TYPE_UINT64);
+      NULL, G_TYPE_UINT64, 2, G_TYPE_BOOLEAN, G_TYPE_UINT64);
 
   gstelement_class->change_state =
       GST_DEBUG_FUNCPTR (gst_rtp_jitter_buffer_change_state);
@@ -1000,6 +990,10 @@ gst_rtp_jitter_buffer_class_init (GstRtpJitterBufferClass * klass)
   GST_DEBUG_CATEGORY_INIT
       (rtpjitterbuffer_debug, "rtpjitterbuffer", 0, "RTP Jitter Buffer");
   GST_DEBUG_REGISTER_FUNCPTR (gst_rtp_jitter_buffer_chain_rtcp);
+
+#ifndef TIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK
+  gst_type_mark_as_plugin_api (RTP_TYPE_JITTER_BUFFER_MODE, 0);
+#endif
 }
 
 static void
@@ -1016,6 +1010,8 @@ gst_rtp_jitter_buffer_init (GstRtpJitterBuffer * jitterbuffer)
   priv->ts_offset = DEFAULT_TS_OFFSET;
   priv->max_ts_offset_adjustment = DEFAULT_MAX_TS_OFFSET_ADJUSTMENT;
   priv->do_lost = DEFAULT_DO_LOST;
+  priv->post_drop_messages = DEFAULT_POST_DROP_MESSAGES;
+  priv->drop_messages_interval_ms = DEFAULT_DROP_MESSAGES_INTERVAL_MS;
   priv->do_retransmission = DEFAULT_DO_RETRANSMISSION;
   priv->rtx_next_seqnum = DEFAULT_RTX_NEXT_SEQNUM;
   priv->rtx_delay = DEFAULT_RTX_DELAY;
@@ -1037,9 +1033,12 @@ gst_rtp_jitter_buffer_init (GstRtpJitterBuffer * jitterbuffer)
   priv->last_pts = -1;
   priv->last_rtptime = -1;
   priv->avg_jitter = 0;
+  priv->last_drop_msg_timestamp = GST_CLOCK_TIME_NONE;
+  priv->num_too_late = 0;
+  priv->num_drop_on_latency = 0;
   priv->segment_seqnum = GST_SEQNUM_INVALID;
-  priv->timers = g_array_new (FALSE, TRUE, sizeof (TimerData));
-  priv->rtx_stats_timers = timer_queue_new ();
+  priv->timers = rtp_timer_queue_new ();
+  priv->rtx_stats_timers = rtp_timer_queue_new ();
   priv->jbuf = rtp_jitter_buffer_new ();
   g_mutex_init (&priv->jbuf_lock);
   g_cond_init (&priv->jbuf_queue);
@@ -1049,7 +1048,7 @@ gst_rtp_jitter_buffer_init (GstRtpJitterBuffer * jitterbuffer)
   g_queue_init (&priv->gap_packets);
   gst_segment_init (&priv->segment, GST_FORMAT_TIME);
 
-  /* reset skew detection initialy */
+  /* reset skew detection initially */
   rtp_jitter_buffer_reset_skew (priv->jbuf);
   rtp_jitter_buffer_set_delay (priv->jbuf, priv->latency_ns);
   rtp_jitter_buffer_set_buffering (priv->jbuf, FALSE);
@@ -1085,55 +1084,19 @@ gst_rtp_jitter_buffer_init (GstRtpJitterBuffer * jitterbuffer)
   GST_OBJECT_FLAG_SET (jitterbuffer, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
 }
 
-#define IS_DROPABLE(it) (((it)->type == ITEM_TYPE_BUFFER) || ((it)->type == ITEM_TYPE_LOST))
-
-#define ITEM_TYPE_BUFFER        0
-#define ITEM_TYPE_LOST          1
-#define ITEM_TYPE_EVENT         2
-#define ITEM_TYPE_QUERY         3
-
-static RTPJitterBufferItem *
-alloc_item (gpointer data, guint type, GstClockTime dts, GstClockTime pts,
-    guint seqnum, guint count, guint rtptime)
-{
-  RTPJitterBufferItem *item;
-
-  item = g_slice_new (RTPJitterBufferItem);
-  item->data = data;
-  item->next = NULL;
-  item->prev = NULL;
-  item->type = type;
-  item->dts = dts;
-  item->pts = pts;
-  item->seqnum = seqnum;
-  item->count = count;
-  item->rtptime = rtptime;
-
-  return item;
-}
-
-static void
-free_item (RTPJitterBufferItem * item)
-{
-  g_return_if_fail (item != NULL);
-
-  if (item->data && item->type != ITEM_TYPE_QUERY)
-    gst_mini_object_unref (item->data);
-  g_slice_free (RTPJitterBufferItem, item);
-}
-
 static void
-free_item_and_retain_events (RTPJitterBufferItem * item, gpointer user_data)
+free_item_and_retain_sticky_events (RTPJitterBufferItem * item,
+    gpointer user_data)
 {
   GList **l = user_data;
 
   if (item->data && item->type == ITEM_TYPE_EVENT
       && GST_EVENT_IS_STICKY (item->data)) {
     *l = g_list_prepend (*l, item->data);
-  } else if (item->data && item->type != ITEM_TYPE_QUERY) {
-    gst_mini_object_unref (item->data);
+    item->data = NULL;
   }
-  g_slice_free (RTPJitterBufferItem, item);
+
+  rtp_jitter_buffer_free_item (item);
 }
 
 static void
@@ -1145,15 +1108,15 @@ gst_rtp_jitter_buffer_finalize (GObject * object)
   jitterbuffer = GST_RTP_JITTER_BUFFER (object);
   priv = jitterbuffer->priv;
 
-  g_array_free (priv->timers, TRUE);
-  timer_queue_free (priv->rtx_stats_timers);
+  g_object_unref (priv->timers);
+  g_object_unref (priv->rtx_stats_timers);
   g_mutex_clear (&priv->jbuf_lock);
   g_cond_clear (&priv->jbuf_queue);
   g_cond_clear (&priv->jbuf_timer);
   g_cond_clear (&priv->jbuf_event);
   g_cond_clear (&priv->jbuf_query);
 
-  rtp_jitter_buffer_flush (priv->jbuf, (GFunc) free_item, NULL);
+  rtp_jitter_buffer_flush (priv->jbuf, NULL, NULL);
   g_queue_foreach (&priv->gap_packets, (GFunc) gst_buffer_unref, NULL);
   g_queue_clear (&priv->gap_packets);
   g_object_unref (priv->jbuf);
@@ -1407,6 +1370,24 @@ gst_rtp_jitter_buffer_getcaps (GstPad * pad, GstCaps * filter)
   return caps;
 }
 
+/* g_ascii_string_to_unsigned is available since 2.54. Get rid of this wrapper
+ * when we bump the version in 1.18 */
+#if !GLIB_CHECK_VERSION(2,54,0)
+#define g_ascii_string_to_unsigned _gst_jitter_buffer_ascii_string_to_unsigned
+static gboolean
+_gst_jitter_buffer_ascii_string_to_unsigned (const gchar * str, guint base,
+    guint64 min, guint64 max, guint64 * out_num, GError ** error)
+{
+  gchar *endptr = NULL;
+  *out_num = g_ascii_strtoull (str, &endptr, base);
+  if (errno)
+    return FALSE;
+  if (endptr == str)
+    return FALSE;
+  return TRUE;
+}
+#endif
+
 /*
  * Must be called with JBUF_LOCK held
  */
@@ -1554,8 +1535,9 @@ gst_jitter_buffer_sink_parse_caps (GstRtpJitterBuffer * jitterbuffer,
     if ((mediaclk = gst_structure_get_string (caps_struct, "a-mediaclk"))) {
       GST_DEBUG_OBJECT (jitterbuffer, "Got media clock %s", mediaclk);
 
-      if (!g_str_has_prefix (mediaclk, "direct=")
-          || sscanf (mediaclk, "direct=%" G_GUINT64_FORMAT, &clock_offset) != 1)
+      if (!g_str_has_prefix (mediaclk, "direct=") ||
+          !g_ascii_string_to_unsigned (&mediaclk[8], 10, 0, G_MAXUINT64,
+              &clock_offset, NULL))
         GST_FIXME_OBJECT (jitterbuffer, "Unsupported media clock");
       if (strstr (mediaclk, "rate=") != NULL) {
         GST_FIXME_OBJECT (jitterbuffer, "Rate property not supported");
@@ -1633,11 +1615,14 @@ gst_rtp_jitter_buffer_flush_stop (GstRtpJitterBuffer * jitterbuffer)
   priv->last_in_pts = 0;
   priv->equidistant = 0;
   priv->segment_seqnum = GST_SEQNUM_INVALID;
+  priv->last_drop_msg_timestamp = GST_CLOCK_TIME_NONE;
+  priv->num_too_late = 0;
+  priv->num_drop_on_latency = 0;
   GST_DEBUG_OBJECT (jitterbuffer, "flush and reset jitterbuffer");
-  rtp_jitter_buffer_flush (priv->jbuf, (GFunc) free_item, NULL);
+  rtp_jitter_buffer_flush (priv->jbuf, NULL, NULL);
   rtp_jitter_buffer_disable_buffering (priv->jbuf, FALSE);
   rtp_jitter_buffer_reset_skew (priv->jbuf);
-  remove_all_timers (jitterbuffer);
+  rtp_timer_queue_remove_all (priv->timers);
   g_queue_foreach (&priv->gap_packets, (GFunc) gst_buffer_unref, NULL);
   g_queue_clear (&priv->gap_packets);
   JBUF_UNLOCK (priv);
@@ -1810,7 +1795,6 @@ static gboolean
 queue_event (GstRtpJitterBuffer * jitterbuffer, GstEvent * event)
 {
   GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  RTPJitterBufferItem *item;
   gboolean head;
 
   switch (GST_EVENT_TYPE (event)) {
@@ -1850,10 +1834,8 @@ queue_event (GstRtpJitterBuffer * jitterbuffer, GstEvent * event)
       break;
   }
 
-
   GST_DEBUG_OBJECT (jitterbuffer, "adding event");
-  item = alloc_item (event, ITEM_TYPE_EVENT, -1, -1, -1, 0, -1);
-  rtp_jitter_buffer_insert (priv->jbuf, item, &head, NULL);
+  head = rtp_jitter_buffer_append_event (priv->jbuf, event);
   if (head || priv->eos)
     JBUF_SIGNAL_EVENT (priv);
 
@@ -1959,7 +1941,7 @@ gst_rtp_jitter_buffer_sink_rtcp_event (GstPad * pad, GstObject * parent,
 }
 
 /*
- * Must be called with JBUF_LOCK held, will release the LOCK when emiting the
+ * Must be called with JBUF_LOCK held, will release the LOCK when emitting the
  * signal. The function returns GST_FLOW_ERROR when a parsing error happened and
  * GST_FLOW_FLUSHING when the element is shutting down. On success
  * GST_FLOW_OK is returned.
@@ -2040,6 +2022,89 @@ check_buffering_percent (GstRtpJitterBuffer * jitterbuffer, gint percent)
   return message;
 }
 
+/* call with jbuf lock held */
+static GstMessage *
+new_drop_message (GstRtpJitterBuffer * jitterbuffer, guint seqnum,
+    GstClockTime timestamp, DropMessageReason reason)
+{
+
+  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
+  GstMessage *drop_msg = NULL;
+  GstStructure *s;
+  GstClockTime current_time;
+  GstClockTime time_diff;
+  const gchar *reason_str;
+
+  current_time = get_current_running_time (jitterbuffer);
+  time_diff = current_time - priv->last_drop_msg_timestamp;
+
+  if (reason == REASON_TOO_LATE) {
+    priv->num_too_late++;
+    reason_str = "too-late";
+  } else if (reason == REASON_DROP_ON_LATENCY) {
+    priv->num_drop_on_latency++;
+    reason_str = "drop-on-latency";
+  } else {
+    GST_WARNING_OBJECT (jitterbuffer, "Invalid reason for drop message");
+    return drop_msg;
+  }
+
+  /* Only create new drop_msg if time since last drop_msg is larger that
+   * that the set interval, or if it is the first drop message posted */
+  if ((time_diff >= priv->drop_messages_interval_ms * GST_MSECOND) ||
+      (priv->last_drop_msg_timestamp == GST_CLOCK_TIME_NONE)) {
+
+    s = gst_structure_new ("drop-msg",
+        "seqnum", G_TYPE_UINT, seqnum,
+        "timestamp", GST_TYPE_CLOCK_TIME, timestamp,
+        "reason", G_TYPE_STRING, reason_str,
+        "num-too-late", G_TYPE_UINT, priv->num_too_late,
+        "num-drop-on-latency", G_TYPE_UINT, priv->num_drop_on_latency, NULL);
+
+    priv->last_drop_msg_timestamp = current_time;
+    priv->num_too_late = 0;
+    priv->num_drop_on_latency = 0;
+    drop_msg = gst_message_new_element (GST_OBJECT (jitterbuffer), s);
+  }
+  return drop_msg;
+}
+
+
+static inline GstClockTimeDiff
+timeout_offset (GstRtpJitterBuffer * jitterbuffer)
+{
+  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
+  return priv->ts_offset + priv->out_offset + priv->latency_ns;
+}
+
+static inline GstClockTime
+get_pts_timeout (const RtpTimer * timer)
+{
+  if (timer->timeout == -1)
+    return -1;
+
+  return timer->timeout - timer->offset;
+}
+
+static void
+update_timer_offsets (GstRtpJitterBuffer * jitterbuffer)
+{
+  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
+  RtpTimer *test = rtp_timer_queue_peek_earliest (priv->timers);
+  GstClockTimeDiff new_offset = timeout_offset (jitterbuffer);
+
+  while (test) {
+    if (test->type != RTP_TIMER_EXPECTED) {
+      test->timeout = get_pts_timeout (test) + new_offset;
+      test->offset = new_offset;
+      /* as we apply the offset on all timers, the order of timers won't
+       * change and we can skip updating the timer queue */
+    }
+
+    test = rtp_timer_get_next (test);
+  }
+}
+
 static void
 update_offset (GstRtpJitterBuffer * jitterbuffer)
 {
@@ -2063,6 +2128,8 @@ update_offset (GstRtpJitterBuffer * jitterbuffer)
       priv->ts_offset += priv->ts_offset_remainder;
       priv->ts_offset_remainder = 0;
     }
+
+    update_timer_offsets (jitterbuffer);
   }
 }
 
@@ -2084,86 +2151,6 @@ apply_offset (GstRtpJitterBuffer * jitterbuffer, GstClockTime timestamp)
   return timestamp;
 }
 
-static TimerQueue *
-timer_queue_new (void)
-{
-  TimerQueue *queue;
-
-  queue = g_slice_new (TimerQueue);
-  queue->timers = g_queue_new ();
-  queue->hashtable = g_hash_table_new (NULL, NULL);
-
-  return queue;
-}
-
-static void
-timer_queue_free (TimerQueue * queue)
-{
-  if (!queue)
-    return;
-
-  g_hash_table_destroy (queue->hashtable);
-  g_queue_free_full (queue->timers, g_free);
-  g_slice_free (TimerQueue, queue);
-}
-
-static void
-timer_queue_append (TimerQueue * queue, const TimerData * timer,
-    GstClockTime timeout, gboolean lost)
-{
-  TimerData *copy;
-
-  copy = g_memdup (timer, sizeof (*timer));
-  copy->timeout = timeout;
-  copy->type = lost ? TIMER_TYPE_LOST : TIMER_TYPE_EXPECTED;
-  copy->idx = -1;
-
-  GST_LOG ("Append rtx-stats timer #%d, %" GST_TIME_FORMAT,
-      copy->seqnum, GST_TIME_ARGS (copy->timeout));
-  g_queue_push_tail (queue->timers, copy);
-  g_hash_table_insert (queue->hashtable, GINT_TO_POINTER (copy->seqnum), copy);
-}
-
-static void
-timer_queue_clear_until (TimerQueue * queue, GstClockTime timeout)
-{
-  TimerData *test;
-
-  test = g_queue_peek_head (queue->timers);
-  while (test && test->timeout < timeout) {
-    GST_LOG ("Pop rtx-stats timer #%d, %" GST_TIME_FORMAT " < %"
-        GST_TIME_FORMAT, test->seqnum, GST_TIME_ARGS (test->timeout),
-        GST_TIME_ARGS (timeout));
-    g_hash_table_remove (queue->hashtable, GINT_TO_POINTER (test->seqnum));
-    g_free (g_queue_pop_head (queue->timers));
-    test = g_queue_peek_head (queue->timers);
-  }
-}
-
-static TimerData *
-timer_queue_find (TimerQueue * queue, guint16 seqnum)
-{
-  return g_hash_table_lookup (queue->hashtable, GINT_TO_POINTER (seqnum));
-}
-
-static TimerData *
-find_timer (GstRtpJitterBuffer * jitterbuffer, guint16 seqnum)
-{
-  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  TimerData *timer = NULL;
-  gint i, len;
-
-  len = priv->timers->len;
-  for (i = 0; i < len; i++) {
-    TimerData *test = &g_array_index (priv->timers, TimerData, i);
-    if (test->seqnum == seqnum) {
-      timer = test;
-      break;
-    }
-  }
-  return timer;
-}
-
 static void
 unschedule_current_timer (GstRtpJitterBuffer * jitterbuffer)
 {
@@ -2176,173 +2163,35 @@ unschedule_current_timer (GstRtpJitterBuffer * jitterbuffer)
   }
 }
 
-static GstClockTime
-get_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer)
-{
-  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  GstClockTime test_timeout;
-
-  if ((test_timeout = timer->timeout) == -1)
-    return -1;
-
-  if (timer->type != TIMER_TYPE_EXPECTED) {
-    /* add our latency and offset to get output times. */
-    test_timeout = apply_offset (jitterbuffer, test_timeout);
-    test_timeout += priv->latency_ns;
-  }
-  return test_timeout;
-}
-
-static void
-recalculate_timer (GstRtpJitterBuffer * jitterbuffer, TimerData * timer)
-{
-  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-
-  if (priv->clock_id) {
-    GstClockTime timeout = get_timeout (jitterbuffer, timer);
-
-    GST_DEBUG ("%" GST_TIME_FORMAT " <> %" GST_TIME_FORMAT,
-        GST_TIME_ARGS (timeout), GST_TIME_ARGS (priv->timer_timeout));
-
-    if (timeout == -1 || timeout < priv->timer_timeout)
-      unschedule_current_timer (jitterbuffer);
-  }
-}
-
-static TimerData *
-add_timer (GstRtpJitterBuffer * jitterbuffer, TimerType type,
-    guint16 seqnum, guint num, GstClockTime timeout, GstClockTime delay,
-    GstClockTime duration)
-{
-  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  TimerData *timer;
-  gint len;
-
-  GST_DEBUG_OBJECT (jitterbuffer,
-      "add timer %d for seqnum %d to %" GST_TIME_FORMAT ", delay %"
-      GST_TIME_FORMAT, type, seqnum, GST_TIME_ARGS (timeout),
-      GST_TIME_ARGS (delay));
-
-  len = priv->timers->len;
-  g_array_set_size (priv->timers, len + 1);
-  timer = &g_array_index (priv->timers, TimerData, len);
-  timer->idx = len;
-  timer->type = type;
-  timer->seqnum = seqnum;
-  timer->num = num;
-  timer->timeout = timeout + delay;
-  timer->duration = duration;
-  if (type == TIMER_TYPE_EXPECTED) {
-    timer->rtx_base = timeout;
-    timer->rtx_delay = delay;
-    timer->rtx_retry = 0;
-  }
-  timer->rtx_last = GST_CLOCK_TIME_NONE;
-  timer->num_rtx_retry = 0;
-  timer->num_rtx_received = 0;
-  recalculate_timer (jitterbuffer, timer);
-  JBUF_SIGNAL_TIMER (priv);
-
-  return timer;
-}
-
 static void
-reschedule_timer (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
-    guint16 seqnum, GstClockTime timeout, GstClockTime delay, gboolean reset)
+update_current_timer (GstRtpJitterBuffer * jitterbuffer)
 {
   GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  gboolean seqchange, timechange;
-  guint16 oldseq;
-  GstClockTime new_timeout;
+  RtpTimer *timer;
 
-  oldseq = timer->seqnum;
-  new_timeout = timeout + delay;
-  seqchange = oldseq != seqnum;
-  timechange = timer->timeout != new_timeout;
+  timer = rtp_timer_queue_peek_earliest (priv->timers);
 
-  if (!seqchange && !timechange) {
-    GST_DEBUG_OBJECT (jitterbuffer,
-        "No changes in seqnum (%d) and timeout (%" GST_TIME_FORMAT
-        "), skipping", oldseq, GST_TIME_ARGS (timer->timeout));
+  /* we never need to wakeup the timer thread when there is no more timers, if
+   * it was waiting on a clock id, it will simply do later and then wait on
+   * the conditions */
+  if (timer == NULL) {
+    GST_DEBUG_OBJECT (jitterbuffer, "no more timers");
     return;
   }
 
-  GST_DEBUG_OBJECT (jitterbuffer,
-      "replace timer %d for seqnum %d->%d timeout %" GST_TIME_FORMAT
-      "->%" GST_TIME_FORMAT, timer->type, oldseq, seqnum,
-      GST_TIME_ARGS (timer->timeout), GST_TIME_ARGS (new_timeout));
-
-  timer->timeout = new_timeout;
-  timer->seqnum = seqnum;
-  if (reset) {
-    GST_DEBUG_OBJECT (jitterbuffer, "reset rtx delay %" GST_TIME_FORMAT
-        "->%" GST_TIME_FORMAT, GST_TIME_ARGS (timer->rtx_delay),
-        GST_TIME_ARGS (delay));
-    timer->rtx_base = timeout;
-    timer->rtx_delay = delay;
-    timer->rtx_retry = 0;
-  }
-  if (seqchange) {
-    timer->num_rtx_retry = 0;
-    timer->num_rtx_received = 0;
-  }
-
-  if (priv->clock_id) {
-    /* we changed the seqnum and there is a timer currently waiting with this
-     * seqnum, unschedule it */
-    if (seqchange && priv->timer_seqnum == oldseq)
-      unschedule_current_timer (jitterbuffer);
-    /* we changed the time, check if it is earlier than what we are waiting
-     * for and unschedule if so */
-    else if (timechange)
-      recalculate_timer (jitterbuffer, timer);
-  }
-}
-
-static TimerData *
-set_timer (GstRtpJitterBuffer * jitterbuffer, TimerType type,
-    guint16 seqnum, GstClockTime timeout)
-{
-  TimerData *timer;
-
-  /* find the seqnum timer */
-  timer = find_timer (jitterbuffer, seqnum);
-  if (timer == NULL) {
-    timer = add_timer (jitterbuffer, type, seqnum, 0, timeout, 0, -1);
-  } else {
-    reschedule_timer (jitterbuffer, timer, seqnum, timeout, 0, FALSE);
-  }
-  return timer;
-}
+  GST_DEBUG_OBJECT (jitterbuffer, "waiting till %" GST_TIME_FORMAT
+      " and earliest timeout is at %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (priv->timer_timeout), GST_TIME_ARGS (timer->timeout));
 
-static void
-remove_timer (GstRtpJitterBuffer * jitterbuffer, TimerData * timer)
-{
-  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  guint idx;
+  /* wakeup the timer thread in case the timer queue was empty */
+  JBUF_SIGNAL_TIMER (priv);
 
-  if (timer->idx == -1)
+  /* no need to wait if the current wait is earlier or later */
+  if (timer->timeout != -1 && timer->timeout >= priv->timer_timeout)
     return;
 
-  if (priv->clock_id && priv->timer_seqnum == timer->seqnum)
-    unschedule_current_timer (jitterbuffer);
-
-  idx = timer->idx;
-  GST_DEBUG_OBJECT (jitterbuffer, "removed index %d", idx);
-  g_array_remove_index_fast (priv->timers, idx);
-  timer->idx = idx;
-
-  JBUF_SIGNAL_TIMER (priv);
-}
-
-static void
-remove_all_timers (GstRtpJitterBuffer * jitterbuffer)
-{
-  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  GST_DEBUG_OBJECT (jitterbuffer, "removed all timers");
-  g_array_set_size (priv->timers, 0);
+  /* for other cases, force a reschedule of the timer thread */
   unschedule_current_timer (jitterbuffer);
-  JBUF_SIGNAL_TIMER (priv);
 }
 
 /* get the extra delay to wait before sending RTX */
@@ -2352,6 +2201,15 @@ get_rtx_delay (GstRtpJitterBufferPrivate * priv)
   GstClockTime delay;
 
   if (priv->rtx_delay == -1) {
+    /* the maximum delay for any RTX-packet is given by the latency, since
+       anything after that is considered lost. For various calulcations,
+       (given large avg_jitter and/or packet_spacing), the resulting delay
+       could exceed the configured latency, ending up issuing an RTX-request
+       that would never arrive in time. To help this we cap the delay
+       for any RTX with the last possible time it could still arrive in time. */
+    GstClockTime delay_max = (priv->latency_ns > priv->avg_rtx_rtt) ?
+        priv->latency_ns - priv->avg_rtx_rtt : priv->latency_ns;
+
     if (priv->avg_jitter == 0 && priv->packet_spacing == 0) {
       delay = DEFAULT_AUTO_RTX_DELAY;
     } else {
@@ -2359,6 +2217,8 @@ get_rtx_delay (GstRtpJitterBufferPrivate * priv)
        * packet spacing is a good margin */
       delay = MAX (priv->avg_jitter * 2, priv->packet_spacing / 2);
     }
+
+    delay = MIN (delay_max, delay);
   } else {
     delay = priv->rtx_delay * GST_MSECOND;
   }
@@ -2368,30 +2228,6 @@ get_rtx_delay (GstRtpJitterBufferPrivate * priv)
   return delay;
 }
 
-/* Check if packet with seqnum is already considered definitely lost by being
- * part of a "lost timer" for multiple packets */
-static gboolean
-already_lost (GstRtpJitterBuffer * jitterbuffer, guint16 seqnum)
-{
-  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  gint i, len;
-
-  len = priv->timers->len;
-  for (i = 0; i < len; i++) {
-    TimerData *test = &g_array_index (priv->timers, TimerData, i);
-    gint gap = gst_rtp_buffer_compare_seqnum (test->seqnum, seqnum);
-
-    if (test->num > 1 && test->type == TIMER_TYPE_LOST && gap >= 0 &&
-        gap < test->num) {
-      GST_DEBUG ("seqnum #%d already considered definitely lost (#%d->#%d)",
-          seqnum, test->seqnum, (test->seqnum + test->num - 1) & 0xffff);
-      return TRUE;
-    }
-  }
-
-  return FALSE;
-}
-
 /* we just received a packet with seqnum and dts.
  *
  * First check for old seqnum that we are still expecting. If the gap with the
@@ -2405,36 +2241,52 @@ already_lost (GstRtpJitterBuffer * jitterbuffer, guint16 seqnum)
 static void
 update_timers (GstRtpJitterBuffer * jitterbuffer, guint16 seqnum,
     GstClockTime dts, GstClockTime pts, gboolean do_next_seqnum,
-    gboolean is_rtx, TimerData * timer)
+    gboolean is_rtx, RtpTimer * timer)
 {
   GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
+  gboolean is_stats_timer = FALSE;
+
+  if (timer && rtp_timer_queue_find (priv->rtx_stats_timers, timer->seqnum))
+    is_stats_timer = TRUE;
 
-  /* go through all timers and unschedule the ones with a large gap */
+  /* schedule immediatly expected timer which exceed the maximum RTX delay
+   * reorder configuration */
   if (priv->do_retransmission && priv->rtx_delay_reorder > 0) {
-    gint i, len;
-    len = priv->timers->len;
-    for (i = 0; i < len; i++) {
-      TimerData *test = &g_array_index (priv->timers, TimerData, i);
+    RtpTimer *test = rtp_timer_queue_peek_earliest (priv->timers);
+    while (test) {
       gint gap;
 
+      /* filter the timer type to speed up this loop */
+      if (test->type != RTP_TIMER_EXPECTED) {
+        test = rtp_timer_get_next (test);
+        continue;
+      }
+
       gap = gst_rtp_buffer_compare_seqnum (test->seqnum, seqnum);
 
       GST_DEBUG_OBJECT (jitterbuffer, "%d, #%d<->#%d gap %d",
           test->type, test->seqnum, seqnum, gap);
 
-      if (gap > priv->rtx_delay_reorder) {
-        /* max gap, we exceeded the max reorder distance and we don't expect the
-         * missing packet to be this reordered */
-        if (test->num_rtx_retry == 0 && test->type == TIMER_TYPE_EXPECTED)
-          reschedule_timer (jitterbuffer, test, test->seqnum, -1, 0, FALSE);
-      }
+      /* if this expected packet have a smaller gap then the configured one,
+       * then earlier timer are not expected to have bigger gap as the timer
+       * queue is ordered */
+      if (gap <= priv->rtx_delay_reorder)
+        break;
+
+      /* max gap, we exceeded the max reorder distance and we don't expect the
+       * missing packet to be this reordered */
+      if (test->num_rtx_retry == 0 && test->type == RTP_TIMER_EXPECTED)
+        rtp_timer_queue_update_timer (priv->timers, test, test->seqnum,
+            -1, 0, 0, FALSE);
+
+      test = rtp_timer_get_next (test);
     }
   }
 
   do_next_seqnum = do_next_seqnum && priv->packet_spacing > 0
       && priv->do_retransmission && priv->rtx_next_seqnum;
 
-  if (timer && timer->type != TIMER_TYPE_DEADLINE) {
+  if (timer && timer->type != RTP_TIMER_DEADLINE) {
     if (timer->num_rtx_retry > 0) {
       if (is_rtx) {
         update_rtx_stats (jitterbuffer, timer, dts, TRUE);
@@ -2444,14 +2296,16 @@ update_timers (GstRtpJitterBuffer * jitterbuffer, guint16 seqnum,
         do_next_seqnum = FALSE;
       }
 
-      if (!is_rtx || timer->num_rtx_retry > 1) {
+      if (!is_stats_timer && (!is_rtx || timer->num_rtx_retry > 1)) {
+        RtpTimer *stats_timer = rtp_timer_dup (timer);
         /* Store timer in order to record stats when/if the retransmitted
          * packet arrives. We should also store timer information if we've
          * requested retransmission more than once since we may receive
          * several retransmitted packets. For accuracy we should update the
          * stats also when the redundant retransmitted packets arrives. */
-        timer_queue_append (priv->rtx_stats_timers, timer,
-            pts + priv->rtx_stats_timeout * GST_MSECOND, FALSE);
+        stats_timer->timeout = pts + priv->rtx_stats_timeout * GST_MSECOND;
+        stats_timer->type = RTP_TIMER_EXPECTED;
+        rtp_timer_queue_insert (priv->rtx_stats_timers, stats_timer);
       }
     }
   }
@@ -2471,18 +2325,19 @@ update_timers (GstRtpJitterBuffer * jitterbuffer, guint16 seqnum,
         GST_TIME_ARGS (expected), GST_TIME_ARGS (delay),
         GST_TIME_ARGS (priv->packet_spacing), GST_TIME_ARGS (priv->avg_jitter));
 
-    if (timer) {
-      timer->type = TIMER_TYPE_EXPECTED;
-      reschedule_timer (jitterbuffer, timer, priv->next_in_seqnum, expected,
-          delay, TRUE);
+    if (timer && !is_stats_timer) {
+      timer->type = RTP_TIMER_EXPECTED;
+      rtp_timer_queue_update_timer (priv->timers, timer, priv->next_in_seqnum,
+          expected, delay, 0, TRUE);
     } else {
-      add_timer (jitterbuffer, TIMER_TYPE_EXPECTED, priv->next_in_seqnum, 0,
+      rtp_timer_queue_set_expected (priv->timers, priv->next_in_seqnum,
           expected, delay, priv->packet_spacing);
     }
-  } else if (timer && timer->type != TIMER_TYPE_DEADLINE) {
+  } else if (timer && timer->type != RTP_TIMER_DEADLINE && !is_stats_timer) {
     /* if we had a timer, remove it, we don't know when to expect the next
      * packet. */
-    remove_timer (jitterbuffer, timer);
+    rtp_timer_queue_unschedule (priv->timers, timer);
+    rtp_timer_free (timer);
   }
 }
 
@@ -2527,17 +2382,63 @@ calculate_packet_spacing (GstRtpJitterBuffer * jitterbuffer, guint32 rtptime,
 }
 
 static void
+insert_lost_event (GstRtpJitterBuffer * jitterbuffer,
+    guint16 seqnum, guint lost_packets, GstClockTime timestamp,
+    GstClockTime duration, guint num_rtx_retry)
+{
+  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
+  GstEvent *event = NULL;
+  guint next_in_seqnum;
+
+  /* we had a gap and thus we lost some packets. Create an event for this.  */
+  if (lost_packets > 1)
+    GST_DEBUG_OBJECT (jitterbuffer, "Packets #%d -> #%d lost", seqnum,
+        seqnum + lost_packets - 1);
+  else
+    GST_DEBUG_OBJECT (jitterbuffer, "Packet #%d lost", seqnum);
+
+  priv->num_lost += lost_packets;
+  priv->num_rtx_failed += num_rtx_retry;
+
+  next_in_seqnum = (seqnum + lost_packets) & 0xffff;
+
+  /* we now only accept seqnum bigger than this */
+  if (gst_rtp_buffer_compare_seqnum (priv->next_in_seqnum, next_in_seqnum) > 0) {
+    priv->next_in_seqnum = next_in_seqnum;
+    priv->last_in_pts = timestamp;
+  }
+
+  /* Avoid creating events if we don't need it. Note that we still need to create
+   * the lost *ITEM* since it will be used to notify the outgoing thread of
+   * lost items (so that we can set discont flags and such) */
+  if (priv->do_lost) {
+    /* create packet lost event */
+    if (duration == GST_CLOCK_TIME_NONE && priv->packet_spacing > 0)
+      duration = priv->packet_spacing;
+    event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
+        gst_structure_new ("GstRTPPacketLost",
+            "seqnum", G_TYPE_UINT, (guint) seqnum,
+            "timestamp", G_TYPE_UINT64, timestamp,
+            "duration", G_TYPE_UINT64, duration,
+            "retry", G_TYPE_UINT, num_rtx_retry, NULL));
+  }
+  if (rtp_jitter_buffer_append_lost_event (priv->jbuf,
+          event, seqnum, lost_packets))
+    JBUF_SIGNAL_EVENT (priv);
+}
+
+static void
 calculate_expected (GstRtpJitterBuffer * jitterbuffer, guint32 expected,
     guint16 seqnum, GstClockTime pts, gint gap)
 {
   GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  GstClockTime duration, expected_pts, delay;
-  TimerType type;
+  GstClockTime duration, expected_pts;
   gboolean equidistant = priv->equidistant > 0;
+  GstClockTime last_in_pts = priv->last_in_pts;
 
   GST_DEBUG_OBJECT (jitterbuffer,
       "pts %" GST_TIME_FORMAT ", last %" GST_TIME_FORMAT,
-      GST_TIME_ARGS (pts), GST_TIME_ARGS (priv->last_in_pts));
+      GST_TIME_ARGS (pts), GST_TIME_ARGS (last_in_pts));
 
   if (pts == GST_CLOCK_TIME_NONE) {
     GST_WARNING_OBJECT (jitterbuffer, "Have no PTS");
@@ -2547,8 +2448,8 @@ calculate_expected (GstRtpJitterBuffer * jitterbuffer, guint32 expected,
   if (equidistant) {
     GstClockTime total_duration;
     /* the total duration spanned by the missing packets */
-    if (pts >= priv->last_in_pts)
-      total_duration = pts - priv->last_in_pts;
+    if (pts >= last_in_pts)
+      total_duration = pts - last_in_pts;
     else
       total_duration = 0;
 
@@ -2585,17 +2486,30 @@ calculate_expected (GstRtpJitterBuffer * jitterbuffer, guint32 expected,
           GST_TIME_ARGS (priv->latency_ns), lost_packets,
           GST_TIME_ARGS (gap_time));
 
-      /* this timer will fire immediately and the lost event will be pushed from
-       * the timer thread */
+      /* this multi-lost-packet event will be inserted directly into the packet-queue
+         for immediate processing */
       if (lost_packets > 0) {
-        add_timer (jitterbuffer, TIMER_TYPE_LOST, expected, lost_packets,
-            priv->last_in_pts + duration, 0, gap_time);
+        RtpTimer *timer;
+        GstClockTime timestamp =
+            apply_offset (jitterbuffer, last_in_pts + duration);
+        insert_lost_event (jitterbuffer, expected, lost_packets, timestamp,
+            gap_time, 0);
+
+        timer = rtp_timer_queue_find (priv->timers, expected);
+        if (timer && timer->type == RTP_TIMER_EXPECTED) {
+          if (timer->queued)
+            rtp_timer_queue_unschedule (priv->timers, timer);
+          GST_DEBUG_OBJECT (jitterbuffer, "removing timer for seqnum #%u",
+              expected);
+          rtp_timer_free (timer);
+        }
+
         expected += lost_packets;
-        priv->last_in_pts += gap_time;
+        last_in_pts += gap_time;
       }
     }
 
-    expected_pts = priv->last_in_pts + duration;
+    expected_pts = last_in_pts + duration;
   } else {
     /* If we cannot assume equidistant packet spacing, the only thing we now
      * for sure is that the missing packets have expected pts not later than
@@ -2604,34 +2518,40 @@ calculate_expected (GstRtpJitterBuffer * jitterbuffer, guint32 expected,
     expected_pts = pts;
   }
 
-  delay = 0;
-
   if (priv->do_retransmission) {
-    TimerData *timer = find_timer (jitterbuffer, expected);
-
-    type = TIMER_TYPE_EXPECTED;
-    delay = get_rtx_delay (priv);
+    RtpTimer *timer = rtp_timer_queue_find (priv->timers, expected);
+    GstClockTime rtx_delay = get_rtx_delay (priv);
 
     /* if we had a timer for the first missing packet, update it. */
-    if (timer && timer->type == TIMER_TYPE_EXPECTED) {
+    if (timer && timer->type == RTP_TIMER_EXPECTED) {
       GstClockTime timeout = timer->timeout;
+      GstClockTime delay = MAX (rtx_delay, pts - expected_pts);
 
       timer->duration = duration;
       if (timeout > (expected_pts + delay) && timer->num_rtx_retry == 0) {
-        reschedule_timer (jitterbuffer, timer, timer->seqnum, expected_pts,
-            delay, TRUE);
+        rtp_timer_queue_update_timer (priv->timers, timer, timer->seqnum,
+            expected_pts, delay, 0, TRUE);
       }
       expected++;
       expected_pts += duration;
     }
-  } else {
-    type = TIMER_TYPE_LOST;
-  }
 
-  while (gst_rtp_buffer_compare_seqnum (expected, seqnum) > 0) {
-    add_timer (jitterbuffer, type, expected, 0, expected_pts, delay, duration);
-    expected_pts += duration;
-    expected++;
+    while (gst_rtp_buffer_compare_seqnum (expected, seqnum) > 0) {
+      /* minimum delay the expected-timer has "waited" is the elapsed time
+       * since expected arrival of the missing packet */
+      GstClockTime delay = MAX (rtx_delay, pts - expected_pts);
+      rtp_timer_queue_set_expected (priv->timers, expected, expected_pts,
+          delay, duration);
+      expected_pts += duration;
+      expected++;
+    }
+  } else {
+    while (gst_rtp_buffer_compare_seqnum (expected, seqnum) > 0) {
+      rtp_timer_queue_set_lost (priv->timers, expected, expected_pts,
+          duration, timeout_offset (jitterbuffer));
+      expected_pts += duration;
+      expected++;
+    }
   }
 }
 
@@ -2683,10 +2603,10 @@ calculate_jitter (GstRtpJitterBuffer * jitterbuffer, GstClockTime dts,
   priv->avg_jitter = (diff + (15 * priv->avg_jitter)) >> 4;
 
   GST_LOG_OBJECT (jitterbuffer,
-      "dtsdiff %" GST_TIME_FORMAT " rtptime %" GST_TIME_FORMAT
-      ", clock-rate %d, diff %" GST_TIME_FORMAT ", jitter: %" GST_TIME_FORMAT,
-      GST_TIME_ARGS (dtsdiff), GST_TIME_ARGS (rtpdiffns), priv->clock_rate,
-      GST_TIME_ARGS (diff), GST_TIME_ARGS (priv->avg_jitter));
+      "dtsdiff %" GST_STIME_FORMAT " rtptime %" GST_STIME_FORMAT
+      ", clock-rate %d, diff %" GST_STIME_FORMAT ", jitter: %" GST_TIME_FORMAT,
+      GST_STIME_ARGS (dtsdiff), GST_STIME_ARGS (rtpdiffns), priv->clock_rate,
+      GST_STIME_ARGS (diff), GST_TIME_ARGS (priv->avg_jitter));
 
   return;
 
@@ -2820,13 +2740,12 @@ gst_rtp_jitter_buffer_reset (GstRtpJitterBuffer * jitterbuffer,
   GstFlowReturn ret = GST_FLOW_OK;
   GList *events = NULL, *l;
   GList *buffers;
-  gboolean head;
 
   GST_DEBUG_OBJECT (jitterbuffer, "flush and reset jitterbuffer");
   rtp_jitter_buffer_flush (priv->jbuf,
-      (GFunc) free_item_and_retain_events, &events);
+      (GFunc) free_item_and_retain_sticky_events, &events);
   rtp_jitter_buffer_reset_skew (priv->jbuf);
-  remove_all_timers (jitterbuffer);
+  rtp_timer_queue_remove_all (priv->timers);
   priv->discont = TRUE;
   priv->last_popped_seqnum = -1;
 
@@ -2849,10 +2768,7 @@ gst_rtp_jitter_buffer_reset (GstRtpJitterBuffer * jitterbuffer,
    */
   events = g_list_reverse (events);
   for (l = events; l; l = l->next) {
-    RTPJitterBufferItem *item;
-
-    item = alloc_item (l->data, ITEM_TYPE_EVENT, -1, -1, -1, 0, -1);
-    rtp_jitter_buffer_insert (priv->jbuf, item, &head, NULL);
+    rtp_jitter_buffer_append_event (priv->jbuf, l->data);
   }
   g_list_free (events);
 
@@ -2889,7 +2805,7 @@ gst_rtp_jitter_buffer_fast_start (GstRtpJitterBuffer * jitterbuffer)
 {
   GstRtpJitterBufferPrivate *priv;
   RTPJitterBufferItem *item;
-  TimerData *timer;
+  RtpTimer *timer;
 
   priv = jitterbuffer->priv;
 
@@ -2900,8 +2816,8 @@ gst_rtp_jitter_buffer_fast_start (GstRtpJitterBuffer * jitterbuffer)
   if (!item)
     return FALSE;
 
-  timer = find_timer (jitterbuffer, item->seqnum);
-  if (!timer || timer->type != TIMER_TYPE_DEADLINE)
+  timer = rtp_timer_queue_find (priv->timers, item->seqnum);
+  if (!timer || timer->type != RTP_TIMER_DEADLINE)
     return FALSE;
 
   if (rtp_jitter_buffer_can_fast_start (priv->jbuf,
@@ -2909,6 +2825,7 @@ gst_rtp_jitter_buffer_fast_start (GstRtpJitterBuffer * jitterbuffer)
     GST_INFO_OBJECT (jitterbuffer, "We found %i consecutive packet, start now",
         priv->faststart_min_packets);
     timer->timeout = -1;
+    rtp_timer_queue_reschedule (priv->timers, timer);
     return TRUE;
   }
 
@@ -2927,15 +2844,17 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
   GstClockTime dts, pts;
   guint64 latency_ts;
   gboolean head;
+  gboolean duplicate;
   gint percent = -1;
   guint8 pt;
   GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
   gboolean do_next_seqnum = FALSE;
-  RTPJitterBufferItem *item;
   GstMessage *msg = NULL;
+  GstMessage *drop_msg = NULL;
   gboolean estimated_dts = FALSE;
   gint32 packet_rate, max_dropout, max_misorder;
-  TimerData *timer = NULL;
+  RtpTimer *timer = NULL;
+  gboolean is_rtx;
 
   jitterbuffer = GST_RTP_JITTER_BUFFER_CAST (parent);
 
@@ -2949,6 +2868,8 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
   rtptime = gst_rtp_buffer_get_timestamp (&rtp);
   gst_rtp_buffer_unmap (&rtp);
 
+  is_rtx = GST_BUFFER_IS_RETRANSMISSION (buffer);
+
   /* make sure we have PTS and DTS set */
   pts = GST_BUFFER_PTS (buffer);
   dts = GST_BUFFER_DTS (buffer);
@@ -2980,8 +2901,7 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
 
   GST_DEBUG_OBJECT (jitterbuffer,
       "Received packet #%d at time %" GST_TIME_FORMAT ", discont %d, rtx %d",
-      seqnum, GST_TIME_ARGS (dts), GST_BUFFER_IS_DISCONT (buffer),
-      GST_BUFFER_IS_RETRANSMISSION (buffer));
+      seqnum, GST_TIME_ARGS (dts), GST_BUFFER_IS_DISCONT (buffer), is_rtx);
 
   JBUF_LOCK_CHECK (priv, out_flushing);
 
@@ -3019,7 +2939,7 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
   if (G_UNLIKELY (priv->eos))
     goto have_eos;
 
-  if (!GST_BUFFER_IS_RETRANSMISSION (buffer))
+  if (!is_rtx)
     calculate_jitter (jitterbuffer, dts, rtptime);
 
   if (priv->seqnum_base != -1) {
@@ -3043,18 +2963,35 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
 
   expected = priv->next_in_seqnum;
 
-  packet_rate =
-      gst_rtp_packet_rate_ctx_update (&priv->packet_rate_ctx, seqnum, rtptime);
+  /* don't update packet-rate based on RTX, as those arrive highly unregularly */
+  if (!is_rtx) {
+    packet_rate = gst_rtp_packet_rate_ctx_update (&priv->packet_rate_ctx,
+        seqnum, rtptime);
+    GST_TRACE_OBJECT (jitterbuffer, "updated packet_rate: %d", packet_rate);
+  }
   max_dropout =
       gst_rtp_packet_rate_ctx_get_max_dropout (&priv->packet_rate_ctx,
       priv->max_dropout_time);
   max_misorder =
       gst_rtp_packet_rate_ctx_get_max_misorder (&priv->packet_rate_ctx,
       priv->max_misorder_time);
-  GST_TRACE_OBJECT (jitterbuffer,
-      "packet_rate: %d, max_dropout: %d, max_misorder: %d", packet_rate,
+  GST_TRACE_OBJECT (jitterbuffer, "max_dropout: %d, max_misorder: %d",
       max_dropout, max_misorder);
 
+  timer = rtp_timer_queue_find (priv->timers, seqnum);
+  if (is_rtx) {
+    if (G_UNLIKELY (!priv->do_retransmission))
+      goto unsolicited_rtx;
+
+    if (!timer)
+      timer = rtp_timer_queue_find (priv->rtx_stats_timers, seqnum);
+
+    /* If the first buffer is an (old) rtx, e.g. from before a reset, or
+     * already lost, ignore it */
+    if (!timer || expected == -1)
+      goto unsolicited_rtx;
+  }
+
   /* now check against our expected seqnum */
   if (G_UNLIKELY (expected == -1)) {
     GST_DEBUG_OBJECT (jitterbuffer, "First buffer #%d", seqnum);
@@ -3062,12 +2999,19 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
     /* calculate a pts based on rtptime and arrival time (dts) */
     pts =
         rtp_jitter_buffer_calculate_pts (priv->jbuf, dts, estimated_dts,
-        rtptime, gst_element_get_base_time (GST_ELEMENT_CAST (jitterbuffer)));
+        rtptime, gst_element_get_base_time (GST_ELEMENT_CAST (jitterbuffer)),
+        0, FALSE);
+
+    if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (pts))) {
+      /* A valid timestamp cannot be calculated, discard packet */
+      goto discard_invalid;
+    }
 
     /* we don't know what the next_in_seqnum should be, wait for the last
      * possible moment to push this buffer, maybe we get an earlier seqnum
      * while we wait */
-    set_timer (jitterbuffer, TIMER_TYPE_DEADLINE, seqnum, pts);
+    rtp_timer_queue_set_deadline (priv->timers, seqnum, pts,
+        timeout_offset (jitterbuffer));
 
     do_next_seqnum = TRUE;
     /* take rtptime and pts to calculate packet spacing */
@@ -3081,7 +3025,8 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
     GST_DEBUG_OBJECT (jitterbuffer, "expected #%d, got #%d, gap of %d",
         expected, seqnum, gap);
 
-    if (G_UNLIKELY (gap > 0 && priv->timers->len >= max_dropout)) {
+    if (G_UNLIKELY (gap > 0 &&
+            rtp_timer_queue_length (priv->timers) >= max_dropout)) {
       /* If we have timers for more than RTP_MAX_DROPOUT packets
        * pending this means that we have a huge gap overall. We can
        * reset the jitterbuffer at this point because there's
@@ -3089,13 +3034,14 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
        * sensible with the past data. Just try again from the
        * next packet */
       GST_WARNING_OBJECT (jitterbuffer, "%d pending timers > %d - resetting",
-          priv->timers->len, max_dropout);
-      gst_buffer_unref (buffer);
+          rtp_timer_queue_length (priv->timers), max_dropout);
+      g_queue_insert_sorted (&priv->gap_packets, buffer,
+          (GCompareDataFunc) compare_buffer_seqnum, NULL);
       return gst_rtp_jitter_buffer_reset (jitterbuffer, pad, parent, seqnum);
     }
 
     /* Special handling of large gaps */
-    if ((gap != -1 && gap < -max_misorder) || (gap >= max_dropout)) {
+    if (!is_rtx && ((gap != -1 && gap < -max_misorder) || (gap >= max_dropout))) {
       gboolean reset = handle_big_gap_buffer (jitterbuffer, buffer, pt, seqnum,
           gap, max_dropout, max_misorder);
       if (reset) {
@@ -3116,7 +3062,13 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
     /* If we estimated the DTS, don't consider it in the clock skew calculations */
     pts =
         rtp_jitter_buffer_calculate_pts (priv->jbuf, dts, estimated_dts,
-        rtptime, gst_element_get_base_time (GST_ELEMENT_CAST (jitterbuffer)));
+        rtptime, gst_element_get_base_time (GST_ELEMENT_CAST (jitterbuffer)),
+        gap, is_rtx);
+
+    if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (pts))) {
+      /* A valid timestamp cannot be calculated, discard packet */
+      goto discard_invalid;
+    }
 
     if (G_LIKELY (gap == 0)) {
       /* packet is expected */
@@ -3146,24 +3098,28 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
     priv->next_in_seqnum = (seqnum + 1) & 0xffff;
   }
 
-  timer = find_timer (jitterbuffer, seqnum);
-  if (GST_BUFFER_IS_RETRANSMISSION (buffer)) {
-    if (!timer)
-      timer = timer_queue_find (priv->rtx_stats_timers, seqnum);
-    if (timer)
-      timer->num_rtx_received++;
-  }
+  if (is_rtx)
+    timer->num_rtx_received++;
 
   /* At 2^15, we would detect a seqnum rollover too early, therefore
    * limit the queue size. But let's not limit it to a number that is
    * too small to avoid emptying it needlessly if there is a spurious huge
    * sequence number, let's allow at least 10k packets in any case. */
-  while (rtp_jitter_buffer_get_seqnum_diff (priv->jbuf) >= 32765 &&
-      rtp_jitter_buffer_num_packets (priv->jbuf) > 10000 &&
-      priv->srcresult == GST_FLOW_OK)
+  while (rtp_jitter_buffer_is_full (priv->jbuf) &&
+      priv->srcresult == GST_FLOW_OK) {
+    RtpTimer *timer = rtp_timer_queue_peek_earliest (priv->timers);
+    while (timer) {
+      timer->timeout = -1;
+      if (timer->type == RTP_TIMER_DEADLINE)
+        break;
+      timer = rtp_timer_get_next (timer);
+    }
+
+    update_current_timer (jitterbuffer);
     JBUF_WAIT_QUEUE (priv);
-  if (priv->srcresult != GST_FLOW_OK)
-    goto out_flushing;
+    if (priv->srcresult != GST_FLOW_OK)
+      goto out_flushing;
+  }
 
   /* let's check if this buffer is too late, we can only accept packets with
    * bigger seqnum than the one we last pushed. */
@@ -3175,12 +3131,12 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
     /* priv->last_popped_seqnum >= seqnum, we're too late. */
     if (G_UNLIKELY (gap <= 0)) {
       if (priv->do_retransmission) {
-        if (GST_BUFFER_IS_RETRANSMISSION (buffer) && timer) {
+        if (is_rtx && timer) {
           update_rtx_stats (jitterbuffer, timer, dts, FALSE);
           /* Only count the retranmitted packet too late if it has been
            * considered lost. If the original packet arrived before the
            * retransmitted we just count it as a duplicate. */
-          if (timer->type != TIMER_TYPE_LOST)
+          if (timer->type != RTP_TIMER_LOST)
             goto rtx_duplicate;
         }
       }
@@ -3188,9 +3144,6 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
     }
   }
 
-  if (already_lost (jitterbuffer, seqnum))
-    goto already_lost;
-
   /* let's drop oldest packet if the queue is already full and drop-on-latency
    * is set. We can only do this when there actually is a latency. When no
    * latency is set, we just pump it in the queue and let the other end push it
@@ -3209,7 +3162,12 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
         GST_DEBUG_OBJECT (jitterbuffer, "Queue full, dropping old packet %p",
             old_item);
         priv->next_seqnum = (old_item->seqnum + old_item->count) & 0xffff;
-        free_item (old_item);
+        if (priv->post_drop_messages) {
+          drop_msg =
+              new_drop_message (jitterbuffer, old_item->seqnum, old_item->pts,
+              REASON_DROP_ON_LATENCY);
+        }
+        rtp_jitter_buffer_free_item (old_item);
       }
       /* we might have removed some head buffers, signal the pushing thread to
        * see if it can push now */
@@ -3221,19 +3179,15 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
    * later. The code above always sets dts to pts or the other way around if
    * any of those is valid in the buffer, so we know that if we estimated the
    * dts that both are unknown */
-  if (estimated_dts)
-    item =
-        alloc_item (buffer, ITEM_TYPE_BUFFER, GST_CLOCK_TIME_NONE,
-        pts, seqnum, 1, rtptime);
-  else
-    item = alloc_item (buffer, ITEM_TYPE_BUFFER, dts, pts, seqnum, 1, rtptime);
+  head = rtp_jitter_buffer_append_buffer (priv->jbuf, buffer,
+      estimated_dts ? GST_CLOCK_TIME_NONE : dts, pts, seqnum, rtptime,
+      &duplicate, &percent);
 
   /* now insert the packet into the queue in sorted order. This function returns
    * FALSE if a packet with the same seqnum was already in the queue, meaning we
    * have a duplicate. */
-  if (G_UNLIKELY (!rtp_jitter_buffer_insert (priv->jbuf, item, &head,
-              &percent))) {
-    if (GST_BUFFER_IS_RETRANSMISSION (buffer) && timer)
+  if (G_UNLIKELY (duplicate)) {
+    if (is_rtx && timer)
       update_rtx_stats (jitterbuffer, timer, dts, FALSE);
     goto duplicate;
   }
@@ -3243,8 +3197,7 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
     head = TRUE;
 
   /* update timers */
-  update_timers (jitterbuffer, seqnum, dts, pts, do_next_seqnum,
-      GST_BUFFER_IS_RETRANSMISSION (buffer), timer);
+  update_timers (jitterbuffer, seqnum, dts, pts, do_next_seqnum, is_rtx, timer);
 
   /* we had an unhandled SR, handle it now */
   if (priv->last_sr)
@@ -3254,13 +3207,6 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
     /* signal addition of new buffer when the _loop is waiting. */
     if (G_LIKELY (priv->active))
       JBUF_SIGNAL_EVENT (priv);
-
-    /* let's unschedule and unblock any waiting buffers. We only want to do this
-     * when the head buffer changed */
-    if (G_UNLIKELY (priv->clock_id)) {
-      GST_DEBUG_OBJECT (jitterbuffer, "Unscheduling waiting new buffer");
-      unschedule_current_timer (jitterbuffer);
-    }
   }
 
   GST_DEBUG_OBJECT (jitterbuffer,
@@ -3270,10 +3216,13 @@ gst_rtp_jitter_buffer_chain (GstPad * pad, GstObject * parent,
   msg = check_buffering_percent (jitterbuffer, percent);
 
 finished:
+  update_current_timer (jitterbuffer);
   JBUF_UNLOCK (priv);
 
   if (msg)
     gst_element_post_message (GST_ELEMENT_CAST (jitterbuffer), msg);
+  if (drop_msg)
+    gst_element_post_message (GST_ELEMENT_CAST (jitterbuffer), drop_msg);
 
   return ret;
 
@@ -3312,14 +3261,9 @@ too_late:
     GST_DEBUG_OBJECT (jitterbuffer, "Packet #%d too late as #%d was already"
         " popped, dropping", seqnum, priv->last_popped_seqnum);
     priv->num_late++;
-    gst_buffer_unref (buffer);
-    goto finished;
-  }
-already_lost:
-  {
-    GST_DEBUG_OBJECT (jitterbuffer, "Packet #%d too late as it was already "
-        "considered lost", seqnum);
-    priv->num_late++;
+    if (priv->post_drop_messages) {
+      drop_msg = new_drop_message (jitterbuffer, seqnum, pts, REASON_TOO_LATE);
+    }
     gst_buffer_unref (buffer);
     goto finished;
   }
@@ -3328,7 +3272,6 @@ duplicate:
     GST_DEBUG_OBJECT (jitterbuffer, "Duplicate packet #%d detected, dropping",
         seqnum);
     priv->num_duplicates++;
-    free_item (item);
     goto finished;
   }
 rtx_duplicate:
@@ -3339,6 +3282,21 @@ rtx_duplicate:
     gst_buffer_unref (buffer);
     goto finished;
   }
+unsolicited_rtx:
+  {
+    GST_DEBUG_OBJECT (jitterbuffer,
+        "Unsolicited RTX packet #%d detected, dropping", seqnum);
+    gst_buffer_unref (buffer);
+    goto finished;
+  }
+discard_invalid:
+  {
+    GST_DEBUG_OBJECT (jitterbuffer,
+        "cannot calculate a valid pts for #%d (rtx: %d), discard",
+        seqnum, is_rtx);
+    gst_buffer_unref (buffer);
+    goto finished;
+  }
 }
 
 /* FIXME: hopefully we can do something more efficient here, especially when
@@ -3453,7 +3411,8 @@ update_estimated_eos (GstRtpJitterBuffer * jitterbuffer,
       GST_TIME_FORMAT, GST_TIME_ARGS (elapsed), GST_TIME_ARGS (estimated));
 
   if (estimated != -1 && priv->estimated_eos != estimated) {
-    set_timer (jitterbuffer, TIMER_TYPE_EOS, -1, estimated);
+    rtp_timer_queue_set_eos (priv->timers, estimated,
+        timeout_offset (jitterbuffer));
     priv->estimated_eos = estimated;
   }
 }
@@ -3542,7 +3501,7 @@ pop_and_push_next (GstRtpJitterBuffer * jitterbuffer, guint seqnum)
   if (type == ITEM_TYPE_EVENT && outevent &&
       GST_EVENT_TYPE (outevent) == GST_EVENT_EOS) {
     g_assert (priv->eos);
-    while (priv->timers->len > 0) {
+    while (rtp_timer_queue_length (priv->timers) > 0) {
       /* Stopping timers */
       unschedule_current_timer (jitterbuffer);
       JBUF_WAIT_TIMER (priv);
@@ -3552,7 +3511,7 @@ pop_and_push_next (GstRtpJitterBuffer * jitterbuffer, guint seqnum)
   JBUF_UNLOCK (priv);
 
   item->data = NULL;
-  free_item (item);
+  rtp_jitter_buffer_free_item (item);
 
   if (msg)
     gst_element_post_message (GST_ELEMENT_CAST (jitterbuffer), msg);
@@ -3565,6 +3524,7 @@ pop_and_push_next (GstRtpJitterBuffer * jitterbuffer, guint seqnum)
           seqnum, GST_TIME_ARGS (GST_BUFFER_DTS (outbuf)),
           GST_TIME_ARGS (GST_BUFFER_PTS (outbuf)));
       priv->num_pushed++;
+      GST_BUFFER_DTS (outbuf) = GST_CLOCK_TIME_NONE;
       result = gst_pad_push (priv->srcpad, outbuf);
 
       JBUF_LOCK_CHECK (priv, out_flushing);
@@ -3671,7 +3631,7 @@ handle_next_buffer (GstRtpJitterBuffer * jitterbuffer)
       GST_DEBUG_OBJECT (jitterbuffer, "Old packet #%d, next #%d dropping",
           seqnum, next_seqnum);
       item = rtp_jitter_buffer_pop (priv->jbuf, NULL);
-      free_item (item);
+      rtp_jitter_buffer_free_item (item);
       result = GST_FLOW_OK;
     } else {
       /* the chain function has scheduled timers to request retransmission or
@@ -3680,7 +3640,8 @@ handle_next_buffer (GstRtpJitterBuffer * jitterbuffer)
           "Sequence number GAP detected: expected %d instead of %d (%d missing)",
           next_seqnum, seqnum, gap);
       /* if we have reached EOS, just keep processing */
-      if (priv->eos) {
+      /* Also do the same if we block input because the JB is full */
+      if (priv->eos || rtp_jitter_buffer_is_full (priv->jbuf)) {
         result = pop_and_push_next (jitterbuffer, seqnum);
         result = GST_FLOW_OK;
       } else {
@@ -3777,7 +3738,7 @@ update_avg_rtx_rtt (GstRtpJitterBufferPrivate * priv, GstClockTime rtt)
 }
 
 static void
-update_rtx_stats (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
+update_rtx_stats (GstRtpJitterBuffer * jitterbuffer, const RtpTimer * timer,
     GstClockTime dts, gboolean success)
 {
   GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
@@ -3824,8 +3785,8 @@ update_rtx_stats (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
 
 /* the timeout for when we expected a packet expired */
 static gboolean
-do_expected_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
-    GstClockTime now)
+do_expected_timeout (GstRtpJitterBuffer * jitterbuffer, RtpTimer * timer,
+    GstClockTime now, GQueue * events)
 {
   GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
   GstEvent *event;
@@ -3835,6 +3796,7 @@ do_expected_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
   GstClockTime rtx_retry_period;
   GstClockTime rtx_retry_timeout;
   GstClock *clock;
+  GstClockTimeDiff offset = 0;
 
   GST_DEBUG_OBJECT (jitterbuffer, "expected %d didn't arrive, now %"
       GST_TIME_FORMAT, timer->seqnum, GST_TIME_ARGS (now));
@@ -3862,6 +3824,7 @@ do_expected_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
           "deadline", G_TYPE_UINT, rtx_deadline_ms,
           "packet-spacing", G_TYPE_UINT64, priv->packet_spacing,
           "avg-rtt", G_TYPE_UINT, avg_rtx_rtt_ms, NULL));
+  g_queue_push_tail (events, event);
   GST_DEBUG_OBJECT (jitterbuffer, "Request RTX: %" GST_PTR_FORMAT, event);
 
   priv->num_rtx_requests++;
@@ -3878,107 +3841,62 @@ do_expected_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
 
   /* calculate the timeout for the next retransmission attempt */
   timer->rtx_retry += rtx_retry_timeout;
-  GST_DEBUG_OBJECT (jitterbuffer, "base %" GST_TIME_FORMAT ", delay %"
+  GST_DEBUG_OBJECT (jitterbuffer, "timer #%i base %" GST_TIME_FORMAT ", delay %"
       GST_TIME_FORMAT ", retry %" GST_TIME_FORMAT ", num_retry %u",
-      GST_TIME_ARGS (timer->rtx_base), GST_TIME_ARGS (timer->rtx_delay),
-      GST_TIME_ARGS (timer->rtx_retry), timer->num_rtx_retry);
+      timer->seqnum, GST_TIME_ARGS (timer->rtx_base),
+      GST_TIME_ARGS (timer->rtx_delay), GST_TIME_ARGS (timer->rtx_retry),
+      timer->num_rtx_retry);
   if ((priv->rtx_max_retries != -1
           && timer->num_rtx_retry >= priv->rtx_max_retries)
       || (timer->rtx_retry + timer->rtx_delay > rtx_retry_period)
       || (timer->rtx_base + rtx_retry_period < now)) {
-    GST_DEBUG_OBJECT (jitterbuffer, "reschedule as LOST timer");
+    GST_DEBUG_OBJECT (jitterbuffer, "reschedule #%i as LOST timer",
+        timer->seqnum);
     /* too many retransmission request, we now convert the timer
      * to a lost timer, leave the num_rtx_retry as it is for stats */
-    timer->type = TIMER_TYPE_LOST;
+    timer->type = RTP_TIMER_LOST;
     timer->rtx_delay = 0;
     timer->rtx_retry = 0;
+    offset = timeout_offset (jitterbuffer);
   }
-  reschedule_timer (jitterbuffer, timer, timer->seqnum,
-      timer->rtx_base + timer->rtx_retry, timer->rtx_delay, FALSE);
-
-  JBUF_UNLOCK (priv);
-  gst_pad_push_event (priv->sinkpad, event);
-  JBUF_LOCK (priv);
+  rtp_timer_queue_update_timer (priv->timers, timer, timer->seqnum,
+      timer->rtx_base + timer->rtx_retry, timer->rtx_delay, offset, FALSE);
 
   return FALSE;
 }
 
 /* a packet is lost */
 static gboolean
-do_lost_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
+do_lost_timeout (GstRtpJitterBuffer * jitterbuffer, RtpTimer * timer,
     GstClockTime now)
 {
   GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
-  guint seqnum, lost_packets, num_rtx_retry, next_in_seqnum;
-  gboolean head;
-  GstEvent *event = NULL;
-  RTPJitterBufferItem *item;
-
-  seqnum = timer->seqnum;
-  lost_packets = MAX (timer->num, 1);
-  num_rtx_retry = timer->num_rtx_retry;
-
-  /* we had a gap and thus we lost some packets. Create an event for this.  */
-  if (lost_packets > 1)
-    GST_DEBUG_OBJECT (jitterbuffer, "Packets #%d -> #%d lost", seqnum,
-        seqnum + lost_packets - 1);
-  else
-    GST_DEBUG_OBJECT (jitterbuffer, "Packet #%d lost", seqnum);
-
-  priv->num_lost += lost_packets;
-  priv->num_rtx_failed += num_rtx_retry;
-
-  next_in_seqnum = (seqnum + lost_packets) & 0xffff;
-
-  /* we now only accept seqnum bigger than this */
-  if (gst_rtp_buffer_compare_seqnum (priv->next_in_seqnum, next_in_seqnum) > 0) {
-    priv->next_in_seqnum = next_in_seqnum;
-    priv->last_in_pts = apply_offset (jitterbuffer, timer->timeout);
-  }
+  GstClockTime timestamp;
 
-  /* Avoid creating events if we don't need it. Note that we still need to create
-   * the lost *ITEM* since it will be used to notify the outgoing thread of
-   * lost items (so that we can set discont flags and such) */
-  if (priv->do_lost) {
-    GstClockTime duration, timestamp;
-    /* create paket lost event */
-    timestamp = apply_offset (jitterbuffer, timer->timeout);
-    duration = timer->duration;
-    if (duration == GST_CLOCK_TIME_NONE && priv->packet_spacing > 0)
-      duration = priv->packet_spacing;
-    event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
-        gst_structure_new ("GstRTPPacketLost",
-            "seqnum", G_TYPE_UINT, (guint) seqnum,
-            "timestamp", G_TYPE_UINT64, timestamp,
-            "duration", G_TYPE_UINT64, duration,
-            "retry", G_TYPE_UINT, num_rtx_retry, NULL));
-  }
-  item = alloc_item (event, ITEM_TYPE_LOST, -1, -1, seqnum, lost_packets, -1);
-  if (!rtp_jitter_buffer_insert (priv->jbuf, item, &head, NULL))
-    /* Duplicate */
-    free_item (item);
+  timestamp = apply_offset (jitterbuffer, get_pts_timeout (timer));
+  insert_lost_event (jitterbuffer, timer->seqnum, 1, timestamp,
+      timer->duration, timer->num_rtx_retry);
 
   if (GST_CLOCK_TIME_IS_VALID (timer->rtx_last)) {
     /* Store info to update stats if the packet arrives too late */
-    timer_queue_append (priv->rtx_stats_timers, timer,
-        now + priv->rtx_stats_timeout * GST_MSECOND, TRUE);
+    timer->timeout = now + priv->rtx_stats_timeout * GST_MSECOND;
+    timer->type = RTP_TIMER_LOST;
+    rtp_timer_queue_insert (priv->rtx_stats_timers, timer);
+  } else {
+    rtp_timer_free (timer);
   }
-  remove_timer (jitterbuffer, timer);
-
-  if (head)
-    JBUF_SIGNAL_EVENT (priv);
 
   return TRUE;
 }
 
 static gboolean
-do_eos_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
+do_eos_timeout (GstRtpJitterBuffer * jitterbuffer, RtpTimer * timer,
     GstClockTime now)
 {
   GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
 
   GST_INFO_OBJECT (jitterbuffer, "got the NPT timeout");
-  remove_timer (jitterbuffer, timer);
+  rtp_timer_free (timer);
   if (!priv->eos) {
     GstEvent *event;
 
@@ -3994,7 +3912,7 @@ do_eos_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
 }
 
 static gboolean
-do_deadline_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
+do_deadline_timeout (GstRtpJitterBuffer * jitterbuffer, RtpTimer * timer,
     GstClockTime now)
 {
   GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
@@ -4005,35 +3923,64 @@ do_deadline_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
    * only mess with current ongoing seqnum if still unknown */
   if (priv->next_seqnum == -1)
     priv->next_seqnum = timer->seqnum;
-  remove_timer (jitterbuffer, timer);
+  rtp_timer_free (timer);
   JBUF_SIGNAL_EVENT (priv);
 
   return TRUE;
 }
 
 static gboolean
-do_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
-    GstClockTime now)
+do_timeout (GstRtpJitterBuffer * jitterbuffer, RtpTimer * timer,
+    GstClockTime now, GQueue * events)
 {
   gboolean removed = FALSE;
 
   switch (timer->type) {
-    case TIMER_TYPE_EXPECTED:
-      removed = do_expected_timeout (jitterbuffer, timer, now);
+    case RTP_TIMER_EXPECTED:
+      removed = do_expected_timeout (jitterbuffer, timer, now, events);
       break;
-    case TIMER_TYPE_LOST:
+    case RTP_TIMER_LOST:
       removed = do_lost_timeout (jitterbuffer, timer, now);
       break;
-    case TIMER_TYPE_DEADLINE:
+    case RTP_TIMER_DEADLINE:
       removed = do_deadline_timeout (jitterbuffer, timer, now);
       break;
-    case TIMER_TYPE_EOS:
+    case RTP_TIMER_EOS:
       removed = do_eos_timeout (jitterbuffer, timer, now);
       break;
   }
   return removed;
 }
 
+static void
+push_rtx_events_unlocked (GstRtpJitterBuffer * jitterbuffer, GQueue * events)
+{
+  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
+  GstEvent *event;
+
+  while ((event = (GstEvent *) g_queue_pop_head (events)))
+    gst_pad_push_event (priv->sinkpad, event);
+}
+
+/* called with JBUF lock
+ *
+ * Pushes all events in @events queue.
+ *
+ * Returns: %TRUE if the timer thread is not longer running
+ */
+static void
+push_rtx_events (GstRtpJitterBuffer * jitterbuffer, GQueue * events)
+{
+  GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
+
+  if (events->length == 0)
+    return;
+
+  JBUF_UNLOCK (priv);
+  push_rtx_events_unlocked (jitterbuffer, events);
+  JBUF_LOCK (priv);
+}
+
 /* called when we need to wait for the next timeout.
  *
  * We loop over the array of recorded timeouts and wait for the earliest one.
@@ -4049,9 +3996,15 @@ wait_next_timeout (GstRtpJitterBuffer * jitterbuffer)
 
   JBUF_LOCK (priv);
   while (priv->timer_running) {
-    TimerData *timer = NULL;
-    GstClockTime timer_timeout = -1;
-    gint i, len;
+    RtpTimer *timer = NULL;
+    GQueue events = G_QUEUE_INIT;
+
+    /* don't produce data in paused */
+    while (priv->blocked) {
+      JBUF_WAIT_TIMER (priv);
+      if (!priv->timer_running)
+        goto stopping;
+    }
 
     /* If we have a clock, update "now" now with the very
      * latest running time we have. If timers are unscheduled below we
@@ -4074,79 +4027,23 @@ wait_next_timeout (GstRtpJitterBuffer * jitterbuffer)
 
     /* Clear expired rtx-stats timers */
     if (priv->do_retransmission)
-      timer_queue_clear_until (priv->rtx_stats_timers, now);
+      rtp_timer_queue_remove_until (priv->rtx_stats_timers, now);
 
-    /* Iterate "normal" timers */
-    len = priv->timers->len;
-    for (i = 0; i < len;) {
-      TimerData *test = &g_array_index (priv->timers, TimerData, i);
-      GstClockTime test_timeout = get_timeout (jitterbuffer, test);
-      gboolean save_best = FALSE;
+    /* Iterate expired "normal" timers */
+    while ((timer = rtp_timer_queue_pop_until (priv->timers, now)))
+      do_timeout (jitterbuffer, timer, now, &events);
 
-      GST_DEBUG_OBJECT (jitterbuffer,
-          "%d, %d, %d, %" GST_TIME_FORMAT " diff:%" GST_STIME_FORMAT, i,
-          test->type, test->seqnum, GST_TIME_ARGS (test_timeout),
-          GST_STIME_ARGS ((gint64) (test_timeout - now)));
-
-      /* Weed out anything too late */
-      if (test->type == TIMER_TYPE_LOST &&
-          (test_timeout == -1 || test_timeout <= now)) {
-        GST_DEBUG_OBJECT (jitterbuffer, "Weeding out late entry");
-        do_lost_timeout (jitterbuffer, test, now);
-        if (!priv->timer_running)
-          break;
-        /* We don't move the iterator forward since we just removed the current entry,
-         * but we update the termination condition */
-        len = priv->timers->len;
-      } else {
-        /* find the smallest timeout */
-        if (timer == NULL) {
-          save_best = TRUE;
-        } else if (timer_timeout == -1) {
-          /* we already have an immediate timeout, the new timer must be an
-           * immediate timer with smaller seqnum to become the best */
-          if (test_timeout == -1
-              && (gst_rtp_buffer_compare_seqnum (test->seqnum,
-                      timer->seqnum) > 0))
-            save_best = TRUE;
-        } else if (test_timeout == -1) {
-          /* first immediate timer */
-          save_best = TRUE;
-        } else if (test_timeout < timer_timeout) {
-          /* earlier timer */
-          save_best = TRUE;
-        } else if (test_timeout == timer_timeout
-            && (gst_rtp_buffer_compare_seqnum (test->seqnum,
-                    timer->seqnum) > 0)) {
-          /* same timer, smaller seqnum */
-          save_best = TRUE;
-        }
-
-        if (save_best) {
-          GST_DEBUG_OBJECT (jitterbuffer, "new best %d", i);
-          timer = test;
-          timer_timeout = test_timeout;
-        }
-        i++;
-      }
-    }
-    if (timer && !priv->blocked) {
+    timer = rtp_timer_queue_peek_earliest (priv->timers);
+    if (timer) {
       GstClock *clock;
       GstClockTime sync_time;
       GstClockID id;
       GstClockReturn ret;
       GstClockTimeDiff clock_jitter;
 
-      if (timer_timeout == -1 || timer_timeout <= now || priv->eos) {
-        /* We have normally removed all lost timers in the loop above */
-        g_assert (timer->type != TIMER_TYPE_LOST);
-
-        do_timeout (jitterbuffer, timer, now);
-        /* check here, do_timeout could have released the lock */
-        if (!priv->timer_running)
-          break;
-        continue;
-      }
+      /* we poped all immediate and due timer, so this should just never
+       * happens */
+      g_assert (GST_CLOCK_TIME_IS_VALID (timer->timeout));
 
       GST_OBJECT_LOCK (jitterbuffer);
       clock = GST_ELEMENT_CLOCK (jitterbuffer);
@@ -4154,53 +4051,68 @@ wait_next_timeout (GstRtpJitterBuffer * jitterbuffer)
         GST_OBJECT_UNLOCK (jitterbuffer);
         /* let's just push if there is no clock */
         GST_DEBUG_OBJECT (jitterbuffer, "No clock, timeout right away");
-        now = timer_timeout;
+        now = timer->timeout;
+        push_rtx_events (jitterbuffer, &events);
         continue;
       }
 
       /* prepare for sync against clock */
-      sync_time = timer_timeout + GST_ELEMENT_CAST (jitterbuffer)->base_time;
+      sync_time = timer->timeout + GST_ELEMENT_CAST (jitterbuffer)->base_time;
       /* add latency of peer to get input time */
       sync_time += priv->peer_latency;
 
-      GST_DEBUG_OBJECT (jitterbuffer, "sync to timestamp %" GST_TIME_FORMAT
-          " with sync time %" GST_TIME_FORMAT,
-          GST_TIME_ARGS (timer_timeout), GST_TIME_ARGS (sync_time));
+      GST_DEBUG_OBJECT (jitterbuffer, "timer #%i sync to timestamp %"
+          GST_TIME_FORMAT " with sync time %" GST_TIME_FORMAT, timer->seqnum,
+          GST_TIME_ARGS (get_pts_timeout (timer)), GST_TIME_ARGS (sync_time));
 
       /* create an entry for the clock */
       id = priv->clock_id = gst_clock_new_single_shot_id (clock, sync_time);
-      priv->timer_timeout = timer_timeout;
+      priv->timer_timeout = timer->timeout;
       priv->timer_seqnum = timer->seqnum;
       GST_OBJECT_UNLOCK (jitterbuffer);
 
       /* release the lock so that the other end can push stuff or unlock */
       JBUF_UNLOCK (priv);
 
+      push_rtx_events_unlocked (jitterbuffer, &events);
+
       ret = gst_clock_id_wait (id, &clock_jitter);
 
       JBUF_LOCK (priv);
+
       if (!priv->timer_running) {
+        g_queue_clear_full (&events, (GDestroyNotify) gst_event_unref);
         gst_clock_id_unref (id);
         priv->clock_id = NULL;
         break;
       }
 
       if (ret != GST_CLOCK_UNSCHEDULED) {
-        now = timer_timeout + MAX (clock_jitter, 0);
+        now = priv->timer_timeout + MAX (clock_jitter, 0);
         GST_DEBUG_OBJECT (jitterbuffer,
             "sync done, %d, #%d, %" GST_STIME_FORMAT, ret, priv->timer_seqnum,
             GST_STIME_ARGS (clock_jitter));
       } else {
         GST_DEBUG_OBJECT (jitterbuffer, "sync unscheduled");
       }
+
       /* and free the entry */
       gst_clock_id_unref (id);
       priv->clock_id = NULL;
     } else {
+      push_rtx_events_unlocked (jitterbuffer, &events);
+
+      /* when draining the timers, the pusher thread will reuse our
+       * condition to wait for completion. Signal that thread before
+       * sleeping again here */
+      if (priv->eos)
+        JBUF_SIGNAL_TIMER (priv);
+
       /* no timers, wait for activity */
       JBUF_WAIT_TIMER (priv);
     }
   }
+stopping:
   JBUF_UNLOCK (priv);
 
   GST_DEBUG_OBJECT (jitterbuffer, "we are stopping");
@@ -4208,7 +4120,7 @@ wait_next_timeout (GstRtpJitterBuffer * jitterbuffer)
 }
 
 /*
- * This funcion implements the main pushing loop on the source pad.
+ * This function implements the main pushing loop on the source pad.
  *
  * It first tries to push as many buffers as possible. If there is a seqnum
  * mismatch, we wait for the next timeouts.
@@ -4224,9 +4136,9 @@ gst_rtp_jitter_buffer_loop (GstRtpJitterBuffer * jitterbuffer)
   JBUF_LOCK_CHECK (priv, flushing);
   do {
     result = handle_next_buffer (jitterbuffer);
-    JBUF_SIGNAL_QUEUE (priv);
     if (G_LIKELY (result == GST_FLOW_WAIT)) {
       /* now wait for the next event */
+      JBUF_SIGNAL_QUEUE (priv);
       JBUF_WAIT_EVENT (priv, flushing);
       result = GST_FLOW_OK;
     }
@@ -4262,7 +4174,7 @@ pause:
   }
 }
 
-/* collect the info from the lastest RTCP packet and the jitterbuffer sync, do
+/* collect the info from the latest RTCP packet and the jitterbuffer sync, do
  * some sanity checks and then emit the handle-sync signal with the parameters.
  * This function must be called with the LOCK */
 static void
@@ -4456,16 +4368,11 @@ gst_rtp_jitter_buffer_sink_query (GstPad * pad, GstObject * parent,
     }
     default:
       if (GST_QUERY_IS_SERIALIZED (query)) {
-        RTPJitterBufferItem *item;
-        gboolean head;
-
         JBUF_LOCK_CHECK (priv, out_flushing);
         if (rtp_jitter_buffer_get_mode (priv->jbuf) !=
             RTP_JITTER_BUFFER_MODE_BUFFER) {
           GST_DEBUG_OBJECT (jitterbuffer, "adding serialized query");
-          item = alloc_item (query, ITEM_TYPE_QUERY, -1, -1, -1, 0, -1);
-          rtp_jitter_buffer_insert (priv->jbuf, item, &head, NULL);
-          if (head)
+          if (rtp_jitter_buffer_append_query (priv->jbuf, query))
             JBUF_SIGNAL_EVENT (priv);
           JBUF_WAIT_QUERY (priv, out_flushing);
           res = priv->last_query;
@@ -4639,6 +4546,7 @@ gst_rtp_jitter_buffer_set_property (GObject * object,
       } else {
         priv->ts_offset = g_value_get_int64 (value);
         priv->ts_offset_remainder = 0;
+        update_timer_offsets (jitterbuffer);
       }
       priv->ts_discont = TRUE;
       JBUF_UNLOCK (priv);
@@ -4653,6 +4561,16 @@ gst_rtp_jitter_buffer_set_property (GObject * object,
       priv->do_lost = g_value_get_boolean (value);
       JBUF_UNLOCK (priv);
       break;
+    case PROP_POST_DROP_MESSAGES:
+      JBUF_LOCK (priv);
+      priv->post_drop_messages = g_value_get_boolean (value);
+      JBUF_UNLOCK (priv);
+      break;
+    case PROP_DROP_MESSAGES_INTERVAL:
+      JBUF_LOCK (priv);
+      priv->drop_messages_interval_ms = g_value_get_uint (value);
+      JBUF_UNLOCK (priv);
+      break;
     case PROP_MODE:
       JBUF_LOCK (priv);
       rtp_jitter_buffer_set_mode (priv->jbuf, g_value_get_enum (value));
@@ -4781,6 +4699,16 @@ gst_rtp_jitter_buffer_get_property (GObject * object,
       g_value_set_boolean (value, priv->do_lost);
       JBUF_UNLOCK (priv);
       break;
+    case PROP_POST_DROP_MESSAGES:
+      JBUF_LOCK (priv);
+      g_value_set_boolean (value, priv->post_drop_messages);
+      JBUF_UNLOCK (priv);
+      break;
+    case PROP_DROP_MESSAGES_INTERVAL:
+      JBUF_LOCK (priv);
+      g_value_set_uint (value, priv->drop_messages_interval_ms);
+      JBUF_UNLOCK (priv);
+      break;
     case PROP_MODE:
       JBUF_LOCK (priv);
       g_value_set_enum (value, rtp_jitter_buffer_get_mode (priv->jbuf));
index c6cfc6c..4d16d5d 100644 (file)
 
 /**
  * SECTION:element-rtpmux
+ * @title: rtpmux
  * @see_also: rtpdtmfmux
  *
  * The rtp muxer takes multiple RTP streams having the same clock-rate and
  * muxes into a single stream with a single SSRC.
  *
- * <refsect2>
- * <title>Example pipelines</title>
+ * ## Example pipelines
  * |[
  * gst-launch-1.0 rtpmux name=mux ! udpsink host=127.0.0.1 port=8888        \
  *              alsasrc ! alawenc ! rtppcmapay !                        \
@@ -45,7 +45,7 @@
  * In this example, an audio stream is captured from ALSA and another is
  * generated, both are encoded into different payload types and muxed together
  * so they can be sent on the same port.
- * </refsect2>
+ *
  */
 
 #ifdef HAVE_CONFIG_H
index b94d408..65343da 100644 (file)
@@ -51,8 +51,7 @@ typedef struct
 } GstRTPMuxPadPrivate;
 
 
-/**
- * GstRTPMux:
+/* GstRTPMux:
  *
  * The opaque #GstRTPMux structure.
  */
index 483966b..d8d54ba 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
  * RTP Demux element
  *
  * Copyright (C) 2005 Nokia Corporation.
 
 /**
  * SECTION:element-rtpptdemux
+ * @title: rtpptdemux
  *
  * rtpptdemux acts as a demuxer for RTP packets based on the payload type of
  * the packets. Its main purpose is to allow an application to easily receive
  * and decode an RTP stream with multiple payload types.
- * 
+ *
  * For each payload type that is detected, a new pad will be created and the
  * #GstRtpPtDemux::new-payload-type signal will be emitted. When the payload for
  * the RTP stream changes, the #GstRtpPtDemux::payload-type-change signal will be
  * emitted.
- * 
+ *
  * The element will try to set complete and unique application/x-rtp caps
  * on the output pads based on the result of the #GstRtpPtDemux::request-pt-map
  * signal.
- * 
- * <refsect2>
- * <title>Example pipelines</title>
+ *
+ * ## Example pipelines
  * |[
  * gst-launch-1.0 udpsrc caps="application/x-rtp" ! rtpptdemux ! fakesink
  * ]| Takes an RTP stream and send the RTP packets with the first detected
  * payload type to fakesink, discarding the other payload types.
- * </refsect2>
+ *
  */
 
 /*
@@ -99,8 +99,8 @@ GST_DEBUG_CATEGORY_STATIC (gst_rtp_pt_demux_debug);
  */
 struct _GstRtpPtDemuxPad
 {
-  GstPad *pad;        /**< pointer to the actual pad */
-  gint pt;             /**< RTP payload-type attached to pad */
+  GstPad *pad;                  /*< pointer to the actual pad */
+  gint pt;                      /*< RTP payload-type attached to pad */
   gboolean newcaps;
 };
 
@@ -195,7 +195,7 @@ gst_rtp_pt_demux_class_init (GstRtpPtDemuxClass * klass)
   gst_rtp_pt_demux_signals[SIGNAL_REQUEST_PT_MAP] =
       g_signal_new ("request-pt-map", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpPtDemuxClass, request_pt_map),
-      NULL, NULL, g_cclosure_marshal_generic, GST_TYPE_CAPS, 1, G_TYPE_UINT);
+      NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);
 
   /**
    * GstRtpPtDemux::new-payload-type:
@@ -203,26 +203,24 @@ gst_rtp_pt_demux_class_init (GstRtpPtDemuxClass * klass)
    * @pt: the payload type
    * @pad: the pad with the new payload
    *
-   * Emited when a new payload type pad has been created in @demux.
+   * Emitted when a new payload type pad has been created in @demux.
    */
   gst_rtp_pt_demux_signals[SIGNAL_NEW_PAYLOAD_TYPE] =
       g_signal_new ("new-payload-type", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpPtDemuxClass, new_payload_type),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      GST_TYPE_PAD);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, GST_TYPE_PAD);
 
   /**
    * GstRtpPtDemux::payload-type-change:
    * @demux: the object which received the signal
    * @pt: the new payload type
    *
-   * Emited when the payload type changed.
+   * Emitted when the payload type changed.
    */
   gst_rtp_pt_demux_signals[SIGNAL_PAYLOAD_TYPE_CHANGE] =
       g_signal_new ("payload-type-change", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpPtDemuxClass,
-          payload_type_change), NULL, NULL, g_cclosure_marshal_VOID__UINT,
-      G_TYPE_NONE, 1, G_TYPE_UINT);
+          payload_type_change), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
 
   /**
    * GstRtpPtDemux::clear-pt-map:
@@ -234,8 +232,7 @@ gst_rtp_pt_demux_class_init (GstRtpPtDemuxClass * klass)
   gst_rtp_pt_demux_signals[SIGNAL_CLEAR_PT_MAP] =
       g_signal_new ("clear-pt-map", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_ACTION | G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpPtDemuxClass,
-          clear_pt_map), NULL, NULL, g_cclosure_marshal_VOID__VOID,
-      G_TYPE_NONE, 0, G_TYPE_NONE);
+          clear_pt_map), NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE);
 
   gobject_klass->set_property = gst_rtp_pt_demux_set_property;
   gobject_klass->get_property = gst_rtp_pt_demux_get_property;
@@ -316,10 +313,11 @@ gst_rtp_pt_demux_finalize (GObject * object)
 static GstCaps *
 gst_rtp_pt_demux_get_caps (GstRtpPtDemux * rtpdemux, guint pt)
 {
-  GstCaps *caps;
+  guint32 ssrc = 0;
+  gboolean have_ssrc = FALSE;
+  GstCaps *caps, *sink_caps;
   GValue ret = { 0 };
   GValue args[2] = { {0}, {0} };
-  GstCaps *sink_caps;
 
   /* figure out the caps */
   g_value_init (&args[0], GST_TYPE_ELEMENT);
@@ -336,30 +334,26 @@ gst_rtp_pt_demux_get_caps (GstRtpPtDemux * rtpdemux, guint pt)
   g_value_unset (&args[0]);
   g_value_unset (&args[1]);
   caps = g_value_dup_boxed (&ret);
-  g_value_unset (&ret);
-
   sink_caps = gst_pad_get_current_caps (rtpdemux->sink);
+  g_value_unset (&ret);
 
-  if (sink_caps) {
-    if (caps == NULL) {
-      caps = gst_caps_ref (sink_caps);
-    } else {
-      GstStructure *s1;
-      GstStructure *s2;
-      guint ssrc;
-
-      caps = gst_caps_make_writable (caps);
-      s1 = gst_caps_get_structure (sink_caps, 0);
-      s2 = gst_caps_get_structure (caps, 0);
-
-      gst_structure_get_uint (s1, "ssrc", &ssrc);
-      gst_structure_set (s2, "ssrc", G_TYPE_UINT, ssrc, NULL);
-    }
-
+  if (caps == NULL) {
+    caps = sink_caps;
+  } else if (sink_caps) {
+    have_ssrc =
+        gst_structure_get_uint (gst_caps_get_structure (sink_caps, 0), "ssrc",
+        &ssrc);
     gst_caps_unref (sink_caps);
   }
 
-  GST_DEBUG ("pt %d, got caps %" GST_PTR_FORMAT, pt, caps);
+  if (caps != NULL) {
+    caps = gst_caps_make_writable (caps);
+    gst_caps_set_simple (caps, "payload", G_TYPE_INT, pt, NULL);
+    if (have_ssrc)
+      gst_caps_set_simple (caps, "ssrc", G_TYPE_UINT, ssrc, NULL);
+  }
+
+  GST_DEBUG_OBJECT (rtpdemux, "pt %d, got caps %" GST_PTR_FORMAT, pt, caps);
 
   return caps;
 }
@@ -370,7 +364,7 @@ gst_rtp_pt_demux_clear_pt_map (GstRtpPtDemux * rtpdemux)
   GSList *walk;
 
   GST_OBJECT_LOCK (rtpdemux);
-  GST_DEBUG ("clearing pt map");
+  GST_DEBUG_OBJECT (rtpdemux, "clearing pt map");
   for (walk = rtpdemux->srcpads; walk; walk = g_slist_next (walk)) {
     GstRtpPtDemuxPad *pad = walk->data;
 
@@ -492,7 +486,7 @@ gst_rtp_pt_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
     g_free (padname);
     gst_pad_set_event_function (srcpad, gst_rtp_pt_demux_src_event);
 
-    GST_DEBUG ("Adding pt=%d to the list.", pt);
+    GST_DEBUG_OBJECT (rtpdemux, "Adding pt=%d to the list.", pt);
     rtpdemuxpad = g_slice_new0 (GstRtpPtDemuxPad);
     rtpdemuxpad->pt = pt;
     rtpdemuxpad->newcaps = FALSE;
@@ -504,14 +498,11 @@ gst_rtp_pt_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
 
     gst_pad_set_active (srcpad, TRUE);
 
-
     /* First push the stream-start event, it must always come first */
     gst_pad_push_event (srcpad,
         gst_pad_get_sticky_event (rtpdemux->sink, GST_EVENT_STREAM_START, 0));
 
     /* Then caps event is sent */
-    caps = gst_caps_make_writable (caps);
-    gst_caps_set_simple (caps, "payload", G_TYPE_INT, pt, NULL);
     gst_pad_set_caps (srcpad, caps);
     gst_caps_unref (caps);
 
@@ -521,7 +512,7 @@ gst_rtp_pt_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
 
     gst_element_add_pad (GST_ELEMENT_CAST (rtpdemux), srcpad);
 
-    GST_DEBUG ("emitting new-payload-type for pt %d", pt);
+    GST_DEBUG_OBJECT (rtpdemux, "emitting new-payload-type for pt %d", pt);
     g_signal_emit (G_OBJECT (rtpdemux),
         gst_rtp_pt_demux_signals[SIGNAL_NEW_PAYLOAD_TYPE], 0, pt, srcpad);
   }
@@ -531,21 +522,20 @@ gst_rtp_pt_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
 
     /* our own signal with an extra flag that this is the only pad */
     rtpdemux->last_pt = pt;
-    GST_DEBUG ("emitting payload-type-changed for pt %d", emit_pt);
+    GST_DEBUG_OBJECT (rtpdemux, "emitting payload-type-changed for pt %d",
+        emit_pt);
     g_signal_emit (G_OBJECT (rtpdemux),
         gst_rtp_pt_demux_signals[SIGNAL_PAYLOAD_TYPE_CHANGE], 0, emit_pt);
   }
 
   while (need_caps_for_pt (rtpdemux, pt)) {
-    GST_DEBUG ("need new caps for %d", pt);
+    GST_DEBUG_OBJECT (rtpdemux, "need new caps for %d", pt);
     caps = gst_rtp_pt_demux_get_caps (rtpdemux, pt);
     if (!caps)
       goto no_caps;
 
     clear_newcaps_for_pt (rtpdemux, pt);
 
-    caps = gst_caps_make_writable (caps);
-    gst_caps_set_simple (caps, "payload", G_TYPE_INT, pt, NULL);
     gst_pad_set_caps (srcpad, caps);
     gst_caps_unref (caps);
   }
index 578e489..4beae86 100644 (file)
@@ -34,12 +34,12 @@ typedef struct _GstRtpPtDemuxPad GstRtpPtDemuxPad;
 
 struct _GstRtpPtDemux
 {
-  GstElement parent;  /**< parent class */
+  GstElement parent;  /*< parent class */
 
-  GstPad *sink;       /**< the sink pad */
-  guint16 last_pt;    /**< pt of the last packet 0xFFFF if none */
-  GSList *srcpads;    /**< a linked list of GstRtpPtDemuxPad objects */
-  GValue ignored_pts; /**< a GstValueArray of payload types that will not have pads created for */
+  GstPad *sink;       /*< the sink pad */
+  guint16 last_pt;    /*< pt of the last packet 0xFFFF if none */
+  GSList *srcpads;    /*< a linked list of GstRtpPtDemuxPad objects */
+  GValue ignored_pts; /*< a GstValueArray of payload types that will not have pads created for */
 };
 
 struct _GstRtpPtDemuxClass
@@ -49,7 +49,7 @@ struct _GstRtpPtDemuxClass
   /* get the caps for pt */
   GstCaps* (*request_pt_map)      (GstRtpPtDemux *demux, guint pt);
 
-  /* signal emmited when a new PT is found from the incoming stream */
+  /* signal emitted when a new PT is found from the incoming stream */
   void     (*new_payload_type)    (GstRtpPtDemux *demux, guint pt, GstPad * pad);
 
   /* signal emitted when the payload type changes */
index a0d6cbe..97678eb 100644 (file)
 
 /**
  * SECTION:element-rtprtxqueue
+ * @title: rtprtxqueue
  *
  * rtprtxqueue maintains a queue of transmitted RTP packets, up to a
- * configurable limit (see #GstRTPRtxQueue::max-size-time,
- * #GstRTPRtxQueue::max-size-packets), and retransmits them upon request
+ * configurable limit (see #GstRTPRtxQueue:max-size-time,
+ * #GstRTPRtxQueue:max-size-packets), and retransmits them upon request
  * from the downstream rtpsession (GstRTPRetransmissionRequest event).
  *
  * This element is similar to rtprtxsend, but it has differences:
  * See also #GstRtpRtxSend, #GstRtpRtxReceive
  *
  * # Example pipelines
+ *
  * |[
  * gst-launch-1.0 rtpbin name=b rtp-profile=avpf \
  *    audiotestsrc is-live=true ! opusenc ! rtpopuspay pt=96 ! rtprtxqueue ! b.send_rtp_sink_0 \
  *    b.send_rtp_src_0 ! identity drop-probability=0.01 ! udpsink host="127.0.0.1" port=5000 \
  *    udpsrc port=5001 ! b.recv_rtcp_sink_0 \
  *    b.send_rtcp_src_0 ! udpsink host="127.0.0.1" port=5002 sync=false async=false
- * ]| Sender pipeline
+ * ]|
+ * Sender pipeline
+ *
  * |[
  * gst-launch-1.0 rtpbin name=b rtp-profile=avpf do-retransmission=true \
  *    udpsrc port=5000 caps="application/x-rtp,media=(string)audio,clock-rate=(int)48000,encoding-name=(string)OPUS,payload=(int)96" ! \
@@ -59,7 +63,8 @@
  *    b. ! rtpopusdepay ! opusdec ! audioconvert ! audioresample ! autoaudiosink \
  *    udpsrc port=5002 ! b.recv_rtcp_sink_0 \
  *    b.send_rtcp_src_0 ! udpsink host="127.0.0.1" port=5001 sync=false async=false
- * ]| Receiver pipeline
+ * ]|
+ * Receiver pipeline
  */
 
 #ifdef HAVE_CONFIG_H
index 9a8a666..77fe784 100644 (file)
@@ -23,6 +23,7 @@
 
 /**
  * SECTION:element-rtprtxreceive
+ * @title: rtprtxreceive
  * @see_also: rtprtxsend, rtpsession, rtpjitterbuffer
  *
  * rtprtxreceive listens to the retransmission events from the
@@ -45,7 +46,8 @@
  * rtpbin instead, with its #GstRtpBin::request-aux-sender and
  * #GstRtpBin::request-aux-receiver signals. See #GstRtpBin.
  *
- * # Example pipelines
+ * ## Example pipelines
+ *
  * |[
  * gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \
  *     audiotestsrc is-live=true ! opusenc ! rtpopuspay pt=96 ! \
@@ -58,6 +60,7 @@
  *         sync=false async=false
  * ]| Send audio stream through port 5000 (5001 and 5002 are just the rtcp
  * link with the receiver)
+ *
  * |[
  * gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \
  *     udpsrc port=5000 caps="application/x-rtp,media=(string)audio,clock-rate=(int)48000,encoding-name=(string)OPUS,payload=(int)96" ! \
@@ -69,7 +72,8 @@
  *     rtpsession.send_rtcp_src ! \
  *         udpsink host="127.0.0.1" port=5001 sync=false async=false \
  *     udpsrc port=5002 ! rtpsession.recv_rtcp_sink
- * ]| Receive audio stream from port 5000 (5001 and 5002 are just the rtcp
+ * ]|
+ * Receive audio stream from port 5000 (5001 and 5002 are just the rtcp
  * link with the sender)
  *
  * In this example we can see a simple streaming of an OPUS stream with some
  *     udpsrc port=5001 ! rtpsession.recv_rtcp_sink \
  *     rtpsession.send_rtcp_src ! udpsink host="127.0.0.1" port=5002 \
  *         sync=false async=false
- * ]| Send two audio streams to port 5000.
+ * ]|
+ * Send two audio streams to port 5000.
  * |[
  * gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \
  *     udpsrc port=5000 caps="application/x-rtp,media=(string)audio,clock-rate=(int)48000,encoding-name=(string)OPUS,payload=(int)97" ! \
  *     udpsrc port=5002 ! rtpsession.recv_rtcp_sink \
  *     rtpsession.send_rtcp_src ! udpsink host="127.0.0.1" port=5001 \
  *         sync=false async=false
- * ]| Receive two audio streams from port 5000.
+ * ]|
+ * Receive two audio streams from port 5000.
  *
  * In this example we are streaming two streams of the same type through the
  * same port. They, however, are using a different SSRC (ssrc is randomly
  * It is an error, according to RFC4588 to have two retransmission requests for
  * packets belonging to two different streams but with the same sequence number.
  * Note that the default seqnum-offset value (-1, which means random) would
- * work just fine, but it is overriden here for illustration purposes.
+ * work just fine, but it is overridden here for illustration purposes.
  */
 
 #ifdef HAVE_CONFIG_H
@@ -355,7 +361,7 @@ gst_rtp_rtx_receive_src_event (GstPad * pad, GstObject * parent,
         if (g_hash_table_lookup_extended (rtx->ssrc2_ssrc1_map,
                 GUINT_TO_POINTER (ssrc), NULL, &ssrc2)
             && GPOINTER_TO_UINT (ssrc2) != GPOINTER_TO_UINT (ssrc)) {
-          GST_TRACE_OBJECT (rtx, "Retransmited stream %X already associated "
+          GST_TRACE_OBJECT (rtx, "Retransmitted stream %X already associated "
               "to its master, %X", GPOINTER_TO_UINT (ssrc2), ssrc);
         } else {
           SsrcAssoc *assoc;
@@ -372,7 +378,7 @@ gst_rtp_rtx_receive_src_event (GstPad * pad, GstObject * parent,
                * The jitter may be too impatient of the rtx packet has been
                * lost too.
                * It does not mean we reject the event, we still want to forward
-               * the request to the gstrtpsession to be translater into a FB NACK
+               * the request to the gstrtpsession to be translator into a FB NACK
                */
               GST_LOG_OBJECT (rtx, "Duplicate request: seqnum: %u, ssrc: %X",
                   seqnum, ssrc);
@@ -424,7 +430,7 @@ gst_rtp_rtx_receive_src_event (GstPad * pad, GstObject * parent,
         GST_OBJECT_UNLOCK (rtx);
       }
 
-      /* Transfer event upstream so that the request can acutally by translated
+      /* Transfer event upstream so that the request can actually by translated
        * through gstrtpsession through the network */
       res = gst_pad_event_default (pad, parent, event);
       break;
index 8c08f32..bfa5db8 100644 (file)
 
 /**
  * SECTION:element-rtprtxsend
+ * @title: rtprtxsend
  *
  * See #GstRtpRtxReceive for examples
- * 
+ *
  * The purpose of the sender RTX object is to keep a history of RTP packets up
  * to a configurable limit (max-size-time or max-size-packets). It will listen
  * for upstream custom retransmission events (GstRTPRetransmissionRequest) that
@@ -61,7 +62,8 @@ enum
   PROP_MAX_SIZE_TIME,
   PROP_MAX_SIZE_PACKETS,
   PROP_NUM_RTX_REQUESTS,
-  PROP_NUM_RTX_PACKETS
+  PROP_NUM_RTX_PACKETS,
+  PROP_CLOCK_RATE_MAP,
 };
 
 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
@@ -73,7 +75,7 @@ static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
-    GST_STATIC_CAPS ("application/x-rtp, " "clock-rate = (int) [1, MAX]")
+    GST_STATIC_CAPS ("application/x-rtp")
     );
 
 static gboolean gst_rtp_rtx_send_queue_check_full (GstDataQueue * queue,
@@ -191,6 +193,11 @@ gst_rtp_rtx_send_class_init (GstRtpRtxSendClass * klass)
           " Number of retransmission packets sent", 0, G_MAXUINT,
           0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
+  g_object_class_install_property (gobject_class, PROP_CLOCK_RATE_MAP,
+      g_param_spec_boxed ("clock-rate-map", "Clock Rate Map",
+          "Map of payload types to their clock rates",
+          GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
   gst_element_class_add_static_pad_template (gstelement_class, &sink_factory);
 
@@ -227,6 +234,9 @@ gst_rtp_rtx_send_finalize (GObject * object)
   g_hash_table_unref (rtx->rtx_pt_map);
   if (rtx->rtx_pt_map_structure)
     gst_structure_free (rtx->rtx_pt_map_structure);
+  g_hash_table_unref (rtx->clock_rate_map);
+  if (rtx->clock_rate_map_structure)
+    gst_structure_free (rtx->clock_rate_map_structure);
   g_object_unref (rtx->queue);
 
   G_OBJECT_CLASS (gst_rtp_rtx_send_parent_class)->finalize (object);
@@ -267,6 +277,7 @@ gst_rtp_rtx_send_init (GstRtpRtxSend * rtx)
       NULL, (GDestroyNotify) ssrc_rtx_data_free);
   rtx->rtx_ssrcs = g_hash_table_new (g_direct_hash, g_direct_equal);
   rtx->rtx_pt_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+  rtx->clock_rate_map = g_hash_table_new (g_direct_hash, g_direct_equal);
 
   rtx->max_size_time = DEFAULT_MAX_SIZE_TIME;
   rtx->max_size_packets = DEFAULT_MAX_SIZE_PACKETS;
@@ -534,7 +545,7 @@ gst_rtp_rtx_send_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
 
         GST_OBJECT_LOCK (rtx);
 
-        /* choose another ssrc for our retransmited stream */
+        /* choose another ssrc for our retransmitted stream */
         if (g_hash_table_contains (rtx->rtx_ssrcs, GUINT_TO_POINTER (ssrc))) {
           guint master_ssrc;
           SSRCRtxData *data;
@@ -624,6 +635,9 @@ gst_rtp_rtx_send_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
       if (!gst_structure_get_int (s, "payload", &payload))
         payload = -1;
 
+      if (payload == -1 || ssrc == G_MAXUINT)
+        break;
+
       if (payload == -1)
         GST_WARNING_OBJECT (rtx, "No payload in caps");
 
@@ -722,6 +736,12 @@ process_buffer (GstRtpRtxSend * rtx, GstBuffer * buffer)
   if (g_hash_table_contains (rtx->rtx_pt_map, GUINT_TO_POINTER (payload_type))) {
     data = gst_rtp_rtx_send_get_ssrc_data (rtx, ssrc);
 
+    if (data->clock_rate == 0 && rtx->clock_rate_map_structure) {
+      data->clock_rate =
+          GPOINTER_TO_INT (g_hash_table_lookup (rtx->clock_rate_map,
+              GUINT_TO_POINTER (payload_type)));
+    }
+
     /* add current rtp buffer to queue history */
     item = g_slice_new0 (BufferQueueItem);
     item->seqnum = seqnum;
@@ -870,6 +890,11 @@ gst_rtp_rtx_send_get_property (GObject * object,
       g_value_set_uint (value, rtx->num_rtx_packets);
       GST_OBJECT_UNLOCK (rtx);
       break;
+    case PROP_CLOCK_RATE_MAP:
+      GST_OBJECT_LOCK (rtx);
+      g_value_set_boxed (value, rtx->clock_rate_map_structure);
+      GST_OBJECT_UNLOCK (rtx);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -926,6 +951,16 @@ gst_rtp_rtx_send_set_property (GObject * object,
       rtx->max_size_packets = g_value_get_uint (value);
       GST_OBJECT_UNLOCK (rtx);
       break;
+    case PROP_CLOCK_RATE_MAP:
+      GST_OBJECT_LOCK (rtx);
+      if (rtx->clock_rate_map_structure)
+        gst_structure_free (rtx->clock_rate_map_structure);
+      rtx->clock_rate_map_structure = g_value_dup_boxed (value);
+      g_hash_table_remove_all (rtx->clock_rate_map);
+      gst_structure_foreach (rtx->clock_rate_map_structure,
+          structure_to_hash_table, rtx->clock_rate_map);
+      GST_OBJECT_UNLOCK (rtx);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
index 5f084e8..b25a511 100644 (file)
@@ -62,6 +62,11 @@ struct _GstRtpRtxSend
   /* orig pt (string) -> rtx pt (uint) */
   GstStructure *rtx_pt_map_structure;
 
+  /* orig pt (uint) -> clock rate (uint) */
+  GHashTable *clock_rate_map;
+  /* orig pt (string) -> clock rate (uint) */
+  GstStructure *clock_rate_map_structure;
+
   /* buffering control properties */
   guint max_size_time;
   guint max_size_packets;
index 04fca37..3835033 100644 (file)
@@ -19,6 +19,7 @@
 
 /**
  * SECTION:element-rtpsession
+ * @title: rtpsession
  * @see_also: rtpjitterbuffer, rtpbin, rtpptdemux, rtpssrcdemux
  *
  * The RTP session manager models participants with unique SSRC in an RTP
  * functionality can be activated.
  *
  * The session manager currently implements RFC 3550 including:
- * <itemizedlist>
- *   <listitem>
- *     <para>RTP packet validation based on consecutive sequence numbers.</para>
- *   </listitem>
- *   <listitem>
- *     <para>Maintainance of the SSRC participant database.</para>
- *   </listitem>
- *   <listitem>
- *     <para>Keeping per participant statistics based on received RTCP packets.</para>
- *   </listitem>
- *   <listitem>
- *     <para>Scheduling of RR/SR RTCP packets.</para>
- *   </listitem>
- *   <listitem>
- *     <para>Support for multiple sender SSRC.</para>
- *   </listitem>
- * </itemizedlist>
+ *
+ *   * RTP packet validation based on consecutive sequence numbers.
+ *
+ *   * Maintenance of the SSRC participant database.
+ *
+ *   * Keeping per participant statistics based on received RTCP packets.
+ *
+ *   * Scheduling of RR/SR RTCP packets.
+ *
+ *   * Support for multiple sender SSRC.
  *
  * The rtpsession will not demux packets based on SSRC or payload type, nor will
- * it correct for packet reordering and jitter. Use #GstRtpsSrcDemux,
+ * it correct for packet reordering and jitter. Use #GstRtpSsrcDemux,
  * #GstRtpPtDemux and GstRtpJitterBuffer in addition to #GstRtpSession to
  * perform these tasks. It is usually a good idea to use #GstRtpBin, which
  * combines all these features in one element.
@@ -75,8 +69,7 @@
  * mapping. One can clear the cached values with the #GstRtpSession::clear-pt-map
  * signal.
  *
- * <refsect2>
- * <title>Example pipelines</title>
+ * ## Example pipelines
  * |[
  * gst-launch-1.0 udpsrc port=5000 caps="application/x-rtp, ..." ! .recv_rtp_sink rtpsession .recv_rtp_src ! rtptheoradepay ! theoradec ! xvimagesink
  * ]| Receive theora RTP packets from port 5000 and send them to the depayloader,
  * correctly because the second udpsink will not preroll correctly (no RTCP
  * packets are sent in the PAUSED state). Applications should manually set and
  * keep (see gst_element_set_locked_state()) the RTCP udpsink to the PLAYING state.
- * </refsect2>
+ *
  */
 
 #ifdef HAVE_CONFIG_H
 GST_DEBUG_CATEGORY_STATIC (gst_rtp_session_debug);
 #define GST_CAT_DEFAULT gst_rtp_session_debug
 
+#define GST_TYPE_RTP_NTP_TIME_SOURCE (gst_rtp_ntp_time_source_get_type ())
 GType
 gst_rtp_ntp_time_source_get_type (void)
 {
@@ -246,6 +240,7 @@ enum
   PROP_MAX_DROPOUT_TIME,
   PROP_MAX_MISORDER_TIME,
   PROP_STATS,
+  PROP_TWCC_STATS,
   PROP_RTP_PROFILE,
   PROP_NTP_TIME_SOURCE,
   PROP_RTCP_SYNC_SEND_TIME
@@ -283,6 +278,14 @@ struct _GstRtpSessionPrivate
 
   guint recv_rtx_req_count;
   guint sent_rtx_req_count;
+
+  GstStructure *last_twcc_stats;
+
+  /*
+   * This is the list of processed packets in the receive path when upstream
+   * pushed a buffer list.
+   */
+  GstBufferList *processed_list;
 };
 
 /* callbacks to handle actions from the session manager */
@@ -303,11 +306,15 @@ static GstClockTime gst_rtp_session_request_time (RTPSession * session,
     gpointer user_data);
 static void gst_rtp_session_notify_nack (RTPSession * sess,
     guint16 seqnum, guint16 blp, guint32 ssrc, gpointer user_data);
+static void gst_rtp_session_notify_twcc (RTPSession * sess,
+    GstStructure * twcc_packets, GstStructure * twcc_stats, gpointer user_data);
 static void gst_rtp_session_reconfigure (RTPSession * sess, gpointer user_data);
 static void gst_rtp_session_notify_early_rtcp (RTPSession * sess,
     gpointer user_data);
 static GstFlowReturn gst_rtp_session_chain_recv_rtp (GstPad * pad,
     GstObject * parent, GstBuffer * buffer);
+static GstFlowReturn gst_rtp_session_chain_recv_rtp_list (GstPad * pad,
+    GstObject * parent, GstBufferList * list);
 static GstFlowReturn gst_rtp_session_chain_recv_rtcp (GstPad * pad,
     GstObject * parent, GstBuffer * buffer);
 static GstFlowReturn gst_rtp_session_chain_send_rtp (GstPad * pad,
@@ -325,6 +332,7 @@ static RTPSessionCallbacks callbacks = {
   gst_rtp_session_request_key_unit,
   gst_rtp_session_request_time,
   gst_rtp_session_notify_nack,
+  gst_rtp_session_notify_twcc,
   gst_rtp_session_reconfigure,
   gst_rtp_session_notify_early_rtcp
 };
@@ -507,7 +515,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_REQUEST_PT_MAP] =
       g_signal_new ("request-pt-map", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass, request_pt_map),
-      NULL, NULL, g_cclosure_marshal_generic, GST_TYPE_CAPS, 1, G_TYPE_UINT);
+      NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);
   /**
    * GstRtpSession::clear-pt-map:
    * @sess: the object which received the signal
@@ -518,7 +526,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
       g_signal_new ("clear-pt-map", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
       G_STRUCT_OFFSET (GstRtpSessionClass, clear_pt_map),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
+      NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE);
 
   /**
    * GstRtpSession::on-new-ssrc:
@@ -530,7 +538,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_NEW_SSRC] =
       g_signal_new ("on-new-ssrc", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass, on_new_ssrc),
-      NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
   /**
    * GstRtpSession::on-ssrc_collision:
    * @sess: the object which received the signal
@@ -541,8 +549,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_SSRC_COLLISION] =
       g_signal_new ("on-ssrc-collision", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass,
-          on_ssrc_collision), NULL, NULL, g_cclosure_marshal_VOID__UINT,
-      G_TYPE_NONE, 1, G_TYPE_UINT);
+          on_ssrc_collision), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
   /**
    * GstRtpSession::on-ssrc_validated:
    * @sess: the object which received the signal
@@ -553,8 +560,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_SSRC_VALIDATED] =
       g_signal_new ("on-ssrc-validated", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass,
-          on_ssrc_validated), NULL, NULL, g_cclosure_marshal_VOID__UINT,
-      G_TYPE_NONE, 1, G_TYPE_UINT);
+          on_ssrc_validated), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
   /**
    * GstRtpSession::on-ssrc-active:
    * @sess: the object which received the signal
@@ -565,8 +571,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_SSRC_ACTIVE] =
       g_signal_new ("on-ssrc-active", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass,
-          on_ssrc_active), NULL, NULL, g_cclosure_marshal_VOID__UINT,
-      G_TYPE_NONE, 1, G_TYPE_UINT);
+          on_ssrc_active), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
   /**
    * GstRtpSession::on-ssrc-sdes:
    * @session: the object which received the signal
@@ -577,7 +582,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_SSRC_SDES] =
       g_signal_new ("on-ssrc-sdes", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass, on_ssrc_sdes),
-      NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
 
   /**
    * GstRtpSession::on-bye-ssrc:
@@ -589,7 +594,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_BYE_SSRC] =
       g_signal_new ("on-bye-ssrc", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass, on_bye_ssrc),
-      NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
   /**
    * GstRtpSession::on-bye-timeout:
    * @sess: the object which received the signal
@@ -600,7 +605,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_BYE_TIMEOUT] =
       g_signal_new ("on-bye-timeout", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass, on_bye_timeout),
-      NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
   /**
    * GstRtpSession::on-timeout:
    * @sess: the object which received the signal
@@ -611,7 +616,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_TIMEOUT] =
       g_signal_new ("on-timeout", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass, on_timeout),
-      NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
   /**
    * GstRtpSession::on-sender-timeout:
    * @sess: the object which received the signal
@@ -622,8 +627,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_SENDER_TIMEOUT] =
       g_signal_new ("on-sender-timeout", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass,
-          on_sender_timeout), NULL, NULL, g_cclosure_marshal_VOID__UINT,
-      G_TYPE_NONE, 1, G_TYPE_UINT);
+          on_sender_timeout), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
 
   /**
    * GstRtpSession::on-new-sender-ssrc:
@@ -637,7 +641,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_NEW_SENDER_SSRC] =
       g_signal_new ("on-new-sender-ssrc", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass, on_new_ssrc),
-      NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
 
   /**
    * GstRtpSession::on-sender-ssrc-active:
@@ -651,8 +655,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   gst_rtp_session_signals[SIGNAL_ON_SENDER_SSRC_ACTIVE] =
       g_signal_new ("on-sender-ssrc-active", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpSessionClass,
-          on_ssrc_active), NULL, NULL, g_cclosure_marshal_VOID__UINT,
-      G_TYPE_NONE, 1, G_TYPE_UINT);
+          on_ssrc_active), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
 
   g_object_class_install_property (gobject_class, PROP_BANDWIDTH,
       g_param_spec_double ("bandwidth", "Bandwidth",
@@ -682,7 +685,12 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   g_object_class_install_property (gobject_class, PROP_SDES,
       g_param_spec_boxed ("sdes", "SDES",
           "The SDES items of this session",
+#ifndef TIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK
+          GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
+          | GST_PARAM_DOC_SHOW_DEFAULT));
+#else
           GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+#endif
 
   g_object_class_install_property (gobject_class, PROP_NUM_SOURCES,
       g_param_spec_uint ("num-sources", "Num Sources",
@@ -732,22 +740,22 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
-   * GstRtpSession::stats:
+   * GstRtpSession:stats:
    *
-   * Various session statistics. This property returns a GstStructure
-   * with name application/x-rtp-session-stats with the following fields:
+   * Various session statistics. This property returns a #GstStructure
+   * with name `application/x-rtp-session-stats` with the following fields:
    *
-   *  "recv-rtx-req-count  G_TYPE_UINT   The number of retransmission event
+   * * "recv-rtx-req-count"  G_TYPE_UINT   The number of retransmission events
    *      received from downstream (in receiver mode) (Since 1.16)
-   *  "sent-rtx-req-count" G_TYPE_UINT   The number of retransmission event
+   * * "sent-rtx-req-count" G_TYPE_UINT   The number of retransmission events
    *      sent downstream (in sender mode) (Since 1.16)
-   *  "rtx-count"          G_TYPE_UINT   DEPRECATED Since 1.16, same as
+   * * "rtx-count"          G_TYPE_UINT   DEPRECATED Since 1.16, same as
    *      "recv-rtx-req-count".
-   *  "rtx-drop-count"     G_TYPE_UINT   The number of retransmission events
+   * * "rtx-drop-count"     G_TYPE_UINT   The number of retransmission events
    *      dropped (due to bandwidth constraints)
-   *  "sent-nack-count"    G_TYPE_UINT   Number of NACKs sent
-   *  "recv-nack-count"    G_TYPE_UINT   Number of NACKs received
-   *  "source-stats"       G_TYPE_BOXED  GValueArray of #RTPSource::stats for all
+   * * "sent-nack-count"    G_TYPE_UINT   Number of NACKs sent
+   * * "recv-nack-count"    G_TYPE_UINT   Number of NACKs received
+   * * "source-stats"       G_TYPE_BOXED  GValueArray of #RTPSource:stats for all
    *      RTP sources (Since 1.8)
    *
    * Since: 1.4
@@ -757,6 +765,30 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
           "Various statistics", GST_TYPE_STRUCTURE,
           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
+  /**
+   * GstRtpSession:twcc-stats:
+   *
+   * Various statistics derived from TWCC. This property returns a GstStructure
+   * with name RTPTWCCStats with the following fields:
+   *
+   *  "bitrate-sent"     G_TYPE_UINT    The actual sent bitrate of TWCC packets
+   *  "bitrate-recv"     G_TYPE_UINT    The estimated bitrate for the receiver.
+   *  "packets-sent"     G_TYPE_UINT    Number of packets sent
+   *  "packets-recv"     G_TYPE_UINT    Number of packets reported recevied
+   *  "packet-loss-pct"  G_TYPE_DOUBLE  Packetloss percentage, based on
+   *      packets reported as lost from the recevier.
+   *  "avg-delta-of-delta", G_TYPE_INT64 In nanoseconds, a moving window
+   *      average of the difference in inter-packet spacing between
+   *      sender and receiver. A sudden increase in this number can indicate
+   *      network congestion.
+   *
+   * Since: 1.18
+   */
+  g_object_class_install_property (gobject_class, PROP_TWCC_STATS,
+      g_param_spec_boxed ("twcc-stats", "TWCC Statistics",
+          "Various statistics from TWCC", GST_TYPE_STRUCTURE,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_property (gobject_class, PROP_RTP_PROFILE,
       g_param_spec_enum ("rtp-profile", "RTP Profile",
           "RTP profile to use", GST_TYPE_RTP_PROFILE, DEFAULT_RTP_PROFILE,
@@ -765,7 +797,7 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
   g_object_class_install_property (gobject_class, PROP_NTP_TIME_SOURCE,
       g_param_spec_enum ("ntp-time-source", "NTP Time Source",
           "NTP time source for RTCP packets",
-          gst_rtp_ntp_time_source_get_type (), DEFAULT_NTP_TIME_SOURCE,
+          GST_TYPE_RTP_NTP_TIME_SOURCE, DEFAULT_NTP_TIME_SOURCE,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_property (gobject_class, PROP_RTCP_SYNC_SEND_TIME,
@@ -810,10 +842,16 @@ gst_rtp_session_class_init (GstRtpSessionClass * klass)
       "rtpsession", 0, "RTP Session");
 
   GST_DEBUG_REGISTER_FUNCPTR (gst_rtp_session_chain_recv_rtp);
+  GST_DEBUG_REGISTER_FUNCPTR (gst_rtp_session_chain_recv_rtp_list);
   GST_DEBUG_REGISTER_FUNCPTR (gst_rtp_session_chain_recv_rtcp);
   GST_DEBUG_REGISTER_FUNCPTR (gst_rtp_session_chain_send_rtp);
   GST_DEBUG_REGISTER_FUNCPTR (gst_rtp_session_chain_send_rtp_list);
 
+#ifndef TIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK
+  gst_type_mark_as_plugin_api (GST_TYPE_RTP_NTP_TIME_SOURCE, 0);
+  gst_type_mark_as_plugin_api (RTP_TYPE_SESSION, 0);
+  gst_type_mark_as_plugin_api (RTP_TYPE_SOURCE, 0);
+#endif
 }
 
 static void
@@ -882,6 +920,8 @@ gst_rtp_session_finalize (GObject * object)
   g_cond_clear (&rtpsession->priv->cond);
   g_object_unref (rtpsession->priv->sysclock);
   g_object_unref (rtpsession->priv->session);
+  if (rtpsession->priv->last_twcc_stats)
+    gst_structure_free (rtpsession->priv->last_twcc_stats);
 
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
@@ -1006,6 +1046,11 @@ gst_rtp_session_get_property (GObject * object, guint prop_id,
     case PROP_STATS:
       g_value_take_boxed (value, gst_rtp_session_create_stats (rtpsession));
       break;
+    case PROP_TWCC_STATS:
+      GST_RTP_SESSION_LOCK (rtpsession);
+      g_value_set_boxed (value, priv->last_twcc_stats);
+      GST_RTP_SESSION_UNLOCK (rtpsession);
+      break;
     case PROP_RTP_PROFILE:
       g_object_get_property (G_OBJECT (priv->session), "rtp-profile", value);
       break;
@@ -1061,11 +1106,8 @@ get_current_times (GstRtpSession * rtpsession, GstClockTime * running_time,
       switch (rtpsession->priv->ntp_time_source) {
         case GST_RTP_NTP_TIME_SOURCE_NTP:
         case GST_RTP_NTP_TIME_SOURCE_UNIX:{
-          GTimeVal current;
-
           /* get current NTP time */
-          g_get_current_time (&current);
-          ntpns = GST_TIMEVAL_TO_TIME (current);
+          ntpns = g_get_real_time () * GST_USECOND;
 
           /* add constant to convert from 1970 based time to 1900 based time */
           if (rtpsession->priv->ntp_time_source == GST_RTP_NTP_TIME_SOURCE_NTP)
@@ -1205,7 +1247,7 @@ start_rtcp_thread (GstRtpSession * rtpsession)
       g_thread_join (rtpsession->priv->thread);
     /* only create a new thread if the old one was stopped. Otherwise we can
      * just reuse the currently running one. */
-    rtpsession->priv->thread = g_thread_try_new ("rtpsession-rtcp-thread",
+    rtpsession->priv->thread = g_thread_try_new ("rtpsession-rtcp",
         (GThreadFunc) rtcp_thread, rtpsession, &error);
     rtpsession->priv->thread_stopped = FALSE;
   }
@@ -1324,8 +1366,7 @@ gst_rtp_session_clear_pt_map (GstRtpSession * rtpsession)
   GST_RTP_SESSION_UNLOCK (rtpsession);
 }
 
-/* called when the session manager has an RTP packet or a list of packets
- * ready for further processing */
+/* called when the session manager has an RTP packet ready to be pushed */
 static GstFlowReturn
 gst_rtp_session_process_rtp (RTPSession * sess, RTPSource * src,
     GstBuffer * buffer, gpointer user_data)
@@ -1342,8 +1383,14 @@ gst_rtp_session_process_rtp (RTPSession * sess, RTPSource * src,
   GST_RTP_SESSION_UNLOCK (rtpsession);
 
   if (rtp_src) {
-    GST_LOG_OBJECT (rtpsession, "pushing received RTP packet");
-    result = gst_pad_push (rtp_src, buffer);
+    if (rtpsession->priv->processed_list) {
+      GST_LOG_OBJECT (rtpsession, "queueing received RTP packet");
+      gst_buffer_list_add (rtpsession->priv->processed_list, buffer);
+      result = GST_FLOW_OK;
+    } else {
+      GST_LOG_OBJECT (rtpsession, "pushing received RTP packet");
+      result = gst_pad_push (rtp_src, buffer);
+    }
     gst_object_unref (rtp_src);
   } else {
     GST_DEBUG_OBJECT (rtpsession, "dropping received RTP packet");
@@ -1563,12 +1610,15 @@ gst_rtp_session_cache_caps (GstRtpSession * rtpsession, GstCaps * caps)
   GST_DEBUG_OBJECT (rtpsession, "parsing caps");
 
   s = gst_caps_get_structure (caps, 0);
+
   if (!gst_structure_get_int (s, "payload", &payload))
     return;
 
   if (g_hash_table_lookup (priv->ptmap, GINT_TO_POINTER (payload)))
     return;
 
+  rtp_session_update_recv_caps_structure (rtpsession->priv->session, s);
+
   g_hash_table_insert (priv->ptmap, GINT_TO_POINTER (payload),
       gst_caps_ref (caps));
 }
@@ -1980,6 +2030,60 @@ push_error:
 }
 
 static gboolean
+process_received_buffer_in_list (GstBuffer ** buffer, guint idx, gpointer data)
+{
+  gint ret;
+
+  ret = gst_rtp_session_chain_recv_rtp (NULL, data, *buffer);
+  if (ret != GST_FLOW_OK)
+    GST_ERROR ("Processing individual buffer in a list failed");
+
+  /*
+   * The buffer has been processed, remove it from the original list, if it was
+   * a valid RTP buffer it has been added to the "processed" list in
+   * gst_rtp_session_process_rtp().
+   */
+  *buffer = NULL;
+  return TRUE;
+}
+
+static GstFlowReturn
+gst_rtp_session_chain_recv_rtp_list (GstPad * pad, GstObject * parent,
+    GstBufferList * list)
+{
+  GstRtpSession *rtpsession = GST_RTP_SESSION (parent);
+  GstBufferList *processed_list;
+
+  processed_list = gst_buffer_list_new ();
+
+  /* Set some private data to detect that a buffer list is being pushed. */
+  rtpsession->priv->processed_list = processed_list;
+
+  /*
+   * Individually process the buffers from the incoming buffer list as the
+   * incoming RTP packets in the list can be mixed in all sorts of ways:
+   *    - different frames,
+   *    - different sources,
+   *    - different types (RTP or RTCP)
+   */
+  gst_buffer_list_foreach (list,
+      (GstBufferListFunc) process_received_buffer_in_list, parent);
+
+  gst_buffer_list_unref (list);
+
+  /* Clean up private data in case the next push does not use a buffer list. */
+  rtpsession->priv->processed_list = NULL;
+
+  if (gst_buffer_list_length (processed_list) == 0 || !rtpsession->recv_rtp_src) {
+    gst_buffer_list_unref (processed_list);
+    return GST_FLOW_OK;
+  }
+
+  GST_LOG_OBJECT (rtpsession, "pushing received RTP list");
+  return gst_pad_push_list (rtpsession->recv_rtp_src, processed_list);
+}
+
+static gboolean
 gst_rtp_session_event_recv_rtcp_sink (GstPad * pad, GstObject * parent,
     GstEvent * event)
 {
@@ -2380,6 +2484,8 @@ create_recv_rtp_sink (GstRtpSession * rtpsession)
       "recv_rtp_sink");
   gst_pad_set_chain_function (rtpsession->recv_rtp_sink,
       gst_rtp_session_chain_recv_rtp);
+  gst_pad_set_chain_list_function (rtpsession->recv_rtp_sink,
+      gst_rtp_session_chain_recv_rtp_list);
   gst_pad_set_event_function (rtpsession->recv_rtp_sink,
       gst_rtp_session_event_recv_rtp_sink);
   gst_pad_set_iterate_internal_links_function (rtpsession->recv_rtp_sink,
@@ -2746,6 +2852,31 @@ gst_rtp_session_notify_nack (RTPSession * sess, guint16 seqnum,
 }
 
 static void
+gst_rtp_session_notify_twcc (RTPSession * sess,
+    GstStructure * twcc_packets, GstStructure * twcc_stats, gpointer user_data)
+{
+  GstRtpSession *rtpsession = GST_RTP_SESSION (user_data);
+  GstEvent *event;
+  GstPad *send_rtp_sink;
+
+  GST_RTP_SESSION_LOCK (rtpsession);
+  if ((send_rtp_sink = rtpsession->send_rtp_sink))
+    gst_object_ref (send_rtp_sink);
+  if (rtpsession->priv->last_twcc_stats)
+    gst_structure_free (rtpsession->priv->last_twcc_stats);
+  rtpsession->priv->last_twcc_stats = twcc_stats;
+  GST_RTP_SESSION_UNLOCK (rtpsession);
+
+  if (send_rtp_sink) {
+    event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, twcc_packets);
+    gst_pad_push_event (send_rtp_sink, event);
+    gst_object_unref (send_rtp_sink);
+  }
+
+  g_object_notify (G_OBJECT (rtpsession), "twcc-stats");
+}
+
+static void
 gst_rtp_session_reconfigure (RTPSession * sess, gpointer user_data)
 {
   GstRtpSession *rtpsession = GST_RTP_SESSION (user_data);
index 880e8f2..a1fee7b 100644 (file)
 
 /**
  * SECTION:element-rtpssrcdemux
+ * @title: rtpssrcdemux
  *
  * rtpssrcdemux acts as a demuxer for RTP packets based on the SSRC of the
  * packets. Its main purpose is to allow an application to easily receive and
  * decode an RTP stream with multiple SSRCs.
- * 
+ *
  * For each SSRC that is detected, a new pad will be created and the
- * #GstRtpSsrcDemux::new-ssrc-pad signal will be emitted. 
- * 
- * <refsect2>
- * <title>Example pipelines</title>
+ * #GstRtpSsrcDemux::new-ssrc-pad signal will be emitted.
+ *
+ * ## Example pipelines
  * |[
  * gst-launch-1.0 udpsrc caps="application/x-rtp" ! rtpssrcdemux ! fakesink
  * ]| Takes an RTP stream and send the RTP packets with the first detected SSRC
  * to fakesink, discarding the other SSRCs.
- * </refsect2>
+ *
  */
 
 #ifdef HAVE_CONFIG_H
@@ -89,6 +89,13 @@ typedef enum
   RTCP_PAD
 } PadType;
 
+#define DEFAULT_MAX_STREAMS G_MAXUINT
+enum
+{
+  PROP_0,
+  PROP_MAX_STREAMS
+};
+
 /* signals */
 enum
 {
@@ -273,6 +280,7 @@ find_or_create_demux_pad_for_ssrc (GstRtpSsrcDemux * demux, guint32 ssrc,
   gchar *padname;
   GstRtpSsrcDemuxPad *demuxpad;
   GstPad *retpad;
+  guint num_streams;
 
   INTERNAL_STREAM_LOCK (demux);
 
@@ -281,6 +289,13 @@ find_or_create_demux_pad_for_ssrc (GstRtpSsrcDemux * demux, guint32 ssrc,
     INTERNAL_STREAM_UNLOCK (demux);
     return retpad;
   }
+  /* We create 2 src pads per ssrc (RTP & RTCP). Checking if we are allowed
+     to create 2 more pads */
+  num_streams = (GST_ELEMENT_CAST (demux)->numsrcpads) >> 1;
+  if (num_streams >= demux->max_streams) {
+    INTERNAL_STREAM_UNLOCK (demux);
+    return NULL;
+  }
 
   GST_DEBUG_OBJECT (demux, "creating new pad for SSRC %08x", ssrc);
 
@@ -348,6 +363,40 @@ find_or_create_demux_pad_for_ssrc (GstRtpSsrcDemux * demux, guint32 ssrc,
 }
 
 static void
+gst_rtp_ssrc_demux_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstRtpSsrcDemux *demux;
+
+  demux = GST_RTP_SSRC_DEMUX (object);
+  switch (prop_id) {
+    case PROP_MAX_STREAMS:
+      demux->max_streams = g_value_get_uint (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_rtp_ssrc_demux_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstRtpSsrcDemux *demux;
+
+  demux = GST_RTP_SSRC_DEMUX (object);
+  switch (prop_id) {
+    case PROP_MAX_STREAMS:
+      g_value_set_uint (value, demux->max_streams);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
 gst_rtp_ssrc_demux_class_init (GstRtpSsrcDemuxClass * klass)
 {
   GObjectClass *gobject_klass;
@@ -360,6 +409,14 @@ gst_rtp_ssrc_demux_class_init (GstRtpSsrcDemuxClass * klass)
 
   gobject_klass->dispose = gst_rtp_ssrc_demux_dispose;
   gobject_klass->finalize = gst_rtp_ssrc_demux_finalize;
+  gobject_klass->set_property = gst_rtp_ssrc_demux_set_property;
+  gobject_klass->get_property = gst_rtp_ssrc_demux_get_property;
+
+  g_object_class_install_property (gobject_klass, PROP_MAX_STREAMS,
+      g_param_spec_uint ("max-streams", "Max Streams",
+          "The maximum number of streams allowed",
+          0, G_MAXUINT, DEFAULT_MAX_STREAMS,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
    * GstRtpSsrcDemux::new-ssrc-pad:
@@ -367,14 +424,13 @@ gst_rtp_ssrc_demux_class_init (GstRtpSsrcDemuxClass * klass)
    * @ssrc: the SSRC of the pad
    * @pad: the new pad.
    *
-   * Emited when a new SSRC pad has been created.
+   * Emitted when a new SSRC pad has been created.
    */
   gst_rtp_ssrc_demux_signals[SIGNAL_NEW_SSRC_PAD] =
       g_signal_new ("new-ssrc-pad",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
       G_STRUCT_OFFSET (GstRtpSsrcDemuxClass, new_ssrc_pad),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      GST_TYPE_PAD);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, GST_TYPE_PAD);
 
   /**
    * GstRtpSsrcDemux::removed-ssrc-pad:
@@ -382,14 +438,13 @@ gst_rtp_ssrc_demux_class_init (GstRtpSsrcDemuxClass * klass)
    * @ssrc: the SSRC of the pad
    * @pad: the removed pad.
    *
-   * Emited when a SSRC pad has been removed.
+   * Emitted when a SSRC pad has been removed.
    */
   gst_rtp_ssrc_demux_signals[SIGNAL_REMOVED_SSRC_PAD] =
       g_signal_new ("removed-ssrc-pad",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
       G_STRUCT_OFFSET (GstRtpSsrcDemuxClass, removed_ssrc_pad),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_UINT,
-      GST_TYPE_PAD);
+      NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, GST_TYPE_PAD);
 
   /**
    * GstRtpSsrcDemux::clear-ssrc:
@@ -402,7 +457,7 @@ gst_rtp_ssrc_demux_class_init (GstRtpSsrcDemuxClass * klass)
       g_signal_new ("clear-ssrc",
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
       G_STRUCT_OFFSET (GstRtpSsrcDemuxClass, clear_ssrc),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_UINT);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
 
   gstelement_klass->change_state =
       GST_DEBUG_FUNCPTR (gst_rtp_ssrc_demux_change_state);
@@ -453,6 +508,8 @@ gst_rtp_ssrc_demux_init (GstRtpSsrcDemux * demux)
       gst_rtp_ssrc_demux_iterate_internal_links_sink);
   gst_element_add_pad (GST_ELEMENT_CAST (demux), demux->rtcp_sink);
 
+  demux->max_streams = DEFAULT_MAX_STREAMS;
+
   g_rec_mutex_init (&demux->padlock);
 }
 
@@ -638,18 +695,17 @@ gst_rtp_ssrc_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
   /* ERRORS */
 invalid_payload:
   {
-    /* this is fatal and should be filtered earlier */
-    GST_ELEMENT_ERROR (demux, STREAM, DECODE, (NULL),
-        ("Dropping invalid RTP payload"));
+    GST_DEBUG_OBJECT (demux, "Dropping invalid RTP packet");
     gst_buffer_unref (buf);
-    return GST_FLOW_ERROR;
+    return GST_FLOW_OK;
   }
 create_failed:
   {
-    GST_ELEMENT_ERROR (demux, STREAM, DECODE, (NULL),
-        ("Could not create new pad"));
     gst_buffer_unref (buf);
-    return GST_FLOW_ERROR;
+    GST_WARNING_OBJECT (demux,
+        "Dropping buffer SSRC %08x. "
+        "Max streams number reached (%u)", ssrc, demux->max_streams);
+    return GST_FLOW_OK;
   }
 }
 
@@ -688,6 +744,8 @@ gst_rtp_ssrc_demux_rtcp_chain (GstPad * pad, GstObject * parent,
       ssrc = gst_rtcp_packet_rr_get_ssrc (&packet);
       break;
     case GST_RTCP_TYPE_APP:
+      ssrc = gst_rtcp_packet_app_get_ssrc (&packet);
+      break;
     case GST_RTCP_TYPE_RTPFB:
     case GST_RTCP_TYPE_PSFB:
       ssrc = gst_rtcp_packet_fb_get_sender_ssrc (&packet);
@@ -726,11 +784,9 @@ gst_rtp_ssrc_demux_rtcp_chain (GstPad * pad, GstObject * parent,
   /* ERRORS */
 invalid_rtcp:
   {
-    /* this is fatal and should be filtered earlier */
-    GST_ELEMENT_ERROR (demux, STREAM, DECODE, (NULL),
-        ("Dropping invalid RTCP packet"));
+    GST_DEBUG_OBJECT (demux, "Dropping invalid RTCP packet");
     gst_buffer_unref (buf);
-    return GST_FLOW_ERROR;
+    return GST_FLOW_OK;
   }
 unexpected_rtcp:
   {
@@ -740,10 +796,11 @@ unexpected_rtcp:
   }
 create_failed:
   {
-    GST_ELEMENT_ERROR (demux, STREAM, DECODE, (NULL),
-        ("Could not create new pad"));
     gst_buffer_unref (buf);
-    return GST_FLOW_ERROR;
+    GST_WARNING_OBJECT (demux,
+        "Dropping buffer SSRC %08x. "
+        "Max streams number reached (%u)", ssrc, demux->max_streams);
+    return GST_FLOW_OK;
   }
 }
 
index 82df444..3bb210c 100644 (file)
@@ -41,6 +41,7 @@ struct _GstRtpSsrcDemux
 
   GRecMutex padlock;
   GSList *srcpads;
+  guint max_streams;
 };
 
 struct _GstRtpSsrcDemuxClass
index c524ff4..118a1e1 100644 (file)
@@ -13,6 +13,8 @@ rtpmanager_sources = [
   'rtpsession.c',
   'rtpsource.c',
   'rtpstats.c',
+  'rtptimerqueue.c',
+  'rtptwcc.c',
   'gstrtpsession.c',
   'gstrtpfunnel.c',
 ]
@@ -26,3 +28,4 @@ gstrtpmanager = library('gstrtpmanager',
   install_dir : plugins_install_dir,
 )
 pkgconfig.generate(gstrtpmanager, install_dir : plugins_pkgconfig_install_dir)
+plugins += [gstrtpmanager]
index 64d89a8..6ef7df2 100644 (file)
@@ -87,7 +87,7 @@ rtp_jitter_buffer_init (RTPJitterBuffer * jbuf)
 {
   g_mutex_init (&jbuf->clock_lock);
 
-  jbuf->packets = g_queue_new ();
+  g_queue_init (&jbuf->packets);
   jbuf->mode = RTP_JITTER_BUFFER_MODE_SLAVE;
 
   rtp_jitter_buffer_reset_skew (jbuf);
@@ -112,7 +112,10 @@ rtp_jitter_buffer_finalize (GObject * object)
   if (jbuf->pipeline_clock)
     gst_object_unref (jbuf->pipeline_clock);
 
-  g_queue_free (jbuf->packets);
+  /* We cannot use g_queue_clear() as it would pass the wrong size to
+   * g_slice_free() which may lead to data corruption in the slice allocator.
+   */
+  rtp_jitter_buffer_flush (jbuf, NULL, NULL);
 
   g_mutex_clear (&jbuf->clock_lock);
 
@@ -385,7 +388,7 @@ get_buffer_level (RTPJitterBuffer * jbuf)
   guint64 level;
 
   /* first buffer with timestamp */
-  high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (jbuf->packets);
+  high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (&jbuf->packets);
   while (high_buf) {
     if (high_buf->dts != -1 || high_buf->pts != -1)
       break;
@@ -393,7 +396,7 @@ get_buffer_level (RTPJitterBuffer * jbuf)
     high_buf = (RTPJitterBufferItem *) g_list_previous (high_buf);
   }
 
-  low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (jbuf->packets);
+  low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (&jbuf->packets);
   while (low_buf) {
     if (low_buf->dts != -1 || low_buf->pts != -1)
       break;
@@ -504,7 +507,7 @@ update_buffer_level (RTPJitterBuffer * jbuf, gint * percent)
  *    Cri    : The time of the clock at the receiver for packet i
  *    D + ni : The jitter when receiving packet i
  *
- * We see that the network delay is irrelevant here as we can elliminate D:
+ * We see that the network delay is irrelevant here as we can eliminate D:
  *
  *  recv_diff(i) = (Cri + ni) - (Cr0 + n0))
  *
@@ -530,7 +533,7 @@ update_buffer_level (RTPJitterBuffer * jbuf, gint * percent)
  */
 static GstClockTime
 calculate_skew (RTPJitterBuffer * jbuf, guint64 ext_rtptime,
-    GstClockTime gstrtptime, GstClockTime time)
+    GstClockTime gstrtptime, GstClockTime time, gint gap, gboolean is_rtx)
 {
   guint64 send_diff, recv_diff;
   gint64 delta;
@@ -544,7 +547,7 @@ calculate_skew (RTPJitterBuffer * jbuf, guint64 ext_rtptime,
 
   /* we don't have an arrival timestamp so we can't do skew detection. we
    * should still apply a timestamp based on RTP timestamp and base_time */
-  if (time == -1 || jbuf->base_time == -1)
+  if (time == -1 || jbuf->base_time == -1 || is_rtx)
     goto no_skew;
 
   /* elapsed time at receiver, includes the jitter */
@@ -574,8 +577,14 @@ calculate_skew (RTPJitterBuffer * jbuf, guint64 ext_rtptime,
     rtp_jitter_buffer_resync (jbuf, time, gstrtptime, ext_rtptime, TRUE);
     send_diff = 0;
     delta = 0;
+    gap = 0;
   }
 
+  /* only do skew calculations if we didn't have a gap. if too much time
+   * has elapsed despite there being a gap, we resynced already. */
+  if (G_UNLIKELY (gap != 0))
+    goto no_skew;
+
   pos = jbuf->window_pos;
 
   if (G_UNLIKELY (jbuf->window_filling)) {
@@ -672,7 +681,7 @@ no_skew:
 static void
 queue_do_insert (RTPJitterBuffer * jbuf, GList * list, GList * item)
 {
-  GQueue *queue = jbuf->packets;
+  GQueue *queue = &jbuf->packets;
 
   /* It's more likely that the packet was inserted at the tail of the queue */
   if (G_LIKELY (list)) {
@@ -693,7 +702,8 @@ queue_do_insert (RTPJitterBuffer * jbuf, GList * list, GList * item)
 
 GstClockTime
 rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
-    gboolean estimated_dts, guint32 rtptime, GstClockTime base_time)
+    gboolean estimated_dts, guint32 rtptime, GstClockTime base_time,
+    gint gap, gboolean is_rtx)
 {
   guint64 ext_rtptime;
   GstClockTime gstrtptime, pts;
@@ -714,10 +724,18 @@ rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
     ext_rtptime = gst_rtp_buffer_ext_timestamp (&ext_rtptime, rtptime);
     if (ext_rtptime > jbuf->last_rtptime + 3 * jbuf->clock_rate ||
         ext_rtptime + 3 * jbuf->clock_rate < jbuf->last_rtptime) {
-      /* reset even if we don't have valid incoming time;
-       * still better than producing possibly very bogus output timestamp */
-      GST_WARNING ("rtp delta too big, reset skew");
-      rtp_jitter_buffer_reset_skew (jbuf);
+      if (!is_rtx) {
+        /* reset even if we don't have valid incoming time;
+         * still better than producing possibly very bogus output timestamp */
+        GST_WARNING ("rtp delta too big, reset skew");
+        rtp_jitter_buffer_reset_skew (jbuf);
+      } else {
+        GST_WARNING ("rtp delta too big: ignore rtx packet");
+        media_clock = NULL;
+        pipeline_clock = NULL;
+        pts = GST_CLOCK_TIME_NONE;
+        goto done;
+      }
     }
   }
 
@@ -743,11 +761,17 @@ rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
   if (G_LIKELY (jbuf->base_rtptime != -1)) {
     /* check elapsed time in RTP units */
     if (gstrtptime < jbuf->base_rtptime) {
-      /* elapsed time at sender, timestamps can go backwards and thus be
-       * smaller than our base time, schedule to take a new base time in
-       * that case. */
-      GST_WARNING ("backward timestamps at server, schedule resync");
-      jbuf->need_resync = TRUE;
+      if (!is_rtx) {
+        /* elapsed time at sender, timestamps can go backwards and thus be
+         * smaller than our base time, schedule to take a new base time in
+         * that case. */
+        GST_WARNING ("backward timestamps at server, schedule resync");
+        jbuf->need_resync = TRUE;
+      } else {
+        GST_WARNING ("backward timestamps: ignore rtx packet");
+        pts = GST_CLOCK_TIME_NONE;
+        goto done;
+      }
     }
   }
 
@@ -777,6 +801,11 @@ rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
   /* need resync, lock on to time and gstrtptime if we can, otherwise we
    * do with the previous values */
   if (G_UNLIKELY (jbuf->need_resync && dts != -1)) {
+    if (is_rtx) {
+      GST_DEBUG ("not resyncing on rtx packet, discard");
+      pts = GST_CLOCK_TIME_NONE;
+      goto done;
+    }
     GST_INFO ("resync to time %" GST_TIME_FORMAT ", rtptime %"
         GST_TIME_FORMAT, GST_TIME_ARGS (dts), GST_TIME_ARGS (gstrtptime));
     rtp_jitter_buffer_resync (jbuf, dts, gstrtptime, ext_rtptime, FALSE);
@@ -873,12 +902,12 @@ rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
 
     if (ntprtptime > rtptime_tmp)
       ntptime -=
-          gst_util_uint64_scale (ntprtptime - rtptime_tmp, jbuf->clock_rate,
-          GST_SECOND);
+          gst_util_uint64_scale (ntprtptime - rtptime_tmp, GST_SECOND,
+          jbuf->clock_rate);
     else
       ntptime +=
-          gst_util_uint64_scale (rtptime_tmp - ntprtptime, jbuf->clock_rate,
-          GST_SECOND);
+          gst_util_uint64_scale (rtptime_tmp - ntprtptime, GST_SECOND,
+          jbuf->clock_rate);
 
     rtpsystime =
         gst_clock_adjust_with_calibration (media_clock, ntptime, internal,
@@ -899,7 +928,7 @@ rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
 
     /* do skew calculation by measuring the difference between rtptime and the
      * receive dts, this function will return the skew corrected rtptime. */
-    pts = calculate_skew (jbuf, ext_rtptime, gstrtptime, dts);
+    pts = calculate_skew (jbuf, ext_rtptime, gstrtptime, dts, gap, is_rtx);
   }
 
   /* check if timestamps are not going backwards, we can only check this if we
@@ -921,13 +950,14 @@ rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
     }
   }
 
-  if (dts != -1 && pts + jbuf->delay < dts) {
+  if (gap == 0 && dts != -1 && pts + jbuf->delay < dts) {
     /* if we are going to produce a timestamp that is later than the input
      * timestamp, we need to reset the jitterbuffer. Likely the server paused
      * temporarily */
     GST_DEBUG ("out %" GST_TIME_FORMAT " + %" G_GUINT64_FORMAT " < time %"
-        GST_TIME_FORMAT ", reset jitterbuffer", GST_TIME_ARGS (pts),
+        GST_TIME_FORMAT ", reset jitterbuffer and discard", GST_TIME_ARGS (pts),
         jbuf->delay, GST_TIME_ARGS (dts));
+    rtp_jitter_buffer_reset_skew (jbuf);
     rtp_jitter_buffer_resync (jbuf, dts, gstrtptime, ext_rtptime, TRUE);
     pts = dts;
   }
@@ -935,6 +965,7 @@ rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
   jbuf->prev_out_time = pts;
   jbuf->prev_send_diff = gstrtptime - jbuf->base_rtptime;
 
+done:
   if (media_clock)
     gst_object_unref (media_clock);
   if (pipeline_clock)
@@ -961,7 +992,7 @@ rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
  *
  * Returns: %FALSE if a packet with the same number already existed.
  */
-gboolean
+static gboolean
 rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item,
     gboolean * head, gint * percent)
 {
@@ -971,7 +1002,7 @@ rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item,
   g_return_val_if_fail (jbuf != NULL, FALSE);
   g_return_val_if_fail (item != NULL, FALSE);
 
-  list = jbuf->packets->tail;
+  list = jbuf->packets.tail;
 
   /* no seqnum, simply append then */
   if (item->seqnum == -1)
@@ -1039,11 +1070,157 @@ duplicate:
     GST_DEBUG ("duplicate packet %d found", (gint) seqnum);
     if (G_LIKELY (head))
       *head = FALSE;
+    if (percent)
+      *percent = -1;
     return FALSE;
   }
 }
 
 /**
+ * rtp_jitter_buffer_alloc_item:
+ * @data: The data stored in this item
+ * @type: User specific item type
+ * @dts: Decoding Timestamp
+ * @pts: Presentation Timestamp
+ * @seqnum: Sequence number
+ * @count: Number of packet this item represent
+ * @rtptime: The RTP specific timestamp
+ * @free_data: A function to free @data (optional)
+ *
+ * Create an item that can then be stored in the jitter buffer.
+ *
+ * Returns: a newly allocated RTPJitterbufferItem
+ */
+static RTPJitterBufferItem *
+rtp_jitter_buffer_alloc_item (gpointer data, guint type, GstClockTime dts,
+    GstClockTime pts, guint seqnum, guint count, guint rtptime,
+    GDestroyNotify free_data)
+{
+  RTPJitterBufferItem *item;
+
+  item = g_slice_new (RTPJitterBufferItem);
+  item->data = data;
+  item->next = NULL;
+  item->prev = NULL;
+  item->type = type;
+  item->dts = dts;
+  item->pts = pts;
+  item->seqnum = seqnum;
+  item->count = count;
+  item->rtptime = rtptime;
+  item->free_data = free_data;
+
+  return item;
+}
+
+static inline RTPJitterBufferItem *
+alloc_event_item (GstEvent * event)
+{
+  return rtp_jitter_buffer_alloc_item (event, ITEM_TYPE_EVENT, -1, -1, -1, 0,
+      -1, (GDestroyNotify) gst_mini_object_unref);
+}
+
+/**
+ * rtp_jitter_buffer_append_event:
+ * @jbuf: an #RTPJitterBuffer
+ * @event: an #GstEvent to insert
+
+ * Inserts @event into the packet queue of @jbuf.
+ *
+ * Returns: %TRUE if the event is at the head of the queue
+ */
+gboolean
+rtp_jitter_buffer_append_event (RTPJitterBuffer * jbuf, GstEvent * event)
+{
+  RTPJitterBufferItem *item = alloc_event_item (event);
+  gboolean head;
+  rtp_jitter_buffer_insert (jbuf, item, &head, NULL);
+  return head;
+}
+
+/**
+ * rtp_jitter_buffer_append_query:
+ * @jbuf: an #RTPJitterBuffer
+ * @query: an #GstQuery to insert
+
+ * Inserts @query into the packet queue of @jbuf.
+ *
+ * Returns: %TRUE if the query is at the head of the queue
+ */
+gboolean
+rtp_jitter_buffer_append_query (RTPJitterBuffer * jbuf, GstQuery * query)
+{
+  RTPJitterBufferItem *item =
+      rtp_jitter_buffer_alloc_item (query, ITEM_TYPE_QUERY, -1, -1, -1, 0, -1,
+      NULL);
+  gboolean head;
+  rtp_jitter_buffer_insert (jbuf, item, &head, NULL);
+  return head;
+}
+
+/**
+ * rtp_jitter_buffer_append_lost_event:
+ * @jbuf: an #RTPJitterBuffer
+ * @event: an #GstEvent to insert
+ * @seqnum: Sequence number
+ * @lost_packets: Number of lost packet this item represent
+
+ * Inserts @event into the packet queue of @jbuf.
+ *
+ * Returns: %TRUE if the event is at the head of the queue
+ */
+gboolean
+rtp_jitter_buffer_append_lost_event (RTPJitterBuffer * jbuf, GstEvent * event,
+    guint16 seqnum, guint lost_packets)
+{
+  RTPJitterBufferItem *item = rtp_jitter_buffer_alloc_item (event,
+      ITEM_TYPE_LOST, -1, -1, seqnum, lost_packets, -1,
+      (GDestroyNotify) gst_mini_object_unref);
+  gboolean head;
+
+  if (!rtp_jitter_buffer_insert (jbuf, item, &head, NULL)) {
+    /* Duplicate */
+    rtp_jitter_buffer_free_item (item);
+    head = FALSE;
+  }
+
+  return head;
+}
+
+/**
+ * rtp_jitter_buffer_append_buffer:
+ * @jbuf: an #RTPJitterBuffer
+ * @buf: an #GstBuffer to insert
+ * @seqnum: Sequence number
+ * @duplicate: TRUE when the packet inserted is a duplicate
+ * @percent: the buffering percent after insertion
+ *
+ * Inserts @buf into the packet queue of @jbuf.
+ *
+ * Returns: %TRUE if the buffer is at the head of the queue
+ */
+gboolean
+rtp_jitter_buffer_append_buffer (RTPJitterBuffer * jbuf, GstBuffer * buf,
+    GstClockTime dts, GstClockTime pts, guint16 seqnum, guint rtptime,
+    gboolean * duplicate, gint * percent)
+{
+  RTPJitterBufferItem *item = rtp_jitter_buffer_alloc_item (buf,
+      ITEM_TYPE_BUFFER, dts, pts, seqnum, 1, rtptime,
+      (GDestroyNotify) gst_mini_object_unref);
+  gboolean head;
+  gboolean inserted;
+
+  inserted = rtp_jitter_buffer_insert (jbuf, item, &head, percent);
+  if (!inserted)
+    rtp_jitter_buffer_free_item (item);
+
+  if (duplicate)
+    *duplicate = !inserted;
+
+  return head;
+}
+
+/**
  * rtp_jitter_buffer_pop:
  * @jbuf: an #RTPJitterBuffer
  * @percent: the buffering percent
@@ -1062,7 +1239,7 @@ rtp_jitter_buffer_pop (RTPJitterBuffer * jbuf, gint * percent)
 
   g_return_val_if_fail (jbuf != NULL, NULL);
 
-  queue = jbuf->packets;
+  queue = &jbuf->packets;
 
   item = queue->head;
   if (item) {
@@ -1080,6 +1257,10 @@ rtp_jitter_buffer_pop (RTPJitterBuffer * jbuf, gint * percent)
   else if (percent)
     *percent = -1;
 
+  /* let's clear the pointers so we can ensure we don't free items that are
+   * still in the jitterbuffer */
+  item->next = item->prev = NULL;
+
   return (RTPJitterBufferItem *) item;
 }
 
@@ -1099,13 +1280,13 @@ rtp_jitter_buffer_peek (RTPJitterBuffer * jbuf)
 {
   g_return_val_if_fail (jbuf != NULL, NULL);
 
-  return (RTPJitterBufferItem *) jbuf->packets->head;
+  return (RTPJitterBufferItem *) jbuf->packets.head;
 }
 
 /**
  * rtp_jitter_buffer_flush:
  * @jbuf: an #RTPJitterBuffer
- * @free_func: function to free each item
+ * @free_func: function to free each item (optional)
  * @user_data: user data passed to @free_func
  *
  * Flush all packets from the jitterbuffer.
@@ -1117,9 +1298,11 @@ rtp_jitter_buffer_flush (RTPJitterBuffer * jbuf, GFunc free_func,
   GList *item;
 
   g_return_if_fail (jbuf != NULL);
-  g_return_if_fail (free_func != NULL);
 
-  while ((item = g_queue_pop_head_link (jbuf->packets)))
+  if (free_func == NULL)
+    free_func = (GFunc) rtp_jitter_buffer_free_item;
+
+  while ((item = g_queue_pop_head_link (&jbuf->packets)))
     free_func ((RTPJitterBufferItem *) item, user_data);
 }
 
@@ -1191,7 +1374,7 @@ rtp_jitter_buffer_num_packets (RTPJitterBuffer * jbuf)
 {
   g_return_val_if_fail (jbuf != NULL, 0);
 
-  return jbuf->packets->length;
+  return jbuf->packets.length;
 }
 
 /**
@@ -1212,8 +1395,8 @@ rtp_jitter_buffer_get_ts_diff (RTPJitterBuffer * jbuf)
 
   g_return_val_if_fail (jbuf != NULL, 0);
 
-  high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (jbuf->packets);
-  low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (jbuf->packets);
+  high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (&jbuf->packets);
+  low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (&jbuf->packets);
 
   if (!high_buf || !low_buf || high_buf == low_buf)
     return 0;
@@ -1231,7 +1414,7 @@ rtp_jitter_buffer_get_ts_diff (RTPJitterBuffer * jbuf)
 }
 
 
-/**
+/*
  * rtp_jitter_buffer_get_seqnum_diff:
  * @jbuf: an #RTPJitterBuffer
  *
@@ -1240,7 +1423,7 @@ rtp_jitter_buffer_get_ts_diff (RTPJitterBuffer * jbuf)
  *
  * Returns: The difference expressed in seqnum.
  */
-guint16
+static guint16
 rtp_jitter_buffer_get_seqnum_diff (RTPJitterBuffer * jbuf)
 {
   guint32 high_seqnum, low_seqnum;
@@ -1249,8 +1432,8 @@ rtp_jitter_buffer_get_seqnum_diff (RTPJitterBuffer * jbuf)
 
   g_return_val_if_fail (jbuf != NULL, 0);
 
-  high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (jbuf->packets);
-  low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (jbuf->packets);
+  high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (&jbuf->packets);
+  low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (&jbuf->packets);
 
   while (high_buf && high_buf->seqnum == -1)
     high_buf = (RTPJitterBufferItem *) high_buf->prev;
@@ -1341,3 +1524,30 @@ rtp_jitter_buffer_can_fast_start (RTPJitterBuffer * jbuf, gint num_packet)
 
   return ret;
 }
+
+gboolean
+rtp_jitter_buffer_is_full (RTPJitterBuffer * jbuf)
+{
+  return rtp_jitter_buffer_get_seqnum_diff (jbuf) >= 32765 &&
+      rtp_jitter_buffer_num_packets (jbuf) > 10000;
+}
+
+
+/**
+ * rtp_jitter_buffer_free_item:
+ * @item: the item to be freed
+ *
+ * Free the jitter buffer item.
+ */
+void
+rtp_jitter_buffer_free_item (RTPJitterBufferItem * item)
+{
+  g_return_if_fail (item != NULL);
+  /* needs to be unlinked first */
+  g_return_if_fail (item->next == NULL);
+  g_return_if_fail (item->prev == NULL);
+
+  if (item->data && item->free_data)
+    item->free_data (item->data);
+  g_slice_free (RTPJitterBufferItem, item);
+}
index b65b603..8accee4 100644 (file)
@@ -73,7 +73,7 @@ GType rtp_jitter_buffer_mode_get_type (void);
 struct _RTPJitterBuffer {
   GObject        object;
 
-  GQueue        *packets;
+  GQueue         packets;
 
   RTPJitterBufferMode mode;
 
@@ -116,6 +116,12 @@ struct _RTPJitterBufferClass {
   GObjectClass   parent_class;
 };
 
+#define IS_DROPABLE(it) (((it)->type == ITEM_TYPE_BUFFER) || ((it)->type == ITEM_TYPE_LOST))
+#define ITEM_TYPE_BUFFER        0
+#define ITEM_TYPE_LOST          1
+#define ITEM_TYPE_EVENT         2
+#define ITEM_TYPE_QUERY         3
+
 /**
  * RTPJitterBufferItem:
  * @data: the data of the item
@@ -129,19 +135,27 @@ struct _RTPJitterBufferClass {
  *   append.
  * @count: amount of seqnum in this item
  * @rtptime: rtp timestamp
+ * @data_free: Function to free @data (optional)
  *
- * An object containing an RTP packet or event.
+ * An object containing an RTP packet or event. First members of this structure
+ * copied from GList so they can be inserted into lists without doing more
+ * allocations.
  */
 struct _RTPJitterBufferItem {
+  /* a GList */
   gpointer data;
   GList *next;
   GList *prev;
+
+  /* item metadata */
   guint type;
   GstClockTime dts;
   GstClockTime pts;
   guint seqnum;
   guint count;
   guint rtptime;
+
+  GDestroyNotify free_data;
 };
 
 GType rtp_jitter_buffer_get_type (void);
@@ -166,9 +180,14 @@ void                  rtp_jitter_buffer_set_rfc7273_sync (RTPJitterBuffer *jbuf,
 
 void                  rtp_jitter_buffer_reset_skew       (RTPJitterBuffer *jbuf);
 
-gboolean              rtp_jitter_buffer_insert           (RTPJitterBuffer *jbuf,
-                                                          RTPJitterBufferItem *item,
-                                                          gboolean *head, gint *percent);
+gboolean              rtp_jitter_buffer_append_event      (RTPJitterBuffer * jbuf, GstEvent * event);
+gboolean              rtp_jitter_buffer_append_query      (RTPJitterBuffer * jbuf, GstQuery * query);
+gboolean              rtp_jitter_buffer_append_lost_event (RTPJitterBuffer * jbuf, GstEvent * event,
+                                                           guint16 seqnum, guint lost_packets);
+gboolean              rtp_jitter_buffer_append_buffer     (RTPJitterBuffer * jbuf, GstBuffer * buf,
+                                                           GstClockTime dts, GstClockTime pts,
+                                                           guint16 seqnum, guint rtptime,
+                                                           gboolean * duplicate, gint * percent);
 
 void                  rtp_jitter_buffer_disable_buffering (RTPJitterBuffer *jbuf, gboolean disabled);
 
@@ -184,15 +203,19 @@ gint                  rtp_jitter_buffer_get_percent      (RTPJitterBuffer * jbuf
 
 guint                 rtp_jitter_buffer_num_packets      (RTPJitterBuffer *jbuf);
 guint32               rtp_jitter_buffer_get_ts_diff      (RTPJitterBuffer *jbuf);
-guint16               rtp_jitter_buffer_get_seqnum_diff  (RTPJitterBuffer * jbuf);
 
 void                  rtp_jitter_buffer_get_sync         (RTPJitterBuffer *jbuf, guint64 *rtptime,
                                                           guint64 *timestamp, guint32 *clock_rate,
                                                           guint64 *last_rtptime);
 
 GstClockTime          rtp_jitter_buffer_calculate_pts    (RTPJitterBuffer * jbuf, GstClockTime dts, gboolean estimated_dts,
-                                                          guint32 rtptime, GstClockTime base_time);
+                                                          guint32 rtptime, GstClockTime base_time, gint gap,
+                                                          gboolean is_rtx);
 
 gboolean              rtp_jitter_buffer_can_fast_start   (RTPJitterBuffer * jbuf, gint num_packet);
 
+gboolean              rtp_jitter_buffer_is_full          (RTPJitterBuffer * jbuf);
+
+void                  rtp_jitter_buffer_free_item        (RTPJitterBufferItem * item);
+
 #endif /* __RTP_JITTER_BUFFER_H__ */
index fa9dfed..7aa50a6 100644 (file)
@@ -22,6 +22,7 @@
 #define GLIB_DISABLE_DEPRECATION_WARNINGS
 
 #include <string.h>
+#include <stdlib.h>
 
 #include <gst/rtp/gstrtpbuffer.h>
 #include <gst/rtp/gstrtcpbuffer.h>
@@ -30,7 +31,7 @@
 
 #include "rtpsession.h"
 
-GST_DEBUG_CATEGORY_STATIC (rtp_session_debug);
+GST_DEBUG_CATEGORY (rtp_session_debug);
 #define GST_CAT_DEFAULT rtp_session_debug
 
 /* signals and args */
@@ -115,6 +116,8 @@ enum
    (avg) = ((val) + (15 * (avg))) >> 4;
 
 
+#define TWCC_EXTMAP_STR "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
+
 /* GObject vmethods */
 static void rtp_session_finalize (GObject * object);
 static void rtp_session_set_property (GObject * object, guint prop_id,
@@ -172,7 +175,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_GET_SOURCE_BY_SSRC] =
       g_signal_new ("get-source-by-ssrc", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (RTPSessionClass,
-          get_source_by_ssrc), NULL, NULL, g_cclosure_marshal_generic,
+          get_source_by_ssrc), NULL, NULL, NULL,
       RTP_TYPE_SOURCE, 1, G_TYPE_UINT);
 
   /**
@@ -185,8 +188,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_NEW_SSRC] =
       g_signal_new ("on-new-ssrc", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_new_ssrc),
-      NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
-      RTP_TYPE_SOURCE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
   /**
    * RTPSession::on-ssrc-collision:
    * @session: the object which received the signal
@@ -197,8 +199,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_SSRC_COLLISION] =
       g_signal_new ("on-ssrc-collision", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_ssrc_collision),
-      NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
-      RTP_TYPE_SOURCE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
   /**
    * RTPSession::on-ssrc-validated:
    * @session: the object which received the signal
@@ -209,8 +210,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_SSRC_VALIDATED] =
       g_signal_new ("on-ssrc-validated", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_ssrc_validated),
-      NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
-      RTP_TYPE_SOURCE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
   /**
    * RTPSession::on-ssrc-active:
    * @session: the object which received the signal
@@ -221,8 +221,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_SSRC_ACTIVE] =
       g_signal_new ("on-ssrc-active", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_ssrc_active),
-      NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
-      RTP_TYPE_SOURCE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
   /**
    * RTPSession::on-ssrc-sdes:
    * @session: the object which received the signal
@@ -233,8 +232,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_SSRC_SDES] =
       g_signal_new ("on-ssrc-sdes", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_ssrc_sdes),
-      NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
-      RTP_TYPE_SOURCE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
   /**
    * RTPSession::on-bye-ssrc:
    * @session: the object which received the signal
@@ -245,8 +243,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_BYE_SSRC] =
       g_signal_new ("on-bye-ssrc", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_bye_ssrc),
-      NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
-      RTP_TYPE_SOURCE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
   /**
    * RTPSession::on-bye-timeout:
    * @session: the object which received the signal
@@ -257,8 +254,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_BYE_TIMEOUT] =
       g_signal_new ("on-bye-timeout", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_bye_timeout),
-      NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
-      RTP_TYPE_SOURCE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
   /**
    * RTPSession::on-timeout:
    * @session: the object which received the signal
@@ -269,8 +265,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_TIMEOUT] =
       g_signal_new ("on-timeout", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_timeout),
-      NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
-      RTP_TYPE_SOURCE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
   /**
    * RTPSession::on-sender-timeout:
    * @session: the object which received the signal
@@ -281,8 +276,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_SENDER_TIMEOUT] =
       g_signal_new ("on-sender-timeout", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_sender_timeout),
-      NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
-      RTP_TYPE_SOURCE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
 
   /**
    * RTPSession::on-sending-rtcp
@@ -299,7 +293,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_SENDING_RTCP] =
       g_signal_new ("on-sending-rtcp", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_sending_rtcp),
-      accumulate_trues, NULL, g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 2,
+      accumulate_trues, NULL, NULL, G_TYPE_BOOLEAN, 2,
       GST_TYPE_BUFFER | G_SIGNAL_TYPE_STATIC_SCOPE, G_TYPE_BOOLEAN);
 
   /**
@@ -316,8 +310,8 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_APP_RTCP] =
       g_signal_new ("on-app-rtcp", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_app_rtcp),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 4,
-      G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, GST_TYPE_BUFFER);
+      NULL, NULL, NULL, G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_UINT,
+      G_TYPE_STRING, GST_TYPE_BUFFER);
 
   /**
    * RTPSession::on-feedback-rtcp:
@@ -335,8 +329,8 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_FEEDBACK_RTCP] =
       g_signal_new ("on-feedback-rtcp", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_feedback_rtcp),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 5, G_TYPE_UINT,
-      G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, GST_TYPE_BUFFER);
+      NULL, NULL, NULL, G_TYPE_NONE, 5, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT,
+      G_TYPE_UINT, GST_TYPE_BUFFER);
 
   /**
    * RTPSession::send-rtcp:
@@ -353,7 +347,7 @@ rtp_session_class_init (RTPSessionClass * klass)
       g_signal_new ("send-rtcp", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
       G_STRUCT_OFFSET (RTPSessionClass, send_rtcp), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_UINT64);
+      NULL, G_TYPE_NONE, 1, G_TYPE_UINT64);
 
   /**
    * RTPSession::send-rtcp-full:
@@ -375,7 +369,7 @@ rtp_session_class_init (RTPSessionClass * klass)
       g_signal_new ("send-rtcp-full", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
       G_STRUCT_OFFSET (RTPSessionClass, send_rtcp), NULL, NULL,
-      g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1, G_TYPE_UINT64);
+      NULL, G_TYPE_BOOLEAN, 1, G_TYPE_UINT64);
 
   /**
    * RTPSession::on-receiving-rtcp
@@ -390,7 +384,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_RECEIVING_RTCP] =
       g_signal_new ("on-receiving-rtcp", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_receiving_rtcp),
-      NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1,
+      NULL, NULL, NULL, G_TYPE_NONE, 1,
       GST_TYPE_BUFFER | G_SIGNAL_TYPE_STATIC_SCOPE);
 
   /**
@@ -405,8 +399,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_NEW_SENDER_SSRC] =
       g_signal_new ("on-new-sender-ssrc", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_new_sender_ssrc),
-      NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
-      RTP_TYPE_SOURCE);
+      NULL, NULL, NULL, G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
 
   /**
    * RTPSession::on-sender-ssrc-active:
@@ -420,7 +413,7 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_SENDER_SSRC_ACTIVE] =
       g_signal_new ("on-sender-ssrc-active", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass,
-          on_sender_ssrc_active), NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
+          on_sender_ssrc_active), NULL, NULL, NULL,
       G_TYPE_NONE, 1, RTP_TYPE_SOURCE);
 
   /**
@@ -452,14 +445,20 @@ rtp_session_class_init (RTPSessionClass * klass)
   rtp_session_signals[SIGNAL_ON_SENDING_NACKS] =
       g_signal_new ("on-sending-nacks", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RTPSessionClass, on_sending_nacks),
-      g_signal_accumulator_first_wins, NULL, g_cclosure_marshal_generic,
+      g_signal_accumulator_first_wins, NULL, NULL,
       G_TYPE_UINT, 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_ARRAY,
       GST_TYPE_BUFFER | G_SIGNAL_TYPE_STATIC_SCOPE);
 
   g_object_class_install_property (gobject_class, PROP_INTERNAL_SSRC,
       g_param_spec_uint ("internal-ssrc", "Internal SSRC",
           "The internal SSRC used for the session (deprecated)",
-          0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+          0, G_MAXUINT, 0,
+#ifndef TIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+          GST_PARAM_DOC_SHOW_DEFAULT));
+#else
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+#endif
 
   g_object_class_install_property (gobject_class, PROP_INTERNAL_SOURCE,
       g_param_spec_object ("internal-source", "Internal Source",
@@ -499,7 +498,12 @@ rtp_session_class_init (RTPSessionClass * klass)
   g_object_class_install_property (gobject_class, PROP_SDES,
       g_param_spec_boxed ("sdes", "SDES",
           "The SDES items of this session",
+#ifndef TIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK
+          GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
+          | GST_PARAM_DOC_SHOW_DEFAULT));
+#else
           GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+#endif
 
   g_object_class_install_property (gobject_class, PROP_NUM_SOURCES,
       g_param_spec_uint ("num-sources", "Num Sources",
@@ -512,13 +516,13 @@ rtp_session_class_init (RTPSessionClass * klass)
           DEFAULT_NUM_ACTIVE_SOURCES,
           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
   /**
-   * RTPSource::sources
+   * RTPSource:sources
    *
    * Get a GValue Array of all sources in the session.
    *
-   * <example>
-   * <title>Getting the #RTPSources of a session
-   * <programlisting>
+   * ## Getting the #RTPSources of a session
+   *
+   * ``` C
    * {
    *   GValueArray *arr;
    *   GValue *val;
@@ -534,8 +538,7 @@ rtp_session_class_init (RTPSessionClass * klass)
    *   }
    *   g_value_array_free (arr);
    * }
-   * </programlisting>
-   * </example>
+   * ```
    */
   g_object_class_install_property (gobject_class, PROP_SOURCES,
       g_param_spec_boxed ("sources", "Sources",
@@ -589,16 +592,16 @@ rtp_session_class_init (RTPSessionClass * klass)
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
-   * RTPSession::stats:
+   * RTPSession:stats:
    *
    * Various session statistics. This property returns a GstStructure
    * with name application/x-rtp-session-stats with the following fields:
    *
-   *  "rtx-drop-count"  G_TYPE_UINT   The number of retransmission events
+   * * "rtx-drop-count"  G_TYPE_UINT   The number of retransmission events
    *      dropped (due to bandwidth constraints)
-   *  "sent-nack-count" G_TYPE_UINT   Number of NACKs sent
-   *  "recv-nack-count" G_TYPE_UINT   Number of NACKs received
-   *  "source-stats"    G_TYPE_BOXED  GValueArray of #RTPSource::stats for all
+   *  "sent-nack-count" G_TYPE_UINT   Number of NACKs sent
+   *  "recv-nack-count" G_TYPE_UINT   Number of NACKs received
+   * *  "source-stats"    G_TYPE_BOXED  GValueArray of #RTPSource:stats for all
    *      RTP sources (Since 1.8)
    *
    * Since: 1.4
@@ -620,7 +623,7 @@ rtp_session_class_init (RTPSessionClass * klass)
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
-   * RTPSession::disable-sr-timestamp:
+   * RTPSession:disable-sr-timestamp:
    *
    * Whether sender reports should be timestamped.
    *
@@ -674,7 +677,7 @@ rtp_session_init (RTPSession * sess)
   sess->rtcp_rs_bandwidth = DEFAULT_RTCP_RS_BANDWIDTH;
 
   /* default UDP header length */
-  sess->header_len = 28;
+  sess->header_len = UDP_IP_HEADER_OVERHEAD;
   sess->mtu = DEFAULT_RTCP_MTU;
 
   sess->probation = DEFAULT_PROBATION;
@@ -717,6 +720,9 @@ rtp_session_init (RTPSession * sess)
   sess->timestamp_sender_reports = !DEFAULT_RTCP_DISABLE_SR_TIMESTAMP;
 
   sess->is_doing_ptp = TRUE;
+
+  sess->twcc = rtp_twcc_manager_new (sess->mtu);
+  sess->twcc_stats = rtp_twcc_stats_new ();
 }
 
 static void
@@ -738,6 +744,9 @@ rtp_session_finalize (GObject * object)
   for (i = 0; i < 1; i++)
     g_hash_table_destroy (sess->ssrcs[i]);
 
+  g_object_unref (sess->twcc);
+  rtp_twcc_stats_free (sess->twcc_stats);
+
   g_mutex_clear (&sess->lock);
 
   G_OBJECT_CLASS (rtp_session_parent_class)->finalize (object);
@@ -858,6 +867,7 @@ rtp_session_set_property (GObject * object, guint prop_id,
       break;
     case PROP_RTCP_MTU:
       sess->mtu = g_value_get_uint (value);
+      rtp_twcc_manager_set_mtu (sess->twcc, sess->mtu);
       break;
     case PROP_SDES:
       rtp_session_set_sdes_struct (sess, g_value_get_boxed (value));
@@ -1217,6 +1227,10 @@ rtp_session_set_callbacks (RTPSession * sess, RTPSessionCallbacks * callbacks,
     sess->callbacks.notify_nack = callbacks->notify_nack;
     sess->notify_nack_user_data = user_data;
   }
+  if (callbacks->notify_twcc) {
+    sess->callbacks.notify_twcc = callbacks->notify_twcc;
+    sess->notify_twcc_user_data = user_data;
+  }
   if (callbacks->reconfigure) {
     sess->callbacks.reconfigure = callbacks->reconfigure;
     sess->reconfigure_user_data = user_data;
@@ -1580,6 +1594,27 @@ rtp_session_add_conflicting_address (RTPSession * sess,
       add_conflicting_address (sess->conflicting_addresses, address, time);
 }
 
+static void
+rtp_session_have_conflict (RTPSession * sess, RTPSource * source,
+    GSocketAddress * address, GstClockTime current_time)
+{
+  guint32 ssrc = rtp_source_get_ssrc (source);
+
+  /* Its a new collision, lets change our SSRC */
+  rtp_session_add_conflicting_address (sess, address, current_time);
+
+  /* mark the source BYE */
+  rtp_source_mark_bye (source, "SSRC Collision");
+  /* if we were suggesting this SSRC, change to something else */
+  if (sess->suggested_ssrc == ssrc) {
+    sess->suggested_ssrc = rtp_session_create_new_ssrc (sess);
+    sess->internal_ssrc_set = TRUE;
+  }
+
+  on_ssrc_collision (sess, source);
+
+  rtp_session_schedule_bye_locked (sess, current_time);
+}
 
 static gboolean
 check_collision (RTPSession * sess, RTPSource * source,
@@ -1673,22 +1708,11 @@ check_collision (RTPSession * sess, RTPSource * source,
        */
       GST_DEBUG ("Our packets are being looped back to us, dropping");
     } else {
-      /* Its a new collision, lets change our SSRC */
-      rtp_session_add_conflicting_address (sess, pinfo->address,
-          pinfo->current_time);
-
-      GST_DEBUG ("Collision for SSRC %x", ssrc);
-      /* mark the source BYE */
-      rtp_source_mark_bye (source, "SSRC Collision");
-      /* if we were suggesting this SSRC, change to something else */
-      if (sess->suggested_ssrc == ssrc) {
-        sess->suggested_ssrc = rtp_session_create_new_ssrc (sess);
-        sess->internal_ssrc_set = TRUE;
-      }
+      GST_DEBUG ("Collision for SSRC %x from new incoming packet,"
+          " change our sender ssrc", ssrc);
 
-      on_ssrc_collision (sess, source);
-
-      rtp_session_schedule_bye_locked (sess, pinfo->current_time);
+      rtp_session_have_conflict (sess, source, pinfo->address,
+          pinfo->current_time);
     }
   }
 
@@ -2068,10 +2092,15 @@ update_packet (GstBuffer ** buffer, guint idx, RTPPacketInfo * pinfo)
       pinfo->seqnum = gst_rtp_buffer_get_seq (&rtp);
       pinfo->pt = gst_rtp_buffer_get_payload_type (&rtp);
       pinfo->rtptime = gst_rtp_buffer_get_timestamp (&rtp);
+      pinfo->marker = gst_rtp_buffer_get_marker (&rtp);
       /* copy available csrc */
       pinfo->csrc_count = gst_rtp_buffer_get_csrc_count (&rtp);
       for (i = 0; i < pinfo->csrc_count; i++)
         pinfo->csrcs[i] = gst_rtp_buffer_get_csrc (&rtp, i);
+
+      /* RTP header extensions */
+      pinfo->header_ext = gst_rtp_buffer_get_extension_bytes (&rtp,
+          &pinfo->header_ext_bit_pattern);
     }
     gst_rtp_buffer_unmap (&rtp);
   }
@@ -2120,6 +2149,7 @@ update_packet_info (RTPSession * sess, RTPPacketInfo * pinfo,
   pinfo->bytes = 0;
   pinfo->payload_len = 0;
   pinfo->packets = 0;
+  pinfo->marker = FALSE;
 
   if (is_list) {
     GstBufferList *list = GST_BUFFER_LIST_CAST (data);
@@ -2130,6 +2160,7 @@ update_packet_info (RTPSession * sess, RTPPacketInfo * pinfo,
     GstBuffer *buffer = GST_BUFFER_CAST (data);
     res = update_packet (&buffer, 0, pinfo);
   }
+
   return res;
 }
 
@@ -2142,6 +2173,24 @@ clean_packet_info (RTPPacketInfo * pinfo)
     gst_mini_object_unref (pinfo->data);
     pinfo->data = NULL;
   }
+  if (pinfo->header_ext)
+    g_bytes_unref (pinfo->header_ext);
+}
+
+static gint32
+packet_info_get_twcc_seqnum (RTPPacketInfo * pinfo, guint8 ext_id)
+{
+  gint32 val = -1;
+  gpointer data;
+  guint size;
+
+  if (pinfo->header_ext &&
+      gst_rtp_buffer_get_extension_onebyte_header_from_bytes (pinfo->header_ext,
+          pinfo->header_ext_bit_pattern, ext_id, 0, &data, &size)) {
+    if (size == 2)
+      val = GST_READ_UINT16_BE (data);
+  }
+  return val;
 }
 
 static gboolean
@@ -2166,6 +2215,30 @@ source_update_active (RTPSession * sess, RTPSource * source,
   return TRUE;
 }
 
+static void
+process_twcc_packet (RTPSession * sess, RTPPacketInfo * pinfo)
+{
+  gint32 twcc_seqnum;
+
+  if (sess->twcc_recv_ext_id == 0)
+    return;
+
+  twcc_seqnum = packet_info_get_twcc_seqnum (pinfo, sess->twcc_recv_ext_id);
+  if (twcc_seqnum == -1)
+    return;
+
+  if (rtp_twcc_manager_recv_packet (sess->twcc, twcc_seqnum, pinfo)) {
+    RTP_SESSION_UNLOCK (sess);
+
+    /* TODO: find a better rational for this number, and possibly tune it based
+       on factors like framerate / bandwidth etc */
+    if (!rtp_session_send_rtcp (sess, 100 * GST_MSECOND)) {
+      GST_INFO ("Could not schedule TWCC straight away");
+    }
+    RTP_SESSION_LOCK (sess);
+  }
+}
+
 static gboolean
 source_update_sender (RTPSession * sess, RTPSource * source,
     gboolean prevsender)
@@ -2245,6 +2318,7 @@ rtp_session_process_rtp (RTPSession * sess, GstBuffer * buffer,
 
   /* let source process the packet */
   result = rtp_source_process_rtp (source, &pinfo);
+  process_twcc_packet (sess, &pinfo);
 
   /* source became active */
   if (source_update_active (sess, source, prevactive))
@@ -2803,6 +2877,35 @@ rtp_session_process_nack (RTPSession * sess, guint32 sender_ssrc,
 }
 
 static void
+rtp_session_process_twcc (RTPSession * sess, guint32 sender_ssrc,
+    guint32 media_ssrc, guint8 * fci_data, guint fci_length)
+{
+  GArray *twcc_packets;
+  GstStructure *twcc_packets_s;
+  GstStructure *twcc_stats_s;
+
+  twcc_packets = rtp_twcc_manager_parse_fci (sess->twcc,
+      fci_data, fci_length * sizeof (guint32));
+  if (twcc_packets == NULL)
+    return;
+
+  twcc_packets_s = rtp_twcc_stats_get_packets_structure (twcc_packets);
+  twcc_stats_s =
+      rtp_twcc_stats_process_packets (sess->twcc_stats, twcc_packets);
+
+  GST_DEBUG_OBJECT (sess, "Parsed TWCC: %" GST_PTR_FORMAT, twcc_packets_s);
+  GST_INFO_OBJECT (sess, "Current TWCC stats %" GST_PTR_FORMAT, twcc_stats_s);
+
+  g_array_unref (twcc_packets);
+
+  RTP_SESSION_UNLOCK (sess);
+  if (sess->callbacks.notify_twcc)
+    sess->callbacks.notify_twcc (sess, twcc_packets_s, twcc_stats_s,
+        sess->notify_twcc_user_data);
+  RTP_SESSION_LOCK (sess);
+}
+
+static void
 rtp_session_process_feedback (RTPSession * sess, GstRTCPPacket * packet,
     RTPPacketInfo * pinfo, GstClockTime current_time)
 {
@@ -2863,7 +2966,9 @@ rtp_session_process_feedback (RTPSession * sess, GstRTCPPacket * packet,
 
   if ((src && src->internal) ||
       /* PSFB FIR puts the media ssrc inside the FCI */
-      (type == GST_RTCP_TYPE_PSFB && fbtype == GST_RTCP_PSFB_TYPE_FIR)) {
+      (type == GST_RTCP_TYPE_PSFB && fbtype == GST_RTCP_PSFB_TYPE_FIR) ||
+      /* TWCC is for all sources, so a single media-ssrc is not enough */
+      (type == GST_RTCP_TYPE_RTPFB && fbtype == GST_RTCP_RTPFB_TYPE_TWCC)) {
     switch (type) {
       case GST_RTCP_TYPE_PSFB:
         switch (fbtype) {
@@ -2891,6 +2996,10 @@ rtp_session_process_feedback (RTPSession * sess, GstRTCPPacket * packet,
             rtp_session_process_nack (sess, sender_ssrc, media_ssrc,
                 fci_data, fci_length, current_time);
             break;
+          case GST_RTCP_RTPFB_TYPE_TWCC:
+            rtp_session_process_twcc (sess, sender_ssrc, media_ssrc,
+                fci_data, fci_length);
+            break;
           default:
             break;
         }
@@ -3022,6 +3131,29 @@ invalid_packet:
   }
 }
 
+static guint8
+_get_extmap_id_for_attribute (const GstStructure * s, const gchar * ext_name)
+{
+  guint i;
+  guint8 extmap_id = 0;
+  guint n_fields = gst_structure_n_fields (s);
+
+  for (i = 0; i < n_fields; i++) {
+    const gchar *field_name = gst_structure_nth_field_name (s, i);
+    if (g_str_has_prefix (field_name, "extmap-")) {
+      const gchar *str = gst_structure_get_string (s, field_name);
+      if (str && g_strcmp0 (str, ext_name) == 0) {
+        gint64 id = g_ascii_strtoll (field_name + 7, NULL, 10);
+        if (id > 0 && id < 15) {
+          extmap_id = id;
+          break;
+        }
+      }
+    }
+  }
+  return extmap_id;
+}
+
 /**
  * rtp_session_update_send_caps:
  * @sess: an #RTPSession
@@ -3076,8 +3208,30 @@ rtp_session_update_send_caps (RTPSession * sess, GstCaps * caps)
   } else {
     sess->internal_ssrc_from_caps_or_property = FALSE;
   }
+
+  sess->twcc_send_ext_id = _get_extmap_id_for_attribute (s, TWCC_EXTMAP_STR);
+  if (sess->twcc_send_ext_id > 0) {
+    GST_INFO ("TWCC enabled for send using extension id: %u",
+        sess->twcc_send_ext_id);
+  }
+}
+
+static void
+send_twcc_packet (RTPSession * sess, RTPPacketInfo * pinfo)
+{
+  gint32 twcc_seqnum;
+
+  if (sess->twcc_send_ext_id == 0)
+    return;
+
+  twcc_seqnum = packet_info_get_twcc_seqnum (pinfo, sess->twcc_send_ext_id);
+  if (twcc_seqnum == -1)
+    return;
+
+  rtp_twcc_manager_send_packet (sess->twcc, twcc_seqnum, pinfo);
 }
 
+
 /**
  * rtp_session_send_rtp:
  * @sess: an #RTPSession
@@ -3112,13 +3266,37 @@ rtp_session_send_rtp (RTPSession * sess, gpointer data, gboolean is_list,
           current_time, running_time, -1))
     goto invalid_packet;
 
+  send_twcc_packet (sess, &pinfo);
+
   source = obtain_internal_source (sess, pinfo.ssrc, &created, current_time);
   if (created)
     on_new_sender_ssrc (sess, source);
 
-  if (!source->internal)
-    /* FIXME: Send GstRTPCollision upstream  */
-    goto collision;
+  if (!source->internal) {
+    GSocketAddress *from;
+
+    if (source->rtp_from)
+      from = source->rtp_from;
+    else
+      from = source->rtcp_from;
+    if (from) {
+      if (rtp_session_find_conflicting_address (sess, from, current_time)) {
+        /* Its a known conflict, its probably a loop, not a collision
+         * lets just drop the incoming packet
+         */
+        GST_LOG ("Our packets are being looped back to us, ignoring collision");
+      } else {
+        GST_DEBUG ("Collision for SSRC %x, change our sender ssrc", pinfo.ssrc);
+
+        rtp_session_have_conflict (sess, source, from, current_time);
+
+        goto collision;
+      }
+    } else {
+      GST_LOG ("Ignoring collision on sent SSRC %x because remote source"
+          " doesn't have an address", pinfo.ssrc);
+    }
+  }
 
   prevsender = RTP_SOURCE_IS_SENDER (source);
   oldrate = source->bitrate;
@@ -3147,7 +3325,7 @@ invalid_packet:
 collision:
   {
     g_object_unref (source);
-    gst_mini_object_unref (GST_MINI_OBJECT_CAST (data));
+    clean_packet_info (&pinfo);
     RTP_SESSION_UNLOCK (sess);
     GST_WARNING ("non-internal source with same ssrc %08x, drop packet",
         pinfo.ssrc);
@@ -4094,6 +4272,37 @@ remove_closing_sources (const gchar * key, RTPSource * source,
 }
 
 static void
+generate_twcc (const gchar * key, RTPSource * source, ReportData * data)
+{
+  RTPSession *sess = data->sess;
+  GstBuffer *buf;
+
+  /* only generate RTCP for active internal sources */
+  if (!source->internal || source->sent_bye)
+    return;
+
+  /* ignore other sources when we do the timeout after a scheduled BYE */
+  if (sess->scheduled_bye && !source->marked_bye)
+    return;
+
+  /* skip if RTCP is disabled */
+  if (source->disable_rtcp) {
+    GST_DEBUG ("source %08x has RTCP disabled", source->ssrc);
+    return;
+  }
+
+  while ((buf = rtp_twcc_manager_get_feedback (sess->twcc, source->ssrc))) {
+    ReportOutput *output = g_slice_new (ReportOutput);
+    output->source = g_object_ref (source);
+    output->is_bye = FALSE;
+    output->buffer = buf;
+    /* queue the RTCP packet to push later */
+    g_queue_push_tail (&data->output, output);
+  }
+}
+
+
+static void
 generate_rtcp (const gchar * key, RTPSource * source, ReportData * data)
 {
   RTPSession *sess = data->sess;
@@ -4306,7 +4515,7 @@ rtp_session_on_timeout (RTPSession * sess, GstClockTime current_time,
   if (!is_rtcp_time (sess, current_time, &data))
     goto done;
 
-  /* check if all the buffers are empty afer generation */
+  /* check if all the buffers are empty after generation */
   all_empty = TRUE;
 
   GST_DEBUG
@@ -4317,6 +4526,9 @@ rtp_session_on_timeout (RTPSession * sess, GstClockTime current_time,
   g_hash_table_foreach (sess->ssrcs[sess->mask_idx],
       (GHFunc) generate_rtcp, &data);
 
+  g_hash_table_foreach (sess->ssrcs[sess->mask_idx],
+      (GHFunc) generate_twcc, &data);
+
   /* update the generation for all the sources that have been reported */
   g_hash_table_foreach (sess->ssrcs[sess->mask_idx],
       (GHFunc) update_generation, &data);
@@ -4700,3 +4912,22 @@ no_source:
     return FALSE;
   }
 }
+
+/**
+ * rtp_session_update_recv_caps_structure:
+ * @sess: an #RTPSession
+ * @s: a #GstStructure from a #GstCaps
+ *
+ * Update the caps of the receiver in the rtp session.
+ */
+void
+rtp_session_update_recv_caps_structure (RTPSession * sess,
+    const GstStructure * s)
+{
+  guint8 ext_id = _get_extmap_id_for_attribute (s, TWCC_EXTMAP_STR);
+  if (ext_id > 0) {
+    sess->twcc_recv_ext_id = ext_id;
+    GST_INFO ("TWCC enabled for recv using extension id: %u",
+        sess->twcc_recv_ext_id);
+  }
+}
index 7ab229a..949fcc4 100644 (file)
@@ -23,6 +23,7 @@
 #include <gst/gst.h>
 
 #include "rtpsource.h"
+#include "rtptwcc.h"
 
 typedef struct _RTPSession RTPSession;
 typedef struct _RTPSessionClass RTPSessionClass;
@@ -157,6 +158,15 @@ typedef void (*RTPSessionNotifyNACK) (RTPSession *sess,
     guint16 seqnum, guint16 blp, guint32 ssrc, gpointer user_data);
 
 /**
+ * RTPSessionNotifyTWCC:
+ * @user_data: user data specified when registering
+ *
+ * Notifies of Transport-wide congestion control packets and stats.
+ */
+typedef void (*RTPSessionNotifyTWCC) (RTPSession *sess,
+    GstStructure * twcc_packets, GstStructure * twcc_stats, gpointer user_data);
+
+/**
  * RTPSessionReconfigure:
  * @sess: an #RTPSession
  * @user_data: user data specified when registering
@@ -186,6 +196,7 @@ typedef void (*RTPSessionNotifyEarlyRTCP) (RTPSession *sess,
  * @RTPSessionRequestKeyUnit: callback for requesting a new key unit
  * @RTPSessionRequestTime: callback for requesting the current time
  * @RTPSessionNotifyNACK: callback for notifying NACK
+ * @RTPSessionNotifyTWCC: callback for notifying TWCC
  * @RTPSessionReconfigure: callback for requesting reconfiguration
  * @RTPSessionNotifyEarlyRTCP: callback for notifying early RTCP
  *
@@ -203,6 +214,7 @@ typedef struct {
   RTPSessionRequestKeyUnit request_key_unit;
   RTPSessionRequestTime request_time;
   RTPSessionNotifyNACK  notify_nack;
+  RTPSessionNotifyTWCC  notify_twcc;
   RTPSessionReconfigure reconfigure;
   RTPSessionNotifyEarlyRTCP notify_early_rtcp;
 } RTPSessionCallbacks;
@@ -280,6 +292,7 @@ struct _RTPSession {
   gpointer              request_key_unit_user_data;
   gpointer              request_time_user_data;
   gpointer              notify_nack_user_data;
+  gpointer              notify_twcc_user_data;
   gpointer              reconfigure_user_data;
   gpointer              notify_early_rtcp_user_data;
 
@@ -295,6 +308,12 @@ struct _RTPSession {
   GList         *conflicting_addresses;
 
   gboolean timestamp_sender_reports;
+
+  /* Transport-wide cc-extension */
+  RTPTWCCManager *twcc;
+  RTPTWCCStats *twcc_stats;
+  guint8 twcc_recv_ext_id;
+  guint8 twcc_send_ext_id;
 };
 
 /**
@@ -404,7 +423,7 @@ GstClockTime    rtp_session_next_timeout           (RTPSession *sess, GstClockTi
 GstFlowReturn   rtp_session_on_timeout             (RTPSession *sess, GstClockTime current_time,
                                                     guint64 ntpnstime, GstClockTime running_time);
 
-/* request the transmittion of an early RTCP packet */
+/* request the transmission of an early RTCP packet */
 gboolean        rtp_session_request_early_rtcp     (RTPSession * sess, GstClockTime current_time,
                                                     GstClockTime max_delay);
 
@@ -418,5 +437,7 @@ gboolean        rtp_session_request_nack           (RTPSession * sess,
                                                     guint16 seqnum,
                                                     GstClockTime max_delay);
 
+void            rtp_session_update_recv_caps_structure (RTPSession * sess, const GstStructure * s);
+
 
 #endif /* __RTP_SESSION_H__ */
index 7079e16..12aa125 100644 (file)
@@ -105,7 +105,7 @@ rtp_source_class_init (RTPSourceClass * klass)
           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   /**
-   * RTPSource::sdes
+   * RTPSource:sdes
    *
    * The current SDES items of the source. Returns a structure with name
    * application/x-rtp-source-sdes and may contain the following fields:
@@ -127,7 +127,7 @@ rtp_source_class_init (RTPSourceClass * klass)
           GST_TYPE_STRUCTURE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   /**
-   * RTPSource::stats
+   * RTPSource:stats
    *
    * This property returns a GstStructure named application/x-rtp-source-stats with
    * fields useful for statistics and diagnostics.
@@ -144,76 +144,77 @@ rtp_source_class_init (RTPSourceClass * klass)
    *
    * The following fields are always present.
    *
-   *  "ssrc"         G_TYPE_UINT     the SSRC of this source
-   *  "internal"     G_TYPE_BOOLEAN  this source is a source of the session
-   *  "validated"    G_TYPE_BOOLEAN  the source is validated
-   *  "received-bye" G_TYPE_BOOLEAN  we received a BYE from this source
-   *  "is-csrc"      G_TYPE_BOOLEAN  this source was found as CSRC
-   *  "is-sender"    G_TYPE_BOOLEAN  this source is a sender
-   *  "seqnum-base"  G_TYPE_INT      first seqnum if known
-   *  "clock-rate"   G_TYPE_INT      the clock rate of the media
+   * * "ssrc"         G_TYPE_UINT     the SSRC of this source
+   * * "internal"     G_TYPE_BOOLEAN  this source is a source of the session
+   * * "validated"    G_TYPE_BOOLEAN  the source is validated
+   * * "received-bye" G_TYPE_BOOLEAN  we received a BYE from this source
+   * * "is-csrc"      G_TYPE_BOOLEAN  this source was found as CSRC
+   * * "is-sender"    G_TYPE_BOOLEAN  this source is a sender
+   * * "seqnum-base"  G_TYPE_INT      first seqnum if known
+   * * "clock-rate"   G_TYPE_INT      the clock rate of the media
    *
    * The following fields are only present when known.
    *
-   *  "rtp-from"     G_TYPE_STRING   where we received the last RTP packet from
-   *  "rtcp-from"    G_TYPE_STRING   where we received the last RTCP packet from
+   * * "rtp-from"     G_TYPE_STRING   where we received the last RTP packet from
+   * * "rtcp-from"    G_TYPE_STRING   where we received the last RTCP packet from
    *
    * The following fields make sense for internal sources and will only increase
    * when "is-sender" is TRUE.
    *
-   *  "octets-sent"  G_TYPE_UINT64   number of bytes we sent
-   *  "packets-sent" G_TYPE_UINT64   number of packets we sent
+   * * "octets-sent"  G_TYPE_UINT64   number of payload bytes we sent
+   * * "packets-sent" G_TYPE_UINT64   number of packets we sent
    *
    * The following fields make sense for non-internal sources and will only
    * increase when "is-sender" is TRUE.
    *
-   *  "octets-received"  G_TYPE_UINT64  total number of bytes received
-   *  "packets-received" G_TYPE_UINT64  total number of packets received
+   * * "octets-received"  G_TYPE_UINT64  total number of payload bytes received
+   * * "packets-received" G_TYPE_UINT64  total number of packets received
+   * * "bytes-received"   G_TYPE_UINT64  total number of bytes received including lower level headers overhead
    *
    * Following fields are updated when "is-sender" is TRUE.
    *
-   *  "bitrate"      G_TYPE_UINT64   bitrate in bits per second
-   *  "jitter"       G_TYPE_UINT     estimated jitter (in clock rate units)
-   *  "packets-lost" G_TYPE_INT      estimated amount of packets lost
+   * * "bitrate"      G_TYPE_UINT64   bitrate in bits per second
+   * * "jitter"       G_TYPE_UINT     estimated jitter (in clock rate units)
+   * * "packets-lost" G_TYPE_INT      estimated amount of packets lost
    *
    * The last SR report this source sent. This only updates when "is-sender" is
    * TRUE.
    *
-   *  "have-sr"         G_TYPE_BOOLEAN  the source has sent SR
-   *  "sr-ntptime"      G_TYPE_UINT64   NTP time of SR (in NTP Timestamp Format, 32.32 fixed point)
-   *  "sr-rtptime"      G_TYPE_UINT     RTP time of SR (in clock rate units)
-   *  "sr-octet-count"  G_TYPE_UINT     the number of bytes in the SR
-   *  "sr-packet-count" G_TYPE_UINT     the number of packets in the SR
+   * * "have-sr"         G_TYPE_BOOLEAN  the source has sent SR
+   * * "sr-ntptime"      G_TYPE_UINT64   NTP time of SR (in NTP Timestamp Format, 32.32 fixed point)
+   * * "sr-rtptime"      G_TYPE_UINT     RTP time of SR (in clock rate units)
+   * * "sr-octet-count"  G_TYPE_UINT     the number of bytes in the SR
+   * * "sr-packet-count" G_TYPE_UINT     the number of packets in the SR
    *
    * The following fields are only present for non-internal sources and
    * represent the content of the last RB packet that was sent to this source.
    * These values are only updated when the source is sending.
    *
-   *  "sent-rb"               G_TYPE_BOOLEAN  we have sent an RB
-   *  "sent-rb-fractionlost"  G_TYPE_UINT     calculated lost 8-bit fraction
-   *  "sent-rb-packetslost"   G_TYPE_INT      lost packets
-   *  "sent-rb-exthighestseq" G_TYPE_UINT     last seen seqnum
-   *  "sent-rb-jitter"        G_TYPE_UINT     jitter (in clock rate units)
-   *  "sent-rb-lsr"           G_TYPE_UINT     last SR time (seconds in NTP Short Format, 16.16 fixed point)
-   *  "sent-rb-dlsr"          G_TYPE_UINT     delay since last SR (seconds in NTP Short Format, 16.16 fixed point)
+   * * "sent-rb"               G_TYPE_BOOLEAN  we have sent an RB
+   * * "sent-rb-fractionlost"  G_TYPE_UINT     calculated lost 8-bit fraction
+   * * "sent-rb-packetslost"   G_TYPE_INT      lost packets
+   * * "sent-rb-exthighestseq" G_TYPE_UINT     last seen seqnum
+   * * "sent-rb-jitter"        G_TYPE_UINT     jitter (in clock rate units)
+   * * "sent-rb-lsr"           G_TYPE_UINT     last SR time (seconds in NTP Short Format, 16.16 fixed point)
+   * * "sent-rb-dlsr"          G_TYPE_UINT     delay since last SR (seconds in NTP Short Format, 16.16 fixed point)
    *
    * The following fields are only present for non-internal sources and
    * represents the last RB that this source sent. This is only updated
    * when the source is receiving data and sending RB blocks.
    *
-   *  "have-rb"          G_TYPE_BOOLEAN  the source has sent RB
-   *  "rb-fractionlost"  G_TYPE_UINT     lost 8-bit fraction
-   *  "rb-packetslost"   G_TYPE_INT      lost packets
-   *  "rb-exthighestseq" G_TYPE_UINT     highest received seqnum
-   *  "rb-jitter"        G_TYPE_UINT     reception jitter (in clock rate units)
-   *  "rb-lsr"           G_TYPE_UINT     last SR time (seconds in NTP Short Format, 16.16 fixed point)
-   *  "rb-dlsr"          G_TYPE_UINT     delay since last SR (seconds in NTP Short Format, 16.16 fixed point)
+   * * "have-rb"          G_TYPE_BOOLEAN  the source has sent RB
+   * * "rb-fractionlost"  G_TYPE_UINT     lost 8-bit fraction
+   * * "rb-packetslost"   G_TYPE_INT      lost packets
+   * * "rb-exthighestseq" G_TYPE_UINT     highest received seqnum
+   * * "rb-jitter"        G_TYPE_UINT     reception jitter (in clock rate units)
+   * * "rb-lsr"           G_TYPE_UINT     last SR time (seconds in NTP Short Format, 16.16 fixed point)
+   * * "rb-dlsr"          G_TYPE_UINT     delay since last SR (seconds in NTP Short Format, 16.16 fixed point)
    *
    * The round trip of this source is calculated from the last RB
    * values and the reception time of the last RB packet. It is only present for
    * non-internal sources.
    *
-   *  "rb-round-trip"    G_TYPE_UINT     the round-trip time (seconds in NTP Short Format, 16.16 fixed point)
+   * * "rb-round-trip"    G_TYPE_UINT     the round-trip time (seconds in NTP Short Format, 16.16 fixed point)
    *
    */
   g_object_class_install_property (gobject_class, PROP_STATS,
@@ -240,7 +241,7 @@ rtp_source_class_init (RTPSourceClass * klass)
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   /**
-   * RTPSession::disable-rtcp:
+   * RTPSource:disable-rtcp:
    *
    * Allow disabling the sending of RTCP packets for this source.
    */
@@ -415,6 +416,7 @@ rtp_source_create_stats (RTPSource * src)
       "packets-sent", G_TYPE_UINT64, src->stats.packets_sent,
       "octets-received", G_TYPE_UINT64, src->stats.octets_received,
       "packets-received", G_TYPE_UINT64, src->stats.packets_received,
+      "bytes-received", G_TYPE_UINT64, src->stats.bytes_received,
       "bitrate", G_TYPE_UINT64, src->bitrate,
       "packets-lost", G_TYPE_INT,
       (gint) rtp_stats_get_packets_lost (&src->stats), "jitter", G_TYPE_UINT,
@@ -424,7 +426,9 @@ rtp_source_create_stats (RTPSource * src)
       "sent-fir-count", G_TYPE_UINT, src->stats.sent_fir_count,
       "recv-fir-count", G_TYPE_UINT, src->stats.recv_fir_count,
       "sent-nack-count", G_TYPE_UINT, src->stats.sent_nack_count,
-      "recv-nack-count", G_TYPE_UINT, src->stats.recv_nack_count, NULL);
+      "recv-nack-count", G_TYPE_UINT, src->stats.recv_nack_count,
+      "recv-packet-rate", G_TYPE_UINT,
+      gst_rtp_packet_rate_ctx_get (&src->packet_rate_ctx), NULL);
 
   /* get the last SR. */
   have_sr = rtp_source_get_last_sr (src, &time, &ntptime, &rtptime,
@@ -916,8 +920,8 @@ push_packet (RTPSource * src, GstBuffer * buffer)
   return ret;
 }
 
-static gint
-get_clock_rate (RTPSource * src, guint8 payload)
+static void
+fetch_clock_rate_from_payload (RTPSource * src, guint8 payload)
 {
   if (src->payload == -1) {
     /* first payload received, nothing was in the caps, lock on to this payload */
@@ -942,7 +946,6 @@ get_clock_rate (RTPSource * src, guint8 payload)
     src->clock_rate = clock_rate;
     gst_rtp_packet_rate_ctx_reset (&src->packet_rate_ctx, clock_rate);
   }
-  return src->clock_rate;
 }
 
 /* Jitter is the variation in the delay of received packets in a flow. It is
@@ -956,26 +959,23 @@ calculate_jitter (RTPSource * src, RTPPacketInfo * pinfo)
   GstClockTime running_time;
   guint32 rtparrival, transit, rtptime;
   gint32 diff;
-  gint clock_rate;
-  guint8 pt;
 
   /* get arrival time */
   if ((running_time = pinfo->running_time) == GST_CLOCK_TIME_NONE)
     goto no_time;
 
-  pt = pinfo->pt;
+  GST_LOG ("SSRC %08x got payload %d", src->ssrc, pinfo->pt);
 
-  GST_LOG ("SSRC %08x got payload %d", src->ssrc, pt);
-
-  /* get clockrate */
-  if ((clock_rate = get_clock_rate (src, pt)) == -1)
+  /* check if clock-rate is valid */
+  if (src->clock_rate == -1)
     goto no_clock_rate;
 
   rtptime = pinfo->rtptime;
 
   /* convert arrival time to RTP timestamp units, truncate to 32 bits, we don't
    * care about the absolute value, just the difference. */
-  rtparrival = gst_util_uint64_scale_int (running_time, clock_rate, GST_SECOND);
+  rtparrival =
+      gst_util_uint64_scale_int (running_time, src->clock_rate, GST_SECOND);
 
   /* transit time is difference with RTP timestamp */
   transit = rtparrival - rtptime;
@@ -998,7 +998,7 @@ calculate_jitter (RTPSource * src, RTPPacketInfo * pinfo)
   src->stats.last_rtptime = rtparrival;
 
   GST_LOG ("rtparrival %u, rtptime %u, clock-rate %d, diff %d, jitter: %f",
-      rtparrival, rtptime, clock_rate, diff, (src->stats.jitter) / 16.0);
+      rtparrival, rtptime, src->clock_rate, diff, (src->stats.jitter) / 16.0);
 
   return;
 
@@ -1010,12 +1010,34 @@ no_time:
   }
 no_clock_rate:
   {
-    GST_WARNING ("cannot get clock-rate for pt %d", pt);
+    GST_WARNING ("cannot get clock-rate for pt %d", pinfo->pt);
     return;
   }
 }
 
 static void
+update_queued_stats (GstBuffer * buffer, RTPSource * src)
+{
+  GstRTPBuffer rtp = { NULL };
+  guint payload_len;
+  guint64 bytes;
+
+  /* no need to check the return value, a queued packet is a valid RTP one */
+  gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp);
+  payload_len = gst_rtp_buffer_get_payload_len (&rtp);
+
+  bytes = gst_buffer_get_size (buffer) + UDP_IP_HEADER_OVERHEAD;
+
+  src->stats.octets_received += payload_len;
+  src->stats.bytes_received += bytes;
+  src->stats.packets_received++;
+  /* for the bitrate estimation consider all lower level headers */
+  src->bytes_received += bytes;
+
+  gst_rtp_buffer_unmap (&rtp);
+}
+
+static void
 init_seq (RTPSource * src, guint16 seq)
 {
   src->stats.base_seq = seq;
@@ -1030,6 +1052,9 @@ init_seq (RTPSource * src, guint16 seq)
   src->stats.recv_pli_count = 0;
   src->stats.recv_fir_count = 0;
 
+  /* if there are queued packets, consider them too in the stats */
+  g_queue_foreach (src->packets, (GFunc) update_queued_stats, src);
+
   GST_DEBUG ("base_seq %d", seq);
 }
 
@@ -1188,8 +1213,8 @@ update_receiver_stats (RTPSource * src, RTPPacketInfo * pinfo,
   src->stats.octets_received += pinfo->payload_len;
   src->stats.bytes_received += pinfo->bytes;
   src->stats.packets_received += pinfo->packets;
-  /* for the bitrate estimation */
-  src->bytes_received += pinfo->payload_len;
+  /* for the bitrate estimation consider all lower level headers */
+  src->bytes_received += pinfo->bytes;
 
   GST_LOG ("seq %u, PC: %" G_GUINT64_FORMAT ", OC: %" G_GUINT64_FORMAT,
       seqnr, src->stats.packets_received, src->stats.octets_received);
@@ -1236,6 +1261,8 @@ rtp_source_process_rtp (RTPSource * src, RTPPacketInfo * pinfo)
   g_return_val_if_fail (RTP_IS_SOURCE (src), GST_FLOW_ERROR);
   g_return_val_if_fail (pinfo != NULL, GST_FLOW_ERROR);
 
+  fetch_clock_rate_from_payload (src, pinfo->pt);
+
   if (!update_receiver_stats (src, pinfo, TRUE))
     return GST_FLOW_OK;
 
@@ -1261,7 +1288,7 @@ rtp_source_process_rtp (RTPSource * src, RTPPacketInfo * pinfo)
  * @reason: the reason for leaving
  *
  * Mark @src in the BYE state. This can happen when the source wants to
- * leave the sesssion or when a BYE packets has been received.
+ * leave the session or when a BYE packets has been received.
  *
  * This will make the source inactive.
  */
@@ -1319,7 +1346,7 @@ rtp_source_send_rtp (RTPSource * src, RTPPacketInfo * pinfo)
   /* update stats for the SR */
   src->stats.packets_sent += pinfo->packets;
   src->stats.octets_sent += pinfo->payload_len;
-  src->bytes_sent += pinfo->payload_len;
+  src->bytes_sent += pinfo->bytes;
 
   running_time = pinfo->running_time;
 
@@ -1524,7 +1551,7 @@ rtp_source_get_new_sr (RTPSource * src, guint64 ntpnstime,
   if (src->clock_rate == -1 && src->pt_set) {
     GST_INFO ("no clock-rate, getting for pt %u and SSRC %u", src->pt,
         src->ssrc);
-    get_clock_rate (src, src->pt);
+    fetch_clock_rate_from_payload (src, src->pt);
   }
 
   if (src->clock_rate != -1) {
index 73bd189..45fff37 100644 (file)
  * Boston, MA 02110-1301, USA.
  */
 
+#define GLIB_DISABLE_DEPRECATION_WARNINGS
+
 #include "rtpstats.h"
+#include "rtptwcc.h"
 
 void
 gst_rtp_packet_rate_ctx_reset (RTPPacketRateCtx * ctx, gint32 clock_rate)
@@ -37,6 +40,7 @@ gst_rtp_packet_rate_ctx_update (RTPPacketRateCtx * ctx, guint16 seqnum,
   guint64 new_ts, diff_ts;
   gint diff_seqnum;
   gint32 new_packet_rate;
+  gint32 base;
 
   if (ctx->clock_rate <= 0) {
     return ctx->avg_packet_rate;
@@ -46,16 +50,22 @@ gst_rtp_packet_rate_ctx_update (RTPPacketRateCtx * ctx, guint16 seqnum,
   gst_rtp_buffer_ext_timestamp (&new_ts, ts);
 
   if (!ctx->probed) {
-    ctx->last_seqnum = seqnum;
-    ctx->last_ts = new_ts;
     ctx->probed = TRUE;
-    return ctx->avg_packet_rate;
+    goto done_but_save;
   }
 
   diff_seqnum = gst_rtp_buffer_compare_seqnum (ctx->last_seqnum, seqnum);
-  if (diff_seqnum <= 0 || new_ts <= ctx->last_ts) {
-    return ctx->avg_packet_rate;
-  }
+  /* Ignore seqnums that are over 15,000 away from the latest one, it's close
+   * to 2^14 but far enough to avoid any risk of computing error.
+   */
+  if (diff_seqnum > 15000)
+    goto done_but_save;
+
+  /* Ignore any packet that is in the past, we're only interested in newer
+   * packets to compute the packet rate.
+   */
+  if (diff_seqnum <= 0 || new_ts <= ctx->last_ts)
+    goto done;
 
   diff_ts = new_ts - ctx->last_ts;
   diff_ts = gst_util_uint64_scale_int (diff_ts, GST_SECOND, ctx->clock_rate);
@@ -67,15 +77,25 @@ gst_rtp_packet_rate_ctx_update (RTPPacketRateCtx * ctx, guint16 seqnum,
    * This is useful for bursty cases, where a lot of packets are close
    * to each other and should allow a higher reorder/dropout there.
    * Round up the new average.
+   * We do it on different rates depending on the packet rate, so it's not too
+   * jumpy.
    */
-  if (ctx->avg_packet_rate > new_packet_rate) {
-    ctx->avg_packet_rate = (7 * ctx->avg_packet_rate + new_packet_rate + 7) / 8;
-  } else {
-    ctx->avg_packet_rate = (ctx->avg_packet_rate + new_packet_rate + 1) / 2;
-  }
+  if (ctx->avg_packet_rate > new_packet_rate)
+    base = MAX (ctx->avg_packet_rate / 3, 8);   /* about 333 ms */
+  else
+    base = MAX (ctx->avg_packet_rate / 15, 2);  /* about 66 ms */
+
+  diff_seqnum = MIN (diff_seqnum, base - 1);
+
+  ctx->avg_packet_rate = (((base - diff_seqnum) * ctx->avg_packet_rate) +
+      (new_packet_rate * diff_seqnum)) / base;
+
+
+done_but_save:
 
   ctx->last_seqnum = seqnum;
   ctx->last_ts = new_ts;
+done:
 
   return ctx->avg_packet_rate;
 }
@@ -89,7 +109,7 @@ gst_rtp_packet_rate_ctx_get (RTPPacketRateCtx * ctx)
 guint32
 gst_rtp_packet_rate_ctx_get_max_dropout (RTPPacketRateCtx * ctx, gint32 time_ms)
 {
-  if (time_ms <= 0 || !ctx->probed) {
+  if (time_ms <= 0 || !ctx->probed || ctx->avg_packet_rate == -1) {
     return RTP_DEF_DROPOUT;
   }
 
@@ -100,7 +120,7 @@ guint32
 gst_rtp_packet_rate_ctx_get_max_misorder (RTPPacketRateCtx * ctx,
     gint32 time_ms)
 {
-  if (time_ms <= 0 || !ctx->probed) {
+  if (time_ms <= 0 || !ctx->probed || ctx->avg_packet_rate == -1) {
     return RTP_DEF_MISORDER;
   }
 
@@ -428,3 +448,232 @@ __g_socket_address_to_string (GSocketAddress * addr)
 
   return ret;
 }
+
+static void
+_append_structure_to_value_array (GValueArray * array, GstStructure * s)
+{
+  GValue *val;
+  g_value_array_append (array, NULL);
+  val = g_value_array_get_nth (array, array->n_values - 1);
+  g_value_init (val, GST_TYPE_STRUCTURE);
+  g_value_take_boxed (val, s);
+}
+
+static void
+_structure_take_value_array (GstStructure * s,
+    const gchar * field_name, GValueArray * array)
+{
+  GValue value = G_VALUE_INIT;
+  g_value_init (&value, G_TYPE_VALUE_ARRAY);
+  g_value_take_boxed (&value, array);
+  gst_structure_take_value (s, field_name, &value);
+  g_value_unset (&value);
+}
+
+GstStructure *
+rtp_twcc_stats_get_packets_structure (GArray * twcc_packets)
+{
+  GstStructure *ret = gst_structure_new_empty ("RTPTWCCPackets");
+  GValueArray *array = g_value_array_new (0);
+  guint i;
+
+  for (i = 0; i < twcc_packets->len; i++) {
+    RTPTWCCPacket *pkt = &g_array_index (twcc_packets, RTPTWCCPacket, i);
+
+    GstStructure *pkt_s = gst_structure_new ("RTPTWCCPacket",
+        "seqnum", G_TYPE_UINT, pkt->seqnum,
+        "local-ts", G_TYPE_UINT64, pkt->local_ts,
+        "remote-ts", G_TYPE_UINT64, pkt->remote_ts,
+        "size", G_TYPE_UINT, pkt->size,
+        "lost", G_TYPE_BOOLEAN, pkt->status == RTP_TWCC_PACKET_STATUS_NOT_RECV,
+        NULL);
+    _append_structure_to_value_array (array, pkt_s);
+  }
+
+  _structure_take_value_array (ret, "packets", array);
+  return ret;
+}
+
+static void
+rtp_twcc_stats_calculate_stats (RTPTWCCStats * stats, GArray * twcc_packets)
+{
+  guint packets_recv = 0;
+  guint i;
+
+  for (i = 0; i < twcc_packets->len; i++) {
+    RTPTWCCPacket *pkt = &g_array_index (twcc_packets, RTPTWCCPacket, i);
+
+    if (pkt->status != RTP_TWCC_PACKET_STATUS_NOT_RECV)
+      packets_recv++;
+
+    if (GST_CLOCK_TIME_IS_VALID (pkt->local_ts) &&
+        GST_CLOCK_TIME_IS_VALID (stats->last_local_ts)) {
+      pkt->local_delta = GST_CLOCK_DIFF (stats->last_local_ts, pkt->local_ts);
+    }
+
+    if (GST_CLOCK_TIME_IS_VALID (pkt->remote_ts) &&
+        GST_CLOCK_TIME_IS_VALID (stats->last_remote_ts)) {
+      pkt->remote_delta =
+          GST_CLOCK_DIFF (stats->last_remote_ts, pkt->remote_ts);
+    }
+
+    if (GST_CLOCK_STIME_IS_VALID (pkt->local_delta) &&
+        GST_CLOCK_STIME_IS_VALID (pkt->remote_delta)) {
+      pkt->delta_delta = pkt->remote_delta - pkt->local_delta;
+    }
+
+    stats->last_local_ts = pkt->local_ts;
+    stats->last_remote_ts = pkt->remote_ts;
+  }
+
+  stats->packets_sent = twcc_packets->len;
+  stats->packets_recv = packets_recv;
+}
+
+static gint
+_get_window_start_index (RTPTWCCStats * stats, GstClockTime duration,
+    GstClockTime * local_duration, GstClockTime * remote_duration)
+{
+  RTPTWCCPacket *last = NULL;
+  guint i;
+
+  if (stats->packets->len < 2)
+    return -1;
+
+  for (i = 0; i < stats->packets->len; i++) {
+    guint start_index = stats->packets->len - 1 - i;
+    RTPTWCCPacket *pkt =
+        &g_array_index (stats->packets, RTPTWCCPacket, start_index);
+    if (GST_CLOCK_TIME_IS_VALID (pkt->local_ts)
+        && GST_CLOCK_TIME_IS_VALID (pkt->remote_ts)) {
+      /* first find the last valid packet */
+      if (last == NULL) {
+        last = pkt;
+      } else {
+        /* and then get the duration in local ts */
+        GstClockTimeDiff ld = GST_CLOCK_DIFF (pkt->local_ts, last->local_ts);
+        if (ld >= duration) {
+          *local_duration = ld;
+          *remote_duration = GST_CLOCK_DIFF (pkt->remote_ts, last->remote_ts);
+          return start_index;
+        }
+      }
+    }
+  }
+
+  return -1;
+}
+
+static void
+rtp_twcc_stats_calculate_windowed_stats (RTPTWCCStats * stats)
+{
+  guint i;
+  gint start_idx;
+  guint bits_sent = 0;
+  guint bits_recv = 0;
+  guint packets_sent = 0;
+  guint packets_recv = 0;
+  guint packets_lost;
+  GstClockTimeDiff delta_delta_sum = 0;
+  guint delta_delta_count = 0;
+  GstClockTime local_duration;
+  GstClockTime remote_duration;
+
+  start_idx = _get_window_start_index (stats, stats->window_size,
+      &local_duration, &remote_duration);
+  if (start_idx == -1) {
+    return;
+  }
+
+  /* remove the old packets */
+  if (start_idx > 0)
+    g_array_remove_range (stats->packets, 0, start_idx);
+
+  packets_sent = stats->packets->len - 1;
+
+  for (i = 0; i < packets_sent; i++) {
+    RTPTWCCPacket *pkt = &g_array_index (stats->packets, RTPTWCCPacket, i);
+
+    if (GST_CLOCK_TIME_IS_VALID (pkt->local_ts)) {
+      bits_sent += pkt->size * 8;
+    }
+
+    if (GST_CLOCK_TIME_IS_VALID (pkt->remote_ts)) {
+      bits_recv += pkt->size * 8;
+      packets_recv++;
+    }
+
+    if (GST_CLOCK_STIME_IS_VALID (pkt->delta_delta)) {
+      delta_delta_sum += pkt->delta_delta;
+      delta_delta_count++;
+    }
+  }
+
+  packets_lost = packets_sent - packets_recv;
+  stats->packet_loss_pct = (packets_lost * 100) / (gfloat) packets_sent;
+
+  if (delta_delta_count) {
+    GstClockTimeDiff avg_delta_of_delta = delta_delta_sum / delta_delta_count;
+    if (GST_CLOCK_STIME_IS_VALID (stats->avg_delta_of_delta)) {
+      stats->avg_delta_of_delta_change =
+          (avg_delta_of_delta -
+          stats->avg_delta_of_delta) / (250 * GST_USECOND);
+    }
+    stats->avg_delta_of_delta = avg_delta_of_delta;
+  }
+
+  if (local_duration > 0)
+    stats->bitrate_sent =
+        gst_util_uint64_scale (bits_sent, GST_SECOND, local_duration);
+  if (remote_duration > 0)
+    stats->bitrate_recv =
+        gst_util_uint64_scale (bits_recv, GST_SECOND, remote_duration);
+
+  GST_DEBUG ("Got stats: bits_sent: %u, bits_recv: %u, packets_sent = %u, "
+      "packets_recv: %u, packetlost_pct = %f, sent_bitrate = %u, "
+      "recv_bitrate = %u, delta-delta-avg = %" GST_STIME_FORMAT ", "
+      "delta-delta-change: %f", bits_sent, bits_recv, stats->packets_sent,
+      packets_recv, stats->packet_loss_pct, stats->bitrate_sent,
+      stats->bitrate_recv, GST_STIME_ARGS (stats->avg_delta_of_delta),
+      stats->avg_delta_of_delta_change);
+}
+
+RTPTWCCStats *
+rtp_twcc_stats_new (void)
+{
+  RTPTWCCStats *stats = g_new0 (RTPTWCCStats, 1);
+  stats->packets = g_array_new (FALSE, FALSE, sizeof (RTPTWCCPacket));
+  stats->last_local_ts = GST_CLOCK_TIME_NONE;
+  stats->last_remote_ts = GST_CLOCK_TIME_NONE;
+  stats->avg_delta_of_delta = GST_CLOCK_STIME_NONE;
+  stats->window_size = 300 * GST_MSECOND;       /* FIXME: could be configurable? */
+  return stats;
+}
+
+void
+rtp_twcc_stats_free (RTPTWCCStats * stats)
+{
+  g_array_unref (stats->packets);
+  g_free (stats);
+}
+
+static GstStructure *
+rtp_twcc_stats_get_stats_structure (RTPTWCCStats * stats)
+{
+  return gst_structure_new ("RTPTWCCStats",
+      "bitrate-sent", G_TYPE_UINT, stats->bitrate_sent,
+      "bitrate-recv", G_TYPE_UINT, stats->bitrate_recv,
+      "packets-sent", G_TYPE_UINT, stats->packets_sent,
+      "packets-recv", G_TYPE_UINT, stats->packets_recv,
+      "packet-loss-pct", G_TYPE_DOUBLE, stats->packet_loss_pct,
+      "avg-delta-of-delta", G_TYPE_INT64, stats->avg_delta_of_delta, NULL);
+}
+
+GstStructure *
+rtp_twcc_stats_process_packets (RTPTWCCStats * stats, GArray * twcc_packets)
+{
+  rtp_twcc_stats_calculate_stats (stats, twcc_packets);
+  g_array_append_vals (stats->packets, twcc_packets->data, twcc_packets->len);
+  rtp_twcc_stats_calculate_windowed_stats (stats);
+  return rtp_twcc_stats_get_stats_structure (stats);
+}
index b8b26ec..776651f 100644 (file)
@@ -27,6 +27,9 @@
 #include <gst/rtp/rtp.h>
 #include <gio/gio.h>
 
+/* UDP/IP is assumed for bandwidth calculation */
+#define UDP_IP_HEADER_OVERHEAD 28
+
 /**
  * RTPSenderReport:
  *
@@ -74,6 +77,10 @@ typedef struct {
  * @seqnum: the seqnum of the packet
  * @pt: the payload type of the packet
  * @rtptime: the RTP time of the packet
+ * @marker: the marker bit
+ *
+ * @tw_seqnum_ext_id: the extension-header ID for transport-wide seqnums
+ * @tw_seqnum: the transport-wide seqnum of the packet
  *
  * Structure holding information about the packet.
  */
@@ -94,8 +101,11 @@ typedef struct {
   guint16       seqnum;
   guint8        pt;
   guint32       rtptime;
+  gboolean      marker;
   guint32       csrc_count;
   guint32       csrcs[16];
+  GBytes        *header_ext;
+  guint16       header_ext_bit_pattern;
 } RTPPacketInfo;
 
 /**
@@ -242,6 +252,27 @@ typedef struct {
   guint         nacks_received;
 } RTPSessionStats;
 
+/**
+ * RTPTWCCStats:
+ *
+ * Stats kept for a session and used to produce TWCC stats.
+ */
+typedef struct {
+  GArray       *packets;
+  GstClockTime window_size;
+  GstClockTime  last_local_ts;
+  GstClockTime  last_remote_ts;
+
+  guint bitrate_sent;
+  guint bitrate_recv;
+  guint packets_sent;
+  guint packets_recv;
+  gfloat packet_loss_pct;
+  GstClockTimeDiff avg_delta_of_delta;
+  gfloat avg_delta_of_delta_change;
+} RTPTWCCStats;
+
+
 void           rtp_stats_init_defaults              (RTPSessionStats *stats);
 
 void           rtp_stats_set_bandwidths             (RTPSessionStats *stats,
@@ -261,4 +292,10 @@ void           rtp_stats_set_min_interval           (RTPSessionStats *stats,
 gboolean __g_socket_address_equal (GSocketAddress *a, GSocketAddress *b);
 gchar * __g_socket_address_to_string (GSocketAddress * addr);
 
+RTPTWCCStats * rtp_twcc_stats_new (void);
+void rtp_twcc_stats_free (RTPTWCCStats * stats);
+GstStructure * rtp_twcc_stats_process_packets (RTPTWCCStats * stats,
+    GArray * twcc_packets);
+GstStructure * rtp_twcc_stats_get_packets_structure (GArray * twcc_packets);
+
 #endif /* __RTP_STATS_H__ */
diff --git a/gst/rtpmanager/rtptimerqueue.c b/gst/rtpmanager/rtptimerqueue.c
new file mode 100644 (file)
index 0000000..446a70e
--- /dev/null
@@ -0,0 +1,746 @@
+/* GStreamer RTP Manager
+ *
+ * Copyright (C) 2019 Net Insight AB
+ *     Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#include <string.h>
+
+#include <gst/rtp/gstrtpbuffer.h>
+
+#include "rtptimerqueue.h"
+
+struct _RtpTimerQueue
+{
+  GObject parent;
+
+  GQueue timers;
+  GHashTable *hashtable;
+};
+
+G_DEFINE_TYPE (RtpTimerQueue, rtp_timer_queue, G_TYPE_OBJECT);
+
+/* some timer private helpers */
+
+static RtpTimer *
+rtp_timer_new (void)
+{
+  return g_slice_new0 (RtpTimer);
+}
+
+static inline void
+rtp_timer_set_next (RtpTimer * timer, RtpTimer * next)
+{
+  GList *list = (GList *) timer;
+  list->next = (GList *) next;
+}
+
+static inline void
+rtp_timer_set_prev (RtpTimer * timer, RtpTimer * prev)
+{
+  GList *list = (GList *) timer;
+  list->prev = (GList *) prev;
+}
+
+static inline gboolean
+rtp_timer_is_later (RtpTimer * timer, RtpTimer * next)
+{
+  if (next == NULL)
+    return FALSE;
+
+  if (GST_CLOCK_TIME_IS_VALID (next->timeout)) {
+    if (!GST_CLOCK_TIME_IS_VALID (timer->timeout))
+      return FALSE;
+
+    if (timer->timeout > next->timeout)
+      return TRUE;
+  }
+
+  if (timer->timeout == next->timeout &&
+      gst_rtp_buffer_compare_seqnum (timer->seqnum, next->seqnum) < 0)
+    return TRUE;
+
+  return FALSE;
+}
+
+static inline gboolean
+rtp_timer_is_sooner (RtpTimer * timer, RtpTimer * prev)
+{
+  if (prev == NULL)
+    return FALSE;
+
+  if (GST_CLOCK_TIME_IS_VALID (prev->timeout)) {
+    if (!GST_CLOCK_TIME_IS_VALID (timer->timeout))
+      return TRUE;
+
+    if (timer->timeout < prev->timeout)
+      return TRUE;
+  }
+
+  if (timer->timeout == prev->timeout &&
+      gst_rtp_buffer_compare_seqnum (timer->seqnum, prev->seqnum) > 0)
+    return TRUE;
+
+  return FALSE;
+}
+
+static inline gboolean
+rtp_timer_is_closer_to_head (RtpTimer * timer, RtpTimer * head)
+{
+  RtpTimer *prev = rtp_timer_get_prev (timer);
+  GstClockTimeDiff prev_delta = 0;
+  GstClockTimeDiff head_delta = 0;
+
+  if (prev == NULL)
+    return FALSE;
+
+  if (rtp_timer_is_sooner (timer, head))
+    return TRUE;
+
+  if (rtp_timer_is_later (timer, prev))
+    return FALSE;
+
+  if (prev->timeout == head->timeout) {
+    gint prev_gap, head_gap;
+
+    prev_gap = gst_rtp_buffer_compare_seqnum (timer->seqnum, prev->seqnum);
+    head_gap = gst_rtp_buffer_compare_seqnum (head->seqnum, timer->seqnum);
+
+    if (head_gap < prev_gap)
+      return TRUE;
+  }
+
+  if (GST_CLOCK_TIME_IS_VALID (timer->timeout) &&
+      GST_CLOCK_TIME_IS_VALID (head->timeout)) {
+    prev_delta = GST_CLOCK_DIFF (timer->timeout, prev->timeout);
+    head_delta = GST_CLOCK_DIFF (head->timeout, timer->timeout);
+
+    if (head_delta < prev_delta)
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+static inline gboolean
+rtp_timer_is_closer_to_tail (RtpTimer * timer, RtpTimer * tail)
+{
+  RtpTimer *next = rtp_timer_get_next (timer);
+  GstClockTimeDiff tail_delta = 0;
+  GstClockTimeDiff next_delta = 0;
+
+  if (next == NULL)
+    return FALSE;
+
+  if (rtp_timer_is_later (timer, tail))
+    return TRUE;
+
+  if (rtp_timer_is_sooner (timer, next))
+    return FALSE;
+
+  if (tail->timeout == next->timeout) {
+    gint tail_gap, next_gap;
+
+    tail_gap = gst_rtp_buffer_compare_seqnum (timer->seqnum, tail->seqnum);
+    next_gap = gst_rtp_buffer_compare_seqnum (next->seqnum, timer->seqnum);
+
+    if (tail_gap < next_gap)
+      return TRUE;
+  }
+
+  if (GST_CLOCK_TIME_IS_VALID (timer->timeout) &&
+      GST_CLOCK_TIME_IS_VALID (next->timeout)) {
+    tail_delta = GST_CLOCK_DIFF (timer->timeout, tail->timeout);
+    next_delta = GST_CLOCK_DIFF (next->timeout, timer->timeout);
+
+    if (tail_delta < next_delta)
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+static inline RtpTimer *
+rtp_timer_queue_get_tail (RtpTimerQueue * queue)
+{
+  return (RtpTimer *) queue->timers.tail;
+}
+
+static inline void
+rtp_timer_queue_set_tail (RtpTimerQueue * queue, RtpTimer * timer)
+{
+  queue->timers.tail = (GList *) timer;
+  g_assert (queue->timers.tail->next == NULL);
+}
+
+static inline RtpTimer *
+rtp_timer_queue_get_head (RtpTimerQueue * queue)
+{
+  return (RtpTimer *) queue->timers.head;
+}
+
+static inline void
+rtp_timer_queue_set_head (RtpTimerQueue * queue, RtpTimer * timer)
+{
+  queue->timers.head = (GList *) timer;
+  g_assert (queue->timers.head->prev == NULL);
+}
+
+static void
+rtp_timer_queue_insert_before (RtpTimerQueue * queue, RtpTimer * sibling,
+    RtpTimer * timer)
+{
+  if (sibling == rtp_timer_queue_get_head (queue)) {
+    rtp_timer_queue_set_head (queue, timer);
+  } else {
+    rtp_timer_set_prev (timer, rtp_timer_get_prev (sibling));
+    rtp_timer_set_next (rtp_timer_get_prev (sibling), timer);
+  }
+
+  rtp_timer_set_next (timer, sibling);
+  rtp_timer_set_prev (sibling, timer);
+
+  queue->timers.length++;
+}
+
+static void
+rtp_timer_queue_insert_after (RtpTimerQueue * queue, RtpTimer * sibling,
+    RtpTimer * timer)
+{
+  if (sibling == rtp_timer_queue_get_tail (queue)) {
+    rtp_timer_queue_set_tail (queue, timer);
+  } else {
+    rtp_timer_set_next (timer, rtp_timer_get_next (sibling));
+    rtp_timer_set_prev (rtp_timer_get_next (sibling), timer);
+  }
+
+  rtp_timer_set_prev (timer, sibling);
+  rtp_timer_set_next (sibling, timer);
+
+  queue->timers.length++;
+}
+
+static void
+rtp_timer_queue_insert_tail (RtpTimerQueue * queue, RtpTimer * timer)
+{
+  RtpTimer *it = rtp_timer_queue_get_tail (queue);
+
+  while (it) {
+    if (!GST_CLOCK_TIME_IS_VALID (it->timeout))
+      break;
+
+    if (timer->timeout > it->timeout)
+      break;
+
+    if (timer->timeout == it->timeout &&
+        gst_rtp_buffer_compare_seqnum (timer->seqnum, it->seqnum) < 0)
+      break;
+
+    it = rtp_timer_get_prev (it);
+  }
+
+  /* the queue is empty, or this is the earliest timeout */
+  if (it == NULL)
+    g_queue_push_head_link (&queue->timers, (GList *) timer);
+  else
+    rtp_timer_queue_insert_after (queue, it, timer);
+}
+
+static void
+rtp_timer_queue_insert_head (RtpTimerQueue * queue, RtpTimer * timer)
+{
+  RtpTimer *it = rtp_timer_queue_get_head (queue);
+
+  while (it) {
+    if (GST_CLOCK_TIME_IS_VALID (it->timeout)) {
+      if (!GST_CLOCK_TIME_IS_VALID (timer->timeout))
+        break;
+
+      if (timer->timeout < it->timeout)
+        break;
+    }
+
+    if (timer->timeout == it->timeout &&
+        gst_rtp_buffer_compare_seqnum (timer->seqnum, it->seqnum) > 0)
+      break;
+
+    it = rtp_timer_get_next (it);
+  }
+
+  /* the queue is empty, or this is the oldest */
+  if (it == NULL)
+    g_queue_push_tail_link (&queue->timers, (GList *) timer);
+  else
+    rtp_timer_queue_insert_before (queue, it, timer);
+}
+
+static void
+rtp_timer_queue_init (RtpTimerQueue * queue)
+{
+  queue->hashtable = g_hash_table_new (NULL, NULL);
+}
+
+static void
+rtp_timer_queue_finalize (GObject * object)
+{
+  RtpTimerQueue *queue = RTP_TIMER_QUEUE (object);
+  RtpTimer *timer;
+
+  while ((timer = rtp_timer_queue_pop_until (queue, GST_CLOCK_TIME_NONE)))
+    rtp_timer_free (timer);
+  g_hash_table_unref (queue->hashtable);
+  g_assert (queue->timers.length == 0);
+}
+
+static void
+rtp_timer_queue_class_init (RtpTimerQueueClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = rtp_timer_queue_finalize;
+}
+
+/**
+ * rtp_timer_free:
+ *
+ * Free a #RtpTimer structure. This should be used after a timer has been
+ * poped or unscheduled. The timer must be queued.
+ */
+void
+rtp_timer_free (RtpTimer * timer)
+{
+  g_return_if_fail (timer);
+  g_return_if_fail (timer->queued == FALSE);
+  g_return_if_fail (timer->list.next == NULL);
+  g_return_if_fail (timer->list.prev == NULL);
+
+  g_slice_free (RtpTimer, timer);
+}
+
+/**
+ * rtp_timer_dup:
+ * @timer: a #RtpTimer
+ *
+ * This allow cloning a #RtpTimer structure.
+ *
+ * Returns: a copy of @timer
+ */
+RtpTimer *
+rtp_timer_dup (const RtpTimer * timer)
+{
+  RtpTimer *copy = g_slice_new (RtpTimer);
+  memcpy (copy, timer, sizeof (RtpTimer));
+  memset (&copy->list, 0, sizeof (GList));
+  copy->queued = FALSE;
+  return copy;
+}
+
+/**
+ * rtp_timer_queue_find:
+ * @queue: the #RtpTimerQueue object
+ * @seqnum: the sequence number of the #RtpTimer
+ *
+ * Lookup for a timer with @seqnum. Only one timer per seqnum exist withing
+ * the #RtpTimerQueue. This operation is o(1).
+ *
+ * Rerturn: the matching #RtpTimer or %NULL
+ */
+RtpTimer *
+rtp_timer_queue_find (RtpTimerQueue * queue, guint seqnum)
+{
+  return g_hash_table_lookup (queue->hashtable, GINT_TO_POINTER (seqnum));
+}
+
+/**
+ * rtp_timer_queue_peek_earliest:
+ * @queue: the #RtpTimerQueue object
+ *
+ * Rerturns: the #RtpTimer with earliest timeout value
+ */
+RtpTimer *
+rtp_timer_queue_peek_earliest (RtpTimerQueue * queue)
+{
+  return rtp_timer_queue_get_head (queue);
+}
+
+
+/**
+ * rtp_timer_queue_new:
+ *
+ * Returns: a freshly allocated #RtpTimerQueue
+ */
+RtpTimerQueue *
+rtp_timer_queue_new (void)
+{
+  return g_object_new (RTP_TYPE_TIMER_QUEUE, NULL);
+}
+
+/**
+ * rtp_timer_queue_insert:
+ * @queue: the #RtpTimerQueue object
+ * @timer: (transfer full): the #RtpTimer to insert
+ *
+ * Insert a timer into the queue. Earliest timer are at the head and then
+ * timer are sorted by seqnum (smaller seqnum first). This function is o(n)
+ * but it is expected that most timers added are schedule later, in which case
+ * the insertion will be faster.
+ *
+ * Returns: %FALSE if a timer with the same seqnum already existed
+ */
+gboolean
+rtp_timer_queue_insert (RtpTimerQueue * queue, RtpTimer * timer)
+{
+  g_return_val_if_fail (timer->queued == FALSE, FALSE);
+
+  if (rtp_timer_queue_find (queue, timer->seqnum)) {
+    rtp_timer_free (timer);
+    GST_WARNING ("Timer queue collision, freeing duplicate.");
+    return FALSE;
+  }
+
+  if (timer->timeout == -1)
+    rtp_timer_queue_insert_head (queue, timer);
+  else
+    rtp_timer_queue_insert_tail (queue, timer);
+
+  g_hash_table_insert (queue->hashtable,
+      GINT_TO_POINTER (timer->seqnum), timer);
+  timer->queued = TRUE;
+
+  return TRUE;
+}
+
+/**
+ * rtp_timer_queue_reschedule:
+ * @queue: the #RtpTimerQueue object
+ * @timer: the #RtpTimer to reschedule
+ *
+ * This function moves @timer inside the queue to put it back to it's new
+ * location. This function is o(n) but it is assumed that nearby modification
+ * of the timeout will occure.
+ *
+ * Returns: %TRUE if the timer was moved
+ */
+gboolean
+rtp_timer_queue_reschedule (RtpTimerQueue * queue, RtpTimer * timer)
+{
+  RtpTimer *it = timer;
+
+  g_return_val_if_fail (timer->queued == TRUE, FALSE);
+
+  if (rtp_timer_is_closer_to_head (timer, rtp_timer_queue_get_head (queue))) {
+    g_queue_unlink (&queue->timers, (GList *) timer);
+    rtp_timer_queue_insert_head (queue, timer);
+    return TRUE;
+  }
+
+  while (rtp_timer_is_sooner (timer, rtp_timer_get_prev (it)))
+    it = rtp_timer_get_prev (it);
+
+  if (it != timer) {
+    g_queue_unlink (&queue->timers, (GList *) timer);
+    rtp_timer_queue_insert_before (queue, it, timer);
+    return TRUE;
+  }
+
+  if (rtp_timer_is_closer_to_tail (timer, rtp_timer_queue_get_tail (queue))) {
+    g_queue_unlink (&queue->timers, (GList *) timer);
+    rtp_timer_queue_insert_tail (queue, timer);
+    return TRUE;
+  }
+
+  while (rtp_timer_is_later (timer, rtp_timer_get_next (it)))
+    it = rtp_timer_get_next (it);
+
+  if (it != timer) {
+    g_queue_unlink (&queue->timers, (GList *) timer);
+    rtp_timer_queue_insert_after (queue, it, timer);
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/**
+ * rtp_timer_queue_unschedule:
+ * @queue: the #RtpTimerQueue
+ * @timer: the #RtpTimer to unschedule
+ *
+ * This removes a timer from the queue. The timer structure can be reused,
+ * or freed using rtp_timer_free(). This function is o(1).
+ */
+void
+rtp_timer_queue_unschedule (RtpTimerQueue * queue, RtpTimer * timer)
+{
+  g_return_if_fail (timer->queued == TRUE);
+
+  g_queue_unlink (&queue->timers, (GList *) timer);
+  g_hash_table_remove (queue->hashtable, GINT_TO_POINTER (timer->seqnum));
+  timer->queued = FALSE;
+}
+
+/**
+ * rtp_timer_queue_pop_until:
+ * @queue: the #RtpTimerQueue
+ * @timeout: Time at witch timers expired
+ *
+ * Unschdedule and return the earliest packet that has a timeout smaller or
+ * equal to @timeout. The returns #RtpTimer must be freed with
+ * rtp_timer_free(). This function is o(1).
+ *
+ * Returns: an expired timer according to @timeout, or %NULL.
+ */
+RtpTimer *
+rtp_timer_queue_pop_until (RtpTimerQueue * queue, GstClockTime timeout)
+{
+  RtpTimer *timer;
+
+  timer = (RtpTimer *) g_queue_peek_head_link (&queue->timers);
+  if (!timer)
+    return NULL;
+
+  if (!GST_CLOCK_TIME_IS_VALID (timer->timeout) || timer->timeout <= timeout) {
+    rtp_timer_queue_unschedule (queue, timer);
+    return timer;
+  }
+
+  return NULL;
+}
+
+/**
+ * rtp_timer_queue_remove_until:
+ * @queue: the #RtpTimerQueue
+ * @timeout: Time at witch timers expired
+ *
+ * Unschedule and free all timers that has a timeout smaller or equal to
+ * @timeout.
+ */
+void
+rtp_timer_queue_remove_until (RtpTimerQueue * queue, GstClockTime timeout)
+{
+  RtpTimer *timer;
+
+  while ((timer = rtp_timer_queue_pop_until (queue, timeout))) {
+    GST_LOG ("Removing expired timer #%d, %" GST_TIME_FORMAT " < %"
+        GST_TIME_FORMAT, timer->seqnum, GST_TIME_ARGS (timer->timeout),
+        GST_TIME_ARGS (timeout));
+    rtp_timer_free (timer);
+  }
+}
+
+/**
+ * rtp_timer_queue_remove_all:
+ * @queue: the #RtpTimerQueue
+ *
+ * Unschedule and free all timers from the queue.
+ */
+void
+rtp_timer_queue_remove_all (RtpTimerQueue * queue)
+{
+  rtp_timer_queue_remove_until (queue, GST_CLOCK_TIME_NONE);
+}
+
+/**
+ * rtp_timer_queue_set_timer:
+ * @queue: the #RtpTimerQueue
+ * @type: the #RtpTimerType
+ * @senum: the timer seqnum
+ * @timeout: the timer timeout
+ * @delay: the additional delay (will be added to @timeout)
+ * @duration: the duration of the event related to the timer
+ * @offset: offset that can be used to convert the timeout to timestamp
+ *
+ * If there exist a timer with this seqnum it will be updated other a new
+ * timer is created and inserted into the queue. This function is o(n) except
+ * that it's optimized for later timer insertion.
+ */
+void
+rtp_timer_queue_set_timer (RtpTimerQueue * queue, RtpTimerType type,
+    guint16 seqnum, GstClockTime timeout, GstClockTime delay,
+    GstClockTime duration, GstClockTimeDiff offset)
+{
+  RtpTimer *timer;
+
+  timer = rtp_timer_queue_find (queue, seqnum);
+  if (!timer)
+    timer = rtp_timer_new ();
+
+  /* for new timers or on seqnum change reset the RTX data */
+  if (!timer->queued || timer->seqnum != seqnum) {
+    if (type == RTP_TIMER_EXPECTED) {
+      timer->rtx_base = timeout;
+      timer->rtx_delay = delay;
+      timer->rtx_retry = 0;
+    }
+
+    timer->rtx_last = GST_CLOCK_TIME_NONE;
+    timer->num_rtx_retry = 0;
+    timer->num_rtx_received = 0;
+  }
+
+  timer->type = type;
+  timer->seqnum = seqnum;
+
+  if (timeout == -1)
+    timer->timeout = -1;
+  else
+    timer->timeout = timeout + delay + offset;
+
+  timer->offset = offset;
+  timer->duration = duration;
+
+  if (timer->queued)
+    rtp_timer_queue_reschedule (queue, timer);
+  else
+    rtp_timer_queue_insert (queue, timer);
+}
+
+/**
+ * rtp_timer_queue_set_expected:
+ * @queue: the #RtpTimerQueue
+ * @senum: the timer seqnum
+ * @timeout: the timer timeout
+ * @delay: the additional delay (will be added to @timeout)
+ * @duration: the duration of the event related to the timer
+ *
+ * Specialized version of rtp_timer_queue_set_timer() that creates or updates a
+ * timer with type %RTP_TIMER_EXPECTED. Expected timers do not carry
+ * a timestamp, hence have no offset.
+ */
+void
+rtp_timer_queue_set_expected (RtpTimerQueue * queue, guint16 seqnum,
+    GstClockTime timeout, GstClockTime delay, GstClockTime duration)
+{
+  rtp_timer_queue_set_timer (queue, RTP_TIMER_EXPECTED, seqnum, timeout,
+      delay, duration, 0);
+}
+
+/**
+ * rtp_timer_queue_set_lost:
+ * @queue: the #RtpTimerQueue
+ * @senum: the timer seqnum
+ * @timeout: the timer timeout
+ * @duration: the duration of the event related to the timer
+ * @offset: offset that can be used to convert the timeout to timestamp
+ *
+ * Specialized version of rtp_timer_queue_set_timer() that creates or updates a
+ * timer with type %RTP_TIMER_LOST.
+ */
+void
+rtp_timer_queue_set_lost (RtpTimerQueue * queue, guint16 seqnum,
+    GstClockTime timeout, GstClockTime duration, GstClockTimeDiff offset)
+{
+  rtp_timer_queue_set_timer (queue, RTP_TIMER_LOST, seqnum, timeout, 0,
+      duration, offset);
+}
+
+/**
+ * rtp_timer_queue_set_eos:
+ * @queue: the #RtpTimerQueue
+ * @timeout: the timer timeout
+ * @offset: offset that can be used to convert the timeout to timestamp
+ *
+ * Specialized version of rtp_timer_queue_set_timer() that creates or updates a
+ * timer with type %RTP_TIMER_EOS. There is only one such a timer and it has
+ * the special seqnum value -1 (FIXME this is not an invalid seqnum,).
+ */
+void
+rtp_timer_queue_set_eos (RtpTimerQueue * queue, GstClockTime timeout,
+    GstClockTimeDiff offset)
+{
+  rtp_timer_queue_set_timer (queue, RTP_TIMER_EOS, -1, timeout, 0, 0, offset);
+}
+
+/**
+ * rtp_timer_queue_set_deadline:
+ * @queue: the #RtpTimerQueue
+ * @senum: the timer seqnum
+ * @timeout: the timer timeout
+ * @offset: offset that can be used to convert the timeout to timestamp
+ *
+ * Specialized version of rtp_timer_queue_set_timer() that creates or updates a
+ * timer with type %RTP_TIMER_DEADLINE. There should be only one such a timer,
+ * its seqnum matches the first packet to be output.
+ */
+void
+rtp_timer_queue_set_deadline (RtpTimerQueue * queue, guint16 seqnum,
+    GstClockTime timeout, GstClockTimeDiff offset)
+{
+  rtp_timer_queue_set_timer (queue, RTP_TIMER_DEADLINE, seqnum, timeout, 0,
+      0, offset);
+}
+
+/**
+ * rtp_timer_queue_update_timer:
+ * @queue: the #RtpTimerQueue
+ * @senum: the timer seqnum
+ * @timeout: the timer timeout
+ * @delay: the additional delay (will be added to @timeout)
+ * @offset: offset that can be used to convert the timeout to timestamp
+ * @reset: if the RTX statistics should be reset
+ *
+ * A utility to update an already queued timer.
+ */
+void
+rtp_timer_queue_update_timer (RtpTimerQueue * queue, RtpTimer * timer,
+    guint16 seqnum, GstClockTime timeout, GstClockTime delay,
+    GstClockTimeDiff offset, gboolean reset)
+{
+  g_return_if_fail (timer != NULL);
+
+  if (reset) {
+    GST_DEBUG ("reset rtx delay %" GST_TIME_FORMAT "->%" GST_TIME_FORMAT,
+        GST_TIME_ARGS (timer->rtx_delay), GST_TIME_ARGS (delay));
+    timer->rtx_base = timeout;
+    timer->rtx_delay = delay;
+    timer->rtx_retry = 0;
+  }
+
+  if (timer->seqnum != seqnum) {
+    timer->num_rtx_retry = 0;
+    timer->num_rtx_received = 0;
+
+    if (timer->queued) {
+      g_hash_table_remove (queue->hashtable, GINT_TO_POINTER (timer->seqnum));
+      g_hash_table_insert (queue->hashtable, GINT_TO_POINTER (seqnum), timer);
+    }
+  }
+
+  if (timeout == -1)
+    timer->timeout = -1;
+  else
+    timer->timeout = timeout + delay + offset;
+
+  timer->seqnum = seqnum;
+  timer->offset = offset;
+
+  if (timer->queued)
+    rtp_timer_queue_reschedule (queue, timer);
+  else
+    rtp_timer_queue_insert (queue, timer);
+}
+
+/**
+ * rtp_timer_queue_length:
+ * @queue: the #RtpTimerQueue
+ *
+ * Returns: the number of timers in the #RtpTimerQueue
+ */
+guint
+rtp_timer_queue_length (RtpTimerQueue * queue)
+{
+  return queue->timers.length;
+}
diff --git a/gst/rtpmanager/rtptimerqueue.h b/gst/rtpmanager/rtptimerqueue.h
new file mode 100644 (file)
index 0000000..969cbd3
--- /dev/null
@@ -0,0 +1,127 @@
+/* GStreamer RTP Manager
+ *
+ * Copyright 2007 Collabora Ltd,
+ * Copyright 2007 Nokia Corporation
+ *   @author: Philippe Kalaf <philippe.kalaf@collabora.co.uk>.
+ * Copyright 2007 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright 2015 Kurento (http://kurento.org/)
+ *   @author: Miguel París <mparisdiaz@gmail.com>
+ * Copyright 2016 Pexip AS
+ *   @author: Havard Graff <havard@pexip.com>
+ *   @author: Stian Selnes <stian@pexip.com>
+ * Copyright (C) 2019 Net Insight AB
+ *     Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <gst/gst.h>
+
+#ifndef __RTP_TIMER_QUEUE_H__
+#define __RTP_TIMER_QUEUE_H__
+
+#define RTP_TYPE_TIMER_QUEUE rtp_timer_queue_get_type()
+G_DECLARE_FINAL_TYPE (RtpTimerQueue, rtp_timer_queue, RTP_TIMER, QUEUE, GObject);
+
+/**
+ * RtpTimerType:
+ * @RTP_TIMER_EXPECTED: This is used to track when to emit retranmission
+ *                      requests. They may be converted into %RTP_TIMER_LOST
+ *                      timer if the number of retry has been exhausted.
+ * @RTP_TIMER_LOST: This is used to track when a packet is considered lost.
+ * @RTP_TIMER_DEADLINE: This is used to track when the jitterbuffer should
+ *                      start pushing buffers.
+ * @RTP_TIMER_EOS:      This is used to track when end of stream is reached.
+ */
+typedef enum
+{
+  RTP_TIMER_EXPECTED,
+  RTP_TIMER_LOST,
+  RTP_TIMER_DEADLINE,
+  RTP_TIMER_EOS
+} RtpTimerType;
+
+typedef struct
+{
+  GList list;
+  gboolean queued;
+
+  guint16 seqnum;
+  RtpTimerType type;
+  GstClockTime timeout;
+  GstClockTimeDiff offset;
+  GstClockTime duration;
+  GstClockTime rtx_base;
+  GstClockTime rtx_delay;
+  GstClockTime rtx_retry;
+  GstClockTime rtx_last;
+  guint num_rtx_retry;
+  guint num_rtx_received;
+} RtpTimer;
+
+void         rtp_timer_free (RtpTimer * timer);
+RtpTimer *   rtp_timer_dup (const RtpTimer * timer);
+
+static inline RtpTimer * rtp_timer_get_next (RtpTimer * timer)
+{
+  GList *list = (GList *) timer;
+  return (RtpTimer *) list->next;
+}
+
+static inline RtpTimer * rtp_timer_get_prev (RtpTimer * timer)
+{
+  GList *list = (GList *) timer;
+  return (RtpTimer *) list->prev;
+}
+
+RtpTimerQueue * rtp_timer_queue_new (void);
+
+RtpTimer *      rtp_timer_queue_find (RtpTimerQueue * queue, guint seqnum);
+
+RtpTimer *      rtp_timer_queue_peek_earliest (RtpTimerQueue * queue);
+
+gboolean        rtp_timer_queue_insert (RtpTimerQueue * queue, RtpTimer * timer);
+
+gboolean        rtp_timer_queue_reschedule (RtpTimerQueue * queue, RtpTimer * timer);
+
+void            rtp_timer_queue_unschedule (RtpTimerQueue * queue, RtpTimer * timer);
+
+RtpTimer *      rtp_timer_queue_pop_until (RtpTimerQueue * queue, GstClockTime timeout);
+
+void            rtp_timer_queue_remove_until (RtpTimerQueue * queue, GstClockTime timeout);
+
+void            rtp_timer_queue_remove_all (RtpTimerQueue * queue);
+
+void            rtp_timer_queue_set_timer (RtpTimerQueue * queue, RtpTimerType type,
+                                           guint16 seqnum, GstClockTime timeout,
+                                           GstClockTime delay, GstClockTime duration,
+                                           GstClockTimeDiff offset);
+void            rtp_timer_queue_set_expected (RtpTimerQueue * queue, guint16 seqnum,
+                                              GstClockTime timeout, GstClockTime delay,
+                                              GstClockTime duration);
+void            rtp_timer_queue_set_lost (RtpTimerQueue * queue, guint16 seqnum,
+                                          GstClockTime timeout,
+                                          GstClockTime duration, GstClockTimeDiff offset);
+void            rtp_timer_queue_set_eos (RtpTimerQueue * queue, GstClockTime timeout,
+                                         GstClockTimeDiff offset);
+void            rtp_timer_queue_set_deadline (RtpTimerQueue * queue, guint16 seqnum,
+                                              GstClockTime timeout, GstClockTimeDiff offset);
+void            rtp_timer_queue_update_timer (RtpTimerQueue * queue, RtpTimer * timer, guint16 seqnum,
+                                              GstClockTime timeout, GstClockTime delay,
+                                              GstClockTimeDiff offset, gboolean reset);
+guint           rtp_timer_queue_length       (RtpTimerQueue * queue);
+
+#endif
diff --git a/gst/rtpmanager/rtptwcc.c b/gst/rtpmanager/rtptwcc.c
new file mode 100644 (file)
index 0000000..f5b5351
--- /dev/null
@@ -0,0 +1,909 @@
+/* GStreamer
+ * Copyright (C)  2019 Pexip (http://pexip.com/)
+ *   @author: Havard Graff <havard@pexip.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 "rtptwcc.h"
+#include <gst/rtp/gstrtcpbuffer.h>
+#include <gst/base/gstbitreader.h>
+#include <gst/base/gstbitwriter.h>
+
+GST_DEBUG_CATEGORY_EXTERN (rtp_session_debug);
+#define GST_CAT_DEFAULT rtp_session_debug
+
+#define REF_TIME_UNIT (64 * GST_MSECOND)
+#define DELTA_UNIT (250 * GST_USECOND)
+#define MAX_TS_DELTA (0xff * DELTA_UNIT)
+
+typedef enum
+{
+  RTP_TWCC_CHUNK_TYPE_RUN_LENGTH = 0,
+  RTP_TWCC_CHUNK_TYPE_STATUS_VECTOR = 1,
+} RTPTWCCChunkType;
+
+typedef struct
+{
+  guint8 base_seqnum[2];
+  guint8 packet_count[2];
+  guint8 base_time[3];
+  guint8 fb_pkt_count[1];
+} RTPTWCCHeader;
+
+typedef struct
+{
+  GstClockTime ts;
+  guint16 seqnum;
+
+  gint64 delta;
+  RTPTWCCPacketStatus status;
+  guint16 missing_run;
+  guint equal_run;
+} RecvPacket;
+
+typedef struct
+{
+  GstClockTime ts;
+  GstClockTime socket_ts;
+  GstClockTime remote_ts;
+  guint16 seqnum;
+  guint size;
+  gboolean lost;
+} SentPacket;
+
+struct _RTPTWCCManager
+{
+  GObject object;
+
+  guint mtu;
+  guint max_packets_per_rtcp;
+  GArray *recv_packets;
+
+  guint8 fb_pkt_count;
+  gint32 last_seqnum;
+
+  GArray *sent_packets;
+  GArray *parsed_packets;
+  GQueue *rtcp_buffers;
+
+  guint64 recv_sender_ssrc;
+  guint64 recv_media_ssrc;
+
+  guint16 expected_recv_seqnum;
+
+  gboolean first_fci_parse;
+  guint16 expected_parsed_seqnum;
+  guint8 expected_parsed_fb_pkt_count;
+};
+
+G_DEFINE_TYPE (RTPTWCCManager, rtp_twcc_manager, G_TYPE_OBJECT);
+
+static void
+rtp_twcc_manager_init (RTPTWCCManager * twcc)
+{
+  twcc->recv_packets = g_array_new (FALSE, FALSE, sizeof (RecvPacket));
+  twcc->sent_packets = g_array_new (FALSE, FALSE, sizeof (SentPacket));
+  twcc->parsed_packets = g_array_new (FALSE, FALSE, sizeof (RecvPacket));
+
+  twcc->rtcp_buffers = g_queue_new ();
+
+  twcc->last_seqnum = -1;
+  twcc->recv_media_ssrc = -1;
+  twcc->recv_sender_ssrc = -1;
+
+  twcc->first_fci_parse = TRUE;
+}
+
+static void
+rtp_twcc_manager_finalize (GObject * object)
+{
+  RTPTWCCManager *twcc = RTP_TWCC_MANAGER_CAST (object);
+
+  g_array_unref (twcc->recv_packets);
+  g_array_unref (twcc->sent_packets);
+  g_array_unref (twcc->parsed_packets);
+  g_queue_free_full (twcc->rtcp_buffers, (GDestroyNotify) gst_buffer_unref);
+
+  G_OBJECT_CLASS (rtp_twcc_manager_parent_class)->finalize (object);
+}
+
+static void
+rtp_twcc_manager_class_init (RTPTWCCManagerClass * klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  gobject_class->finalize = rtp_twcc_manager_finalize;
+}
+
+RTPTWCCManager *
+rtp_twcc_manager_new (guint mtu)
+{
+  RTPTWCCManager *twcc = g_object_new (RTP_TYPE_TWCC_MANAGER, NULL);
+
+  rtp_twcc_manager_set_mtu (twcc, mtu);
+
+  return twcc;
+}
+
+static void
+recv_packet_init (RecvPacket * packet, guint16 seqnum, RTPPacketInfo * pinfo)
+{
+  memset (packet, 0, sizeof (RecvPacket));
+  packet->seqnum = seqnum;
+  packet->ts = pinfo->running_time;
+}
+
+void
+rtp_twcc_manager_set_mtu (RTPTWCCManager * twcc, guint mtu)
+{
+  twcc->mtu = mtu;
+
+  /* the absolute worst case is that 7 packets uses
+     header (4 * 4 * 4) 32 bytes) and 
+     packet_chunk 2 bytes +  
+     recv_deltas (2 * 7) 14 bytes */
+  twcc->max_packets_per_rtcp = ((twcc->mtu - 32) * 7) / (2 + 14);
+}
+
+static gint
+_twcc_seqnum_sort (gconstpointer a, gconstpointer b)
+{
+  gint32 seqa = ((RecvPacket *) a)->seqnum;
+  gint32 seqb = ((RecvPacket *) b)->seqnum;
+  gint res = seqa - seqb;
+  if (res < -65000)
+    res = 1;
+  if (res > 65000)
+    res = -1;
+  return res;
+}
+
+static void
+rtp_twcc_write_recv_deltas (guint8 * fci_data, GArray * twcc_packets)
+{
+  guint i;
+  for (i = 0; i < twcc_packets->len; i++) {
+    RecvPacket *pkt = &g_array_index (twcc_packets, RecvPacket, i);
+
+    if (pkt->status == RTP_TWCC_PACKET_STATUS_SMALL_DELTA) {
+      GST_WRITE_UINT8 (fci_data, pkt->delta);
+      fci_data += 1;
+    } else if (pkt->status == RTP_TWCC_PACKET_STATUS_LARGE_NEGATIVE_DELTA) {
+      GST_WRITE_UINT16_BE (fci_data, pkt->delta);
+      fci_data += 2;
+    }
+  }
+}
+
+static void
+rtp_twcc_write_run_length_chunk (GArray * packet_chunks,
+    RTPTWCCPacketStatus status, guint run_length)
+{
+  guint written = 0;
+  while (written < run_length) {
+    GstBitWriter writer;
+    guint16 data = 0;
+    guint len = MIN (run_length - written, 8191);
+
+    GST_LOG ("Writing a run-lenght of %u with status %u", len, status);
+
+    gst_bit_writer_init_with_data (&writer, (guint8 *) & data, 2, FALSE);
+    gst_bit_writer_put_bits_uint8 (&writer, RTP_TWCC_CHUNK_TYPE_RUN_LENGTH, 1);
+    gst_bit_writer_put_bits_uint8 (&writer, status, 2);
+    gst_bit_writer_put_bits_uint16 (&writer, len, 13);
+    g_array_append_val (packet_chunks, data);
+    written += len;
+  }
+}
+
+typedef struct
+{
+  GArray *packet_chunks;
+  GstBitWriter writer;
+  guint16 data;
+  guint symbol_size;
+} ChunkBitWriter;
+
+static void
+chunk_bit_writer_reset (ChunkBitWriter * writer)
+{
+  writer->data = 0;
+  gst_bit_writer_init_with_data (&writer->writer,
+      (guint8 *) & writer->data, 2, FALSE);
+
+  gst_bit_writer_put_bits_uint8 (&writer->writer,
+      RTP_TWCC_CHUNK_TYPE_STATUS_VECTOR, 1);
+  /* 1 for 2-bit symbol-size, 0 for 1-bit */
+  gst_bit_writer_put_bits_uint8 (&writer->writer, writer->symbol_size - 1, 1);
+}
+
+static void
+chunk_bit_writer_configure (ChunkBitWriter * writer, guint symbol_size)
+{
+  writer->symbol_size = symbol_size;
+  chunk_bit_writer_reset (writer);
+}
+
+static gboolean
+chunk_bit_writer_is_empty (ChunkBitWriter * writer)
+{
+  return writer->writer.bit_size == 2;
+}
+
+static gboolean
+chunk_bit_writer_is_full (ChunkBitWriter * writer)
+{
+  return writer->writer.bit_size == 16;
+}
+
+static guint
+chunk_bit_writer_get_available_slots (ChunkBitWriter * writer)
+{
+  return (16 - writer->writer.bit_size) / writer->symbol_size;
+}
+
+static guint
+chunk_bit_writer_get_total_slots (ChunkBitWriter * writer)
+{
+  return 14 / writer->symbol_size;
+}
+
+static void
+chunk_bit_writer_flush (ChunkBitWriter * writer)
+{
+  /* don't append a chunk if no bits have been written */
+  if (!chunk_bit_writer_is_empty (writer)) {
+    g_array_append_val (writer->packet_chunks, writer->data);
+    chunk_bit_writer_reset (writer);
+  }
+}
+
+static void
+chunk_bit_writer_init (ChunkBitWriter * writer,
+    GArray * packet_chunks, guint symbol_size)
+{
+  writer->packet_chunks = packet_chunks;
+  chunk_bit_writer_configure (writer, symbol_size);
+}
+
+static void
+chunk_bit_writer_write (ChunkBitWriter * writer, RTPTWCCPacketStatus status)
+{
+  gst_bit_writer_put_bits_uint8 (&writer->writer, status, writer->symbol_size);
+  if (chunk_bit_writer_is_full (writer)) {
+    chunk_bit_writer_flush (writer);
+  }
+}
+
+static void
+rtp_twcc_write_status_vector_chunk (ChunkBitWriter * writer, RecvPacket * pkt)
+{
+  if (pkt->missing_run > 0) {
+    guint available = chunk_bit_writer_get_available_slots (writer);
+    guint total = chunk_bit_writer_get_total_slots (writer);
+    guint i;
+
+    if (pkt->missing_run > (available + total)) {
+      /* here it is better to finish up the current status-chunk and then
+         go for run-length */
+      for (i = 0; i < available; i++) {
+        chunk_bit_writer_write (writer, RTP_TWCC_PACKET_STATUS_NOT_RECV);
+      }
+      rtp_twcc_write_run_length_chunk (writer->packet_chunks,
+          RTP_TWCC_PACKET_STATUS_NOT_RECV, pkt->missing_run - available);
+    } else {
+      for (i = 0; i < pkt->missing_run; i++) {
+        chunk_bit_writer_write (writer, RTP_TWCC_PACKET_STATUS_NOT_RECV);
+      }
+    }
+  }
+
+  chunk_bit_writer_write (writer, pkt->status);
+}
+
+typedef struct
+{
+  RecvPacket *equal;
+} RunLengthHelper;
+
+static void
+run_lenght_helper_update (RunLengthHelper * rlh, RecvPacket * pkt)
+{
+  /* for missing packets we reset */
+  if (pkt->missing_run > 0) {
+    rlh->equal = NULL;
+  }
+
+  /* all status equal run */
+  if (rlh->equal == NULL) {
+    rlh->equal = pkt;
+    rlh->equal->equal_run = 0;
+  }
+
+  if (rlh->equal->status == pkt->status) {
+    rlh->equal->equal_run++;
+  } else {
+    rlh->equal = pkt;
+    rlh->equal->equal_run = 1;
+  }
+}
+
+static void
+rtp_twcc_write_chunks (GArray * packet_chunks,
+    GArray * twcc_packets, guint symbol_size)
+{
+  ChunkBitWriter writer;
+  guint i;
+  guint bits_per_chunks = 7 * symbol_size;
+
+  chunk_bit_writer_init (&writer, packet_chunks, symbol_size);
+
+  for (i = 0; i < twcc_packets->len; i++) {
+    RecvPacket *pkt = &g_array_index (twcc_packets, RecvPacket, i);
+    guint remaining_packets = twcc_packets->len - i;
+
+    /* we can only start a run-length chunk if the status-chunk is
+       completed */
+    if (chunk_bit_writer_is_empty (&writer)) {
+      /* first write in any preceeding gaps, we use run-length
+         if it would take up more than one chunk (14/7) */
+      if (pkt->missing_run > bits_per_chunks) {
+        rtp_twcc_write_run_length_chunk (packet_chunks,
+            RTP_TWCC_PACKET_STATUS_NOT_RECV, pkt->missing_run);
+      }
+
+      /* we have a run of the same status, write a run-length chunk and skip
+         to the next point */
+      if (pkt->missing_run == 0 &&
+          (pkt->equal_run > bits_per_chunks ||
+              pkt->equal_run == remaining_packets)) {
+        rtp_twcc_write_run_length_chunk (packet_chunks,
+            pkt->status, pkt->equal_run);
+        i += pkt->equal_run - 1;
+        continue;
+      }
+    }
+
+    GST_LOG ("i=%u: Writing a %u-bit vector of status: %u",
+        i, symbol_size, pkt->status);
+    rtp_twcc_write_status_vector_chunk (&writer, pkt);
+  }
+  chunk_bit_writer_flush (&writer);
+}
+
+static void
+rtp_twcc_manager_add_fci (RTPTWCCManager * twcc, GstRTCPPacket * packet)
+{
+  RecvPacket *first, *last, *prev;
+  guint16 packet_count;
+  GstClockTime base_time;
+  GstClockTime ts_rounded;
+  guint i;
+  GArray *packet_chunks = g_array_new (FALSE, FALSE, 2);
+  RTPTWCCHeader header;
+  guint header_size = sizeof (RTPTWCCHeader);
+  guint packet_chunks_size;
+  guint recv_deltas_size = 0;
+  guint16 fci_length;
+  guint16 fci_chunks;
+  guint8 *fci_data;
+  guint8 *fci_data_ptr;
+  RunLengthHelper rlh = { NULL };
+  guint symbol_size = 1;
+  GstClockTimeDiff delta_ts;
+  gint64 delta_ts_rounded;
+
+  g_array_sort (twcc->recv_packets, _twcc_seqnum_sort);
+
+  /* get first and last packet */
+  first = &g_array_index (twcc->recv_packets, RecvPacket, 0);
+  last =
+      &g_array_index (twcc->recv_packets, RecvPacket,
+      twcc->recv_packets->len - 1);
+
+  packet_count = last->seqnum - first->seqnum + 1;
+  base_time = first->ts / REF_TIME_UNIT;
+
+  GST_WRITE_UINT16_BE (header.base_seqnum, first->seqnum);
+  GST_WRITE_UINT16_BE (header.packet_count, packet_count);
+  GST_WRITE_UINT24_BE (header.base_time, base_time);
+  GST_WRITE_UINT8 (header.fb_pkt_count, twcc->fb_pkt_count);
+
+  base_time *= REF_TIME_UNIT;
+  ts_rounded = base_time;
+
+  GST_DEBUG ("Created TWCC feedback: base_seqnum: #%u, packet_count: %u, "
+      "base_time %" GST_TIME_FORMAT " fb_pkt_count: %u",
+      first->seqnum, packet_count, GST_TIME_ARGS (base_time),
+      twcc->fb_pkt_count);
+
+  twcc->fb_pkt_count++;
+  twcc->expected_recv_seqnum = first->seqnum + packet_count;
+
+  /* calculate all deltas and check for gaps etc */
+  prev = first;
+  for (i = 0; i < twcc->recv_packets->len; i++) {
+    RecvPacket *pkt = &g_array_index (twcc->recv_packets, RecvPacket, i);
+    if (i != 0) {
+      pkt->missing_run = pkt->seqnum - prev->seqnum - 1;
+    }
+
+    delta_ts = GST_CLOCK_DIFF (ts_rounded, pkt->ts);
+    pkt->delta = delta_ts / DELTA_UNIT;
+    delta_ts_rounded = pkt->delta * DELTA_UNIT;
+    ts_rounded += delta_ts_rounded;
+
+    if (delta_ts_rounded < 0 || delta_ts_rounded > MAX_TS_DELTA) {
+      pkt->status = RTP_TWCC_PACKET_STATUS_LARGE_NEGATIVE_DELTA;
+      recv_deltas_size += 2;
+      symbol_size = 2;
+    } else {
+      pkt->status = RTP_TWCC_PACKET_STATUS_SMALL_DELTA;
+      recv_deltas_size += 1;
+    }
+    run_lenght_helper_update (&rlh, pkt);
+
+    GST_LOG ("pkt: #%u, ts: %" GST_TIME_FORMAT
+        " ts_rounded: %" GST_TIME_FORMAT
+        " delta_ts: %" GST_STIME_FORMAT
+        " delta_ts_rounded: %" GST_STIME_FORMAT
+        " missing_run: %u, status: %u", pkt->seqnum,
+        GST_TIME_ARGS (pkt->ts), GST_TIME_ARGS (ts_rounded),
+        GST_STIME_ARGS (delta_ts), GST_STIME_ARGS (delta_ts_rounded),
+        pkt->missing_run, pkt->status);
+    prev = pkt;
+  }
+
+  rtp_twcc_write_chunks (packet_chunks, twcc->recv_packets, symbol_size);
+
+  packet_chunks_size = packet_chunks->len * 2;
+  fci_length = header_size + packet_chunks_size + recv_deltas_size;
+  fci_chunks = (fci_length - 1) / sizeof (guint32) + 1;
+
+  if (!gst_rtcp_packet_fb_set_fci_length (packet, fci_chunks)) {
+    GST_ERROR ("Could not fit: %u packets", packet_count);
+    g_assert_not_reached ();
+  }
+
+  fci_data = gst_rtcp_packet_fb_get_fci (packet);
+  fci_data_ptr = fci_data;
+
+  memcpy (fci_data_ptr, &header, header_size);
+  fci_data_ptr += header_size;
+
+  memcpy (fci_data_ptr, packet_chunks->data, packet_chunks_size);
+  fci_data_ptr += packet_chunks_size;
+
+  rtp_twcc_write_recv_deltas (fci_data_ptr, twcc->recv_packets);
+
+  GST_MEMDUMP ("twcc-header:", (guint8 *) & header, header_size);
+  GST_MEMDUMP ("packet-chunks:", (guint8 *) packet_chunks->data,
+      packet_chunks_size);
+  GST_MEMDUMP ("full fci:", fci_data, fci_length);
+
+  g_array_unref (packet_chunks);
+  g_array_set_size (twcc->recv_packets, 0);
+}
+
+static void
+rtp_twcc_manager_create_feedback (RTPTWCCManager * twcc)
+{
+  GstBuffer *buf;
+  GstRTCPBuffer rtcp = GST_RTCP_BUFFER_INIT;
+  GstRTCPPacket packet;
+
+  buf = gst_rtcp_buffer_new (twcc->mtu);
+
+  gst_rtcp_buffer_map (buf, GST_MAP_READWRITE, &rtcp);
+
+  gst_rtcp_buffer_add_packet (&rtcp, GST_RTCP_TYPE_RTPFB, &packet);
+
+  gst_rtcp_packet_fb_set_type (&packet, GST_RTCP_RTPFB_TYPE_TWCC);
+  if (twcc->recv_sender_ssrc != 1)
+    gst_rtcp_packet_fb_set_sender_ssrc (&packet, twcc->recv_sender_ssrc);
+  gst_rtcp_packet_fb_set_media_ssrc (&packet, twcc->recv_media_ssrc);
+
+  rtp_twcc_manager_add_fci (twcc, &packet);
+
+  gst_rtcp_buffer_unmap (&rtcp);
+
+  g_queue_push_tail (twcc->rtcp_buffers, buf);
+}
+
+/* we have calculated a (very pessimistic) max-packets per RTCP feedback,
+   so this is to make sure we don't exceed that */
+static gboolean
+_exceeds_max_packets (RTPTWCCManager * twcc, guint16 seqnum)
+{
+  RecvPacket *first, *last;
+  guint16 packet_count;
+
+  if (twcc->recv_packets->len == 0)
+    return FALSE;
+
+  /* find the delta betwen first stored packet and this seqnum */
+  first = &g_array_index (twcc->recv_packets, RecvPacket, 0);
+  packet_count = seqnum - first->seqnum + 1;
+  if (packet_count > twcc->max_packets_per_rtcp)
+    return TRUE;
+
+  /* then find the delta between last stored packet and this seqnum */
+  last =
+      &g_array_index (twcc->recv_packets, RecvPacket,
+      twcc->recv_packets->len - 1);
+  packet_count = seqnum - (last->seqnum + 1);
+  if (packet_count > twcc->max_packets_per_rtcp)
+    return TRUE;
+
+  return FALSE;
+}
+
+/* in this case we could have lost the packet with the marker bit,
+   so with a large (30) amount of packets, lost packets and still no marker,
+   we send a feedback anyway */
+static gboolean
+_many_packets_some_lost (RTPTWCCManager * twcc, guint16 seqnum)
+{
+  RecvPacket *first;
+  guint16 packet_count;
+  guint received_packets = twcc->recv_packets->len;
+  if (received_packets == 0)
+    return FALSE;
+
+  first = &g_array_index (twcc->recv_packets, RecvPacket, 0);
+  packet_count = seqnum - first->seqnum + 1;
+  /* packet-count larger than recevied-packets means we have lost packets */
+  if (packet_count >= 30 && packet_count > received_packets)
+    return TRUE;
+
+  return FALSE;
+}
+
+gboolean
+rtp_twcc_manager_recv_packet (RTPTWCCManager * twcc,
+    guint16 seqnum, RTPPacketInfo * pinfo)
+{
+  gboolean send_feedback = FALSE;
+  RecvPacket packet;
+  gint32 diff;
+
+  /* if this packet would exceed the capacity of our MTU, we create a feedback
+     with the current packets, and start over with this one */
+  if (_exceeds_max_packets (twcc, seqnum)) {
+    GST_INFO ("twcc-seqnum: %u would overflow max packets: %u, create feedback"
+        " with current packets", seqnum, twcc->max_packets_per_rtcp);
+    rtp_twcc_manager_create_feedback (twcc);
+    send_feedback = TRUE;
+  }
+
+  /* we can have multiple ssrcs here, so just pick the first one */
+  if (twcc->recv_media_ssrc == -1)
+    twcc->recv_media_ssrc = pinfo->ssrc;
+
+  /* check if we are reordered, and treat it as lost if we already sent
+     a feedback msg with a higher seqnum. If the diff is huge, treat
+     it as a restart of a stream */
+  diff = (gint32) seqnum - (gint32) twcc->expected_recv_seqnum;
+  if (twcc->fb_pkt_count > 0 && diff < 0 && diff > -1000) {
+    GST_INFO ("Received out of order packet (%u after %u), treating as lost",
+        seqnum, twcc->expected_recv_seqnum);
+    return FALSE;
+  }
+
+  /* store the packet for Transport-wide RTCP feedback message */
+  recv_packet_init (&packet, seqnum, pinfo);
+  g_array_append_val (twcc->recv_packets, packet);
+  twcc->last_seqnum = seqnum;
+  GST_LOG ("Receive: twcc-seqnum: %u, marker: %d, ts: %" GST_TIME_FORMAT,
+      seqnum, pinfo->marker, GST_TIME_ARGS (pinfo->running_time));
+
+  if (pinfo->marker || _many_packets_some_lost (twcc, seqnum)) {
+    rtp_twcc_manager_create_feedback (twcc);
+    send_feedback = TRUE;
+  }
+
+  return send_feedback;
+}
+
+static void
+_change_rtcp_fb_sender_ssrc (GstBuffer * buf, guint32 sender_ssrc)
+{
+  GstRTCPBuffer rtcp = GST_RTCP_BUFFER_INIT;
+  GstRTCPPacket packet;
+  gst_rtcp_buffer_map (buf, GST_MAP_READWRITE, &rtcp);
+  gst_rtcp_buffer_get_first_packet (&rtcp, &packet);
+  gst_rtcp_packet_fb_set_sender_ssrc (&packet, sender_ssrc);
+  gst_rtcp_buffer_unmap (&rtcp);
+}
+
+GstBuffer *
+rtp_twcc_manager_get_feedback (RTPTWCCManager * twcc, guint sender_ssrc)
+{
+  GstBuffer *buf;
+  buf = g_queue_pop_head (twcc->rtcp_buffers);
+
+  if (buf && twcc->recv_sender_ssrc != sender_ssrc) {
+    _change_rtcp_fb_sender_ssrc (buf, sender_ssrc);
+    twcc->recv_sender_ssrc = sender_ssrc;
+  }
+
+  return buf;
+}
+
+static void
+sent_packet_init (SentPacket * packet, guint16 seqnum, RTPPacketInfo * pinfo)
+{
+  packet->seqnum = seqnum;
+  packet->ts = pinfo->running_time;
+  packet->size = pinfo->payload_len;
+  packet->remote_ts = GST_CLOCK_TIME_NONE;
+  packet->socket_ts = GST_CLOCK_TIME_NONE;
+  packet->lost = FALSE;
+}
+
+void
+rtp_twcc_manager_send_packet (RTPTWCCManager * twcc,
+    guint16 seqnum, RTPPacketInfo * pinfo)
+{
+  SentPacket packet;
+  sent_packet_init (&packet, seqnum, pinfo);
+  g_array_append_val (twcc->sent_packets, packet);
+
+  GST_LOG ("Send: twcc-seqnum: %u, marker: %d, ts: %" GST_TIME_FORMAT,
+      seqnum, pinfo->marker, GST_TIME_ARGS (pinfo->running_time));
+}
+
+void
+rtp_twcc_manager_set_send_packet_ts (RTPTWCCManager * twcc,
+    guint packet_id, GstClockTime ts)
+{
+  SentPacket *pkt = NULL;
+  pkt = &g_array_index (twcc->sent_packets, SentPacket, packet_id);
+  if (pkt) {
+    pkt->socket_ts = ts;
+    GST_DEBUG ("assigning: pkt-id: %u to packet: %u", packet_id, pkt->seqnum);
+  }
+}
+
+static void
+_add_twcc_packet (GArray * twcc_packets, guint16 seqnum, guint status)
+{
+  RTPTWCCPacket packet;
+  memset (&packet, 0, sizeof (RTPTWCCPacket));
+  packet.local_ts = GST_CLOCK_TIME_NONE;
+  packet.remote_ts = GST_CLOCK_TIME_NONE;
+  packet.local_delta = GST_CLOCK_STIME_NONE;
+  packet.remote_delta = GST_CLOCK_STIME_NONE;
+  packet.delta_delta = GST_CLOCK_STIME_NONE;
+  packet.seqnum = seqnum;
+  packet.status = status;
+  g_array_append_val (twcc_packets, packet);
+}
+
+static guint
+_parse_run_length_chunk (GstBitReader * reader, GArray * twcc_packets,
+    guint16 seqnum_offset, guint remaining_packets)
+{
+  guint run_length;
+  guint8 status_code;
+  guint i;
+
+  gst_bit_reader_get_bits_uint8 (reader, &status_code, 2);
+
+  run_length = *(guint16 *) reader->data & ~0xE0;       /* mask out the 3 last bits */
+  run_length = MIN (remaining_packets, GST_READ_UINT16_BE (&run_length));
+
+  for (i = 0; i < run_length; i++) {
+    _add_twcc_packet (twcc_packets, seqnum_offset + i, status_code);
+  }
+
+  return run_length;
+}
+
+static guint
+_parse_status_vector_chunk (GstBitReader * reader, GArray * twcc_packets,
+    guint16 seqnum_offset, guint remaining_packets)
+{
+  guint8 symbol_size;
+  guint num_bits;
+  guint i;
+
+  gst_bit_reader_get_bits_uint8 (reader, &symbol_size, 1);
+  symbol_size += 1;
+  num_bits = MIN (remaining_packets, 14 / symbol_size);
+
+  for (i = 0; i < num_bits; i++) {
+    guint8 status_code;
+    if (gst_bit_reader_get_bits_uint8 (reader, &status_code, symbol_size))
+      _add_twcc_packet (twcc_packets, seqnum_offset + i, status_code);
+  }
+
+  return num_bits;
+}
+
+/* Remove all locally stored packets that has been reported
+   back to us */
+static void
+_prune_sent_packets (RTPTWCCManager * twcc, GArray * twcc_packets)
+{
+  SentPacket *first;
+  RTPTWCCPacket *last;
+  guint16 last_idx;
+
+  if (twcc_packets->len == 0 || twcc->sent_packets->len == 0)
+    return;
+
+  first = &g_array_index (twcc->sent_packets, SentPacket, 0);
+  last = &g_array_index (twcc_packets, RTPTWCCPacket, twcc_packets->len - 1);
+
+  last_idx = last->seqnum - first->seqnum;
+
+  if (last_idx < twcc->sent_packets->len)
+    g_array_remove_range (twcc->sent_packets, 0, last_idx);
+}
+
+static void
+_check_for_lost_packets (RTPTWCCManager * twcc, GArray * twcc_packets,
+    guint16 base_seqnum, guint16 packet_count, guint8 fb_pkt_count)
+{
+  guint packets_lost;
+  guint i;
+
+  /* first packet */
+  if (twcc->first_fci_parse) {
+    twcc->first_fci_parse = FALSE;
+    goto done;
+  }
+
+  /* we have gone backwards, don't reset the expectations,
+     but process the packet nonetheless */
+  if (fb_pkt_count < twcc->expected_parsed_fb_pkt_count) {
+    GST_WARNING ("feedback packet count going backwards (%u < %u)",
+        fb_pkt_count, twcc->expected_parsed_fb_pkt_count);
+    return;
+  }
+
+  /* we have jumped forwards, reset expectations, but don't trigger
+     lost packets in case the missing fb-packet(s) arrive later */
+  if (fb_pkt_count > twcc->expected_parsed_fb_pkt_count) {
+    GST_WARNING ("feedback packet count jumped ahead (%u > %u)",
+        fb_pkt_count, twcc->expected_parsed_fb_pkt_count);
+    goto done;
+  }
+
+  packets_lost = base_seqnum - twcc->expected_parsed_seqnum;
+  for (i = 0; i < packets_lost; i++) {
+    _add_twcc_packet (twcc_packets, twcc->expected_parsed_seqnum + i,
+        RTP_TWCC_PACKET_STATUS_NOT_RECV);
+  }
+
+done:
+  twcc->expected_parsed_seqnum = base_seqnum + packet_count;
+  twcc->expected_parsed_fb_pkt_count = fb_pkt_count + 1;
+  return;
+}
+
+GArray *
+rtp_twcc_manager_parse_fci (RTPTWCCManager * twcc,
+    guint8 * fci_data, guint fci_length)
+{
+  GArray *twcc_packets;
+  guint16 base_seqnum;
+  guint16 packet_count;
+  GstClockTime base_time;
+  GstClockTime ts_rounded;
+  guint8 fb_pkt_count;
+  guint packets_parsed = 0;
+  guint fci_parsed;
+  guint i;
+  SentPacket *first_sent_pkt = NULL;
+
+  if (fci_length < 10) {
+    GST_WARNING ("Malformed TWCC RTCP feedback packet");
+    return NULL;
+  }
+
+  base_seqnum = GST_READ_UINT16_BE (&fci_data[0]);
+  packet_count = GST_READ_UINT16_BE (&fci_data[2]);
+  base_time = GST_READ_UINT24_BE (&fci_data[4]) * REF_TIME_UNIT;
+  fb_pkt_count = fci_data[7];
+
+  GST_DEBUG ("Parsed TWCC feedback: base_seqnum: #%u, packet_count: %u, "
+      "base_time %" GST_TIME_FORMAT " fb_pkt_count: %u",
+      base_seqnum, packet_count, GST_TIME_ARGS (base_time), fb_pkt_count);
+
+  twcc_packets = g_array_sized_new (FALSE, FALSE,
+      sizeof (RTPTWCCPacket), packet_count);
+
+  _check_for_lost_packets (twcc, twcc_packets,
+      base_seqnum, packet_count, fb_pkt_count);
+
+  fci_parsed = 8;
+  while (packets_parsed < packet_count && (fci_parsed + 1) < fci_length) {
+    GstBitReader reader = GST_BIT_READER_INIT (&fci_data[fci_parsed], 2);
+    guint8 chunk_type;
+    guint seqnum_offset = base_seqnum + packets_parsed;
+    guint remaining_packets = packet_count - packets_parsed;
+
+    gst_bit_reader_get_bits_uint8 (&reader, &chunk_type, 1);
+
+    if (chunk_type == RTP_TWCC_CHUNK_TYPE_RUN_LENGTH) {
+      packets_parsed += _parse_run_length_chunk (&reader,
+          twcc_packets, seqnum_offset, remaining_packets);
+    } else {
+      packets_parsed += _parse_status_vector_chunk (&reader,
+          twcc_packets, seqnum_offset, remaining_packets);
+    }
+    fci_parsed += 2;
+  }
+
+  if (twcc->sent_packets->len > 0)
+    first_sent_pkt = &g_array_index (twcc->sent_packets, SentPacket, 0);
+
+  ts_rounded = base_time;
+  for (i = 0; i < twcc_packets->len; i++) {
+    RTPTWCCPacket *pkt = &g_array_index (twcc_packets, RTPTWCCPacket, i);
+    gint16 delta = 0;
+    GstClockTimeDiff delta_ts;
+
+    if (pkt->status == RTP_TWCC_PACKET_STATUS_SMALL_DELTA) {
+      delta = fci_data[fci_parsed];
+      fci_parsed += 1;
+    } else if (pkt->status == RTP_TWCC_PACKET_STATUS_LARGE_NEGATIVE_DELTA) {
+      delta = GST_READ_UINT16_BE (&fci_data[fci_parsed]);
+      fci_parsed += 2;
+    }
+
+    if (fci_parsed > fci_length) {
+      GST_WARNING ("Malformed TWCC RTCP feedback packet");
+      g_array_set_size (twcc_packets, 0);
+      break;
+    }
+
+    if (pkt->status != RTP_TWCC_PACKET_STATUS_NOT_RECV) {
+      delta_ts = delta * DELTA_UNIT;
+      ts_rounded += delta_ts;
+      pkt->remote_ts = ts_rounded;
+
+      GST_LOG ("pkt: #%u, remote_ts: %" GST_TIME_FORMAT
+          " delta_ts: %" GST_STIME_FORMAT
+          " status: %u", pkt->seqnum,
+          GST_TIME_ARGS (pkt->remote_ts), GST_STIME_ARGS (delta_ts),
+          pkt->status);
+    }
+
+    if (first_sent_pkt) {
+      SentPacket *found = NULL;
+      guint16 sent_idx = pkt->seqnum - first_sent_pkt->seqnum;
+      if (sent_idx < twcc->sent_packets->len)
+        found = &g_array_index (twcc->sent_packets, SentPacket, sent_idx);
+      if (found && found->seqnum == pkt->seqnum) {
+        if (GST_CLOCK_TIME_IS_VALID (found->socket_ts)) {
+          pkt->local_ts = found->socket_ts;
+        } else {
+          pkt->local_ts = found->ts;
+        }
+        pkt->size = found->size;
+
+        GST_LOG ("matching pkt: #%u with local_ts: %" GST_TIME_FORMAT
+            " size: %u", pkt->seqnum, GST_TIME_ARGS (pkt->local_ts), pkt->size);
+      }
+    }
+  }
+
+  _prune_sent_packets (twcc, twcc_packets);
+
+  return twcc_packets;
+}
diff --git a/gst/rtpmanager/rtptwcc.h b/gst/rtpmanager/rtptwcc.h
new file mode 100644 (file)
index 0000000..50da704
--- /dev/null
@@ -0,0 +1,72 @@
+/* GStreamer
+ * Copyright (C) 2019 Pexip (http://pexip.com/)
+ *   @author: Havard Graff <havard@pexip.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.
+ */
+
+#ifndef __RTP_TWCC_H__
+#define __RTP_TWCC_H__
+
+#include <gst/gst.h>
+#include <gst/rtp/rtp.h>
+#include "rtpstats.h"
+
+typedef struct _RTPTWCCPacket RTPTWCCPacket;
+typedef enum _RTPTWCCPacketStatus RTPTWCCPacketStatus;
+
+G_DECLARE_FINAL_TYPE (RTPTWCCManager, rtp_twcc_manager, RTP, TWCC_MANAGER, GObject)
+#define RTP_TYPE_TWCC_MANAGER (rtp_twcc_manager_get_type())
+#define RTP_TWCC_MANAGER_CAST(obj) ((RTPTWCCManager *)(obj))
+
+enum _RTPTWCCPacketStatus
+{
+  RTP_TWCC_PACKET_STATUS_NOT_RECV = 0,
+  RTP_TWCC_PACKET_STATUS_SMALL_DELTA = 1,
+  RTP_TWCC_PACKET_STATUS_LARGE_NEGATIVE_DELTA = 2,
+};
+
+struct _RTPTWCCPacket
+{
+  GstClockTime local_ts;
+  GstClockTime remote_ts;
+  GstClockTimeDiff local_delta;
+  GstClockTimeDiff remote_delta;
+  GstClockTimeDiff delta_delta;
+  RTPTWCCPacketStatus status;
+  guint16 seqnum;
+  guint size;
+};
+
+RTPTWCCManager * rtp_twcc_manager_new (guint mtu);
+
+void rtp_twcc_manager_set_mtu (RTPTWCCManager * twcc, guint mtu);
+
+gboolean rtp_twcc_manager_recv_packet (RTPTWCCManager * twcc,
+    guint16 seqnum, RTPPacketInfo * pinfo);
+
+void rtp_twcc_manager_send_packet (RTPTWCCManager * twcc,
+    guint16 seqnum, RTPPacketInfo * pinfo);
+void rtp_twcc_manager_set_send_packet_ts (RTPTWCCManager * twcc,
+    guint packet_id, GstClockTime ts);
+
+GstBuffer * rtp_twcc_manager_get_feedback (RTPTWCCManager * twcc,
+    guint32 sender_ssrc);
+
+GArray * rtp_twcc_manager_parse_fci (RTPTWCCManager * twcc,
+    guint8 * fci_data, guint fci_length);
+
+#endif /* __RTP_TWCC_H__ */
index 4af5b0b..098a955 100644 (file)
@@ -82,6 +82,7 @@ export CFLAGS+=" \
        -DTIZEN_FEATURE_QTDEMUX_MODIFICATION\
        -DTIZEN_FEATURE_FLVDEMUX_MODIFICATION\
        -DTIZEN_FEATURE_GST_UPSTREAM\
+       -DTIZEN_FEATURE_GST_UPSTREAM_AVOID_BUILD_BREAK\
        -DTIZEN_FEATURE_RTSP_MODIFICATION\
        -DTIZEN_FEATURE_GST_MUX_ENHANCEMENT\
        -DTIZEN_FEATURE_SOUP_MODIFICATION\