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