rtpsource.c \
rtpstats.c \
gstrtpsession.c \
- gstrtpfunnel.c
+ gstrtpfunnel.c \
+ rtptimerqueue.c \
+ rtptwcc.c
noinst_HEADERS = gstrtpbin.h \
gstrtpdtmfmux.h \
rtpsource.h \
rtpstats.h \
gstrtpsession.h \
- gstrtpfunnel.h
+ gstrtpfunnel.h \
+ rtptimerqueue.h \
+ rtptwcc.h
libgstrtpmanager_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) \
- /* 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,
* 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
SIGNAL_REQUEST_FEC_DECODER,
SIGNAL_REQUEST_FEC_ENCODER,
+ SIGNAL_REQUEST_JITTERBUFFER,
+
SIGNAL_NEW_JITTERBUFFER,
SIGNAL_NEW_STORAGE,
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).
/* 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);
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);
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);
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);
}
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)
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);
}
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 (¤t);
- 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)
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);
/* 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
/* 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. */
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;
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) {
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;
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 */
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);
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) {
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);
+ }
}
/* ERRORS */
max_streams:
{
- GST_WARNING_OBJECT (rtpbin, "stream exeeds maximum (%d)",
+ GST_WARNING_OBJECT (rtpbin, "stream exceeds maximum (%d)",
rtpbin->max_streams);
return NULL;
}
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);
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:
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:
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:
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:
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:
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:
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
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
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
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
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:
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
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
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
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:
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:
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:
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:
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:
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);
/**
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);
/**
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);
/**
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:
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:
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:
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:
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
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",
*
* 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,
"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);
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
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) {
#else
GST_DEBUG_OBJECT (bin, "stream %p percent %d", stream,
stream->percent);
+
#endif
/* find min percent */
if (min_percent > stream->percent)
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)) {
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);
}
}
-/* 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,
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],
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
/* 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:
/* 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:
/* 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:
/* 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:
}
/* 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)
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);
};
/**
* SECTION:element-rtpdtmfmux
+ * @title: rtpdtmfmux
* @see_also: rtpdtmfsrc, dtmfsrc, rtpmux
*
* The RTP "DTMF" Muxer muxes multiple RTP streams into a valid RTP
* 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;
{
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;
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;
};
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;
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;
}
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)
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;
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;
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;
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:
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,
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:
/**
* 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>
#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
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,
#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
{
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;
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;
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;
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",
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);
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)
"Send an event downstream when a packet is lost", DEFAULT_DO_LOST,
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:
*
-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.
"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
"(-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.
* Various jitterbuffer statistics. This property returns a GstStructure
* with name application/x-rtp-jitterbuffer-stats with the following fields:
*
- * <itemizedlist>
- * <listitem>
- * <para>
- * #guint64
- * <classname>"num-pushed"</classname>:
- * the number of packets pushed out.
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * #guint64
- * <classname>"num-lost"</classname>:
- * the number of packets considered lost.
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * #guint64
- * <classname>"num-late"</classname>:
- * the number of packets arriving too late.
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * #guint64
- * <classname>"num-duplicates"</classname>:
- * the number of duplicate packets.
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * #guint64
- * <classname>"rtx-count"</classname>:
- * the number of retransmissions requested.
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * #guint64
- * <classname>"rtx-success-count"</classname>:
- * the number of successful retransmissions.
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * #gdouble
- * <classname>"rtx-per-packet"</classname>:
- * average number of RTX per packet.
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * #guint64
- * <classname>"rtx-rtt"</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
*/
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
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);
/**
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:
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:
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);
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
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;
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);
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);
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
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);
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
*/
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");
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);
queue_event (GstRtpJitterBuffer * jitterbuffer, GstEvent * event)
{
GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
- RTPJitterBufferItem *item;
gboolean head;
switch (GST_EVENT_TYPE (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);
}
/*
- * 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.
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)
{
priv->ts_offset += priv->ts_offset_remainder;
priv->ts_offset_remainder = 0;
}
+
+ update_timer_offsets (jitterbuffer);
}
}
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)
{
}
}
-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 */
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 {
* 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;
}
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
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);
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);
}
}
}
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);
}
}
}
}
+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");
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;
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
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++;
+ }
}
}
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;
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;
*/
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);
{
GstRtpJitterBufferPrivate *priv;
RTPJitterBufferItem *item;
- TimerData *timer;
+ RtpTimer *timer;
priv = jitterbuffer->priv;
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,
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;
}
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);
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);
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);
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) {
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);
/* 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 */
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
* 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) {
/* 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 */
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. */
/* 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;
}
}
}
}
- 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
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 */
* 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;
}
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)
/* 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,
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;
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;
}
GST_DEBUG_OBJECT (jitterbuffer, "Duplicate packet #%d detected, dropping",
seqnum);
priv->num_duplicates++;
- free_item (item);
goto finished;
}
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
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;
}
}
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);
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);
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);
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
"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 {
}
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;
/* 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;
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));
"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++;
/* 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;
}
static gboolean
-do_deadline_timeout (GstRtpJitterBuffer * jitterbuffer, TimerData * timer,
+do_deadline_timeout (GstRtpJitterBuffer * jitterbuffer, RtpTimer * timer,
GstClockTime now)
{
GstRtpJitterBufferPrivate *priv = jitterbuffer->priv;
* 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.
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
/* 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);
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");
}
/*
- * 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.
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;
}
}
}
-/* 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
}
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;
} 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);
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));
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));
/**
* 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 ! \
* 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
} GstRTPMuxPadPrivate;
-/**
- * GstRTPMux:
+/* GstRTPMux:
*
* The opaque #GstRTPMux structure.
*/
-/*
+/*
* 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>
+ *
*/
/*
*/
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;
};
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:
* @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:
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;
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);
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;
}
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;
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;
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);
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);
}
/* 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);
}
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
/* 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 */
/**
* 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" ! \
* 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
/**
* SECTION:element-rtprtxreceive
+ * @title: rtprtxreceive
* @see_also: rtprtxsend, rtpsession, rtpjitterbuffer
*
* rtprtxreceive listens to the retransmission events from the
* 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 ! \
* 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" ! \
* 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
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;
* 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);
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;
/**
* 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
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",
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,
" 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);
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);
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;
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;
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");
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;
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;
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;
/* 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;
/**
* 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.
* 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)
{
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
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 */
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,
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
};
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
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:
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
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
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
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
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:
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
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
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
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:
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:
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",
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",
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
"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,
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,
"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
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);
}
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;
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 (¤t);
- 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)
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;
}
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)
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");
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));
}
}
}
+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)
"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,
}
}
+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)
{
/**
* 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
RTCP_PAD
} PadType;
+#define DEFAULT_MAX_STREAMS G_MAXUINT
+enum
+{
+ PROP_0,
+ PROP_MAX_STREAMS
+};
+
/* signals */
enum
{
gchar *padname;
GstRtpSsrcDemuxPad *demuxpad;
GstPad *retpad;
+ guint num_streams;
INTERNAL_STREAM_LOCK (demux);
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);
return retpad;
}
+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)
{
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:
* @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:
* @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:
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);
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);
}
/* 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;
}
}
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);
/* 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:
{
}
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;
}
}
GRecMutex padlock;
GSList *srcpads;
+ guint max_streams;
};
struct _GstRtpSsrcDemuxClass
'rtpsession.c',
'rtpsource.c',
'rtpstats.c',
+ 'rtptimerqueue.c',
+ 'rtptwcc.c',
'gstrtpsession.c',
'gstrtpfunnel.c',
]
install_dir : plugins_install_dir,
)
pkgconfig.generate(gstrtpmanager, install_dir : plugins_pkgconfig_install_dir)
+plugins += [gstrtpmanager]
{
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);
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);
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;
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;
* 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))
*
*/
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;
/* 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 */
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)) {
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)) {
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;
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;
+ }
}
}
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;
+ }
}
}
/* 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);
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,
/* 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
}
}
- 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;
}
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)
*
* 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)
{
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)
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
g_return_val_if_fail (jbuf != NULL, NULL);
- queue = jbuf->packets;
+ queue = &jbuf->packets;
item = queue->head;
if (item) {
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;
}
{
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.
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);
}
{
g_return_val_if_fail (jbuf != NULL, 0);
- return jbuf->packets->length;
+ return jbuf->packets.length;
}
/**
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;
}
-/**
+/*
* rtp_jitter_buffer_get_seqnum_diff:
* @jbuf: an #RTPJitterBuffer
*
*
* Returns: The difference expressed in seqnum.
*/
-guint16
+static guint16
rtp_jitter_buffer_get_seqnum_diff (RTPJitterBuffer * jbuf)
{
guint32 high_seqnum, low_seqnum;
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;
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);
+}
struct _RTPJitterBuffer {
GObject object;
- GQueue *packets;
+ GQueue packets;
RTPJitterBufferMode mode;
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
* 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);
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);
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__ */
#define GLIB_DISABLE_DEPRECATION_WARNINGS
#include <string.h>
+#include <stdlib.h>
#include <gst/rtp/gstrtpbuffer.h>
#include <gst/rtp/gstrtcpbuffer.h>
#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 */
(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,
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);
/**
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
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
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
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
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
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
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
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
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
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);
/**
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:
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:
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:
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
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);
/**
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:
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);
/**
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",
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",
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;
* }
* g_value_array_free (arr);
* }
- * </programlisting>
- * </example>
+ * ```
*/
g_object_class_install_property (gobject_class, PROP_SOURCES,
g_param_spec_boxed ("sources", "Sources",
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
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
- * RTPSession::disable-sr-timestamp:
+ * RTPSession:disable-sr-timestamp:
*
* Whether sender reports should be timestamped.
*
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;
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
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);
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));
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;
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,
*/
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);
}
}
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);
}
pinfo->bytes = 0;
pinfo->payload_len = 0;
pinfo->packets = 0;
+ pinfo->marker = FALSE;
if (is_list) {
GstBufferList *list = GST_BUFFER_LIST_CAST (data);
GstBuffer *buffer = GST_BUFFER_CAST (data);
res = update_packet (&buffer, 0, pinfo);
}
+
return res;
}
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
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)
/* 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))
}
}
+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)
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) {
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;
}
}
}
+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
} 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
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;
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);
return FALSE;
}
+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)
{
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
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);
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);
+ }
+}
#include <gst/gst.h>
#include "rtpsource.h"
+#include "rtptwcc.h"
typedef struct _RTPSession RTPSession;
typedef struct _RTPSessionClass RTPSessionClass;
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
* @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
*
RTPSessionRequestKeyUnit request_key_unit;
RTPSessionRequestTime request_time;
RTPSessionNotifyNACK notify_nack;
+ RTPSessionNotifyTWCC notify_twcc;
RTPSessionReconfigure reconfigure;
RTPSessionNotifyEarlyRTCP notify_early_rtcp;
} RTPSessionCallbacks;
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;
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;
};
/**
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);
guint16 seqnum,
GstClockTime max_delay);
+void rtp_session_update_recv_caps_structure (RTPSession * sess, const GstStructure * s);
+
#endif /* __RTP_SESSION_H__ */
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:
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.
*
* 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,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
- * RTPSession::disable-rtcp:
+ * RTPSource:disable-rtcp:
*
* Allow disabling the sending of RTCP packets for this source.
*/
"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,
"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,
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 */
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
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;
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;
}
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.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);
}
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);
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;
* @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.
*/
/* 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;
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) {
* 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)
guint64 new_ts, diff_ts;
gint diff_seqnum;
gint32 new_packet_rate;
+ gint32 base;
if (ctx->clock_rate <= 0) {
return ctx->avg_packet_rate;
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);
* 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;
}
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;
}
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;
}
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);
+}
#include <gst/rtp/rtp.h>
#include <gio/gio.h>
+/* UDP/IP is assumed for bandwidth calculation */
+#define UDP_IP_HEADER_OVERHEAD 28
+
/**
* RTPSenderReport:
*
* @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.
*/
guint16 seqnum;
guint8 pt;
guint32 rtptime;
+ gboolean marker;
guint32 csrc_count;
guint32 csrcs[16];
+ GBytes *header_ext;
+ guint16 header_ext_bit_pattern;
} RTPPacketInfo;
/**
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,
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__ */
--- /dev/null
+/* 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 (©->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;
+}
--- /dev/null
+/* 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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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__ */
-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\