gstreamer/subprojects/gst-plugins-good/gst/rtpmanager/gstrtpbin.c
Sebastian Dröge 8bfba72ea4 rtpbin: Add new never/ntp RTCP sync modes
Never is useful for some RTSP servers that report plain garbage both via
RTCP SR and RTP-Info, for example.

NTP is useful if synchronization should only ever happen based on RTCP
SR or NTP-64 RTP header extension.

Also slightly change the behaviour of always/initial to take RTP-Info
based synchronization into account too. It's supposed to give the same
values as the RTCP SR and is available earlier, so will generally cause
fewer synchronization glitches if it's made use of.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6543>
2024-05-28 11:52:30 +00:00

5913 lines
187 KiB
C

/* GStreamer
* Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-rtpbin
* @title: rtpbin
* @see_also: rtpjitterbuffer, rtpsession, rtpptdemux, rtpssrcdemux
*
* RTP bin combines the functions of #GstRtpSession, #GstRtpSsrcDemux,
* #GstRtpJitterBuffer and #GstRtpPtDemux in one element. It allows for multiple
* RTP sessions that will be synchronized together using RTCP SR packets.
*
* #GstRtpBin is configured with a number of request pads that define the
* functionality that is activated, similar to the #GstRtpSession element.
*
* To use #GstRtpBin as an RTP receiver, request a recv_rtp_sink_\%u pad. The session
* number must be specified in the pad name.
* Data received on the recv_rtp_sink_\%u pad will be processed in the #GstRtpSession
* manager and after being validated forwarded on #GstRtpSsrcDemux element. Each
* RTP stream is demuxed based on the SSRC and send to a #GstRtpJitterBuffer. After
* the packets are released from the jitterbuffer, they will be forwarded to a
* #GstRtpPtDemux element. The #GstRtpPtDemux element will demux the packets based
* on the payload type and will create a unique pad recv_rtp_src_\%u_\%u_\%u on
* rtpbin with the session number, SSRC and payload type respectively as the pad
* name.
*
* To also use #GstRtpBin as an RTCP receiver, request a recv_rtcp_sink_\%u pad. The
* session number must be specified in the pad name.
*
* If you want the session manager to generate and send RTCP packets, request
* the send_rtcp_src_\%u pad with the session number in the pad name. Packet pushed
* on this pad contain SR/RR RTCP reports that should be sent to all participants
* in the session.
*
* 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 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
* and will signal the #GstRtpSession::request-pt-map signal when it needs such a
* mapping. One can clear the cached values with the #GstRtpSession::clear-pt-map
* signal.
*
* Access to the internal statistics of rtpbin is provided with the
* get-internal-session property. This action signal gives access to the
* RTPSession object which further provides action signals to retrieve the
* internal source and other sources.
*
* #GstRtpBin also has signals (#GstRtpBin::request-rtp-encoder,
* #GstRtpBin::request-rtp-decoder, #GstRtpBin::request-rtcp-encoder and
* #GstRtpBin::request-rtp-decoder) to dynamically request for RTP and RTCP encoders
* and decoders in order to support SRTP. The encoders must provide the pads
* rtp_sink_\%u and rtp_src_\%u for RTP and rtcp_sink_\%u and rtcp_src_\%u for
* RTCP. The session number will be used in the pad name. The decoders must provide
* rtp_sink and rtp_src for RTP and rtcp_sink and rtcp_src for RTCP. The decoders will
* be placed before the #GstRtpSession element, thus they must support SSRC demuxing
* internally.
*
* #GstRtpBin has signals (#GstRtpBin::request-aux-sender and
* #GstRtpBin::request-aux-receiver to dynamically request an element that can be
* used to create or merge additional RTP streams. AUX elements are needed to
* implement FEC or retransmission (such as RFC 4588). An AUX sender must have one
* sink_\%u pad that matches the sessionid in the signal and it should have 1 or
* more src_\%u pads. For each src_%\u pad, a session will be made (if needed)
* and the pad will be linked to the session send_rtp_sink pad. Each session will
* then expose its source pad as send_rtp_src_\%u on #GstRtpBin.
* 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
*
* |[
* gst-launch-1.0 udpsrc port=5000 caps="application/x-rtp, ..." ! .recv_rtp_sink_0 \
* rtpbin ! rtptheoradepay ! theoradec ! xvimagesink
* ]| Receive RTP data from port 5000 and send to the session 0 in rtpbin.
* |[
* gst-launch-1.0 rtpbin name=rtpbin \
* v4l2src ! videoconvert ! ffenc_h263 ! rtph263ppay ! rtpbin.send_rtp_sink_0 \
* rtpbin.send_rtp_src_0 ! udpsink port=5000 \
* rtpbin.send_rtcp_src_0 ! udpsink port=5001 sync=false async=false \
* udpsrc port=5005 ! rtpbin.recv_rtcp_sink_0 \
* audiotestsrc ! amrnbenc ! rtpamrpay ! rtpbin.send_rtp_sink_1 \
* rtpbin.send_rtp_src_1 ! udpsink port=5002 \
* rtpbin.send_rtcp_src_1 ! udpsink port=5003 sync=false async=false \
* udpsrc port=5007 ! rtpbin.recv_rtcp_sink_1
* ]| Encode and payload H263 video captured from a v4l2src. Encode and payload AMR
* audio generated from audiotestsrc. The video is sent to session 0 in rtpbin
* and the audio is sent to session 1. Video packets are sent on UDP port 5000
* and audio packets on port 5002. The video RTCP packets for session 0 are sent
* on port 5001 and the audio RTCP packets for session 0 are sent on port 5003.
* RTCP packets for session 0 are received on port 5005 and RTCP for session 1
* is received on port 5007. Since RTCP packets from the sender should be sent
* as soon as possible and do not participate in preroll, sync=false and
* async=false is configured on udpsink
* |[
* gst-launch-1.0 -v rtpbin name=rtpbin \
* udpsrc caps="application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=(string)H263-1998" \
* port=5000 ! rtpbin.recv_rtp_sink_0 \
* rtpbin. ! rtph263pdepay ! ffdec_h263 ! xvimagesink \
* udpsrc port=5001 ! rtpbin.recv_rtcp_sink_0 \
* rtpbin.send_rtcp_src_0 ! udpsink port=5005 sync=false async=false \
* udpsrc caps="application/x-rtp,media=(string)audio,clock-rate=(int)8000,encoding-name=(string)AMR,encoding-params=(string)1,octet-align=(string)1" \
* port=5002 ! rtpbin.recv_rtp_sink_1 \
* rtpbin. ! rtpamrdepay ! amrnbdec ! alsasink \
* udpsrc port=5003 ! rtpbin.recv_rtcp_sink_1 \
* rtpbin.send_rtcp_src_1 ! udpsink port=5007 sync=false async=false
* ]| Receive H263 on port 5000, send it through rtpbin in session 0, depayload,
* decode and display the video.
* Receive AMR on port 5002, send it through rtpbin in session 1, depayload,
* decode and play the audio.
* Receive server RTCP packets for session 0 on port 5001 and RTCP packets for
* session 1 on port 5003. These packets will be used for session management and
* synchronisation.
* Send RTCP reports for session 0 on port 5005 and RTCP reports for session 1
* on port 5007.
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <string.h>
#include <gst/rtp/gstrtpbuffer.h>
#include <gst/rtp/gstrtcpbuffer.h>
#include "gstrtpbin.h"
#include "rtpsession.h"
#include "gstrtpsession.h"
#include "gstrtpjitterbuffer.h"
#include "gstrtputils.h"
#include <gst/glib-compat-private.h>
GST_DEBUG_CATEGORY_STATIC (gst_rtp_bin_debug);
#define GST_CAT_DEFAULT gst_rtp_bin_debug
/* sink pads */
static GstStaticPadTemplate rtpbin_recv_rtp_sink_template =
GST_STATIC_PAD_TEMPLATE ("recv_rtp_sink_%u",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("application/x-rtp;application/x-srtp")
);
/**
* GstRtpBin!recv_fec_sink_%u_%u:
*
* Sink template for receiving Forward Error Correction packets,
* in the form recv_fec_sink_<session_idx>_<fec_stream_idx>
*
* See #GstRTPST_2022_1_FecDec for example usage
*
* Since: 1.20
*/
static GstStaticPadTemplate rtpbin_recv_fec_sink_template =
GST_STATIC_PAD_TEMPLATE ("recv_fec_sink_%u_%u",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("application/x-rtp")
);
/**
* GstRtpBin!send_fec_src_%u_%u:
*
* Src template for sending Forward Error Correction packets,
* in the form send_fec_src_<session_idx>_<fec_stream_idx>
*
* See #GstRTPST_2022_1_FecEnc for example usage
*
* Since: 1.20
*/
static GstStaticPadTemplate rtpbin_send_fec_src_template =
GST_STATIC_PAD_TEMPLATE ("send_fec_src_%u_%u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("application/x-rtp")
);
static GstStaticPadTemplate rtpbin_recv_rtcp_sink_template =
GST_STATIC_PAD_TEMPLATE ("recv_rtcp_sink_%u",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("application/x-rtcp;application/x-srtcp")
);
static GstStaticPadTemplate rtpbin_send_rtp_sink_template =
GST_STATIC_PAD_TEMPLATE ("send_rtp_sink_%u",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("application/x-rtp")
);
/* src pads */
static GstStaticPadTemplate rtpbin_recv_rtp_src_template =
GST_STATIC_PAD_TEMPLATE ("recv_rtp_src_%u_%u_%u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("application/x-rtp")
);
static GstStaticPadTemplate rtpbin_send_rtcp_src_template =
GST_STATIC_PAD_TEMPLATE ("send_rtcp_src_%u",
GST_PAD_SRC,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("application/x-rtcp;application/x-srtcp")
);
static GstStaticPadTemplate rtpbin_send_rtp_src_template =
GST_STATIC_PAD_TEMPLATE ("send_rtp_src_%u",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("application/x-rtp;application/x-srtp")
);
#define GST_RTP_BIN_LOCK(bin) g_mutex_lock (&(bin)->priv->bin_lock)
#define GST_RTP_BIN_UNLOCK(bin) g_mutex_unlock (&(bin)->priv->bin_lock)
/* lock to protect dynamic callbacks, like pad-added and new ssrc. */
#define GST_RTP_BIN_DYN_LOCK(bin) g_mutex_lock (&(bin)->priv->dyn_lock)
#define GST_RTP_BIN_DYN_UNLOCK(bin) g_mutex_unlock (&(bin)->priv->dyn_lock)
/* lock for shutdown */
#define GST_RTP_BIN_SHUTDOWN_LOCK(bin,label) \
G_STMT_START { \
if (g_atomic_int_get (&bin->priv->shutdown)) \
goto label; \
GST_RTP_BIN_DYN_LOCK (bin); \
if (g_atomic_int_get (&bin->priv->shutdown)) { \
GST_RTP_BIN_DYN_UNLOCK (bin); \
goto label; \
} \
} G_STMT_END
/* unlock for shutdown */
#define GST_RTP_BIN_SHUTDOWN_UNLOCK(bin) \
GST_RTP_BIN_DYN_UNLOCK (bin); \
/* Minimum time offset to apply. This compensates for rounding errors in NTP to
* RTP timestamp conversions */
#define MIN_TS_OFFSET_ROUND_OFF_COMP (4 * GST_MSECOND)
struct _GstRtpBinPrivate
{
GMutex bin_lock;
/* lock protecting dynamic adding/removing */
GMutex dyn_lock;
/* if we are shutting down or not */
gint shutdown;
gboolean autoremove;
/* NTP time in ns of last SR sync used */
guint64 last_ntpnstime;
/* list of extra elements */
GList *elements;
};
/* signals and args */
enum
{
SIGNAL_REQUEST_PT_MAP,
SIGNAL_PAYLOAD_TYPE_CHANGE,
SIGNAL_CLEAR_PT_MAP,
SIGNAL_RESET_SYNC,
SIGNAL_GET_SESSION,
SIGNAL_GET_INTERNAL_SESSION,
SIGNAL_GET_STORAGE,
SIGNAL_GET_INTERNAL_STORAGE,
SIGNAL_CLEAR_SSRC,
SIGNAL_ON_NEW_SSRC,
SIGNAL_ON_SSRC_COLLISION,
SIGNAL_ON_SSRC_VALIDATED,
SIGNAL_ON_SSRC_ACTIVE,
SIGNAL_ON_SSRC_SDES,
SIGNAL_ON_BYE_SSRC,
SIGNAL_ON_BYE_TIMEOUT,
SIGNAL_ON_TIMEOUT,
SIGNAL_ON_SENDER_TIMEOUT,
SIGNAL_ON_NPT_STOP,
SIGNAL_REQUEST_RTP_ENCODER,
SIGNAL_REQUEST_RTP_DECODER,
SIGNAL_REQUEST_RTCP_ENCODER,
SIGNAL_REQUEST_RTCP_DECODER,
SIGNAL_REQUEST_FEC_DECODER,
SIGNAL_REQUEST_FEC_DECODER_FULL,
SIGNAL_REQUEST_FEC_ENCODER,
SIGNAL_REQUEST_JITTERBUFFER,
SIGNAL_NEW_JITTERBUFFER,
SIGNAL_NEW_STORAGE,
SIGNAL_REQUEST_AUX_SENDER,
SIGNAL_REQUEST_AUX_RECEIVER,
SIGNAL_ON_NEW_SENDER_SSRC,
SIGNAL_ON_SENDER_SSRC_ACTIVE,
SIGNAL_ON_BUNDLED_SSRC,
LAST_SIGNAL
};
#define DEFAULT_LATENCY_MS 200
#define DEFAULT_DROP_ON_LATENCY FALSE
#define DEFAULT_SDES NULL
#define DEFAULT_DO_LOST FALSE
#define DEFAULT_IGNORE_PT FALSE
#define DEFAULT_NTP_SYNC FALSE
#define DEFAULT_AUTOREMOVE FALSE
#define DEFAULT_BUFFER_MODE RTP_JITTER_BUFFER_MODE_SLAVE
#define DEFAULT_USE_PIPELINE_CLOCK FALSE
#define DEFAULT_RTCP_SYNC GST_RTP_BIN_RTCP_SYNC_ALWAYS
#define DEFAULT_RTCP_SYNC_INTERVAL 0
#define DEFAULT_DO_SYNC_EVENT FALSE
#define DEFAULT_DO_RETRANSMISSION FALSE
#define DEFAULT_RTP_PROFILE GST_RTP_PROFILE_AVP
#define DEFAULT_NTP_TIME_SOURCE GST_RTP_NTP_TIME_SOURCE_NTP
#define DEFAULT_RTCP_SYNC_SEND_TIME TRUE
#define DEFAULT_MAX_RTCP_RTP_TIME_DIFF 1000
#define DEFAULT_MAX_DROPOUT_TIME 60000
#define DEFAULT_MAX_MISORDER_TIME 2000
#define DEFAULT_RFC7273_SYNC FALSE
#define DEFAULT_ADD_REFERENCE_TIMESTAMP_META FALSE
#define DEFAULT_MAX_STREAMS G_MAXUINT
#define DEFAULT_MAX_TS_OFFSET_ADJUSTMENT G_GUINT64_CONSTANT(0)
#define DEFAULT_MAX_TS_OFFSET G_GINT64_CONSTANT(3000000000)
#define DEFAULT_MIN_TS_OFFSET MIN_TS_OFFSET_ROUND_OFF_COMP
#define DEFAULT_TS_OFFSET_SMOOTHING_FACTOR 0
#define DEFAULT_UPDATE_NTP64_HEADER_EXT TRUE
#define DEFAULT_TIMEOUT_INACTIVE_SOURCES TRUE
enum
{
PROP_0,
PROP_LATENCY,
PROP_DROP_ON_LATENCY,
PROP_SDES,
PROP_DO_LOST,
PROP_IGNORE_PT,
PROP_NTP_SYNC,
PROP_RTCP_SYNC,
PROP_RTCP_SYNC_INTERVAL,
PROP_AUTOREMOVE,
PROP_BUFFER_MODE,
PROP_USE_PIPELINE_CLOCK,
PROP_DO_SYNC_EVENT,
PROP_DO_RETRANSMISSION,
PROP_RTP_PROFILE,
PROP_NTP_TIME_SOURCE,
PROP_RTCP_SYNC_SEND_TIME,
PROP_MAX_RTCP_RTP_TIME_DIFF,
PROP_MAX_DROPOUT_TIME,
PROP_MAX_MISORDER_TIME,
PROP_RFC7273_SYNC,
PROP_ADD_REFERENCE_TIMESTAMP_META,
PROP_MAX_STREAMS,
PROP_MAX_TS_OFFSET_ADJUSTMENT,
PROP_MAX_TS_OFFSET,
PROP_MIN_TS_OFFSET,
PROP_TS_OFFSET_SMOOTHING_FACTOR,
PROP_FEC_DECODERS,
PROP_FEC_ENCODERS,
PROP_UPDATE_NTP64_HEADER_EXT,
PROP_TIMEOUT_INACTIVE_SOURCES,
};
#define GST_RTP_BIN_RTCP_SYNC_TYPE (gst_rtp_bin_rtcp_sync_get_type())
static GType
gst_rtp_bin_rtcp_sync_get_type (void)
{
static GType rtcp_sync_type = 0;
static const GEnumValue rtcp_sync_types[] = {
{GST_RTP_BIN_RTCP_SYNC_ALWAYS, "always", "always"},
{GST_RTP_BIN_RTCP_SYNC_INITIAL, "initial", "initial"},
{GST_RTP_BIN_RTCP_SYNC_RTP_INFO, "rtp-info", "rtp-info"},
{GST_RTP_BIN_RTCP_SYNC_NTP, "ntp", "ntp"},
{GST_RTP_BIN_RTCP_SYNC_NEVER, "never", "never"},
{0, NULL, NULL},
};
if (!rtcp_sync_type) {
rtcp_sync_type = g_enum_register_static ("GstRTCPSync", rtcp_sync_types);
}
return rtcp_sync_type;
}
/* helper objects */
typedef struct _GstRtpBinSession GstRtpBinSession;
typedef struct _GstRtpBinStream GstRtpBinStream;
typedef struct _GstRtpBinClient GstRtpBinClient;
static guint gst_rtp_bin_signals[LAST_SIGNAL] = { 0 };
static GstCaps *pt_map_requested (GstElement * element, guint pt,
GstRtpBinSession * session);
static void payload_type_change (GstElement * element, guint pt,
GstRtpBinSession * session);
static void remove_recv_rtp (GstRtpBin * rtpbin, GstRtpBinSession * session);
static void remove_recv_rtcp (GstRtpBin * rtpbin, GstRtpBinSession * session);
static void remove_recv_fec (GstRtpBin * rtpbin, GstRtpBinSession * session);
static void remove_send_rtp (GstRtpBin * rtpbin, GstRtpBinSession * session);
static void remove_send_fec (GstRtpBin * rtpbin, GstRtpBinSession * session);
static void remove_rtcp (GstRtpBin * rtpbin, GstRtpBinSession * session);
static void free_client (GstRtpBinClient * client, GstRtpBin * bin);
static void free_stream (GstRtpBinStream * stream, GstRtpBin * bin);
static GstRtpBinSession *create_session (GstRtpBin * rtpbin, gint id);
static GstPad *complete_session_sink (GstRtpBin * rtpbin,
GstRtpBinSession * session);
static void
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);
typedef enum
{
GST_RTP_BIN_STREAM_SYNCED_NONE,
GST_RTP_BIN_STREAM_SYNCED_NTP,
GST_RTP_BIN_STREAM_SYNCED_RTP_INFO
} GstRtpBinStreamSynced;
/* Manages the RTP stream for one SSRC.
*
* 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).
*/
struct _GstRtpBinStream
{
/* the SSRC of this stream */
guint32 ssrc;
/* parent bin */
GstRtpBin *bin;
/* the session this SSRC belongs to */
GstRtpBinSession *session;
/* the jitterbuffer of the SSRC */
GstElement *buffer;
gulong buffer_handlesync_sig;
gulong buffer_ptreq_sig;
gulong buffer_ntpstop_sig;
gint percent;
/* the PT demuxer of the SSRC */
GstElement *demux;
gulong demux_newpad_sig;
gulong demux_padremoved_sig;
gulong demux_ptreq_sig;
gulong demux_ptchange_sig;
/* if we have synced this stream */
GstRtpBinStreamSynced have_sync;
/* mapping to local RTP and NTP time */
gint64 rt_delta; /* based on RTCP */
gint64 rtp_delta; /* based on RTP-Info */
/* base RTP time */
gint64 base_rtptime;
guint64 base_time;
/* clock-base and npt-start for RTP-Info */
guint64 clock_base;
guint64 npt_start;
/* Smoothing of ts-offset adjustments */
gint64 avg_ts_offset;
gboolean is_initialized;
};
#define GST_RTP_SESSION_LOCK(sess) g_mutex_lock (&(sess)->lock)
#define GST_RTP_SESSION_UNLOCK(sess) g_mutex_unlock (&(sess)->lock)
/* Manages the receiving end of the packets.
*
* There is one such structure for each RTP session (audio/video/...).
* We get the RTP/RTCP packets and stuff them into the session manager. From
* there they are pushed into an SSRC demuxer that splits the stream based on
* SSRC. Each of the SSRC streams go into their own jitterbuffer (managed with
* the GstRtpBinStream above).
*
* Before the SSRC demuxer, a storage element may be inserted for the purpose
* of Forward Error Correction.
*/
struct _GstRtpBinSession
{
/* session id */
gint id;
/* the parent bin */
GstRtpBin *bin;
/* the session element */
GstElement *session;
/* the SSRC demuxer */
GstElement *demux;
gulong demux_newpad_sig;
gulong demux_padremoved_sig;
/* Fec support */
GstElement *storage;
GMutex lock;
/* list of GstRtpBinStream */
GSList *streams;
/* list of elements */
GSList *elements;
/* mapping of payload type to caps */
GHashTable *ptmap;
/* the pads of the session */
GstPad *recv_rtp_sink;
GstPad *recv_rtp_sink_ghost;
GstPad *recv_rtp_src;
GstPad *recv_rtcp_sink;
GstPad *recv_rtcp_sink_ghost;
GstPad *sync_src;
GstPad *send_rtp_sink;
GstPad *send_rtp_sink_ghost;
GstPad *send_rtp_src_ghost;
GstPad *send_rtcp_src;
GstPad *send_rtcp_src_ghost;
GSList *recv_fec_sinks;
GSList *recv_fec_sink_ghosts;
/* fec decoder placed before the rtpjitterbuffer but after the rtpssrcdemux.
* XXX: This does not yet support multiple ssrc's in the same rtp session
*/
GstElement *early_fec_decoder;
GSList *send_fec_src_ghosts;
};
/* Manages the RTP streams that come from one client and should therefore be
* synchronized.
*/
struct _GstRtpBinClient
{
/* the common CNAME for the streams */
gchar *cname;
guint cname_len;
/* the streams */
guint nstreams;
GSList *streams;
};
/* find a session with the given id. Must be called with RTP_BIN_LOCK */
static GstRtpBinSession *
find_session_by_id (GstRtpBin * rtpbin, gint id)
{
GSList *walk;
for (walk = rtpbin->sessions; walk; walk = g_slist_next (walk)) {
GstRtpBinSession *sess = (GstRtpBinSession *) walk->data;
if (sess->id == id)
return sess;
}
return NULL;
}
static gboolean
pad_is_recv_fec (GstRtpBinSession * session, GstPad * pad)
{
return g_slist_find (session->recv_fec_sink_ghosts, pad) != NULL;
}
/* find a session with the given request pad. Must be called with RTP_BIN_LOCK */
static GstRtpBinSession *
find_session_by_pad (GstRtpBin * rtpbin, GstPad * pad)
{
GSList *walk;
for (walk = rtpbin->sessions; walk; walk = g_slist_next (walk)) {
GstRtpBinSession *sess = (GstRtpBinSession *) walk->data;
if ((sess->recv_rtp_sink_ghost == pad) ||
(sess->recv_rtcp_sink_ghost == pad) ||
(sess->send_rtp_sink_ghost == pad) ||
(sess->send_rtcp_src_ghost == pad) || pad_is_recv_fec (sess, pad))
return sess;
}
return NULL;
}
static void
on_new_ssrc (GstElement * session, guint32 ssrc, GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_NEW_SSRC], 0,
sess->id, ssrc);
}
static void
on_ssrc_collision (GstElement * session, guint32 ssrc, GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_SSRC_COLLISION], 0,
sess->id, ssrc);
}
static void
on_ssrc_validated (GstElement * session, guint32 ssrc, GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_SSRC_VALIDATED], 0,
sess->id, ssrc);
}
static void
on_ssrc_active (GstElement * session, guint32 ssrc, GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_SSRC_ACTIVE], 0,
sess->id, ssrc);
}
static void
on_ssrc_sdes (GstElement * session, guint32 ssrc, GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_SSRC_SDES], 0,
sess->id, ssrc);
}
static void
on_bye_ssrc (GstElement * session, guint32 ssrc, GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_BYE_SSRC], 0,
sess->id, ssrc);
}
static void
on_bye_timeout (GstElement * session, guint32 ssrc, GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_BYE_TIMEOUT], 0,
sess->id, ssrc);
if (sess->bin->priv->autoremove)
g_signal_emit_by_name (sess->demux, "clear-ssrc", ssrc, NULL);
}
static void
on_timeout (GstElement * session, guint32 ssrc, GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_TIMEOUT], 0,
sess->id, ssrc);
if (sess->bin->priv->autoremove)
g_signal_emit_by_name (sess->demux, "clear-ssrc", ssrc, NULL);
}
static void
on_sender_timeout (GstElement * session, guint32 ssrc, GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_SENDER_TIMEOUT], 0,
sess->id, ssrc);
}
static void
on_npt_stop (GstElement * jbuf, GstRtpBinStream * stream)
{
g_signal_emit (stream->bin, gst_rtp_bin_signals[SIGNAL_ON_NPT_STOP], 0,
stream->session->id, stream->ssrc);
}
static void
on_new_sender_ssrc (GstElement * session, guint32 ssrc, GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_NEW_SENDER_SSRC], 0,
sess->id, ssrc);
}
static void
on_sender_ssrc_active (GstElement * session, guint32 ssrc,
GstRtpBinSession * sess)
{
g_signal_emit (sess->bin, gst_rtp_bin_signals[SIGNAL_ON_SENDER_SSRC_ACTIVE],
0, sess->id, ssrc);
}
/* must be called with the SESSION lock */
static GstRtpBinStream *
find_stream_by_ssrc (GstRtpBinSession * session, guint32 ssrc)
{
GSList *walk;
for (walk = session->streams; walk; walk = g_slist_next (walk)) {
GstRtpBinStream *stream = (GstRtpBinStream *) walk->data;
if (stream->ssrc == ssrc)
return stream;
}
return NULL;
}
static void
ssrc_demux_pad_removed (GstElement * element, guint ssrc, GstPad * pad,
GstRtpBinSession * session)
{
GstRtpBinStream *stream = NULL;
GstRtpBin *rtpbin;
rtpbin = session->bin;
GST_RTP_BIN_LOCK (rtpbin);
GST_RTP_SESSION_LOCK (session);
if ((stream = find_stream_by_ssrc (session, ssrc)))
session->streams = g_slist_remove (session->streams, stream);
GST_RTP_SESSION_UNLOCK (session);
if (stream)
free_stream (stream, rtpbin);
GST_RTP_BIN_UNLOCK (rtpbin);
}
/* create a session with the given id. Must be called with RTP_BIN_LOCK */
static GstRtpBinSession *
create_session (GstRtpBin * rtpbin, gint id)
{
GstRtpBinSession *sess;
GstElement *session, *demux;
GstElement *storage = NULL;
GstState target;
if (!(session = gst_element_factory_make ("rtpsession", NULL)))
goto no_session;
if (!(demux = gst_element_factory_make ("rtpssrcdemux", NULL)))
goto no_demux;
if (!(storage = gst_element_factory_make ("rtpstorage", NULL)))
goto no_storage;
/* need to sink the storage or otherwise signal handlers from bindings will
* take ownership of it and we don't own it anymore */
gst_object_ref_sink (storage);
g_signal_emit (rtpbin, gst_rtp_bin_signals[SIGNAL_NEW_STORAGE], 0, storage,
id);
sess = g_new0 (GstRtpBinSession, 1);
g_mutex_init (&sess->lock);
sess->id = id;
sess->bin = rtpbin;
sess->session = session;
sess->demux = demux;
sess->storage = storage;
sess->ptmap = g_hash_table_new_full (NULL, NULL, NULL,
(GDestroyNotify) gst_caps_unref);
rtpbin->sessions = g_slist_prepend (rtpbin->sessions, sess);
/* 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);
if (rtpbin->use_pipeline_clock)
g_object_set (session, "use-pipeline-clock", rtpbin->use_pipeline_clock,
NULL);
else
g_object_set (session, "ntp-time-source", rtpbin->ntp_time_source, NULL);
g_object_set (session, "max-dropout-time", rtpbin->max_dropout_time,
"max-misorder-time", rtpbin->max_misorder_time, NULL);
g_object_set (session, "update-ntp64-header-ext",
rtpbin->update_ntp64_header_ext, NULL);
g_object_set (session, "timeout-inactive-sources",
rtpbin->timeout_inactive_sources, NULL);
GST_OBJECT_UNLOCK (rtpbin);
/* provide clock_rate to the session manager when needed */
g_signal_connect (session, "request-pt-map",
(GCallback) pt_map_requested, sess);
g_signal_connect (sess->session, "on-new-ssrc",
(GCallback) on_new_ssrc, sess);
g_signal_connect (sess->session, "on-ssrc-collision",
(GCallback) on_ssrc_collision, sess);
g_signal_connect (sess->session, "on-ssrc-validated",
(GCallback) on_ssrc_validated, sess);
g_signal_connect (sess->session, "on-ssrc-active",
(GCallback) on_ssrc_active, sess);
g_signal_connect (sess->session, "on-ssrc-sdes",
(GCallback) on_ssrc_sdes, sess);
g_signal_connect (sess->session, "on-bye-ssrc",
(GCallback) on_bye_ssrc, sess);
g_signal_connect (sess->session, "on-bye-timeout",
(GCallback) on_bye_timeout, sess);
g_signal_connect (sess->session, "on-timeout", (GCallback) on_timeout, sess);
g_signal_connect (sess->session, "on-sender-timeout",
(GCallback) on_sender_timeout, sess);
g_signal_connect (sess->session, "on-new-sender-ssrc",
(GCallback) on_new_sender_ssrc, sess);
g_signal_connect (sess->session, "on-sender-ssrc-active",
(GCallback) on_sender_ssrc_active, sess);
gst_bin_add (GST_BIN_CAST (rtpbin), session);
gst_bin_add (GST_BIN_CAST (rtpbin), demux);
gst_bin_add (GST_BIN_CAST (rtpbin), storage);
/* unref the storage again, the bin has a reference now and
* we don't need it anymore */
gst_object_unref (storage);
GST_OBJECT_LOCK (rtpbin);
target = GST_STATE_TARGET (rtpbin);
GST_OBJECT_UNLOCK (rtpbin);
/* change state only to what's needed */
gst_element_set_state (demux, target);
gst_element_set_state (session, target);
gst_element_set_state (storage, target);
return sess;
/* ERRORS */
no_session:
{
g_warning ("rtpbin: could not create rtpsession element");
return NULL;
}
no_demux:
{
gst_object_unref (session);
g_warning ("rtpbin: could not create rtpssrcdemux element");
return NULL;
}
no_storage:
{
gst_object_unref (session);
gst_object_unref (demux);
g_warning ("rtpbin: could not create rtpstorage element");
return NULL;
}
}
static gboolean
bin_manage_element (GstRtpBin * bin, GstElement * element)
{
GstRtpBinPrivate *priv = bin->priv;
if (g_list_find (priv->elements, element)) {
GST_DEBUG_OBJECT (bin, "requested element %p already in bin", element);
} else {
GST_DEBUG_OBJECT (bin, "adding requested element %p", element);
if (g_object_is_floating (element))
element = gst_object_ref_sink (element);
if (!gst_bin_add (GST_BIN_CAST (bin), element))
goto add_failed;
if (!gst_element_sync_state_with_parent (element))
GST_WARNING_OBJECT (bin, "unable to sync element state with rtpbin");
}
/* we add the element multiple times, each we need an equal number of
* removes to really remove the element from the bin */
priv->elements = g_list_prepend (priv->elements, element);
return TRUE;
/* ERRORS */
add_failed:
{
GST_WARNING_OBJECT (bin, "unable to add element");
gst_object_unref (element);
return FALSE;
}
}
static void
remove_bin_element (GstElement * element, GstRtpBin * bin)
{
GstRtpBinPrivate *priv = bin->priv;
GList *find;
find = g_list_find (priv->elements, element);
if (find) {
priv->elements = g_list_delete_link (priv->elements, find);
if (!g_list_find (priv->elements, element)) {
gst_element_set_locked_state (element, TRUE);
gst_bin_remove (GST_BIN_CAST (bin), element);
gst_element_set_state (element, GST_STATE_NULL);
}
gst_object_unref (element);
}
}
/* called with RTP_BIN_LOCK */
static void
free_session (GstRtpBinSession * sess, GstRtpBin * bin)
{
GST_DEBUG_OBJECT (bin, "freeing session %p", sess);
gst_element_set_locked_state (sess->demux, TRUE);
gst_element_set_locked_state (sess->session, TRUE);
gst_element_set_locked_state (sess->storage, TRUE);
gst_element_set_state (sess->demux, GST_STATE_NULL);
gst_element_set_state (sess->session, GST_STATE_NULL);
gst_element_set_state (sess->storage, GST_STATE_NULL);
remove_recv_rtp (bin, sess);
remove_recv_rtcp (bin, sess);
remove_recv_fec (bin, sess);
remove_send_rtp (bin, sess);
remove_send_fec (bin, sess);
remove_rtcp (bin, sess);
gst_bin_remove (GST_BIN_CAST (bin), sess->session);
gst_bin_remove (GST_BIN_CAST (bin), sess->demux);
gst_bin_remove (GST_BIN_CAST (bin), sess->storage);
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);
g_mutex_clear (&sess->lock);
g_hash_table_destroy (sess->ptmap);
g_free (sess);
}
/* get the payload type caps for the specific payload @pt in @session */
static GstCaps *
get_pt_map (GstRtpBinSession * session, guint pt)
{
GstCaps *caps = NULL;
GstRtpBin *bin;
GValue ret = { 0 };
GValue args[3] = { {0}, {0}, {0} };
GST_DEBUG ("searching pt %u in cache", pt);
GST_RTP_SESSION_LOCK (session);
/* first look in the cache */
caps = g_hash_table_lookup (session->ptmap, GINT_TO_POINTER (pt));
if (caps) {
gst_caps_ref (caps);
goto done;
}
bin = session->bin;
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);
g_value_set_object (&args[0], bin);
g_value_init (&args[1], G_TYPE_UINT);
g_value_set_uint (&args[1], session->id);
g_value_init (&args[2], G_TYPE_UINT);
g_value_set_uint (&args[2], pt);
g_value_init (&ret, GST_TYPE_CAPS);
g_value_set_boxed (&ret, NULL);
GST_RTP_SESSION_UNLOCK (session);
g_signal_emitv (args, gst_rtp_bin_signals[SIGNAL_REQUEST_PT_MAP], 0, &ret);
GST_RTP_SESSION_LOCK (session);
g_value_unset (&args[0]);
g_value_unset (&args[1]);
g_value_unset (&args[2]);
/* look in the cache again because we let the lock go */
caps = g_hash_table_lookup (session->ptmap, GINT_TO_POINTER (pt));
if (caps) {
gst_caps_ref (caps);
g_value_unset (&ret);
goto done;
}
caps = (GstCaps *) g_value_dup_boxed (&ret);
g_value_unset (&ret);
if (!caps)
goto no_caps;
GST_DEBUG ("caching pt %u as %" GST_PTR_FORMAT, pt, caps);
/* store in cache, take additional ref */
g_hash_table_insert (session->ptmap, GINT_TO_POINTER (pt),
gst_caps_ref (caps));
done:
GST_RTP_SESSION_UNLOCK (session);
return caps;
/* ERRORS */
no_caps:
{
GST_RTP_SESSION_UNLOCK (session);
GST_DEBUG ("no pt map could be obtained");
return NULL;
}
}
static gboolean
return_true (gpointer key, gpointer value, gpointer user_data)
{
return TRUE;
}
static void
gst_rtp_bin_reset_sync (GstRtpBin * rtpbin)
{
GSList *clients, *streams;
GST_DEBUG_OBJECT (rtpbin, "Reset sync on all clients");
GST_RTP_BIN_LOCK (rtpbin);
for (clients = rtpbin->clients; clients; clients = g_slist_next (clients)) {
GstRtpBinClient *client = (GstRtpBinClient *) clients->data;
/* reset sync on all streams for this client */
for (streams = client->streams; streams; streams = g_slist_next (streams)) {
GstRtpBinStream *stream = (GstRtpBinStream *) streams->data;
/* make use require a new SR packet for this stream before we attempt new
* lip-sync */
stream->have_sync = GST_RTP_BIN_STREAM_SYNCED_NONE;
stream->rt_delta = G_MININT64;
stream->rtp_delta = G_MININT64;
stream->base_rtptime = -1;
stream->base_time = GST_CLOCK_TIME_NONE;
stream->clock_base = -1;
stream->npt_start = GST_CLOCK_TIME_NONE;
stream->avg_ts_offset = 0;
stream->is_initialized = FALSE;
}
}
GST_RTP_BIN_UNLOCK (rtpbin);
}
static void
gst_rtp_bin_clear_pt_map (GstRtpBin * bin)
{
GSList *sessions, *streams;
GST_RTP_BIN_LOCK (bin);
GST_DEBUG_OBJECT (bin, "clearing pt map");
for (sessions = bin->sessions; sessions; sessions = g_slist_next (sessions)) {
GstRtpBinSession *session = (GstRtpBinSession *) sessions->data;
GST_DEBUG_OBJECT (bin, "clearing session %p", session);
g_signal_emit_by_name (session->session, "clear-pt-map", NULL);
GST_RTP_SESSION_LOCK (session);
g_hash_table_foreach_remove (session->ptmap, return_true, NULL);
for (streams = session->streams; streams; streams = g_slist_next (streams)) {
GstRtpBinStream *stream = (GstRtpBinStream *) streams->data;
GST_DEBUG_OBJECT (bin, "clearing stream %p", stream);
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);
}
GST_RTP_SESSION_UNLOCK (session);
}
GST_RTP_BIN_UNLOCK (bin);
/* reset sync too */
gst_rtp_bin_reset_sync (bin);
}
static GstElement *
gst_rtp_bin_get_session (GstRtpBin * bin, guint session_id)
{
GstRtpBinSession *session;
GstElement *ret = NULL;
GST_RTP_BIN_LOCK (bin);
GST_DEBUG_OBJECT (bin, "retrieving GstRtpSession, index: %u", session_id);
session = find_session_by_id (bin, (gint) session_id);
if (session) {
ret = gst_object_ref (session->session);
}
GST_RTP_BIN_UNLOCK (bin);
return ret;
}
static RTPSession *
gst_rtp_bin_get_internal_session (GstRtpBin * bin, guint session_id)
{
RTPSession *internal_session = NULL;
GstRtpBinSession *session;
GST_RTP_BIN_LOCK (bin);
GST_DEBUG_OBJECT (bin, "retrieving internal RTPSession object, index: %u",
session_id);
session = find_session_by_id (bin, (gint) session_id);
if (session) {
g_object_get (session->session, "internal-session", &internal_session,
NULL);
}
GST_RTP_BIN_UNLOCK (bin);
return internal_session;
}
static GstElement *
gst_rtp_bin_get_storage (GstRtpBin * bin, guint session_id)
{
GstRtpBinSession *session;
GstElement *res = NULL;
GST_RTP_BIN_LOCK (bin);
GST_DEBUG_OBJECT (bin, "retrieving internal storage object, index: %u",
session_id);
session = find_session_by_id (bin, (gint) session_id);
if (session && session->storage) {
res = gst_object_ref (session->storage);
}
GST_RTP_BIN_UNLOCK (bin);
return res;
}
static GObject *
gst_rtp_bin_get_internal_storage (GstRtpBin * bin, guint session_id)
{
GObject *internal_storage = NULL;
GstRtpBinSession *session;
GST_RTP_BIN_LOCK (bin);
GST_DEBUG_OBJECT (bin, "retrieving internal storage object, index: %u",
session_id);
session = find_session_by_id (bin, (gint) session_id);
if (session && session->storage) {
g_object_get (session->storage, "internal-storage", &internal_storage,
NULL);
}
GST_RTP_BIN_UNLOCK (bin);
return internal_storage;
}
static void
gst_rtp_bin_clear_ssrc (GstRtpBin * bin, guint session_id, guint32 ssrc)
{
GstRtpBinSession *session;
GstElement *demux = NULL;
GST_RTP_BIN_LOCK (bin);
GST_DEBUG_OBJECT (bin, "clearing ssrc %u for session %u", ssrc, session_id);
session = find_session_by_id (bin, (gint) session_id);
if (session)
demux = gst_object_ref (session->demux);
GST_RTP_BIN_UNLOCK (bin);
if (demux) {
g_signal_emit_by_name (demux, "clear-ssrc", ssrc, NULL);
gst_object_unref (demux);
}
}
static GstElement *
gst_rtp_bin_request_encoder (GstRtpBin * bin, guint session_id)
{
GST_DEBUG_OBJECT (bin, "return NULL encoder");
return NULL;
}
static GstElement *
gst_rtp_bin_request_decoder (GstRtpBin * bin, guint session_id)
{
GST_DEBUG_OBJECT (bin, "return NULL decoder");
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)
{
GSList *sessions, *streams;
GST_RTP_BIN_LOCK (bin);
for (sessions = bin->sessions; sessions; sessions = g_slist_next (sessions)) {
GstRtpBinSession *session = (GstRtpBinSession *) sessions->data;
GST_RTP_SESSION_LOCK (session);
for (streams = session->streams; streams; streams = g_slist_next (streams)) {
GstRtpBinStream *stream = (GstRtpBinStream *) streams->data;
GObjectClass *jb_class;
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);
}
GST_RTP_BIN_UNLOCK (bin);
}
static void
gst_rtp_bin_propagate_property_to_session (GstRtpBin * bin,
const gchar * name, const GValue * value)
{
GSList *sessions;
GST_RTP_BIN_LOCK (bin);
for (sessions = bin->sessions; sessions; sessions = g_slist_next (sessions)) {
GstRtpBinSession *sess = (GstRtpBinSession *) sessions->data;
g_object_set_property (G_OBJECT (sess->session), name, value);
}
GST_RTP_BIN_UNLOCK (bin);
}
/* get a client with the given SDES name. Must be called with RTP_BIN_LOCK */
static GstRtpBinClient *
get_client (GstRtpBin * bin, guint8 len, const guint8 * data)
{
GstRtpBinClient *result = NULL;
GSList *walk;
for (walk = bin->clients; walk; walk = g_slist_next (walk)) {
GstRtpBinClient *client = (GstRtpBinClient *) walk->data;
/* In case of RTSP this can be called without a CNAME first, in which case
* we assume that all streams belong to the same client.
*
* If RTCP is happening later we will get CNAMEs. Remove the client in
* that case and create new ones with the actual CNAME once we have one.
*
* All streams are directly moved over to the new client.
*/
if (client->cname_len == 0 && len != 0) {
result = client;
g_free (result->cname);
result->cname = g_strndup ((gchar *) data, len);
result->cname_len = len;
break;
}
if (len != client->cname_len)
continue;
if (len == 0 || !strncmp ((gchar *) data, client->cname, client->cname_len)) {
GST_DEBUG_OBJECT (bin, "found existing client %p with CNAME %s", client,
GST_STR_NULL (client->cname));
result = client;
break;
}
}
/* nothing found, create one */
if (result == NULL) {
result = g_new0 (GstRtpBinClient, 1);
result->cname = g_strndup ((gchar *) data, len);
result->cname_len = len;
bin->clients = g_slist_prepend (bin->clients, result);
GST_DEBUG_OBJECT (bin, "created new client %p with CNAME %s", result,
GST_STR_NULL (result->cname));
}
return result;
}
static void
free_client (GstRtpBinClient * client, GstRtpBin * bin)
{
GST_DEBUG_OBJECT (bin, "freeing client %p", client);
g_slist_free (client->streams);
g_free (client->cname);
g_free (client);
}
static void
get_current_times (GstRtpBin * bin, GstClockTime * running_time,
guint64 * ntpnstime)
{
guint64 ntpns = -1;
GstClock *clock;
GstClockTime base_time, rt, clock_time;
GST_OBJECT_LOCK (bin);
if ((clock = GST_ELEMENT_CLOCK (bin))) {
base_time = GST_ELEMENT_CAST (bin)->base_time;
gst_object_ref (clock);
GST_OBJECT_UNLOCK (bin);
/* get current clock time and convert to running time */
clock_time = gst_clock_get_time (clock);
rt = clock_time - base_time;
if (bin->use_pipeline_clock) {
ntpns = rt;
/* add constant to convert from 1970 based time to 1900 based time */
ntpns += (GST_RTP_NTP_UNIX_OFFSET * GST_SECOND);
} else {
switch (bin->ntp_time_source) {
case GST_RTP_NTP_TIME_SOURCE_NTP:
case GST_RTP_NTP_TIME_SOURCE_UNIX:{
/* get current NTP time */
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)
ntpns += (GST_RTP_NTP_UNIX_OFFSET * GST_SECOND);
break;
}
case GST_RTP_NTP_TIME_SOURCE_RUNNING_TIME:
ntpns = rt;
break;
case GST_RTP_NTP_TIME_SOURCE_CLOCK_TIME:
ntpns = clock_time;
break;
default:
ntpns = -1; /* Fix uninited compiler warning */
g_assert_not_reached ();
break;
}
}
gst_object_unref (clock);
} else {
GST_OBJECT_UNLOCK (bin);
rt = -1;
ntpns = -1;
}
if (running_time)
*running_time = rt;
if (ntpnstime)
*ntpnstime = ntpns;
}
static void
stream_set_ts_offset (GstRtpBin * bin, GstRtpBinStream * stream,
gint64 ts_offset, gint64 max_ts_offset, guint64 min_ts_offset,
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;
}
if (bin->ts_offset_smoothing_factor > 0) {
if (!stream->is_initialized) {
stream->avg_ts_offset = ts_offset;
stream->is_initialized = TRUE;
} else {
/* RMA algorithm using smoothing factor is following, but split into
* parts to check for overflows:
* stream->avg_ts_offset =
* ((bin->ts_offset_smoothing_factor - 1) * stream->avg_ts_offset
* + ts_offset) / bin->ts_offset_smoothing_factor
*/
guint64 max_possible_smoothing_factor = G_MAXUINT64;
gint64 cur_avg_product =
(bin->ts_offset_smoothing_factor - 1) * stream->avg_ts_offset;
if (stream->avg_ts_offset != 0)
max_possible_smoothing_factor =
G_MAXINT64 / ABS (stream->avg_ts_offset);
if ((max_possible_smoothing_factor < bin->ts_offset_smoothing_factor) ||
(cur_avg_product > 0 && G_MAXINT64 - cur_avg_product < ts_offset) ||
(cur_avg_product < 0 && G_MININT64 - cur_avg_product > ts_offset)) {
GST_WARNING_OBJECT (bin,
"ts-offset-smoothing-factor calculation overflow, fallback to using ts-offset directly");
stream->avg_ts_offset = ts_offset;
} else {
stream->avg_ts_offset =
(cur_avg_product + ts_offset) / bin->ts_offset_smoothing_factor;
}
}
} else {
stream->avg_ts_offset = ts_offset;
}
g_object_get (stream->buffer, "ts-offset", &prev_ts_offset, NULL);
/* delta changed, see how much */
if (prev_ts_offset != stream->avg_ts_offset) {
gint64 diff;
diff = prev_ts_offset - stream->avg_ts_offset;
GST_DEBUG_OBJECT (bin,
"ts-offset %" G_GINT64_FORMAT ", prev %" G_GINT64_FORMAT
", diff: %" G_GINT64_FORMAT, stream->avg_ts_offset, prev_ts_offset,
diff);
/* ignore minor offsets */
if (ABS (diff) < min_ts_offset) {
GST_DEBUG_OBJECT (bin, "offset too small, ignoring");
return;
}
/* sanity check offset */
if (max_ts_offset > 0) {
if (stream->avg_ts_offset > 0 && !allow_positive_ts_offset) {
GST_DEBUG_OBJECT (bin,
"offset is positive (clocks are out of sync), ignoring");
return;
}
if (ABS (stream->avg_ts_offset) > max_ts_offset) {
GST_DEBUG_OBJECT (bin, "offset too large, ignoring");
return;
}
}
g_object_set (stream->buffer, "ts-offset", stream->avg_ts_offset, NULL);
}
GST_DEBUG_OBJECT (bin, "stream SSRC %08x, delta %" G_GINT64_FORMAT,
stream->ssrc, stream->avg_ts_offset);
}
static void
gst_rtp_bin_send_sync_event (GstRtpBinStream * stream)
{
if (stream->bin->send_sync_event) {
GstEvent *event;
GstPad *srcpad;
GST_DEBUG_OBJECT (stream->bin,
"sending GstRTCPSRReceived event downstream");
event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
gst_structure_new_empty ("GstRTCPSRReceived"));
srcpad = gst_element_get_static_pad (stream->buffer, "src");
gst_pad_push_event (srcpad, event);
gst_object_unref (srcpad);
}
}
/* Associate a stream to the given CNAME. This will make sure all streams for
* that CNAME are synchronized together.
*
* For RTSP streams (or similar) this can also be called with a NULL CNAME and
* no ntpnstime / last_extrtptime for doing synchronization based on the
* RTP-Info (rtptime maps to rtp_clock_base, and npt_start is provided).
*
* Synchronization based on RTCP / NTP header extension is generally preferred
* over RTP-Info if possible, and this can be configured via the rtcp-sync
* property.
*
* @len: length of CNAME in @data
* @data: CNAME
* @ntpnstime: NTP time in nanoseconds that corresponds to RTP time @extrtptime.
* @extrtptime: Extended RTP time that corresponds to NTP time @ntpnstime.
* @base_rtptime: Extended RTP time that corresponds to the local time @base_time.
* @base_time: Local time that corresponds to RTP time @base_rtptime.
* @clock_rate: RTP clock rate for this stream.
* @rtp_clock_base: RTP time signalled in RTSP RTP-Info for this stream, which
* corresponds to the NPT start of the PLAY request.
*
* Must be called with GST_RTP_BIN_LOCK */
static void
gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream,
const guint8 len, const guint8 * data, const guint64 ntpnstime,
const guint64 extrtptime, const guint64 base_rtptime,
const guint64 base_time, const guint clock_rate,
const guint64 rtp_clock_base, const guint64 npt_start)
{
/* Don't do any stream offsetting in RFC7273 sync mode. Everything is
* handled inside rtpjitterbuffer for this case. */
if (bin->rfc7273_sync) {
GST_DEBUG_OBJECT (bin, "Doing RFC7273 sync");
return;
}
/* In case of NTP sync we can directly calculate the offset for this stream
* here and return immediately */
if (bin->ntp_sync) {
GstClockTime rtp_running_time, diff_rtp;
GstClockTime local_running_time, local_ntpnstime;
gint64 ntpdiff, rtdiff;
GST_DEBUG_OBJECT (bin, "Doing NTP sync");
if (!GST_CLOCK_TIME_IS_VALID (extrtptime)
|| !GST_CLOCK_TIME_IS_VALID (ntpnstime)
|| extrtptime < base_rtptime) {
GST_DEBUG_OBJECT (bin, "invalidated sync data, bailing out");
return;
}
/* Take the extended rtptime we found in the SR packet and map it to the
* local rtptime. The local rtp time is used to construct timestamps on the
* buffers so we will calculate what running_time corresponds to the RTP
* timestamp in the SR packet. */
diff_rtp = extrtptime - base_rtptime;
GST_DEBUG_OBJECT (bin,
"base RTP time %" G_GUINT64_FORMAT ", SR RTP time %" G_GUINT64_FORMAT
", RTP time difference %" G_GUINT64_FORMAT ", clock-rate %d",
base_rtptime, extrtptime, diff_rtp, clock_rate);
/* calculate local RTP time in GStreamer timestamp units, we essentially
* perform the same conversion that a jitterbuffer would use to convert an
* rtp timestamp into a corresponding gstreamer timestamp. Note that the
* base_time also contains the drift between sender and receiver. */
rtp_running_time =
gst_util_uint64_scale_int (diff_rtp, GST_SECOND, clock_rate);
rtp_running_time += base_time;
GST_DEBUG_OBJECT (bin,
"RTP running time %" GST_TIME_FORMAT ", SR NTP time %" GST_TIME_FORMAT,
GST_TIME_ARGS (rtp_running_time), GST_TIME_ARGS (ntpnstime));
/* 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 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
* current NTP time and the NTP time in the last SR packet. */
ntpdiff = local_ntpnstime - ntpnstime;
/* see how far away the running_time is. This is the difference between the
* current running_time and the running_time of the RTP timestamp in the
* last SR packet. */
rtdiff = local_running_time - rtp_running_time;
GST_DEBUG_OBJECT (bin,
"local NTP time %" G_GUINT64_FORMAT ", SR NTP time %" G_GUINT64_FORMAT,
local_ntpnstime, ntpnstime);
GST_DEBUG_OBJECT (bin,
"local running time %" G_GUINT64_FORMAT ", SR RTP running time %"
G_GUINT64_FORMAT, local_running_time, rtp_running_time);
GST_DEBUG_OBJECT (bin,
"NTP diff %" G_GINT64_FORMAT ", RT diff %" G_GINT64_FORMAT, ntpdiff,
rtdiff);
/* combine to get the final diff to apply to the running_time */
stream->rt_delta = rtdiff - ntpdiff;
GST_DEBUG_OBJECT (bin,
"Calculated ts-offset %" GST_STIME_FORMAT " for SSRC %08x",
GST_STIME_ARGS (stream->rt_delta), stream->ssrc);
stream_set_ts_offset (bin, stream, stream->rt_delta, bin->max_ts_offset,
bin->min_ts_offset, FALSE);
gst_rtp_bin_send_sync_event (stream);
return;
}
/* For all other cases (not RFC7273 and not NTP sync) we have to look how
* all streams of a client relate to each other */
/* first find or create the CNAME */
GstRtpBinClient *client = get_client (bin, len, data);
/* find stream in the client */
GSList *walk;
for (walk = client->streams; walk; walk = g_slist_next (walk)) {
GstRtpBinStream *ostream = (GstRtpBinStream *) walk->data;
if (ostream == stream)
break;
}
/* not found, add it to the list */
if (walk == NULL) {
GST_DEBUG_OBJECT (bin,
"new association of SSRC %08x with client %p with CNAME %s",
stream->ssrc, client, GST_STR_NULL (client->cname));
client->streams = g_slist_prepend (client->streams, stream);
client->nstreams++;
} else {
GST_DEBUG_OBJECT (bin,
"found association of SSRC %08x with client %p with CNAME %s",
stream->ssrc, client, GST_STR_NULL (client->cname));
}
/* First decide whether to do RTP-Info or RTCP / NTP header extension sync */
gboolean rtp_info_sync = FALSE;
GST_DEBUG_OBJECT (bin, "RTCP sync mode %d", bin->rtcp_sync);
switch (bin->rtcp_sync) {
case GST_RTP_BIN_RTCP_SYNC_ALWAYS:
case GST_RTP_BIN_RTCP_SYNC_INITIAL:{
if (!GST_CLOCK_TIME_IS_VALID (extrtptime)
|| !GST_CLOCK_TIME_IS_VALID (ntpnstime)
|| extrtptime < base_rtptime) {
if (!GST_CLOCK_TIME_IS_VALID (npt_start)) {
GST_DEBUG_OBJECT (bin, "invalidated sync data, bailing out");
return;
}
rtp_info_sync = TRUE;
GST_DEBUG_OBJECT (bin, "Doing RTP-Info sync");
} else {
GST_DEBUG_OBJECT (bin, "Doing RTCP sync");
}
break;
}
case GST_RTP_BIN_RTCP_SYNC_RTP_INFO:{
if (!GST_CLOCK_TIME_IS_VALID (npt_start)) {
GST_DEBUG_OBJECT (bin, "invalidated sync data, bailing out");
return;
}
rtp_info_sync = TRUE;
GST_DEBUG_OBJECT (bin, "Doing RTP-Info sync");
break;
}
case GST_RTP_BIN_RTCP_SYNC_NTP:{
if (!GST_CLOCK_TIME_IS_VALID (extrtptime)
|| !GST_CLOCK_TIME_IS_VALID (ntpnstime)
|| extrtptime < base_rtptime) {
GST_DEBUG_OBJECT (bin, "invalidated sync data, bailing out");
return;
}
GST_DEBUG_OBJECT (bin, "Doing RTCP sync");
break;
}
case GST_RTP_BIN_RTCP_SYNC_NEVER:{
GST_DEBUG_OBJECT (bin, "Not doing any sync");
return;
}
}
gboolean all_sync = TRUE;
gint64 min = G_MAXINT64, rtp_min = G_MAXINT64;
/* recalc inter stream playout offset based on the selected mode */
if (rtp_info_sync) {
guint64 ext_base = -1;
guint64 rtp_clock_base_ext;
gint64 diff_rtp;
GST_DEBUG_OBJECT (bin,
"base RTP time %" G_GUINT64_FORMAT ", clock-rate %d, " "clock-base %"
G_GUINT64_FORMAT ", NPT start %" GST_TIME_FORMAT, base_rtptime,
clock_rate, rtp_clock_base, GST_TIME_ARGS (npt_start));
/* convert to extended RTP time */
rtp_clock_base_ext =
gst_rtp_buffer_ext_timestamp (&ext_base, rtp_clock_base);
if (rtp_clock_base_ext >= base_rtptime) {
diff_rtp =
gst_util_uint64_scale_int (rtp_clock_base_ext - base_rtptime,
GST_SECOND, clock_rate);
} else {
diff_rtp =
-gst_util_uint64_scale_int (base_rtptime - rtp_clock_base_ext,
GST_SECOND, clock_rate);
}
GST_DEBUG_OBJECT (bin, "RTP time difference %" GST_STIME_FORMAT,
GST_STIME_ARGS (diff_rtp));
/* Calculate the difference between NPT start and the local time */
stream->rtp_delta = npt_start - (base_time + diff_rtp);
for (walk = client->streams; walk; walk = g_slist_next (walk)) {
GstRtpBinStream *ostream = (GstRtpBinStream *) walk->data;
/* if not set yet for the other stream then ignore it */
if (ostream->rtp_delta == G_MININT64) {
all_sync = FALSE;
continue;
}
/* change in current stream's base from previously init'ed value leads
* to reset of all stream's base, e.g. after a seek */
if (stream != ostream && stream->clock_base != -1 &&
(stream->clock_base != rtp_clock_base
|| stream->npt_start != npt_start)) {
GST_DEBUG_OBJECT (bin, "reset upon clock base or NPT start change");
ostream->clock_base = -1;
ostream->npt_start = GST_CLOCK_TIME_NONE;
ostream->rtp_delta = G_MININT64;
if (ostream->have_sync == GST_RTP_BIN_STREAM_SYNCED_RTP_INFO)
ostream->have_sync = GST_RTP_BIN_STREAM_SYNCED_NONE;
continue;
}
if (ostream->rtp_delta < rtp_min)
rtp_min = ostream->rtp_delta;
}
} else {
GstClockTime rtp_running_time;
guint64 diff_rtp;
/* Take the extended rtptime we found in the SR packet and map it to the
* local rtptime. The local rtp time is used to construct timestamps on the
* buffers so we will calculate what running_time corresponds to the RTP
* timestamp in the SR packet.
*
* Note: this is always positive!
*/
diff_rtp = extrtptime - base_rtptime;
GST_DEBUG_OBJECT (bin,
"base RTP time %" G_GUINT64_FORMAT ", SR RTP time %" G_GUINT64_FORMAT
", RTP time difference %" G_GUINT64_FORMAT ", clock-rate %d",
base_rtptime, extrtptime, diff_rtp, clock_rate);
/* calculate local RTP time in gstreamer timestamp, we essentially perform the
* same conversion that a jitterbuffer would use to convert an rtp timestamp
* into a corresponding gstreamer timestamp. Note that the base_time also
* contains the drift between sender and receiver. */
rtp_running_time =
gst_util_uint64_scale_int (diff_rtp, GST_SECOND, clock_rate);
rtp_running_time += base_time;
GST_DEBUG_OBJECT (bin,
"SR RTP running time %" GST_TIME_FORMAT ", SR NTP %" GST_TIME_FORMAT,
GST_TIME_ARGS (rtp_running_time), GST_TIME_ARGS (ntpnstime));
/* calculate delta between server and receiver. ntpnstime is created by
* converting the ntptime in the last SR packet to a gstreamer timestamp. This
* delta expresses the difference to our timeline and the server timeline. The
* difference in itself doesn't mean much but we can combine the delta of
* multiple streams to create a stream specific offset.
*/
stream->rt_delta = ntpnstime - rtp_running_time;
for (walk = client->streams; walk; walk = g_slist_next (walk)) {
GstRtpBinStream *ostream = (GstRtpBinStream *) walk->data;
/* if not set yet for the other stream then ignore it */
if (ostream->rt_delta == G_MININT64) {
all_sync = FALSE;
continue;
}
if (ostream->rt_delta < min)
min = ostream->rt_delta;
}
}
/* Above we calculated the min of all deltas for either mode, ignoring
* streams that did not yet have a valid delta because we did not yet
* receive an SR packet for those streams yet (RTCP mode) or no RTP base
* time (RTP-Info mode).
*
* 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 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.
*
* we also assume that RTP-Info and RTCP sync give the same results.
*/
/* arrange to re-sync for each stream upon significant change, e.g. post-seek
* or when the RTP base time changes because of a jitterbuffer reset */
all_sync = all_sync && stream->base_rtptime == base_rtptime &&
((rtp_info_sync && stream->clock_base == rtp_clock_base
&& stream->npt_start == npt_start)
|| (!rtp_info_sync && stream->base_time == base_time));
stream->base_rtptime = base_rtptime;
if (rtp_info_sync) {
stream->clock_base = rtp_clock_base;
stream->npt_start = npt_start;
} else {
stream->base_time = base_time;
}
/* may need init performed above later on, but nothing more to do now */
if (client->nstreams <= 1)
return;
/* If this stream was synced before but with a different mode then wait
* until all streams of this client that were synced before are ready for
* the new mode. Otherwise this can cause unnecessary glitches when
* temporarily a different reference stream is selected because the old
* reference stream is not ready yet for the new mode */
for (walk = client->streams; walk; walk = g_slist_next (walk)) {
GstRtpBinStream *ostream = (GstRtpBinStream *) walk->data;
if (rtp_info_sync && ostream->have_sync == GST_RTP_BIN_STREAM_SYNCED_NTP) {
if (ostream->rtp_delta == G_MININT64) {
GST_DEBUG_OBJECT (bin,
"Switching sync mode, waiting for all streams to be ready");
return;
}
} else if (!rtp_info_sync
&& ostream->have_sync == GST_RTP_BIN_STREAM_SYNCED_RTP_INFO) {
if (ostream->rt_delta == G_MININT64) {
GST_DEBUG_OBJECT (bin,
"Switching sync mode, waiting for all streams to be ready");
return;
}
}
}
GST_DEBUG_OBJECT (bin,
"client %p RTP-Info sync %d, min delta %" G_GINT64_FORMAT
", min RTP delta %" G_GINT64_FORMAT ", all sync %d", client,
rtp_info_sync, min, rtp_min, all_sync);
/* if all have been synced already, do not bother further */
if (bin->rtcp_sync == GST_RTP_BIN_RTCP_SYNC_INITIAL && all_sync) {
GST_DEBUG_OBJECT (bin, "all streams already synced; done");
return;
}
/* bail out if we adjusted recently enough */
if (all_sync && !rtp_info_sync && GST_CLOCK_TIME_IS_VALID (ntpnstime)
&& GST_CLOCK_TIME_IS_VALID (bin->priv->last_ntpnstime)
&& ntpnstime - bin->priv->last_ntpnstime <
bin->rtcp_sync_interval * GST_MSECOND) {
GST_DEBUG_OBJECT (bin,
"discarding RTCP sender packet for sync; "
"previous sender info too recent " "(previous NTP %" G_GUINT64_FORMAT
")", bin->priv->last_ntpnstime);
return;
}
if (!rtp_info_sync)
bin->priv->last_ntpnstime = ntpnstime;
/* calculate offsets for each stream */
for (walk = client->streams; walk; walk = g_slist_next (walk)) {
GstRtpBinStream *ostream = (GstRtpBinStream *) walk->data;
gint64 ts_offset;
/* ignore streams for which we didn't receive an SR packet yet, we
* can't synchronize them yet. We can however sync other streams just
* fine. */
if ((rtp_info_sync && ostream->rtp_delta == G_MININT64) ||
(!rtp_info_sync && ostream->rt_delta == G_MININT64)) {
continue;
}
/* calculate offset to our reference stream, this should always give a
* positive number. */
if (rtp_info_sync)
ts_offset = ostream->rtp_delta - rtp_min;
else
ts_offset = ostream->rt_delta - min;
GST_DEBUG_OBJECT (bin,
"Calculated ts-offset %" GST_STIME_FORMAT " for SSRC %08x",
GST_STIME_ARGS (ts_offset), ostream->ssrc);
stream_set_ts_offset (bin, ostream, ts_offset, bin->max_ts_offset,
bin->min_ts_offset, TRUE);
if (rtp_info_sync)
ostream->have_sync = GST_RTP_BIN_STREAM_SYNCED_RTP_INFO;
else
ostream->have_sync = GST_RTP_BIN_STREAM_SYNCED_NTP;
}
gst_rtp_bin_send_sync_event (stream);
}
#define GST_RTCP_BUFFER_FOR_PACKETS(b,buffer,packet) \
for ((b) = gst_rtcp_buffer_get_first_packet ((buffer), (packet)); (b); \
(b) = gst_rtcp_packet_move_to_next ((packet)))
#define GST_RTCP_SDES_FOR_ITEMS(b,packet) \
for ((b) = gst_rtcp_packet_sdes_first_item ((packet)); (b); \
(b) = gst_rtcp_packet_sdes_next_item ((packet)))
#define GST_RTCP_SDES_FOR_ENTRIES(b,packet) \
for ((b) = gst_rtcp_packet_sdes_first_entry ((packet)); (b); \
(b) = gst_rtcp_packet_sdes_next_entry ((packet)))
static void
gst_rtp_bin_handle_sync (GstElement * jitterbuffer, GstStructure * s,
GstRtpBinStream * stream)
{
GstRtpBin *bin;
GstRTCPPacket packet;
guint32 ssrc;
guint64 ntpnstime, inband_ntpnstime;
guint64 npt_start = GST_CLOCK_TIME_NONE;
gboolean have_sr;
gboolean more;
guint64 base_rtptime;
guint64 base_time;
guint clock_rate;
guint64 clock_base;
guint64 extrtptime, inband_ext_rtptime;
GstBuffer *buffer;
const gchar *cname;
GstRTCPBuffer rtcp = { NULL, };
bin = stream->bin;
GST_DEBUG_OBJECT (bin, "sync handler called %" GST_PTR_FORMAT, s);
/* get the last relation between the rtp timestamps and the gstreamer
* timestamps. We get this info directly from the jitterbuffer which
* constructs gstreamer timestamps from rtp timestamps and so it know exactly
* what the current situation is. */
if (!gst_structure_get_uint64 (s, "base-rtptime", &base_rtptime) ||
!gst_structure_get_uint64 (s, "base-time", &base_time) ||
!gst_structure_get_uint (s, "clock-rate", &clock_rate) ||
!gst_structure_get_uint64 (s, "clock-base", &clock_base)) {
/* invalid structure */
return;
}
cname = gst_structure_get_string (s, "cname");
gst_structure_get_uint64 (s, "npt-start", &npt_start);
/* if the jitterbuffer directly got the NTP timestamp then don't work
* through the RTCP SR, otherwise extract it from there */
if (gst_structure_get_uint64 (s, "inband-ntpnstime", &inband_ntpnstime)
&& gst_structure_get_uint64 (s, "inband-ext-rtptime", &inband_ext_rtptime)
&& cname && gst_structure_get_uint (s, "ssrc", &ssrc)) {
GST_DEBUG_OBJECT (bin,
"handle sync from inband NTP-64 information for SSRC %08x", ssrc);
if (ssrc != stream->ssrc)
return;
GST_RTP_BIN_LOCK (bin);
gst_rtp_bin_associate (bin, stream, strlen (cname), (const guint8 *) cname,
inband_ntpnstime, inband_ext_rtptime, base_rtptime, base_time,
clock_rate, clock_base, npt_start);
GST_RTP_BIN_UNLOCK (bin);
return;
}
if (!gst_structure_get_uint64 (s, "sr-ext-rtptime", &extrtptime)
|| !gst_structure_has_field_typed (s, "sr-buffer", GST_TYPE_BUFFER)) {
/* invalid structure */
return;
}
GST_DEBUG_OBJECT (bin, "handle sync from RTCP SR information");
/* get RTCP SR ntpnstime if available */
if (gst_structure_get_uint64 (s, "sr-ntpnstime", &ntpnstime) && cname) {
GST_RTP_BIN_LOCK (bin);
/* associate the stream to CNAME */
gst_rtp_bin_associate (bin, stream, strlen (cname),
(const guint8 *) cname, ntpnstime, extrtptime, base_rtptime,
base_time, clock_rate, clock_base, npt_start);
GST_RTP_BIN_UNLOCK (bin);
return;
}
/* otherwise parse the RTCP packet */
buffer = gst_value_get_buffer (gst_structure_get_value (s, "sr-buffer"));
have_sr = FALSE;
gst_rtcp_buffer_map (buffer, GST_MAP_READ, &rtcp);
GST_RTCP_BUFFER_FOR_PACKETS (more, &rtcp, &packet) {
/* first packet must be SR or RR or else the validate would have failed */
switch (gst_rtcp_packet_get_type (&packet)) {
case GST_RTCP_TYPE_SR:
/* only parse first. There is only supposed to be one SR in the packet
* but we will deal with malformed packets gracefully by trying the
* next RTCP packet. */
if (have_sr)
continue;
/* get NTP time */
gst_rtcp_packet_sr_get_sender_info (&packet, &ssrc, &ntpnstime, NULL,
NULL, NULL);
/* convert ntptime to nanoseconds */
ntpnstime = gst_util_uint64_scale (ntpnstime, GST_SECOND,
(G_GINT64_CONSTANT (1) << 32));
GST_DEBUG_OBJECT (bin, "received sync packet from SSRC %08x", ssrc);
/* ignore SR that is not ours and check the next RTCP packet */
if (ssrc != stream->ssrc)
continue;
have_sr = TRUE;
/* If we already have the CNAME don't require parsing SDES */
if (cname) {
GST_RTP_BIN_LOCK (bin);
/* associate the stream to CNAME */
gst_rtp_bin_associate (bin, stream, strlen (cname),
(const guint8 *) cname, ntpnstime, extrtptime, base_rtptime,
base_time, clock_rate, clock_base, npt_start);
GST_RTP_BIN_UNLOCK (bin);
goto out;
}
break;
case GST_RTCP_TYPE_SDES:
{
gboolean more_items;
/* Bail out if we have not seen an SR item yet. */
if (!have_sr)
goto out;
GST_RTCP_SDES_FOR_ITEMS (more_items, &packet) {
gboolean more_entries;
/* skip items that are not about the SSRC of the sender */
if (gst_rtcp_packet_sdes_get_ssrc (&packet) != ssrc)
continue;
/* find the CNAME entry */
GST_RTCP_SDES_FOR_ENTRIES (more_entries, &packet) {
GstRTCPSDESType type;
guint8 len;
const guint8 *data;
gst_rtcp_packet_sdes_get_entry (&packet, &type, &len,
(guint8 **) & data);
if (type == GST_RTCP_SDES_CNAME) {
GST_RTP_BIN_LOCK (bin);
/* associate the stream to CNAME */
gst_rtp_bin_associate (bin, stream, len, data,
ntpnstime, extrtptime, base_rtptime, base_time, clock_rate,
clock_base, npt_start);
GST_RTP_BIN_UNLOCK (bin);
goto out;
}
}
}
/* only deal with first SDES, there is only supposed to be one SDES in
* the RTCP packet but we deal with bad packets gracefully. */
goto out;
}
default:
/* we can ignore these packets */
break;
}
}
out:
gst_rtcp_buffer_unmap (&rtcp);
}
/* create a new stream with @ssrc in @session. Must be called with
* RTP_SESSION_LOCK. */
static GstRtpBinStream *
create_stream (GstRtpBinSession * session, guint32 ssrc)
{
GstElement *buffer, *demux = NULL;
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 =
session_request_element (session, SIGNAL_REQUEST_JITTERBUFFER)))
goto no_jitterbuffer;
if (!rtpbin->ignore_pt) {
if (!(demux = gst_element_factory_make ("rtpptdemux", NULL)))
goto no_demux;
}
stream = g_new0 (GstRtpBinStream, 1);
stream->ssrc = ssrc;
stream->bin = rtpbin;
stream->session = session;
stream->buffer = gst_object_ref (buffer);
stream->demux = demux;
stream->have_sync = GST_RTP_BIN_STREAM_SYNCED_NONE;
stream->rt_delta = G_MININT64;
stream->rtp_delta = G_MININT64;
stream->base_rtptime = -1;
stream->base_time = GST_CLOCK_TIME_NONE;
stream->clock_base = -1;
stream->npt_start = GST_CLOCK_TIME_NONE;
stream->avg_ts_offset = 0;
stream->is_initialized = FALSE;
stream->percent = 100;
session->streams = g_slist_prepend (session->streams, 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 */
if (g_object_class_find_property (jb_class, "latency"))
g_object_set (buffer, "latency", rtpbin->latency_ms, 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, "add-reference-timestamp-meta"))
g_object_set (buffer, "add-reference-timestamp-meta",
rtpbin->add_reference_timestamp_meta, 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);
if (g_object_class_find_property (jb_class, "sync-interval"))
g_object_set (buffer, "sync-interval", rtpbin->rtcp_sync_interval, NULL);
g_signal_emit (rtpbin, gst_rtp_bin_signals[SIGNAL_NEW_JITTERBUFFER], 0,
buffer, session->id, ssrc);
if (!rtpbin->ignore_pt)
gst_bin_add (GST_BIN_CAST (rtpbin), demux);
/* link stuff */
if (demux)
gst_element_link_pads_full (buffer, "src", demux, "sink",
GST_PAD_LINK_CHECK_NOTHING);
if (rtpbin->buffering) {
guint64 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);
}
}
GST_OBJECT_LOCK (rtpbin);
target = GST_STATE_TARGET (rtpbin);
GST_OBJECT_UNLOCK (rtpbin);
/* from sink to source */
if (demux)
gst_element_set_state (demux, target);
gst_element_set_state (buffer, target);
return stream;
/* ERRORS */
max_streams:
{
GST_WARNING_OBJECT (rtpbin, "stream exceeds maximum (%d)",
rtpbin->max_streams);
return NULL;
}
no_jitterbuffer:
{
g_warning ("rtpbin: could not create rtpjitterbuffer element");
return NULL;
}
no_demux:
{
gst_object_unref (buffer);
g_warning ("rtpbin: could not create rtpptdemux element");
return NULL;
}
}
/* called with RTP_BIN_LOCK */
static void
free_stream (GstRtpBinStream * stream, GstRtpBin * bin)
{
GstRtpBinSession *sess = stream->session;
GSList *clients, *next_client;
GST_DEBUG_OBJECT (bin, "freeing stream %p", stream);
gst_element_set_locked_state (stream->buffer, TRUE);
if (stream->demux)
gst_element_set_locked_state (stream->demux, TRUE);
gst_element_set_state (stream->buffer, GST_STATE_NULL);
if (stream->demux)
gst_element_set_state (stream->demux, GST_STATE_NULL);
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);
if (stream->demux)
gst_bin_remove (GST_BIN_CAST (bin), stream->demux);
for (clients = bin->clients; clients; clients = next_client) {
GstRtpBinClient *client = (GstRtpBinClient *) clients->data;
GSList *streams, *next_stream;
next_client = g_slist_next (clients);
for (streams = client->streams; streams; streams = next_stream) {
GstRtpBinStream *ostream = (GstRtpBinStream *) streams->data;
next_stream = g_slist_next (streams);
if (ostream == stream) {
client->streams = g_slist_delete_link (client->streams, streams);
/* If this was the last stream belonging to this client,
* clean up the client. */
if (--client->nstreams == 0) {
bin->clients = g_slist_delete_link (bin->clients, clients);
free_client (client, bin);
break;
}
}
}
}
g_free (stream);
}
/* GObject vmethods */
static void gst_rtp_bin_dispose (GObject * object);
static void gst_rtp_bin_finalize (GObject * object);
static void gst_rtp_bin_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_rtp_bin_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
/* GstElement vmethods */
static GstStateChangeReturn gst_rtp_bin_change_state (GstElement * element,
GstStateChange transition);
static GstPad *gst_rtp_bin_request_new_pad (GstElement * element,
GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
static void gst_rtp_bin_release_pad (GstElement * element, GstPad * pad);
static void gst_rtp_bin_handle_message (GstBin * bin, GstMessage * message);
#define gst_rtp_bin_parent_class parent_class
G_DEFINE_TYPE_WITH_PRIVATE (GstRtpBin, gst_rtp_bin, GST_TYPE_BIN);
GST_ELEMENT_REGISTER_DEFINE (rtpbin, "rtpbin", GST_RANK_NONE, GST_TYPE_RTP_BIN);
static gboolean
_gst_element_accumulator (GSignalInvocationHint * ihint,
GValue * return_accu, const GValue * handler_return, gpointer dummy)
{
GstElement *element;
element = g_value_get_object (handler_return);
GST_DEBUG ("got element %" GST_PTR_FORMAT, element);
g_value_set_object (return_accu, element);
/* stop emission if we have an element */
return (element == NULL);
}
static gboolean
_gst_caps_accumulator (GSignalInvocationHint * ihint,
GValue * return_accu, const GValue * handler_return, gpointer dummy)
{
GstCaps *caps;
caps = g_value_get_boxed (handler_return);
GST_DEBUG ("got caps %" GST_PTR_FORMAT, caps);
g_value_set_boxed (return_accu, caps);
/* stop emission if we have a caps */
return (caps == NULL);
}
static void
gst_rtp_bin_class_init (GstRtpBinClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GstBinClass *gstbin_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gstbin_class = (GstBinClass *) klass;
gobject_class->dispose = gst_rtp_bin_dispose;
gobject_class->finalize = gst_rtp_bin_finalize;
gobject_class->set_property = gst_rtp_bin_set_property;
gobject_class->get_property = gst_rtp_bin_get_property;
g_object_class_install_property (gobject_class, PROP_LATENCY,
g_param_spec_uint ("latency", "Buffer latency in ms",
"Default amount of ms to buffer in the jitterbuffers", 0,
G_MAXUINT, DEFAULT_LATENCY_MS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_DROP_ON_LATENCY,
g_param_spec_boolean ("drop-on-latency",
"Drop buffers when maximum latency is reached",
"Tells the jitterbuffer to never exceed the given latency in size",
DEFAULT_DROP_ON_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin::request-pt-map:
* @rtpbin: the object which received the signal
* @session: the session
* @pt: the pt
*
* Request the payload type as #GstCaps for @pt in @session.
*/
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, NULL, GST_TYPE_CAPS, 2, G_TYPE_UINT,
G_TYPE_UINT);
/**
* GstRtpBin::payload-type-change:
* @rtpbin: the object which received the signal
* @session: the session
* @pt: the pt
*
* Signal that the current payload type changed to @pt in @session.
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::clear-pt-map:
* @rtpbin: the object which received the signal
*
* Clear all previously cached pt-mapping obtained with
* #GstRtpBin::request-pt-map.
*/
gst_rtp_bin_signals[SIGNAL_CLEAR_PT_MAP] =
g_signal_new ("clear-pt-map", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRtpBinClass,
clear_pt_map), NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE);
/**
* GstRtpBin::reset-sync:
* @rtpbin: the object which received the signal
*
* Reset all currently configured lip-sync parameters and require new SR
* packets for all streams before lip-sync is attempted again.
*/
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, NULL, G_TYPE_NONE, 0, G_TYPE_NONE);
/**
* GstRtpBin::get-session:
* @rtpbin: the object which received the signal
* @id: the session id
*
* Request the related GstRtpSession as #GstElement related with session @id.
*
* Since: 1.8
*/
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, NULL, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
/**
* GstRtpBin::get-internal-session:
* @rtpbin: the object which received the signal
* @id: the session id
*
* Request the internal RTPSession object as #GObject in session @id.
*/
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, 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. 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, 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. 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, NULL, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
/**
* GstRtpBin::clear-ssrc:
* @rtpbin: the object which received the signal
* @id: the session id
* @ssrc: the ssrc
*
* Remove all pads from rtpssrcdemux element associated with the specified
* ssrc. This delegate the action signal to the rtpssrcdemux element
* associated with the specified session.
*
* Since: 1.20
*/
gst_rtp_bin_signals[SIGNAL_CLEAR_SSRC] =
g_signal_new ("clear-ssrc", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRtpBinClass,
clear_ssrc), NULL, NULL, NULL, G_TYPE_NONE, 2,
G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-new-ssrc:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the SSRC
*
* Notify of a new SSRC that entered @session.
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-ssrc-collision:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the SSRC
*
* Notify when we have an SSRC collision
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-ssrc-validated:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the SSRC
*
* Notify of a new SSRC that became validated.
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-ssrc-active:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the SSRC
*
* Notify of a SSRC that is active, i.e., sending RTCP.
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-ssrc-sdes:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the SSRC
*
* Notify of a SSRC that is active, i.e., sending RTCP.
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-bye-ssrc:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the SSRC
*
* Notify of an SSRC that became inactive because of a BYE packet.
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-bye-timeout:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the SSRC
*
* Notify of an SSRC that has timed out because of BYE
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-timeout:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the SSRC
*
* Notify of an SSRC that has timed out
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-sender-timeout:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the SSRC
*
* Notify of a sender SSRC that has timed out and became a receiver
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-npt-stop:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the SSRC
*
* Notify that SSRC sender has sent data up to the configured NPT stop time.
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::request-rtp-encoder:
* @rtpbin: the object which received the signal
* @session: the session
*
* Request an RTP encoder element for the given @session. The encoder
* element will be added to the bin if not previously added.
*
* If no handler is connected, no encoder will be used.
*
* Since: 1.4
*/
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, NULL,
GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
/**
* GstRtpBin::request-rtp-decoder:
* @rtpbin: the object which received the signal
* @session: the session
*
* Request an RTP decoder element for the given @session. The decoder
* element will be added to the bin if not previously added.
*
* If no handler is connected, no encoder will be used.
*
* Since: 1.4
*/
gst_rtp_bin_signals[SIGNAL_REQUEST_RTP_DECODER] =
g_signal_new ("request-rtp-decoder", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
request_rtp_decoder), _gst_element_accumulator, NULL,
NULL, GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
/**
* GstRtpBin::request-rtcp-encoder:
* @rtpbin: the object which received the signal
* @session: the session
*
* Request an RTCP encoder element for the given @session. The encoder
* element will be added to the bin if not previously added.
*
* If no handler is connected, no encoder will be used.
*
* Since: 1.4
*/
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, NULL,
GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
/**
* GstRtpBin::request-rtcp-decoder:
* @rtpbin: the object which received the signal
* @session: the session
*
* Request an RTCP decoder element for the given @session. The decoder
* element will be added to the bin if not previously added.
*
* If no handler is connected, no encoder will be used.
*
* Since: 1.4
*/
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, 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);
/**
* GstRtpBin::new-jitterbuffer:
* @rtpbin: the object which received the signal
* @jitterbuffer: the new jitterbuffer
* @session: the session
* @ssrc: the SSRC
*
* Notify that a new @jitterbuffer was created for @session and @ssrc.
* This signal can, for example, be used to configure @jitterbuffer.
*
* Since: 1.4
*/
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, NULL,
G_TYPE_NONE, 3, GST_TYPE_ELEMENT, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::new-storage:
* @rtpbin: the object which received the signal
* @storage: the new storage
* @session: the session
*
* Notify that a new @storage was created for @session.
* This signal can, for example, be used to configure @storage.
*
* Since: 1.14
*/
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, NULL,
G_TYPE_NONE, 2, GST_TYPE_ELEMENT, G_TYPE_UINT);
/**
* GstRtpBin::request-aux-sender:
* @rtpbin: the object which received the signal
* @session: the session
*
* Request an AUX sender element for the given @session. The AUX
* element will be added to the bin.
*
* If no handler is connected, no AUX element will be used.
*
* Since: 1.4
*/
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, NULL,
GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
/**
* GstRtpBin::request-aux-receiver:
* @rtpbin: the object which received the signal
* @session: the session
*
* Request an AUX receiver element for the given @session. The AUX
* element will be added to the bin.
*
* If no handler is connected, no AUX element will be used.
*
* Since: 1.4
*/
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, NULL,
GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
/**
* GstRtpBin::request-fec-decoder:
* @rtpbin: the object which received the signal
* @session: the session index
*
* Request a FEC decoder element for the given @session. The element
* will be added to the bin after the pt demuxer. If there are multiple
* ssrc's and pt's in @session, this signal may be called multiple times for
* the same @session each corresponding to a newly discovered ssrc.
*
* If no handler is connected, no FEC decoder will be used.
*
* Warning: usage of this signal is not appropriate for the BUNDLE case,
* connect to #GstRtpBin::request-fec-decoder-full instead.
*
* Since: 1.14
*/
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, NULL,
GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
/**
* GstRtpBin::request-fec-decoder-full:
* @rtpbin: the object which received the signal
* @session: the session index
* @ssrc: the ssrc of the stream
* @pt: the payload type
*
* Request a FEC decoder element for the given @session. The element
* will be added to the bin after the pt demuxer. If there are multiple
* ssrc's and pt's in @session, this signal may be called multiple times for
* the same @session each corresponding to a newly discovered ssrc and payload
* type, those are provided as parameters.
*
* If no handler is connected, no FEC decoder will be used.
*
* Since: 1.20
*/
gst_rtp_bin_signals[SIGNAL_REQUEST_FEC_DECODER_FULL] =
g_signal_new ("request-fec-decoder-full", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRtpBinClass,
request_fec_decoder), _gst_element_accumulator, NULL, NULL,
GST_TYPE_ELEMENT, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::request-fec-encoder:
* @rtpbin: the object which received the signal
* @session: the session index
*
* Request a FEC encoder element for the given @session. The element
* will be added to the bin after the RTPSession.
*
* If no handler is connected, no FEC encoder will be used.
*
* Since: 1.14
*/
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, NULL,
GST_TYPE_ELEMENT, 1, G_TYPE_UINT);
/**
* GstRtpBin::on-new-sender-ssrc:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the sender SSRC
*
* Notify of a new sender SSRC that entered @session.
*
* Since: 1.8
*/
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, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* GstRtpBin::on-sender-ssrc-active:
* @rtpbin: the object which received the signal
* @session: the session
* @ssrc: the sender SSRC
*
* Notify of a sender SSRC that is active, i.e., sending RTCP.
*
* Since: 1.8
*/
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, 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_PARAM_DOC_SHOW_DEFAULT));
g_object_class_install_property (gobject_class, PROP_DO_LOST,
g_param_spec_boolean ("do-lost", "Do Lost",
"Send an event downstream when a packet is lost", DEFAULT_DO_LOST,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_AUTOREMOVE,
g_param_spec_boolean ("autoremove", "Auto Remove",
"Automatically remove timed out sources", DEFAULT_AUTOREMOVE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_IGNORE_PT,
g_param_spec_boolean ("ignore-pt", "Ignore PT",
"Do not demultiplex based on PT values", DEFAULT_IGNORE_PT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_USE_PIPELINE_CLOCK,
g_param_spec_boolean ("use-pipeline-clock", "Use pipeline clock",
"Use the pipeline running-time to set the NTP time in the RTCP SR messages "
"(DEPRECATED: Use ntp-time-source property)",
DEFAULT_USE_PIPELINE_CLOCK,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED));
/**
* GstRtpBin:buffer-mode:
*
* Control the buffering and timestamping mode used by the jitterbuffer.
*/
g_object_class_install_property (gobject_class, PROP_BUFFER_MODE,
g_param_spec_enum ("buffer-mode", "Buffer Mode",
"Control the buffering algorithm in use", RTP_TYPE_JITTER_BUFFER_MODE,
DEFAULT_BUFFER_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:ntp-sync:
*
* Set the NTP time from the sender reports as the running-time on the
* buffers. When both the sender and receiver have sychronized
* running-time, i.e. when the clock and base-time is shared
* between the receivers and the and the senders, this option can be
* used to synchronize receivers on multiple machines.
*/
g_object_class_install_property (gobject_class, PROP_NTP_SYNC,
g_param_spec_boolean ("ntp-sync", "Sync on NTP clock",
"Synchronize received streams to the NTP clock", DEFAULT_NTP_SYNC,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:rtcp-sync:
*
* If not synchronizing (directly) to the NTP clock, determines how to sync
* the various streams.
*/
g_object_class_install_property (gobject_class, PROP_RTCP_SYNC,
g_param_spec_enum ("rtcp-sync", "RTCP Sync",
"Use of RTCP SR in synchronization", GST_RTP_BIN_RTCP_SYNC_TYPE,
DEFAULT_RTCP_SYNC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:rtcp-sync-interval:
*
* Determines how often to sync streams using RTCP data or inband NTP-64
* header extensions.
*/
g_object_class_install_property (gobject_class, PROP_RTCP_SYNC_INTERVAL,
g_param_spec_uint ("rtcp-sync-interval", "RTCP Sync Interval",
"RTCP SR / NTP-64 interval synchronization (ms) (0 = always)",
0, G_MAXUINT, DEFAULT_RTCP_SYNC_INTERVAL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_DO_SYNC_EVENT,
g_param_spec_boolean ("do-sync-event", "Do Sync Event",
"Send event downstream when a stream is synchronized to the sender",
DEFAULT_DO_SYNC_EVENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:do-retransmission:
*
* 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
* #GstRtpJitterBuffer object instead.
*/
g_object_class_install_property (gobject_class, PROP_DO_RETRANSMISSION,
g_param_spec_boolean ("do-retransmission", "Do retransmission",
"Enable retransmission on all streams",
DEFAULT_DO_RETRANSMISSION,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:rtp-profile:
*
* Sets the default RTP profile of newly created RTP sessions. The
* profile can be changed afterwards on a per-session basis.
*/
g_object_class_install_property (gobject_class, PROP_RTP_PROFILE,
g_param_spec_enum ("rtp-profile", "RTP Profile",
"Default RTP profile of newly created sessions",
GST_TYPE_RTP_PROFILE, DEFAULT_RTP_PROFILE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
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,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_RTCP_SYNC_SEND_TIME,
g_param_spec_boolean ("rtcp-sync-send-time", "RTCP Sync Send Time",
"Use send time or capture time for RTCP sync "
"(TRUE = send time, FALSE = capture time)",
DEFAULT_RTCP_SYNC_SEND_TIME,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_MAX_RTCP_RTP_TIME_DIFF,
g_param_spec_int ("max-rtcp-rtp-time-diff", "Max RTCP RTP Time Diff",
"Maximum amount of time in ms that the RTP time in RTCP SRs "
"is allowed to be ahead (-1 disabled)", -1, G_MAXINT,
DEFAULT_MAX_RTCP_RTP_TIME_DIFF,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_MAX_DROPOUT_TIME,
g_param_spec_uint ("max-dropout-time", "Max dropout time",
"The maximum time (milliseconds) of missing packets tolerated.",
0, G_MAXUINT, DEFAULT_MAX_DROPOUT_TIME,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_MAX_MISORDER_TIME,
g_param_spec_uint ("max-misorder-time", "Max misorder time",
"The maximum time (milliseconds) of misordered packets tolerated.",
0, G_MAXUINT, DEFAULT_MAX_MISORDER_TIME,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_RFC7273_SYNC,
g_param_spec_boolean ("rfc7273-sync", "Sync on RFC7273 clock",
"Synchronize received streams to the RFC7273 clock "
"(requires clock and offset to be provided)", DEFAULT_RFC7273_SYNC,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:add-reference-timestamp-meta:
*
* When syncing to a RFC7273 clock or after clock synchronization via RTCP or
* inband NTP-64 header extensions has happened, add #GstReferenceTimestampMeta
* to buffers with the original reconstructed reference clock timestamp.
*
* Since: 1.22
*/
g_object_class_install_property (gobject_class,
PROP_ADD_REFERENCE_TIMESTAMP_META,
g_param_spec_boolean ("add-reference-timestamp-meta",
"Add Reference Timestamp Meta",
"Add Reference Timestamp Meta to buffers with the original clock timestamp "
"before any adjustments when syncing to an RFC7273 clock or after clock "
"synchronization via RTCP or inband NTP-64 header extensions has happened.",
DEFAULT_ADD_REFERENCE_TIMESTAMP_META,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_MAX_STREAMS,
g_param_spec_uint ("max-streams", "Max Streams",
"The maximum number of streams to create for one session",
0, G_MAXUINT, DEFAULT_MAX_STREAMS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:max-ts-offset-adjustment:
*
* Syncing time stamps to NTP time adds a time offset. This parameter
* specifies the maximum number of nanoseconds per frame that this time offset
* may be adjusted with. This is used to avoid sudden large changes to time
* stamps.
*
* Since: 1.14
*/
g_object_class_install_property (gobject_class, PROP_MAX_TS_OFFSET_ADJUSTMENT,
g_param_spec_uint64 ("max-ts-offset-adjustment",
"Max Timestamp Offset Adjustment",
"The maximum number of nanoseconds per frame that time stamp offsets "
"may be adjusted (0 = no limit).", 0, G_MAXUINT64,
DEFAULT_MAX_TS_OFFSET_ADJUSTMENT, G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:max-ts-offset:
*
* Used to set an upper limit of how large a time offset may be. This
* is used to protect against unrealistic values as a result of either
* client,server or clock issues.
*
* Since: 1.14
*/
g_object_class_install_property (gobject_class, PROP_MAX_TS_OFFSET,
g_param_spec_int64 ("max-ts-offset", "Max TS Offset",
"The maximum absolute value of the time offset in (nanoseconds). "
"Note, if the ntp-sync parameter is set the default value is "
"changed to 0 (no limit)", 0, G_MAXINT64, DEFAULT_MAX_TS_OFFSET,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:min-ts-offset:
*
* Used to set an lower limit for when a time offset is deemed large enough
* to be useful for sync corrections.
*
* When streaming for instance audio, even very small ts_offsets cause
* audible glitches. This property is used for controlling how sensitive the
* adjustments should be to small deviations in ts_offset, occurring for
* instance due to jittery network conditions or system load.
*
* Since: 1.22
*/
g_object_class_install_property (gobject_class, PROP_MIN_TS_OFFSET,
g_param_spec_uint64 ("min-ts-offset", "Min TS Offset",
"The minimum absolute value of the time offset in (nanoseconds). "
"Used to set an lower limit for when a time offset is deemed large "
"enough to be useful for sync corrections."
"Note, if the ntp-sync parameter is set the default value is "
"changed to 0 (no limit)", 0, G_MAXUINT64, DEFAULT_MIN_TS_OFFSET,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:ts-offset-smoothing-factor:
*
* Controls the weighting between previous and current timestamp offsets in
* a running moving average (RMA):
* ts_offset_average(n) =
* ((ts-offset-smoothing-factor - 1) * ts_offset_average(n - 1) + ts_offset(n)) /
* ts-offset-smoothing-factor
*
* This can stabilize the timestamp offset and prevent unnecessary skew
* corrections due to jitter introduced by network or system load.
*
* Since: 1.22
*/
g_object_class_install_property (gobject_class,
PROP_TS_OFFSET_SMOOTHING_FACTOR,
g_param_spec_uint ("ts-offset-smoothing-factor",
"Timestamp Offset Smoothing Factor",
"Sets a smoothing factor for the timestamp offset in number of "
"values for a calculated running moving average. "
"(0 = no smoothing factor)", 0, G_MAXUINT,
DEFAULT_TS_OFFSET_SMOOTHING_FACTOR,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:fec-decoders:
*
* Used to provide a factory used to build the FEC decoder for a
* given session, as a command line alternative to
* #GstRtpBin::request-fec-decoder.
*
* Expects a GstStructure in the form session_id (gint) -> factory (string)
*
* Since: 1.20
*/
g_object_class_install_property (gobject_class, PROP_FEC_DECODERS,
g_param_spec_boxed ("fec-decoders", "Fec Decoders",
"GstStructure mapping from session index to FEC decoder "
"factory, eg "
"fec-decoders='fec,0=\"rtpst2022-1-fecdec\\ size-time\\=1000000000\";'",
GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:fec-encoders:
*
* Used to provide a factory used to build the FEC encoder for a
* given session, as a command line alternative to
* #GstRtpBin::request-fec-encoder.
*
* Expects a GstStructure in the form session_id (gint) -> factory (string)
*
* Since: 1.20
*/
g_object_class_install_property (gobject_class, PROP_FEC_ENCODERS,
g_param_spec_boxed ("fec-encoders", "Fec Encoders",
"GstStructure mapping from session index to FEC encoder "
"factory, eg "
"fec-encoders='fec,0=\"rtpst2022-1-fecenc\\ rows\\=5\\ columns\\=5\";'",
GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:update-ntp64-header-ext:
*
* Whether RTP NTP header extension should be updated with actual
* NTP time. If not, use the NTP time from buffer timestamp metadata
*
* Since: 1.22
*/
g_object_class_install_property (gobject_class,
PROP_UPDATE_NTP64_HEADER_EXT,
g_param_spec_boolean ("update-ntp64-header-ext",
"Update NTP-64 RTP Header Extension",
"Whether RTP NTP header extension should be updated with actual NTP time",
DEFAULT_UPDATE_NTP64_HEADER_EXT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstRtpBin:timeout-inactive-sources:
*
* Whether inactive sources should be timed out
*
* Since: 1.24
*/
g_object_class_install_property (gobject_class,
PROP_TIMEOUT_INACTIVE_SOURCES,
g_param_spec_boolean ("timeout-inactive-sources",
"Time out inactive sources",
"Whether sources that don't receive RTP or RTCP packets for longer "
"than 5x RTCP interval should be removed",
DEFAULT_TIMEOUT_INACTIVE_SOURCES,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
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);
gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_rtp_bin_release_pad);
/* sink pads */
gst_element_class_add_static_pad_template (gstelement_class,
&rtpbin_recv_rtp_sink_template);
gst_element_class_add_static_pad_template (gstelement_class,
&rtpbin_recv_fec_sink_template);
gst_element_class_add_static_pad_template (gstelement_class,
&rtpbin_recv_rtcp_sink_template);
gst_element_class_add_static_pad_template (gstelement_class,
&rtpbin_send_rtp_sink_template);
/* src pads */
gst_element_class_add_static_pad_template (gstelement_class,
&rtpbin_recv_rtp_src_template);
gst_element_class_add_static_pad_template (gstelement_class,
&rtpbin_send_rtcp_src_template);
gst_element_class_add_static_pad_template (gstelement_class,
&rtpbin_send_rtp_src_template);
gst_element_class_add_static_pad_template (gstelement_class,
&rtpbin_send_fec_src_template);
gst_element_class_set_static_metadata (gstelement_class, "RTP Bin",
"Filter/Network/RTP",
"Real-Time Transport Protocol bin",
"Wim Taymans <wim.taymans@gmail.com>");
gstbin_class->handle_message = GST_DEBUG_FUNCPTR (gst_rtp_bin_handle_message);
klass->clear_pt_map = GST_DEBUG_FUNCPTR (gst_rtp_bin_clear_pt_map);
klass->reset_sync = GST_DEBUG_FUNCPTR (gst_rtp_bin_reset_sync);
klass->get_session = GST_DEBUG_FUNCPTR (gst_rtp_bin_get_session);
klass->get_internal_session =
GST_DEBUG_FUNCPTR (gst_rtp_bin_get_internal_session);
klass->get_storage = GST_DEBUG_FUNCPTR (gst_rtp_bin_get_storage);
klass->get_internal_storage =
GST_DEBUG_FUNCPTR (gst_rtp_bin_get_internal_storage);
klass->clear_ssrc = GST_DEBUG_FUNCPTR (gst_rtp_bin_clear_ssrc);
klass->request_rtp_encoder = GST_DEBUG_FUNCPTR (gst_rtp_bin_request_encoder);
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");
gst_type_mark_as_plugin_api (GST_RTP_BIN_RTCP_SYNC_TYPE, 0);
}
static void
gst_rtp_bin_init (GstRtpBin * rtpbin)
{
gchar *cname;
rtpbin->priv = gst_rtp_bin_get_instance_private (rtpbin);
g_mutex_init (&rtpbin->priv->bin_lock);
g_mutex_init (&rtpbin->priv->dyn_lock);
rtpbin->latency_ms = DEFAULT_LATENCY_MS;
rtpbin->latency_ns = DEFAULT_LATENCY_MS * GST_MSECOND;
rtpbin->drop_on_latency = DEFAULT_DROP_ON_LATENCY;
rtpbin->do_lost = DEFAULT_DO_LOST;
rtpbin->ignore_pt = DEFAULT_IGNORE_PT;
rtpbin->ntp_sync = DEFAULT_NTP_SYNC;
rtpbin->rtcp_sync = DEFAULT_RTCP_SYNC;
rtpbin->rtcp_sync_interval = DEFAULT_RTCP_SYNC_INTERVAL;
rtpbin->priv->autoremove = DEFAULT_AUTOREMOVE;
rtpbin->buffer_mode = DEFAULT_BUFFER_MODE;
rtpbin->use_pipeline_clock = DEFAULT_USE_PIPELINE_CLOCK;
rtpbin->send_sync_event = DEFAULT_DO_SYNC_EVENT;
rtpbin->do_retransmission = DEFAULT_DO_RETRANSMISSION;
rtpbin->rtp_profile = DEFAULT_RTP_PROFILE;
rtpbin->ntp_time_source = DEFAULT_NTP_TIME_SOURCE;
rtpbin->rtcp_sync_send_time = DEFAULT_RTCP_SYNC_SEND_TIME;
rtpbin->max_rtcp_rtp_time_diff = DEFAULT_MAX_RTCP_RTP_TIME_DIFF;
rtpbin->max_dropout_time = DEFAULT_MAX_DROPOUT_TIME;
rtpbin->max_misorder_time = DEFAULT_MAX_MISORDER_TIME;
rtpbin->rfc7273_sync = DEFAULT_RFC7273_SYNC;
rtpbin->add_reference_timestamp_meta = DEFAULT_ADD_REFERENCE_TIMESTAMP_META;
rtpbin->max_streams = DEFAULT_MAX_STREAMS;
rtpbin->max_ts_offset_adjustment = DEFAULT_MAX_TS_OFFSET_ADJUSTMENT;
rtpbin->max_ts_offset = DEFAULT_MAX_TS_OFFSET;
rtpbin->max_ts_offset_is_set = FALSE;
rtpbin->min_ts_offset = DEFAULT_MIN_TS_OFFSET;
rtpbin->min_ts_offset_is_set = FALSE;
rtpbin->ts_offset_smoothing_factor = DEFAULT_TS_OFFSET_SMOOTHING_FACTOR;
rtpbin->update_ntp64_header_ext = DEFAULT_UPDATE_NTP64_HEADER_EXT;
rtpbin->timeout_inactive_sources = DEFAULT_TIMEOUT_INACTIVE_SOURCES;
/* some default SDES entries */
cname = g_strdup_printf ("user%u@host-%x", g_random_int (), g_random_int ());
rtpbin->sdes = gst_structure_new ("application/x-rtp-source-sdes",
"cname", G_TYPE_STRING, cname, "tool", G_TYPE_STRING, "GStreamer", NULL);
rtpbin->fec_decoders =
gst_structure_new_empty ("application/x-rtp-fec-decoders");
rtpbin->fec_encoders =
gst_structure_new_empty ("application/x-rtp-fec-encoders");
g_free (cname);
}
static void
gst_rtp_bin_dispose (GObject * object)
{
GstRtpBin *rtpbin;
rtpbin = GST_RTP_BIN (object);
GST_RTP_BIN_LOCK (rtpbin);
GST_DEBUG_OBJECT (object, "freeing sessions");
g_slist_foreach (rtpbin->sessions, (GFunc) free_session, rtpbin);
g_slist_free (rtpbin->sessions);
rtpbin->sessions = NULL;
GST_RTP_BIN_UNLOCK (rtpbin);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_rtp_bin_finalize (GObject * object)
{
GstRtpBin *rtpbin;
rtpbin = GST_RTP_BIN (object);
if (rtpbin->sdes)
gst_structure_free (rtpbin->sdes);
if (rtpbin->fec_decoders)
gst_structure_free (rtpbin->fec_decoders);
if (rtpbin->fec_encoders)
gst_structure_free (rtpbin->fec_encoders);
g_mutex_clear (&rtpbin->priv->bin_lock);
g_mutex_clear (&rtpbin->priv->dyn_lock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_rtp_bin_set_sdes_struct (GstRtpBin * bin, const GstStructure * sdes)
{
GSList *item;
if (sdes == NULL)
return;
GST_RTP_BIN_LOCK (bin);
GST_OBJECT_LOCK (bin);
if (bin->sdes)
gst_structure_free (bin->sdes);
bin->sdes = gst_structure_copy (sdes);
GST_OBJECT_UNLOCK (bin);
/* store in all sessions */
for (item = bin->sessions; item; item = g_slist_next (item)) {
GstRtpBinSession *session = item->data;
g_object_set (session->session, "sdes", sdes, NULL);
}
GST_RTP_BIN_UNLOCK (bin);
}
static void
gst_rtp_bin_set_fec_decoders_struct (GstRtpBin * bin,
const GstStructure * decoders)
{
if (decoders == NULL)
return;
GST_RTP_BIN_LOCK (bin);
GST_OBJECT_LOCK (bin);
if (bin->fec_decoders)
gst_structure_free (bin->fec_decoders);
bin->fec_decoders = gst_structure_copy (decoders);
GST_OBJECT_UNLOCK (bin);
GST_RTP_BIN_UNLOCK (bin);
}
static void
gst_rtp_bin_set_fec_encoders_struct (GstRtpBin * bin,
const GstStructure * encoders)
{
if (encoders == NULL)
return;
GST_RTP_BIN_LOCK (bin);
GST_OBJECT_LOCK (bin);
if (bin->fec_encoders)
gst_structure_free (bin->fec_encoders);
bin->fec_encoders = gst_structure_copy (encoders);
GST_OBJECT_UNLOCK (bin);
GST_RTP_BIN_UNLOCK (bin);
}
static GstStructure *
gst_rtp_bin_get_sdes_struct (GstRtpBin * bin)
{
GstStructure *result;
GST_OBJECT_LOCK (bin);
result = gst_structure_copy (bin->sdes);
GST_OBJECT_UNLOCK (bin);
return result;
}
static GstStructure *
gst_rtp_bin_get_fec_decoders_struct (GstRtpBin * bin)
{
GstStructure *result;
GST_OBJECT_LOCK (bin);
result = gst_structure_copy (bin->fec_decoders);
GST_OBJECT_UNLOCK (bin);
return result;
}
static GstStructure *
gst_rtp_bin_get_fec_encoders_struct (GstRtpBin * bin)
{
GstStructure *result;
GST_OBJECT_LOCK (bin);
result = gst_structure_copy (bin->fec_encoders);
GST_OBJECT_UNLOCK (bin);
return result;
}
static void
gst_rtp_bin_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstRtpBin *rtpbin;
rtpbin = GST_RTP_BIN (object);
switch (prop_id) {
case PROP_LATENCY:
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->latency_ms = g_value_get_uint (value);
rtpbin->latency_ns = rtpbin->latency_ms * GST_MSECOND;
GST_RTP_BIN_UNLOCK (rtpbin);
/* propagate the property down to the jitterbuffer */
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin, "latency", value);
break;
case PROP_DROP_ON_LATENCY:
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->drop_on_latency = g_value_get_boolean (value);
GST_RTP_BIN_UNLOCK (rtpbin);
/* propagate the property down to the jitterbuffer */
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin,
"drop-on-latency", value);
break;
case PROP_SDES:
gst_rtp_bin_set_sdes_struct (rtpbin, g_value_get_boxed (value));
break;
case PROP_DO_LOST:
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->do_lost = g_value_get_boolean (value);
GST_RTP_BIN_UNLOCK (rtpbin);
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin, "do-lost", value);
break;
case PROP_NTP_SYNC:
rtpbin->ntp_sync = g_value_get_boolean (value);
/* The default value of max_ts_offset depends on ntp_sync. If user
* hasn't set it then change default value */
if (!rtpbin->max_ts_offset_is_set) {
if (rtpbin->ntp_sync) {
rtpbin->max_ts_offset = 0;
} else {
rtpbin->max_ts_offset = DEFAULT_MAX_TS_OFFSET;
}
}
if (!rtpbin->min_ts_offset_is_set) {
if (rtpbin->ntp_sync) {
rtpbin->min_ts_offset = 0;
} else {
rtpbin->min_ts_offset = DEFAULT_MIN_TS_OFFSET;
}
}
break;
case PROP_RTCP_SYNC:
g_atomic_int_set (&rtpbin->rtcp_sync, g_value_get_enum (value));
break;
case PROP_RTCP_SYNC_INTERVAL:
rtpbin->rtcp_sync_interval = g_value_get_uint (value);
break;
case PROP_IGNORE_PT:
rtpbin->ignore_pt = g_value_get_boolean (value);
break;
case PROP_AUTOREMOVE:
rtpbin->priv->autoremove = g_value_get_boolean (value);
break;
case PROP_USE_PIPELINE_CLOCK:
{
GSList *sessions;
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->use_pipeline_clock = g_value_get_boolean (value);
for (sessions = rtpbin->sessions; sessions;
sessions = g_slist_next (sessions)) {
GstRtpBinSession *session = (GstRtpBinSession *) sessions->data;
g_object_set (G_OBJECT (session->session),
"use-pipeline-clock", rtpbin->use_pipeline_clock, NULL);
}
GST_RTP_BIN_UNLOCK (rtpbin);
}
break;
case PROP_DO_SYNC_EVENT:
rtpbin->send_sync_event = g_value_get_boolean (value);
break;
case PROP_BUFFER_MODE:
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->buffer_mode = g_value_get_enum (value);
GST_RTP_BIN_UNLOCK (rtpbin);
/* propagate the property down to the jitterbuffer */
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin, "mode", value);
break;
case PROP_DO_RETRANSMISSION:
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->do_retransmission = g_value_get_boolean (value);
GST_RTP_BIN_UNLOCK (rtpbin);
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin,
"do-retransmission", value);
break;
case PROP_RTP_PROFILE:
rtpbin->rtp_profile = g_value_get_enum (value);
break;
case PROP_NTP_TIME_SOURCE:{
GSList *sessions;
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->ntp_time_source = g_value_get_enum (value);
for (sessions = rtpbin->sessions; sessions;
sessions = g_slist_next (sessions)) {
GstRtpBinSession *session = (GstRtpBinSession *) sessions->data;
g_object_set (G_OBJECT (session->session),
"ntp-time-source", rtpbin->ntp_time_source, NULL);
}
GST_RTP_BIN_UNLOCK (rtpbin);
break;
}
case PROP_RTCP_SYNC_SEND_TIME:{
GSList *sessions;
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->rtcp_sync_send_time = g_value_get_boolean (value);
for (sessions = rtpbin->sessions; sessions;
sessions = g_slist_next (sessions)) {
GstRtpBinSession *session = (GstRtpBinSession *) sessions->data;
g_object_set (G_OBJECT (session->session),
"rtcp-sync-send-time", rtpbin->rtcp_sync_send_time, NULL);
}
GST_RTP_BIN_UNLOCK (rtpbin);
break;
}
case PROP_MAX_RTCP_RTP_TIME_DIFF:
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->max_rtcp_rtp_time_diff = g_value_get_int (value);
GST_RTP_BIN_UNLOCK (rtpbin);
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin,
"max-rtcp-rtp-time-diff", value);
break;
case PROP_MAX_DROPOUT_TIME:
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->max_dropout_time = g_value_get_uint (value);
GST_RTP_BIN_UNLOCK (rtpbin);
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin,
"max-dropout-time", value);
gst_rtp_bin_propagate_property_to_session (rtpbin, "max-dropout-time",
value);
break;
case PROP_MAX_MISORDER_TIME:
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->max_misorder_time = g_value_get_uint (value);
GST_RTP_BIN_UNLOCK (rtpbin);
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin,
"max-misorder-time", value);
gst_rtp_bin_propagate_property_to_session (rtpbin, "max-misorder-time",
value);
break;
case PROP_RFC7273_SYNC:
rtpbin->rfc7273_sync = g_value_get_boolean (value);
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin,
"rfc7273-sync", value);
break;
case PROP_ADD_REFERENCE_TIMESTAMP_META:
rtpbin->add_reference_timestamp_meta = g_value_get_boolean (value);
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin,
"add-reference-timestamp-meta", value);
break;
case PROP_MAX_STREAMS:
rtpbin->max_streams = g_value_get_uint (value);
break;
case PROP_MAX_TS_OFFSET_ADJUSTMENT:
rtpbin->max_ts_offset_adjustment = g_value_get_uint64 (value);
gst_rtp_bin_propagate_property_to_jitterbuffer (rtpbin,
"max-ts-offset-adjustment", value);
break;
case PROP_MAX_TS_OFFSET:
rtpbin->max_ts_offset = g_value_get_int64 (value);
rtpbin->max_ts_offset_is_set = TRUE;
break;
case PROP_MIN_TS_OFFSET:
rtpbin->min_ts_offset = g_value_get_uint64 (value);
rtpbin->min_ts_offset_is_set = TRUE;
break;
case PROP_TS_OFFSET_SMOOTHING_FACTOR:
rtpbin->ts_offset_smoothing_factor = g_value_get_uint (value);
break;
case PROP_FEC_DECODERS:
gst_rtp_bin_set_fec_decoders_struct (rtpbin, g_value_get_boxed (value));
break;
case PROP_FEC_ENCODERS:
gst_rtp_bin_set_fec_encoders_struct (rtpbin, g_value_get_boxed (value));
break;
case PROP_UPDATE_NTP64_HEADER_EXT:
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->update_ntp64_header_ext = g_value_get_boolean (value);
GST_RTP_BIN_UNLOCK (rtpbin);
gst_rtp_bin_propagate_property_to_session (rtpbin,
"update-ntp64-header-ext", value);
break;
case PROP_TIMEOUT_INACTIVE_SOURCES:
GST_RTP_BIN_LOCK (rtpbin);
rtpbin->timeout_inactive_sources = g_value_get_boolean (value);
GST_RTP_BIN_UNLOCK (rtpbin);
gst_rtp_bin_propagate_property_to_session (rtpbin,
"timeout-inactive-sources", value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_rtp_bin_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstRtpBin *rtpbin;
rtpbin = GST_RTP_BIN (object);
switch (prop_id) {
case PROP_LATENCY:
GST_RTP_BIN_LOCK (rtpbin);
g_value_set_uint (value, rtpbin->latency_ms);
GST_RTP_BIN_UNLOCK (rtpbin);
break;
case PROP_DROP_ON_LATENCY:
GST_RTP_BIN_LOCK (rtpbin);
g_value_set_boolean (value, rtpbin->drop_on_latency);
GST_RTP_BIN_UNLOCK (rtpbin);
break;
case PROP_SDES:
g_value_take_boxed (value, gst_rtp_bin_get_sdes_struct (rtpbin));
break;
case PROP_DO_LOST:
GST_RTP_BIN_LOCK (rtpbin);
g_value_set_boolean (value, rtpbin->do_lost);
GST_RTP_BIN_UNLOCK (rtpbin);
break;
case PROP_IGNORE_PT:
g_value_set_boolean (value, rtpbin->ignore_pt);
break;
case PROP_NTP_SYNC:
g_value_set_boolean (value, rtpbin->ntp_sync);
break;
case PROP_RTCP_SYNC:
g_value_set_enum (value, g_atomic_int_get (&rtpbin->rtcp_sync));
break;
case PROP_RTCP_SYNC_INTERVAL:
g_value_set_uint (value, rtpbin->rtcp_sync_interval);
break;
case PROP_AUTOREMOVE:
g_value_set_boolean (value, rtpbin->priv->autoremove);
break;
case PROP_BUFFER_MODE:
g_value_set_enum (value, rtpbin->buffer_mode);
break;
case PROP_USE_PIPELINE_CLOCK:
g_value_set_boolean (value, rtpbin->use_pipeline_clock);
break;
case PROP_DO_SYNC_EVENT:
g_value_set_boolean (value, rtpbin->send_sync_event);
break;
case PROP_DO_RETRANSMISSION:
GST_RTP_BIN_LOCK (rtpbin);
g_value_set_boolean (value, rtpbin->do_retransmission);
GST_RTP_BIN_UNLOCK (rtpbin);
break;
case PROP_RTP_PROFILE:
g_value_set_enum (value, rtpbin->rtp_profile);
break;
case PROP_NTP_TIME_SOURCE:
g_value_set_enum (value, rtpbin->ntp_time_source);
break;
case PROP_RTCP_SYNC_SEND_TIME:
g_value_set_boolean (value, rtpbin->rtcp_sync_send_time);
break;
case PROP_MAX_RTCP_RTP_TIME_DIFF:
GST_RTP_BIN_LOCK (rtpbin);
g_value_set_int (value, rtpbin->max_rtcp_rtp_time_diff);
GST_RTP_BIN_UNLOCK (rtpbin);
break;
case PROP_MAX_DROPOUT_TIME:
g_value_set_uint (value, rtpbin->max_dropout_time);
break;
case PROP_MAX_MISORDER_TIME:
g_value_set_uint (value, rtpbin->max_misorder_time);
break;
case PROP_RFC7273_SYNC:
g_value_set_boolean (value, rtpbin->rfc7273_sync);
break;
case PROP_ADD_REFERENCE_TIMESTAMP_META:
g_value_set_boolean (value, rtpbin->add_reference_timestamp_meta);
break;
case PROP_MAX_STREAMS:
g_value_set_uint (value, rtpbin->max_streams);
break;
case PROP_MAX_TS_OFFSET_ADJUSTMENT:
g_value_set_uint64 (value, rtpbin->max_ts_offset_adjustment);
break;
case PROP_MAX_TS_OFFSET:
g_value_set_int64 (value, rtpbin->max_ts_offset);
break;
case PROP_MIN_TS_OFFSET:
g_value_set_uint64 (value, rtpbin->min_ts_offset);
break;
case PROP_TS_OFFSET_SMOOTHING_FACTOR:
g_value_set_uint (value, rtpbin->ts_offset_smoothing_factor);
break;
case PROP_FEC_DECODERS:
g_value_take_boxed (value, gst_rtp_bin_get_fec_decoders_struct (rtpbin));
break;
case PROP_FEC_ENCODERS:
g_value_take_boxed (value, gst_rtp_bin_get_fec_encoders_struct (rtpbin));
break;
case PROP_UPDATE_NTP64_HEADER_EXT:
g_value_set_boolean (value, rtpbin->update_ntp64_header_ext);
break;
case PROP_TIMEOUT_INACTIVE_SOURCES:
g_value_set_boolean (value, rtpbin->timeout_inactive_sources);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_rtp_bin_handle_message (GstBin * bin, GstMessage * message)
{
GstRtpBin *rtpbin;
rtpbin = GST_RTP_BIN (bin);
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ELEMENT:
{
const GstStructure *s = gst_message_get_structure (message);
/* we change the structure name and add the session ID to it */
if (gst_structure_has_name (s, "application/x-rtp-source-sdes")) {
GstRtpBinSession *sess;
/* find the session we set it as object data */
sess = g_object_get_data (G_OBJECT (GST_MESSAGE_SRC (message)),
"GstRTPBin.session");
if (G_LIKELY (sess)) {
message = gst_message_make_writable (message);
s = gst_message_get_structure (message);
gst_structure_set ((GstStructure *) s, "session", G_TYPE_UINT,
sess->id, NULL);
}
}
GST_BIN_CLASS (parent_class)->handle_message (bin, message);
break;
}
case GST_MESSAGE_BUFFERING:
{
gint percent;
gint min_percent = 100;
GSList *sessions, *streams;
GstRtpBinStream *stream;
gboolean change = FALSE, active = FALSE;
GstClockTime min_out_time;
GstBufferingMode mode;
gint avg_in, avg_out;
gint64 buffering_left;
gst_message_parse_buffering (message, &percent);
gst_message_parse_buffering_stats (message, &mode, &avg_in, &avg_out,
&buffering_left);
stream =
g_object_get_data (G_OBJECT (GST_MESSAGE_SRC (message)),
"GstRTPBin.stream");
GST_DEBUG_OBJECT (bin, "got percent %d from stream %p", percent, stream);
/* get the stream */
if (G_LIKELY (stream)) {
GST_RTP_BIN_LOCK (rtpbin);
/* fill in the percent */
stream->percent = percent;
/* calculate the min value for all streams */
for (sessions = rtpbin->sessions; sessions;
sessions = g_slist_next (sessions)) {
GstRtpBinSession *session = (GstRtpBinSession *) sessions->data;
GST_RTP_SESSION_LOCK (session);
if (session->streams) {
for (streams = session->streams; streams;
streams = g_slist_next (streams)) {
GstRtpBinStream *stream = (GstRtpBinStream *) streams->data;
GST_DEBUG_OBJECT (bin, "stream %p percent %d", stream,
stream->percent);
/* find min percent */
if (min_percent > stream->percent)
min_percent = stream->percent;
}
} else {
GST_INFO_OBJECT (bin,
"session has no streams, setting min_percent to 0");
min_percent = 0;
}
GST_RTP_SESSION_UNLOCK (session);
}
GST_DEBUG_OBJECT (bin, "min percent %d", min_percent);
if (rtpbin->buffering) {
if (min_percent == 100) {
rtpbin->buffering = FALSE;
active = TRUE;
change = TRUE;
}
} else {
if (min_percent < 100) {
/* pause the streams */
rtpbin->buffering = TRUE;
active = FALSE;
change = TRUE;
}
}
GST_RTP_BIN_UNLOCK (rtpbin);
gst_message_unref (message);
/* make a new buffering message with the min value */
message =
gst_message_new_buffering (GST_OBJECT_CAST (bin), min_percent);
gst_message_set_buffering_stats (message, mode, avg_in, avg_out,
buffering_left);
if (G_UNLIKELY (change)) {
GstClock *clock;
guint64 running_time = 0;
guint64 offset = 0;
/* figure out the running time when we have a clock */
if (G_LIKELY ((clock =
gst_element_get_clock (GST_ELEMENT_CAST (bin))))) {
guint64 now, base_time;
now = gst_clock_get_time (clock);
base_time = gst_element_get_base_time (GST_ELEMENT_CAST (bin));
running_time = now - base_time;
gst_object_unref (clock);
}
GST_DEBUG_OBJECT (bin,
"running time now %" GST_TIME_FORMAT,
GST_TIME_ARGS (running_time));
GST_RTP_BIN_LOCK (rtpbin);
/* when we reactivate, calculate the offsets so that all streams have
* an output time that is at least as big as the running_time */
offset = 0;
if (active) {
if (running_time > rtpbin->buffer_start) {
offset = running_time - rtpbin->buffer_start;
if (offset >= rtpbin->latency_ns)
offset -= rtpbin->latency_ns;
else
offset = 0;
}
}
/* pause all streams */
min_out_time = -1;
for (sessions = rtpbin->sessions; sessions;
sessions = g_slist_next (sessions)) {
GstRtpBinSession *session = (GstRtpBinSession *) sessions->data;
GST_RTP_SESSION_LOCK (session);
for (streams = session->streams; streams;
streams = g_slist_next (streams)) {
GstRtpBinStream *stream = (GstRtpBinStream *) streams->data;
GstElement *element = stream->buffer;
guint64 last_out = -1;
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);
if (last_out == -1)
last_out = 0;
if (min_out_time == -1 || last_out < min_out_time)
min_out_time = last_out;
}
GST_DEBUG_OBJECT (bin,
"setting %p to %d, offset %" GST_TIME_FORMAT ", last %"
GST_TIME_FORMAT ", percent %d", element, active,
GST_TIME_ARGS (offset), GST_TIME_ARGS (last_out),
stream->percent);
}
GST_RTP_SESSION_UNLOCK (session);
}
GST_DEBUG_OBJECT (bin,
"min out time %" GST_TIME_FORMAT, GST_TIME_ARGS (min_out_time));
/* the buffer_start is the min out time of all paused jitterbuffers */
if (!active)
rtpbin->buffer_start = min_out_time;
GST_RTP_BIN_UNLOCK (rtpbin);
}
}
GST_BIN_CLASS (parent_class)->handle_message (bin, message);
break;
}
default:
{
GST_BIN_CLASS (parent_class)->handle_message (bin, message);
break;
}
}
}
static GstStateChangeReturn
gst_rtp_bin_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn res;
GstRtpBin *rtpbin;
GstRtpBinPrivate *priv;
rtpbin = GST_RTP_BIN (element);
priv = rtpbin->priv;
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
priv->last_ntpnstime = 0;
GST_LOG_OBJECT (rtpbin, "clearing shutdown flag");
g_atomic_int_set (&priv->shutdown, 0);
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_LOG_OBJECT (rtpbin, "setting shutdown flag");
g_atomic_int_set (&priv->shutdown, 1);
/* wait for all callbacks to end by taking the lock. No new callbacks will
* be able to happen as we set the shutdown flag. */
GST_RTP_BIN_DYN_LOCK (rtpbin);
GST_LOG_OBJECT (rtpbin, "dynamic lock taken, we can continue shutdown");
GST_RTP_BIN_DYN_UNLOCK (rtpbin);
break;
default:
break;
}
res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_NULL:
break;
default:
break;
}
return res;
}
static GstElement *
session_request_element_full (GstRtpBinSession * session, guint signal,
guint ssrc, guint8 pt)
{
GstElement *element = NULL;
GstRtpBin *bin = session->bin;
g_signal_emit (bin, gst_rtp_bin_signals[signal], 0, session->id, ssrc, pt,
&element);
if (element) {
if (!bin_manage_element (bin, element))
goto manage_failed;
session->elements = g_slist_prepend (session->elements, element);
}
return element;
/* ERRORS */
manage_failed:
{
GST_WARNING_OBJECT (bin, "unable to manage element");
gst_object_unref (element);
return NULL;
}
}
static GstElement *
session_request_element (GstRtpBinSession * session, guint signal)
{
GstElement *element = NULL;
GstRtpBin *bin = session->bin;
g_signal_emit (bin, gst_rtp_bin_signals[signal], 0, session->id, &element);
if (element) {
if (!bin_manage_element (bin, element))
goto manage_failed;
session->elements = g_slist_prepend (session->elements, element);
}
return element;
/* ERRORS */
manage_failed:
{
GST_WARNING_OBJECT (bin, "unable to manage element");
gst_object_unref (element);
return NULL;
}
}
static gboolean
copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
{
GstPad *gpad = GST_PAD_CAST (user_data);
GST_DEBUG_OBJECT (gpad, "store sticky event %" GST_PTR_FORMAT, *event);
gst_pad_store_sticky_event (gpad, *event);
return TRUE;
}
static gboolean
ensure_early_fec_decoder (GstRtpBin * rtpbin, GstRtpBinSession * session)
{
const gchar *factory;
gchar *sess_id_str;
if (session->early_fec_decoder)
goto done;
sess_id_str = g_strdup_printf ("%u", session->id);
factory = gst_structure_get_string (rtpbin->fec_decoders, sess_id_str);
g_free (sess_id_str);
/* First try the property */
if (factory) {
GError *err = NULL;
session->early_fec_decoder =
gst_parse_bin_from_description_full (factory, TRUE, NULL,
GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS | GST_PARSE_FLAG_FATAL_ERRORS,
&err);
if (!session->early_fec_decoder) {
GST_ERROR_OBJECT (rtpbin, "Failed to build decoder from factory: %s",
err->message);
}
bin_manage_element (session->bin, session->early_fec_decoder);
session->elements =
g_slist_prepend (session->elements, session->early_fec_decoder);
GST_INFO_OBJECT (rtpbin, "Built FEC decoder: %" GST_PTR_FORMAT
" for session %u", session->early_fec_decoder, session->id);
}
/* Do not fallback to the signal as the signal expects a fec decoder to
* be placed at a different place in the pipeline */
done:
return session->early_fec_decoder != NULL;
}
static void
expose_recv_src_pad (GstRtpBin * rtpbin, GstPad * pad, GstRtpBinStream * stream,
guint8 pt)
{
GstElementClass *klass;
GstPadTemplate *templ;
gchar *padname;
GstPad *gpad;
gst_object_ref (pad);
if (stream->session->storage) {
/* First try the legacy signal, with no ssrc and pt as parameters.
* This will likely cause issues for the BUNDLE case. */
GstElement *fec_decoder =
session_request_element (stream->session, SIGNAL_REQUEST_FEC_DECODER);
/* Now try the new signal, where the application can provide a FEC
* decoder according to ssrc and pt. */
if (!fec_decoder) {
fec_decoder =
session_request_element_full (stream->session,
SIGNAL_REQUEST_FEC_DECODER_FULL, stream->ssrc, pt);
}
if (fec_decoder) {
GstPad *sinkpad, *srcpad;
GstPadLinkReturn ret;
sinkpad = gst_element_get_static_pad (fec_decoder, "sink");
if (!sinkpad)
goto fec_decoder_sink_failed;
ret = gst_pad_link (pad, sinkpad);
gst_object_unref (sinkpad);
if (ret != GST_PAD_LINK_OK)
goto fec_decoder_link_failed;
srcpad = gst_element_get_static_pad (fec_decoder, "src");
if (!srcpad)
goto fec_decoder_src_failed;
gst_pad_sticky_events_foreach (pad, copy_sticky_events, srcpad);
gst_object_unref (pad);
pad = srcpad;
}
}
GST_RTP_BIN_SHUTDOWN_LOCK (rtpbin, shutdown);
/* ghost the pad to the parent */
klass = GST_ELEMENT_GET_CLASS (rtpbin);
templ = gst_element_class_get_pad_template (klass, "recv_rtp_src_%u_%u_%u");
padname = g_strdup_printf ("recv_rtp_src_%u_%u_%u",
stream->session->id, stream->ssrc, pt);
gpad = gst_ghost_pad_new_from_template (padname, pad, templ);
g_free (padname);
g_object_set_data (G_OBJECT (pad), "GstRTPBin.ghostpad", gpad);
gst_pad_set_active (gpad, TRUE);
GST_RTP_BIN_SHUTDOWN_UNLOCK (rtpbin);
gst_pad_sticky_events_foreach (pad, copy_sticky_events, gpad);
gst_element_add_pad (GST_ELEMENT_CAST (rtpbin), gpad);
done:
gst_object_unref (pad);
return;
shutdown:
{
GST_DEBUG ("ignoring, we are shutting down");
goto done;
}
fec_decoder_sink_failed:
{
g_warning ("rtpbin: failed to get fec encoder sink pad for session %u",
stream->session->id);
goto done;
}
fec_decoder_src_failed:
{
g_warning ("rtpbin: failed to get fec encoder src pad for session %u",
stream->session->id);
goto done;
}
fec_decoder_link_failed:
{
g_warning ("rtpbin: failed to link fec decoder for session %u",
stream->session->id);
goto done;
}
}
/* 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,
GstRtpBinStream * stream)
{
GstRtpBin *rtpbin;
rtpbin = stream->bin;
GST_DEBUG_OBJECT (rtpbin, "new payload pad %u", pt);
expose_recv_src_pad (rtpbin, pad, stream, pt);
}
static void
payload_pad_removed (GstElement * element, GstPad * pad,
GstRtpBinStream * stream)
{
GstRtpBin *rtpbin;
GstPad *gpad;
rtpbin = stream->bin;
GST_DEBUG ("payload pad removed");
GST_RTP_BIN_DYN_LOCK (rtpbin);
if ((gpad = g_object_get_data (G_OBJECT (pad), "GstRTPBin.ghostpad"))) {
g_object_set_data (G_OBJECT (pad), "GstRTPBin.ghostpad", NULL);
gst_pad_set_active (gpad, FALSE);
gst_element_remove_pad (GST_ELEMENT_CAST (rtpbin), gpad);
}
GST_RTP_BIN_DYN_UNLOCK (rtpbin);
}
static GstCaps *
pt_map_requested (GstElement * element, guint pt, GstRtpBinSession * session)
{
GstRtpBin *rtpbin;
GstCaps *caps;
rtpbin = session->bin;
GST_DEBUG_OBJECT (rtpbin, "payload map requested for pt %u in session %u", pt,
session->id);
caps = get_pt_map (session, pt);
if (!caps)
goto no_caps;
return caps;
/* ERRORS */
no_caps:
{
GST_DEBUG_OBJECT (rtpbin, "could not get caps");
return NULL;
}
}
static GstCaps *
ptdemux_pt_map_requested (GstElement * element, guint pt,
GstRtpBinSession * session)
{
GstCaps *ret = pt_map_requested (element, pt, session);
if (ret && gst_caps_get_size (ret) == 1) {
const GstStructure *s = gst_caps_get_structure (ret, 0);
gboolean is_fec;
if (gst_structure_get_boolean (s, "is-fec", &is_fec) && is_fec) {
GValue v = G_VALUE_INIT;
GValue v2 = G_VALUE_INIT;
GST_INFO_OBJECT (session->bin, "Will ignore FEC pt %u in session %u", pt,
session->id);
g_value_init (&v, GST_TYPE_ARRAY);
g_value_init (&v2, G_TYPE_INT);
g_object_get_property (G_OBJECT (element), "ignored-payload-types", &v);
g_value_set_int (&v2, pt);
gst_value_array_append_value (&v, &v2);
g_value_unset (&v2);
g_object_set_property (G_OBJECT (element), "ignored-payload-types", &v);
g_value_unset (&v);
}
}
return ret;
}
static void
payload_type_change (GstElement * element, guint pt, GstRtpBinSession * session)
{
GST_DEBUG_OBJECT (session->bin,
"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],
0, session->id, pt);
}
/* emitted when caps changed for the session */
static void
caps_changed (GstPad * pad, GParamSpec * pspec, GstRtpBinSession * session)
{
GstRtpBin *bin;
GstCaps *caps;
gint payload;
const GstStructure *s;
bin = session->bin;
g_object_get (pad, "caps", &caps, NULL);
if (caps == NULL)
return;
GST_DEBUG_OBJECT (bin, "got caps %" GST_PTR_FORMAT, caps);
s = gst_caps_get_structure (caps, 0);
/* get payload, finish when it's not there */
if (!gst_structure_get_int (s, "payload", &payload)) {
gst_caps_unref (caps);
return;
}
GST_RTP_SESSION_LOCK (session);
GST_DEBUG_OBJECT (bin, "insert caps for payload %d", payload);
g_hash_table_insert (session->ptmap, GINT_TO_POINTER (payload), caps);
GST_RTP_SESSION_UNLOCK (session);
}
/* a new pad (SSRC) was created in @session */
static void
new_ssrc_pad_found (GstElement * element, guint ssrc, GstPad * pad,
GstRtpBinSession * session)
{
GstRtpBin *rtpbin;
GstRtpBinStream *stream;
GstPad *sinkpad, *srcpad;
gchar *padname;
rtpbin = session->bin;
GST_DEBUG_OBJECT (rtpbin, "new SSRC pad %08x, %s:%s", ssrc,
GST_DEBUG_PAD_NAME (pad));
GST_RTP_BIN_SHUTDOWN_LOCK (rtpbin, shutdown);
GST_RTP_SESSION_LOCK (session);
/* create new stream */
stream = create_stream (session, ssrc);
if (!stream)
goto no_stream;
/* get pad and link */
GST_DEBUG_OBJECT (rtpbin, "linking jitterbuffer RTP");
padname = g_strdup_printf ("src_%u", ssrc);
srcpad = gst_element_get_static_pad (element, padname);
g_free (padname);
if (session->early_fec_decoder) {
GST_DEBUG_OBJECT (rtpbin, "linking fec decoder");
sinkpad = gst_element_get_static_pad (session->early_fec_decoder, "sink");
gst_pad_link_full (srcpad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
gst_object_unref (sinkpad);
gst_object_unref (srcpad);
srcpad = gst_element_get_static_pad (session->early_fec_decoder, "src");
}
sinkpad = gst_element_get_static_pad (stream->buffer, "sink");
gst_pad_link_full (srcpad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
gst_object_unref (sinkpad);
gst_object_unref (srcpad);
sinkpad = gst_element_request_pad_simple (stream->buffer, "sink_rtcp");
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);
}
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
* new pad by ghosting it. */
stream->demux_newpad_sig = g_signal_connect (stream->demux,
"new-payload-type", (GCallback) new_payload_found, stream);
stream->demux_padremoved_sig = g_signal_connect (stream->demux,
"pad-removed", (GCallback) payload_pad_removed, stream);
/* connect to the request-pt-map signal. This signal will be emitted by the
* demuxer so that it can apply a proper caps on the buffers for the
* depayloaders. */
stream->demux_ptreq_sig = g_signal_connect (stream->demux,
"request-pt-map", (GCallback) ptdemux_pt_map_requested, session);
/* connect to the signal so it can be forwarded. */
stream->demux_ptchange_sig = g_signal_connect (stream->demux,
"payload-type-change", (GCallback) payload_type_change, session);
GST_RTP_SESSION_UNLOCK (session);
GST_RTP_BIN_SHUTDOWN_UNLOCK (rtpbin);
} else {
/* add rtpjitterbuffer src pad to pads */
GstPad *pad;
pad = gst_element_get_static_pad (stream->buffer, "src");
GST_RTP_SESSION_UNLOCK (session);
GST_RTP_BIN_SHUTDOWN_UNLOCK (rtpbin);
expose_recv_src_pad (rtpbin, pad, stream, 255);
gst_object_unref (pad);
}
return;
/* ERRORS */
shutdown:
{
GST_DEBUG_OBJECT (rtpbin, "we are shutting down");
return;
}
no_stream:
{
GST_RTP_SESSION_UNLOCK (session);
GST_RTP_BIN_SHUTDOWN_UNLOCK (rtpbin);
GST_DEBUG_OBJECT (rtpbin, "could not create stream");
return;
}
}
static GstPad *
complete_session_sink (GstRtpBin * rtpbin, GstRtpBinSession * session)
{
guint sessid = session->id;
GstPad *recv_rtp_sink;
GstElement *decoder;
g_assert (!session->recv_rtp_sink);
/* get recv_rtp pad and store */
session->recv_rtp_sink =
gst_element_request_pad_simple (session->session, "recv_rtp_sink");
if (session->recv_rtp_sink == NULL)
goto pad_failed;
g_signal_connect (session->recv_rtp_sink, "notify::caps",
(GCallback) caps_changed, session);
GST_DEBUG_OBJECT (rtpbin, "requesting RTP decoder");
decoder = session_request_element (session, SIGNAL_REQUEST_RTP_DECODER);
if (decoder) {
GstPad *decsrc, *decsink;
GstPadLinkReturn ret;
GST_DEBUG_OBJECT (rtpbin, "linking RTP decoder");
decsink = gst_element_get_static_pad (decoder, "rtp_sink");
if (decsink == NULL)
goto dec_sink_failed;
recv_rtp_sink = decsink;
decsrc = gst_element_get_static_pad (decoder, "rtp_src");
if (decsrc == NULL)
goto dec_src_failed;
ret = gst_pad_link (decsrc, session->recv_rtp_sink);
gst_object_unref (decsrc);
if (ret != GST_PAD_LINK_OK)
goto dec_link_failed;
} else {
GST_DEBUG_OBJECT (rtpbin, "no RTP decoder given");
recv_rtp_sink = gst_object_ref (session->recv_rtp_sink);
}
return recv_rtp_sink;
/* ERRORS */
pad_failed:
{
g_warning ("rtpbin: failed to get session recv_rtp_sink pad");
return NULL;
}
dec_sink_failed:
{
g_warning ("rtpbin: failed to get decoder sink pad for session %u", sessid);
return NULL;
}
dec_src_failed:
{
g_warning ("rtpbin: failed to get decoder src pad for session %u", sessid);
gst_object_unref (recv_rtp_sink);
return NULL;
}
dec_link_failed:
{
g_warning ("rtpbin: failed to link rtp decoder for session %u", sessid);
gst_object_unref (recv_rtp_sink);
return NULL;
}
}
static void
complete_session_receiver (GstRtpBin * rtpbin, GstRtpBinSession * session,
guint sessid)
{
GstElement *aux;
GstPad *recv_rtp_src;
g_assert (!session->recv_rtp_src);
session->recv_rtp_src =
gst_element_get_static_pad (session->session, "recv_rtp_src");
if (session->recv_rtp_src == NULL)
goto pad_failed;
/* find out if we need AUX elements */
aux = session_request_element (session, SIGNAL_REQUEST_AUX_RECEIVER);
if (aux) {
gchar *pname;
GstPad *auxsink;
GstPadLinkReturn ret;
GST_DEBUG_OBJECT (rtpbin, "linking AUX receiver");
pname = g_strdup_printf ("sink_%u", sessid);
auxsink = gst_element_get_static_pad (aux, pname);
g_free (pname);
if (auxsink == NULL)
goto aux_sink_failed;
ret = gst_pad_link (session->recv_rtp_src, auxsink);
gst_object_unref (auxsink);
if (ret != GST_PAD_LINK_OK)
goto aux_link_failed;
/* this can be NULL when this AUX element is not to be linked any further */
pname = g_strdup_printf ("src_%u", sessid);
recv_rtp_src = gst_element_get_static_pad (aux, pname);
g_free (pname);
} else {
recv_rtp_src = gst_object_ref (session->recv_rtp_src);
}
/* Add a storage element if needed */
if (recv_rtp_src && session->storage) {
GstPadLinkReturn ret;
GstPad *sinkpad = gst_element_get_static_pad (session->storage, "sink");
ret = gst_pad_link (recv_rtp_src, sinkpad);
gst_object_unref (sinkpad);
gst_object_unref (recv_rtp_src);
if (ret != GST_PAD_LINK_OK)
goto storage_link_failed;
recv_rtp_src = gst_element_get_static_pad (session->storage, "src");
}
if (recv_rtp_src) {
GstPad *sinkdpad;
GST_DEBUG_OBJECT (rtpbin, "getting demuxer RTP sink pad");
sinkdpad = gst_element_get_static_pad (session->demux, "sink");
GST_DEBUG_OBJECT (rtpbin, "linking demuxer RTP sink pad");
gst_pad_link_full (recv_rtp_src, sinkdpad, GST_PAD_LINK_CHECK_NOTHING);
gst_object_unref (sinkdpad);
gst_object_unref (recv_rtp_src);
/* connect to the new-ssrc-pad signal of the SSRC demuxer */
session->demux_newpad_sig = g_signal_connect (session->demux,
"new-ssrc-pad", (GCallback) new_ssrc_pad_found, session);
session->demux_padremoved_sig = g_signal_connect (session->demux,
"removed-ssrc-pad", (GCallback) ssrc_demux_pad_removed, session);
}
return;
pad_failed:
{
g_warning ("rtpbin: failed to get session recv_rtp_src pad");
return;
}
aux_sink_failed:
{
g_warning ("rtpbin: failed to get AUX sink pad for session %u", sessid);
return;
}
aux_link_failed:
{
g_warning ("rtpbin: failed to link AUX pad to session %u", sessid);
return;
}
storage_link_failed:
{
g_warning ("rtpbin: failed to link storage");
return;
}
}
/* Create a pad for receiving RTP for the session in @name. Must be called with
* RTP_BIN_LOCK.
*/
static GstPad *
create_recv_rtp (GstRtpBin * rtpbin, GstPadTemplate * templ, const gchar * name)
{
guint sessid;
GstRtpBinSession *session;
GstPad *recv_rtp_sink;
/* first get the session number */
if (name == NULL || sscanf (name, "recv_rtp_sink_%u", &sessid) != 1)
goto no_name;
GST_DEBUG_OBJECT (rtpbin, "finding session %u", sessid);
/* get or create session */
session = find_session_by_id (rtpbin, sessid);
if (!session) {
GST_DEBUG_OBJECT (rtpbin, "creating session %u", sessid);
/* create session now */
session = create_session (rtpbin, sessid);
if (session == NULL)
goto create_error;
}
/* check if pad was requested */
if (session->recv_rtp_sink_ghost != NULL)
return session->recv_rtp_sink_ghost;
/* setup the session sink pad */
recv_rtp_sink = complete_session_sink (rtpbin, session);
if (!recv_rtp_sink)
goto session_sink_failed;
GST_DEBUG_OBJECT (rtpbin, "ghosting session sink pad");
session->recv_rtp_sink_ghost =
gst_ghost_pad_new_from_template (name, recv_rtp_sink, templ);
gst_object_unref (recv_rtp_sink);
complete_session_receiver (rtpbin, session, sessid);
return session->recv_rtp_sink_ghost;
/* ERRORS */
no_name:
{
g_warning ("rtpbin: cannot find session id for pad: %s",
GST_STR_NULL (name));
return NULL;
}
create_error:
{
/* create_session already warned */
return NULL;
}
session_sink_failed:
{
/* warning already done */
return NULL;
}
}
static void
remove_recv_rtp (GstRtpBin * rtpbin, GstRtpBinSession * session)
{
if (session->demux_newpad_sig) {
g_signal_handler_disconnect (session->demux, session->demux_newpad_sig);
session->demux_newpad_sig = 0;
}
if (session->demux_padremoved_sig) {
g_signal_handler_disconnect (session->demux, session->demux_padremoved_sig);
session->demux_padremoved_sig = 0;
}
if (session->recv_rtp_src) {
gst_object_unref (session->recv_rtp_src);
session->recv_rtp_src = NULL;
}
if (session->recv_rtp_sink) {
gst_element_release_request_pad (session->session, session->recv_rtp_sink);
gst_object_unref (session->recv_rtp_sink);
session->recv_rtp_sink = NULL;
}
if (session->recv_rtp_sink_ghost) {
gst_pad_set_active (session->recv_rtp_sink_ghost, FALSE);
gst_element_remove_pad (GST_ELEMENT_CAST (rtpbin),
session->recv_rtp_sink_ghost);
session->recv_rtp_sink_ghost = NULL;
}
}
static gint
fec_sinkpad_find (const GValue * item, gchar * padname)
{
GstPad *pad = g_value_get_object (item);
return g_strcmp0 (GST_PAD_NAME (pad), padname);
}
static GstPad *
complete_session_fec (GstRtpBin * rtpbin, GstRtpBinSession * session,
guint fec_idx)
{
gboolean have_static_pad;
gchar *padname;
GstPad *ret;
GstIterator *it;
GValue item = { 0, };
if (!ensure_early_fec_decoder (rtpbin, session))
goto no_decoder;
padname = g_strdup_printf ("fec_%u", fec_idx);
GST_DEBUG_OBJECT (rtpbin, "getting FEC sink pad %s", padname);
/* First try to find the decoder static pad that matches the padname */
it = gst_element_iterate_sink_pads (session->early_fec_decoder);
have_static_pad =
gst_iterator_find_custom (it, (GCompareFunc) fec_sinkpad_find, &item,
padname);
if (have_static_pad) {
ret = g_value_get_object (&item);
gst_object_ref (ret);
g_value_unset (&item);
} else {
ret = gst_element_request_pad_simple (session->early_fec_decoder, padname);
}
g_free (padname);
gst_iterator_free (it);
if (ret == NULL)
goto pad_failed;
session->recv_fec_sinks = g_slist_prepend (session->recv_fec_sinks, ret);
return ret;
pad_failed:
{
g_warning ("rtpbin: failed to get decoder fec pad");
return NULL;
}
no_decoder:
{
g_warning ("rtpbin: failed to build FEC decoder for session %u",
session->id);
return NULL;
}
}
static GstPad *
complete_session_rtcp (GstRtpBin * rtpbin, GstRtpBinSession * session,
guint sessid)
{
GstElement *decoder;
GstPad *sinkdpad;
GstPad *decsink = NULL;
/* get recv_rtp pad and store */
GST_DEBUG_OBJECT (rtpbin, "getting RTCP sink pad");
session->recv_rtcp_sink =
gst_element_request_pad_simple (session->session, "recv_rtcp_sink");
if (session->recv_rtcp_sink == NULL)
goto pad_failed;
GST_DEBUG_OBJECT (rtpbin, "getting RTCP decoder");
decoder = session_request_element (session, SIGNAL_REQUEST_RTCP_DECODER);
if (decoder) {
GstPad *decsrc;
GstPadLinkReturn ret;
GST_DEBUG_OBJECT (rtpbin, "linking RTCP decoder");
decsink = gst_element_get_static_pad (decoder, "rtcp_sink");
decsrc = gst_element_get_static_pad (decoder, "rtcp_src");
if (decsink == NULL)
goto dec_sink_failed;
if (decsrc == NULL)
goto dec_src_failed;
ret = gst_pad_link (decsrc, session->recv_rtcp_sink);
gst_object_unref (decsrc);
if (ret != GST_PAD_LINK_OK)
goto dec_link_failed;
} else {
GST_DEBUG_OBJECT (rtpbin, "no RTCP decoder given");
decsink = gst_object_ref (session->recv_rtcp_sink);
}
/* get srcpad, link to SSRCDemux */
GST_DEBUG_OBJECT (rtpbin, "getting sync src pad");
session->sync_src = gst_element_get_static_pad (session->session, "sync_src");
if (session->sync_src == NULL)
goto src_pad_failed;
GST_DEBUG_OBJECT (rtpbin, "getting demuxer RTCP sink pad");
sinkdpad = gst_element_get_static_pad (session->demux, "rtcp_sink");
gst_pad_link_full (session->sync_src, sinkdpad, GST_PAD_LINK_CHECK_NOTHING);
gst_object_unref (sinkdpad);
return decsink;
pad_failed:
{
g_warning ("rtpbin: failed to get session rtcp_sink pad");
return NULL;
}
dec_sink_failed:
{
g_warning ("rtpbin: failed to get decoder sink pad for session %u", sessid);
return NULL;
}
dec_src_failed:
{
g_warning ("rtpbin: failed to get decoder src pad for session %u", sessid);
goto cleanup;
}
dec_link_failed:
{
g_warning ("rtpbin: failed to link rtcp decoder for session %u", sessid);
goto cleanup;
}
src_pad_failed:
{
g_warning ("rtpbin: failed to get session sync_src pad");
}
cleanup:
gst_object_unref (decsink);
return NULL;
}
/* Create a pad for receiving RTCP for the session in @name. Must be called with
* RTP_BIN_LOCK.
*/
static GstPad *
create_recv_rtcp (GstRtpBin * rtpbin, GstPadTemplate * templ,
const gchar * name)
{
guint sessid;
GstRtpBinSession *session;
GstPad *decsink = NULL;
/* first get the session number */
if (name == NULL || sscanf (name, "recv_rtcp_sink_%u", &sessid) != 1)
goto no_name;
GST_DEBUG_OBJECT (rtpbin, "finding session %u", sessid);
/* get or create the session */
session = find_session_by_id (rtpbin, sessid);
if (!session) {
GST_DEBUG_OBJECT (rtpbin, "creating session %u", sessid);
/* create session now */
session = create_session (rtpbin, sessid);
if (session == NULL)
goto create_error;
}
/* check if pad was requested */
if (session->recv_rtcp_sink_ghost != NULL)
return session->recv_rtcp_sink_ghost;
decsink = complete_session_rtcp (rtpbin, session, sessid);
if (!decsink)
goto create_error;
session->recv_rtcp_sink_ghost =
gst_ghost_pad_new_from_template (name, decsink, templ);
gst_object_unref (decsink);
return session->recv_rtcp_sink_ghost;
/* ERRORS */
no_name:
{
g_warning ("rtpbin: cannot find session id for pad: %s",
GST_STR_NULL (name));
return NULL;
}
create_error:
{
/* create_session already warned */
return NULL;
}
}
static GstPad *
create_recv_fec (GstRtpBin * rtpbin, GstPadTemplate * templ, const gchar * name)
{
guint sessid, fec_idx;
GstRtpBinSession *session;
GstPad *decsink = NULL;
GstPad *ghost;
/* first get the session number */
if (name == NULL
|| sscanf (name, "recv_fec_sink_%u_%u", &sessid, &fec_idx) != 2)
goto no_name;
if (fec_idx > 1)
goto invalid_idx;
GST_DEBUG_OBJECT (rtpbin, "finding session %u", sessid);
/* get or create the session */
session = find_session_by_id (rtpbin, sessid);
if (!session) {
GST_DEBUG_OBJECT (rtpbin, "creating session %u", sessid);
/* create session now */
session = create_session (rtpbin, sessid);
if (session == NULL)
goto create_error;
}
decsink = complete_session_fec (rtpbin, session, fec_idx);
if (!decsink)
goto create_error;
ghost = gst_ghost_pad_new_from_template (name, decsink, templ);
session->recv_fec_sink_ghosts =
g_slist_prepend (session->recv_fec_sink_ghosts, ghost);
gst_object_unref (decsink);
return ghost;
/* ERRORS */
no_name:
{
g_warning ("rtpbin: cannot find session id for pad: %s",
GST_STR_NULL (name));
return NULL;
}
invalid_idx:
{
g_warning ("rtpbin: invalid FEC index: %s", GST_STR_NULL (name));
return NULL;
}
create_error:
{
/* create_session already warned */
return NULL;
}
}
static void
remove_recv_rtcp (GstRtpBin * rtpbin, GstRtpBinSession * session)
{
if (session->recv_rtcp_sink_ghost) {
gst_pad_set_active (session->recv_rtcp_sink_ghost, FALSE);
gst_element_remove_pad (GST_ELEMENT_CAST (rtpbin),
session->recv_rtcp_sink_ghost);
session->recv_rtcp_sink_ghost = NULL;
}
if (session->sync_src) {
/* releasing the request pad should also unref the sync pad */
gst_object_unref (session->sync_src);
session->sync_src = NULL;
}
if (session->recv_rtcp_sink) {
gst_element_release_request_pad (session->session, session->recv_rtcp_sink);
gst_object_unref (session->recv_rtcp_sink);
session->recv_rtcp_sink = NULL;
}
}
static void
remove_recv_fec_for_pad (GstRtpBin * rtpbin, GstRtpBinSession * session,
GstPad * ghost)
{
GSList *item;
GstPad *target;
target = gst_ghost_pad_get_target (GST_GHOST_PAD (ghost));
if (target) {
item = g_slist_find (session->recv_fec_sinks, target);
if (item) {
GstPadTemplate *templ;
GstPad *pad;
pad = item->data;
templ = gst_pad_get_pad_template (pad);
if (GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_REQUEST) {
GST_DEBUG_OBJECT (rtpbin,
"Releasing FEC decoder pad %" GST_PTR_FORMAT, pad);
gst_element_release_request_pad (session->early_fec_decoder, pad);
} else {
gst_object_unref (pad);
}
session->recv_fec_sinks =
g_slist_delete_link (session->recv_fec_sinks, item);
gst_object_unref (templ);
}
gst_object_unref (target);
}
item = g_slist_find (session->recv_fec_sink_ghosts, ghost);
if (item)
session->recv_fec_sink_ghosts =
g_slist_delete_link (session->recv_fec_sink_ghosts, item);
gst_pad_set_active (ghost, FALSE);
gst_element_remove_pad (GST_ELEMENT_CAST (rtpbin), ghost);
}
static void
remove_recv_fec (GstRtpBin * rtpbin, GstRtpBinSession * session)
{
GSList *copy;
GSList *tmp;
copy = g_slist_copy (session->recv_fec_sink_ghosts);
for (tmp = copy; tmp; tmp = tmp->next) {
remove_recv_fec_for_pad (rtpbin, session, (GstPad *) tmp->data);
}
g_slist_free (copy);
}
static gboolean
complete_session_src (GstRtpBin * rtpbin, GstRtpBinSession * session)
{
gchar *gname;
guint sessid = session->id;
GstPad *send_rtp_src;
GstElement *encoder;
GstElementClass *klass;
GstPadTemplate *templ;
gboolean ret = FALSE;
/* get srcpad */
send_rtp_src = gst_element_get_static_pad (session->session, "send_rtp_src");
if (send_rtp_src == NULL)
goto no_srcpad;
GST_DEBUG_OBJECT (rtpbin, "getting RTP encoder");
encoder = session_request_element (session, SIGNAL_REQUEST_RTP_ENCODER);
if (encoder) {
gchar *ename;
GstPad *encsrc, *encsink;
GstPadLinkReturn ret;
GST_DEBUG_OBJECT (rtpbin, "linking RTP encoder");
ename = g_strdup_printf ("rtp_src_%u", sessid);
encsrc = gst_element_get_static_pad (encoder, ename);
g_free (ename);
if (encsrc == NULL)
goto enc_src_failed;
ename = g_strdup_printf ("rtp_sink_%u", sessid);
encsink = gst_element_get_static_pad (encoder, ename);
g_free (ename);
if (encsink == NULL)
goto enc_sink_failed;
ret = gst_pad_link (send_rtp_src, encsink);
gst_object_unref (encsink);
gst_object_unref (send_rtp_src);
send_rtp_src = encsrc;
if (ret != GST_PAD_LINK_OK)
goto enc_link_failed;
} else {
GST_DEBUG_OBJECT (rtpbin, "no RTP encoder given");
}
/* ghost the new source pad */
klass = GST_ELEMENT_GET_CLASS (rtpbin);
gname = g_strdup_printf ("send_rtp_src_%u", sessid);
templ = gst_element_class_get_pad_template (klass, "send_rtp_src_%u");
session->send_rtp_src_ghost =
gst_ghost_pad_new_from_template (gname, send_rtp_src, templ);
gst_pad_set_active (session->send_rtp_src_ghost, TRUE);
gst_pad_sticky_events_foreach (send_rtp_src, copy_sticky_events,
session->send_rtp_src_ghost);
gst_element_add_pad (GST_ELEMENT_CAST (rtpbin), session->send_rtp_src_ghost);
g_free (gname);
ret = TRUE;
done:
if (send_rtp_src)
gst_object_unref (send_rtp_src);
return ret;
/* ERRORS */
no_srcpad:
{
g_warning ("rtpbin: failed to get rtp source pad for session %u", sessid);
goto done;
}
enc_src_failed:
{
g_warning ("rtpbin: failed to get %" GST_PTR_FORMAT
" src pad for session %u", encoder, sessid);
goto done;
}
enc_sink_failed:
{
g_warning ("rtpbin: failed to get %" GST_PTR_FORMAT
" sink pad for session %u", encoder, sessid);
goto done;
}
enc_link_failed:
{
g_warning ("rtpbin: failed to link %" GST_PTR_FORMAT " for session %u",
encoder, sessid);
goto done;
}
}
static gboolean
setup_aux_sender_fold (const GValue * item, GValue * result, gpointer user_data)
{
GstPad *pad;
gchar *name;
guint sessid;
GstRtpBinSession *session = user_data, *newsess;
GstRtpBin *rtpbin = session->bin;
GstPadLinkReturn ret;
pad = g_value_get_object (item);
name = gst_pad_get_name (pad);
if (name == NULL || sscanf (name, "src_%u", &sessid) != 1)
goto no_name;
g_free (name);
newsess = find_session_by_id (rtpbin, sessid);
if (newsess == NULL) {
/* create new session */
newsess = create_session (rtpbin, sessid);
if (newsess == NULL)
goto create_error;
} else if (newsess->send_rtp_sink != NULL)
goto existing_session;
/* get send_rtp pad and store */
newsess->send_rtp_sink =
gst_element_request_pad_simple (newsess->session, "send_rtp_sink");
if (newsess->send_rtp_sink == NULL)
goto pad_failed;
ret = gst_pad_link (pad, newsess->send_rtp_sink);
if (ret != GST_PAD_LINK_OK)
goto aux_link_failed;
if (!complete_session_src (rtpbin, newsess))
goto session_src_failed;
return TRUE;
/* ERRORS */
no_name:
{
GST_WARNING ("ignoring invalid pad name %s", GST_STR_NULL (name));
g_free (name);
return TRUE;
}
create_error:
{
/* create_session already warned */
return FALSE;
}
existing_session:
{
GST_DEBUG_OBJECT (rtpbin,
"skipping src_%i setup, since it is already configured.", sessid);
return TRUE;
}
pad_failed:
{
g_warning ("rtpbin: failed to get session pad for session %u", sessid);
return FALSE;
}
aux_link_failed:
{
g_warning ("rtpbin: failed to link AUX for session %u", sessid);
return FALSE;
}
session_src_failed:
{
g_warning ("rtpbin: failed to complete AUX for session %u", sessid);
return FALSE;
}
}
static gboolean
setup_aux_sender (GstRtpBin * rtpbin, GstRtpBinSession * session,
GstElement * aux)
{
GstIterator *it;
GValue result = { 0, };
GstIteratorResult res;
it = gst_element_iterate_src_pads (aux);
res = gst_iterator_fold (it, setup_aux_sender_fold, &result, session);
gst_iterator_free (it);
return res == GST_ITERATOR_DONE;
}
static void
fec_encoder_add_pad_unlocked (GstPad * pad, GstRtpBinSession * session)
{
GstElementClass *klass;
gchar *gname;
GstPadTemplate *templ;
guint fec_idx;
GstPad *ghost;
if (sscanf (GST_PAD_NAME (pad), "fec_%u", &fec_idx) != 1) {
GST_WARNING_OBJECT (session->bin,
"FEC encoder added pad with name not matching fec_%%u (%s)",
GST_PAD_NAME (pad));
goto done;
}
GST_INFO_OBJECT (session->bin, "FEC encoder for session %u exposed new pad",
session->id);
klass = GST_ELEMENT_GET_CLASS (session->bin);
gname = g_strdup_printf ("send_fec_src_%u_%u", session->id, fec_idx);
templ = gst_element_class_get_pad_template (klass, "send_fec_src_%u_%u");
ghost = gst_ghost_pad_new_from_template (gname, pad, templ);
session->send_fec_src_ghosts =
g_slist_prepend (session->send_fec_src_ghosts, ghost);
gst_pad_set_active (ghost, TRUE);
gst_pad_sticky_events_foreach (pad, copy_sticky_events, ghost);
gst_element_add_pad (GST_ELEMENT (session->bin), ghost);
g_free (gname);
done:
return;
}
static void
fec_encoder_add_pad (GstPad * pad, GstRtpBinSession * session)
{
GST_RTP_BIN_LOCK (session->bin);
fec_encoder_add_pad_unlocked (pad, session);
GST_RTP_BIN_UNLOCK (session->bin);
}
static gint
fec_srcpad_iterator_filter (const GValue * item, GValue * unused)
{
guint fec_idx;
GstPad *pad = g_value_get_object (item);
GstPadTemplate *templ = gst_pad_get_pad_template (pad);
gint have_static_pad =
(GST_PAD_TEMPLATE_PRESENCE (templ) == GST_PAD_ALWAYS) &&
(sscanf (GST_PAD_NAME (pad), "fec_%u", &fec_idx) == 1);
gst_object_unref (templ);
/* return 0 to retain pad in filtered iterator */
return !have_static_pad;
}
static void
fec_srcpad_iterator_foreach (const GValue * item, GstRtpBinSession * session)
{
GstPad *pad = g_value_get_object (item);
fec_encoder_add_pad_unlocked (pad, session);
}
static void
fec_encoder_pad_added_cb (GstElement * encoder, GstPad * pad,
GstRtpBinSession * session)
{
fec_encoder_add_pad (pad, session);
}
static GstElement *
request_fec_encoder (GstRtpBin * rtpbin, GstRtpBinSession * session,
guint sessid)
{
GstElement *ret = NULL;
const gchar *factory;
gchar *sess_id_str;
sess_id_str = g_strdup_printf ("%u", sessid);
factory = gst_structure_get_string (rtpbin->fec_encoders, sess_id_str);
g_free (sess_id_str);
/* First try the property */
if (factory) {
GError *err = NULL;
ret =
gst_parse_bin_from_description_full (factory, TRUE, NULL,
GST_PARSE_FLAG_NO_SINGLE_ELEMENT_BINS | GST_PARSE_FLAG_FATAL_ERRORS,
&err);
if (!ret) {
GST_ERROR_OBJECT (rtpbin, "Failed to build encoder from factory: %s",
err->message);
goto done;
}
bin_manage_element (session->bin, ret);
session->elements = g_slist_prepend (session->elements, ret);
GST_INFO_OBJECT (rtpbin, "Built FEC encoder: %" GST_PTR_FORMAT
" for session %u", ret, sessid);
}
/* Fallback to the signal */
if (!ret)
ret = session_request_element (session, SIGNAL_REQUEST_FEC_ENCODER);
if (ret) {
/* First, add encoder pads that match fec_% template and are already present */
GstIterator *it, *filter;
GstIteratorResult it_ret = GST_ITERATOR_OK;
it = gst_element_iterate_src_pads (ret);
filter =
gst_iterator_filter (it, (GCompareFunc) fec_srcpad_iterator_filter,
NULL);
while (it_ret == GST_ITERATOR_OK || it_ret == GST_ITERATOR_RESYNC) {
it_ret =
gst_iterator_foreach (filter,
(GstIteratorForeachFunction) fec_srcpad_iterator_foreach, session);
if (it_ret == GST_ITERATOR_RESYNC)
gst_iterator_resync (filter);
}
gst_iterator_free (filter);
/* Finally, connect to pad-added signal if any of the encoder pads are
* added later */
g_signal_connect (ret, "pad-added", G_CALLBACK (fec_encoder_pad_added_cb),
session);
}
done:
return ret;
}
/* Create a pad for sending RTP for the session in @name. Must be called with
* RTP_BIN_LOCK.
*/
static GstPad *
create_send_rtp (GstRtpBin * rtpbin, GstPadTemplate * templ, const gchar * name)
{
gchar *pname;
guint sessid;
GstPad *send_rtp_sink;
GstElement *aux;
GstElement *encoder;
GstElement *prev = NULL;
GstRtpBinSession *session;
/* first get the session number */
if (name == NULL || sscanf (name, "send_rtp_sink_%u", &sessid) != 1)
goto no_name;
/* get or create session */
session = find_session_by_id (rtpbin, sessid);
if (!session) {
/* create session now */
session = create_session (rtpbin, sessid);
if (session == NULL)
goto create_error;
}
/* check if pad was requested */
if (session->send_rtp_sink_ghost != NULL)
return session->send_rtp_sink_ghost;
/* check if we are already using this session as a sender */
if (session->send_rtp_sink != NULL)
goto existing_session;
encoder = request_fec_encoder (rtpbin, session, sessid);
if (encoder) {
GST_DEBUG_OBJECT (rtpbin, "Linking FEC encoder");
send_rtp_sink = gst_element_get_static_pad (encoder, "sink");
if (!send_rtp_sink)
goto enc_sink_failed;
prev = encoder;
}
GST_DEBUG_OBJECT (rtpbin, "getting RTP AUX sender");
aux = session_request_element (session, SIGNAL_REQUEST_AUX_SENDER);
if (aux) {
GstPad *sinkpad;
GST_DEBUG_OBJECT (rtpbin, "linking AUX sender");
if (!setup_aux_sender (rtpbin, session, aux))
goto aux_session_failed;
pname = g_strdup_printf ("sink_%u", sessid);
sinkpad = gst_element_get_static_pad (aux, pname);
g_free (pname);
if (sinkpad == NULL)
goto aux_sink_failed;
if (!prev) {
send_rtp_sink = sinkpad;
} else {
GstPad *srcpad = gst_element_get_static_pad (prev, "src");
GstPadLinkReturn ret;
ret = gst_pad_link (srcpad, sinkpad);
gst_object_unref (srcpad);
if (ret != GST_PAD_LINK_OK) {
goto aux_link_failed;
}
gst_object_unref (sinkpad);
}
prev = aux;
} else {
/* get send_rtp pad and store */
session->send_rtp_sink =
gst_element_request_pad_simple (session->session, "send_rtp_sink");
if (session->send_rtp_sink == NULL)
goto pad_failed;
if (!complete_session_src (rtpbin, session))
goto session_src_failed;
if (!prev) {
send_rtp_sink = gst_object_ref (session->send_rtp_sink);
} else {
GstPad *srcpad = gst_element_get_static_pad (prev, "src");
GstPadLinkReturn ret;
ret = gst_pad_link (srcpad, session->send_rtp_sink);
gst_object_unref (srcpad);
if (ret != GST_PAD_LINK_OK)
goto session_link_failed;
}
}
session->send_rtp_sink_ghost =
gst_ghost_pad_new_from_template (name, send_rtp_sink, templ);
gst_object_unref (send_rtp_sink);
return session->send_rtp_sink_ghost;
/* ERRORS */
no_name:
{
g_warning ("rtpbin: cannot find session id for pad: %s",
GST_STR_NULL (name));
return NULL;
}
create_error:
{
/* create_session already warned */
return NULL;
}
existing_session:
{
g_warning ("rtpbin: session %u is already in use", sessid);
return NULL;
}
aux_session_failed:
{
g_warning ("rtpbin: failed to get AUX sink pad for session %u", sessid);
return NULL;
}
aux_sink_failed:
{
g_warning ("rtpbin: failed to get AUX sink pad for session %u", sessid);
return NULL;
}
aux_link_failed:
{
g_warning ("rtpbin: failed to link %" GST_PTR_FORMAT " for session %u",
aux, sessid);
return NULL;
}
pad_failed:
{
g_warning ("rtpbin: failed to get session pad for session %u", sessid);
return NULL;
}
session_src_failed:
{
g_warning ("rtpbin: failed to setup source pads for session %u", sessid);
return NULL;
}
session_link_failed:
{
g_warning ("rtpbin: failed to link %" GST_PTR_FORMAT " for session %u",
session, sessid);
return NULL;
}
enc_sink_failed:
{
g_warning ("rtpbin: failed to get %" GST_PTR_FORMAT
" sink pad for session %u", encoder, sessid);
return NULL;
}
}
static void
remove_send_rtp (GstRtpBin * rtpbin, GstRtpBinSession * session)
{
if (session->send_rtp_src_ghost) {
gst_pad_set_active (session->send_rtp_src_ghost, FALSE);
gst_element_remove_pad (GST_ELEMENT_CAST (rtpbin),
session->send_rtp_src_ghost);
session->send_rtp_src_ghost = NULL;
}
if (session->send_rtp_sink) {
gst_element_release_request_pad (GST_ELEMENT_CAST (session->session),
session->send_rtp_sink);
gst_object_unref (session->send_rtp_sink);
session->send_rtp_sink = NULL;
}
if (session->send_rtp_sink_ghost) {
gst_pad_set_active (session->send_rtp_sink_ghost, FALSE);
gst_element_remove_pad (GST_ELEMENT_CAST (rtpbin),
session->send_rtp_sink_ghost);
session->send_rtp_sink_ghost = NULL;
}
}
static void
remove_send_fec (GstRtpBin * rtpbin, GstRtpBinSession * session)
{
GSList *tmp;
for (tmp = session->send_fec_src_ghosts; tmp; tmp = tmp->next) {
GstPad *ghost = GST_PAD (tmp->data);
gst_pad_set_active (ghost, FALSE);
gst_element_remove_pad (GST_ELEMENT_CAST (rtpbin), ghost);
}
g_slist_free (session->send_fec_src_ghosts);
session->send_fec_src_ghosts = NULL;
}
/* Create a pad for sending RTCP for the session in @name. Must be called with
* RTP_BIN_LOCK.
*/
static GstPad *
create_send_rtcp (GstRtpBin * rtpbin, GstPadTemplate * templ,
const gchar * name)
{
guint sessid;
GstPad *encsrc;
GstElement *encoder;
GstRtpBinSession *session;
/* first get the session number */
if (name == NULL || sscanf (name, "send_rtcp_src_%u", &sessid) != 1)
goto no_name;
/* get or create session */
session = find_session_by_id (rtpbin, sessid);
if (!session) {
GST_DEBUG_OBJECT (rtpbin, "creating session %u", sessid);
/* create session now */
session = create_session (rtpbin, sessid);
if (session == NULL)
goto create_error;
}
/* check if pad was requested */
if (session->send_rtcp_src_ghost != NULL)
return session->send_rtcp_src_ghost;
/* get rtcp_src pad and store */
session->send_rtcp_src =
gst_element_request_pad_simple (session->session, "send_rtcp_src");
if (session->send_rtcp_src == NULL)
goto pad_failed;
GST_DEBUG_OBJECT (rtpbin, "getting RTCP encoder");
encoder = session_request_element (session, SIGNAL_REQUEST_RTCP_ENCODER);
if (encoder) {
gchar *ename;
GstPad *encsink;
GstPadLinkReturn ret;
GST_DEBUG_OBJECT (rtpbin, "linking RTCP encoder");
ename = g_strdup_printf ("rtcp_src_%u", sessid);
encsrc = gst_element_get_static_pad (encoder, ename);
g_free (ename);
if (encsrc == NULL)
goto enc_src_failed;
ename = g_strdup_printf ("rtcp_sink_%u", sessid);
encsink = gst_element_get_static_pad (encoder, ename);
g_free (ename);
if (encsink == NULL)
goto enc_sink_failed;
ret = gst_pad_link (session->send_rtcp_src, encsink);
gst_object_unref (encsink);
if (ret != GST_PAD_LINK_OK)
goto enc_link_failed;
} else {
GST_DEBUG_OBJECT (rtpbin, "no RTCP encoder given");
encsrc = gst_object_ref (session->send_rtcp_src);
}
session->send_rtcp_src_ghost =
gst_ghost_pad_new_from_template (name, encsrc, templ);
gst_object_unref (encsrc);
return session->send_rtcp_src_ghost;
/* ERRORS */
no_name:
{
g_warning ("rtpbin: cannot find session id for pad: %s",
GST_STR_NULL (name));
return NULL;
}
create_error:
{
/* create_session already warned */
return NULL;
}
pad_failed:
{
g_warning ("rtpbin: failed to get rtcp pad for session %u", sessid);
return NULL;
}
enc_src_failed:
{
g_warning ("rtpbin: failed to get encoder src pad for session %u", sessid);
return NULL;
}
enc_sink_failed:
{
g_warning ("rtpbin: failed to get encoder sink pad for session %u", sessid);
gst_object_unref (encsrc);
return NULL;
}
enc_link_failed:
{
g_warning ("rtpbin: failed to link rtcp encoder for session %u", sessid);
gst_object_unref (encsrc);
return NULL;
}
}
static void
remove_rtcp (GstRtpBin * rtpbin, GstRtpBinSession * session)
{
if (session->send_rtcp_src_ghost) {
gst_pad_set_active (session->send_rtcp_src_ghost, FALSE);
gst_element_remove_pad (GST_ELEMENT_CAST (rtpbin),
session->send_rtcp_src_ghost);
session->send_rtcp_src_ghost = NULL;
}
if (session->send_rtcp_src) {
gst_element_release_request_pad (session->session, session->send_rtcp_src);
gst_object_unref (session->send_rtcp_src);
session->send_rtcp_src = NULL;
}
}
/* If the requested name is NULL we should create a name with
* 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)
{
gboolean name_found = FALSE;
gint session = 0;
GstIterator *pad_it = NULL;
gchar *pad_name = NULL;
GValue data = { 0, };
GST_DEBUG_OBJECT (element, "find a free pad name for template");
while (!name_found) {
gboolean done = FALSE;
g_free (pad_name);
pad_name = g_strdup_printf (templ->name_template, session++);
pad_it = gst_element_iterate_pads (GST_ELEMENT (element));
name_found = TRUE;
while (!done) {
switch (gst_iterator_next (pad_it, &data)) {
case GST_ITERATOR_OK:
{
GstPad *pad;
gchar *name;
pad = g_value_get_object (&data);
name = gst_pad_get_name (pad);
if (strcmp (name, pad_name) == 0) {
done = TRUE;
name_found = FALSE;
}
g_free (name);
g_value_reset (&data);
break;
}
case GST_ITERATOR_ERROR:
case GST_ITERATOR_RESYNC:
/* restart iteration */
done = TRUE;
name_found = FALSE;
session = 0;
break;
case GST_ITERATOR_DONE:
done = TRUE;
break;
}
}
g_value_unset (&data);
gst_iterator_free (pad_it);
}
GST_DEBUG_OBJECT (element, "free pad name found: '%s'", pad_name);
return pad_name;
}
/*
*/
static GstPad *
gst_rtp_bin_request_new_pad (GstElement * element,
GstPadTemplate * templ, const gchar * name, const GstCaps * caps)
{
GstRtpBin *rtpbin;
GstElementClass *klass;
GstPad *result;
gchar *pad_name = NULL;
g_return_val_if_fail (templ != NULL, NULL);
g_return_val_if_fail (GST_IS_RTP_BIN (element), NULL);
rtpbin = GST_RTP_BIN (element);
klass = GST_ELEMENT_GET_CLASS (element);
GST_RTP_BIN_LOCK (rtpbin);
if (name == NULL) {
/* use a free pad name */
pad_name = gst_rtp_bin_get_free_pad_name (element, templ);
} else {
/* use the provided name */
pad_name = g_strdup (name);
}
GST_DEBUG_OBJECT (rtpbin, "Trying to request a pad with name %s", pad_name);
/* figure out the template */
if (templ == gst_element_class_get_pad_template (klass, "recv_rtp_sink_%u")) {
result = create_recv_rtp (rtpbin, templ, pad_name);
} else if (templ == gst_element_class_get_pad_template (klass,
"recv_rtcp_sink_%u")) {
result = create_recv_rtcp (rtpbin, templ, pad_name);
} else if (templ == gst_element_class_get_pad_template (klass,
"send_rtp_sink_%u")) {
result = create_send_rtp (rtpbin, templ, pad_name);
} else if (templ == gst_element_class_get_pad_template (klass,
"send_rtcp_src_%u")) {
result = create_send_rtcp (rtpbin, templ, pad_name);
} else if (templ == gst_element_class_get_pad_template (klass,
"recv_fec_sink_%u_%u")) {
result = create_recv_fec (rtpbin, templ, pad_name);
} else
goto wrong_template;
g_free (pad_name);
GST_RTP_BIN_UNLOCK (rtpbin);
if (result) {
gst_pad_set_active (result, TRUE);
gst_element_add_pad (GST_ELEMENT_CAST (rtpbin), result);
}
return result;
/* ERRORS */
wrong_template:
{
g_free (pad_name);
GST_RTP_BIN_UNLOCK (rtpbin);
g_warning ("rtpbin: this is not our template");
return NULL;
}
}
static void
gst_rtp_bin_release_pad (GstElement * element, GstPad * pad)
{
GstRtpBinSession *session;
GstRtpBin *rtpbin;
g_return_if_fail (GST_IS_GHOST_PAD (pad));
g_return_if_fail (GST_IS_RTP_BIN (element));
rtpbin = GST_RTP_BIN (element);
GST_RTP_BIN_LOCK (rtpbin);
GST_DEBUG_OBJECT (rtpbin, "Trying to release pad %s:%s",
GST_DEBUG_PAD_NAME (pad));
if (!(session = find_session_by_pad (rtpbin, pad)))
goto unknown_pad;
if (session->recv_rtp_sink_ghost == pad) {
remove_recv_rtp (rtpbin, session);
} else if (session->recv_rtcp_sink_ghost == pad) {
remove_recv_rtcp (rtpbin, session);
} else if (session->send_rtp_sink_ghost == pad) {
remove_send_rtp (rtpbin, session);
} else if (session->send_rtcp_src_ghost == pad) {
remove_rtcp (rtpbin, session);
} else if (pad_is_recv_fec (session, pad)) {
remove_recv_fec_for_pad (rtpbin, session, pad);
}
/* no more request pads, free the complete session */
if (session->recv_rtp_sink_ghost == NULL
&& session->recv_rtcp_sink_ghost == NULL
&& session->send_rtp_sink_ghost == NULL
&& session->send_rtcp_src_ghost == NULL
&& session->recv_fec_sink_ghosts == NULL) {
GST_DEBUG_OBJECT (rtpbin, "no more pads for session %p", session);
rtpbin->sessions = g_slist_remove (rtpbin->sessions, session);
free_session (session, rtpbin);
}
GST_RTP_BIN_UNLOCK (rtpbin);
return;
/* ERROR */
unknown_pad:
{
GST_RTP_BIN_UNLOCK (rtpbin);
g_warning ("rtpbin: %s:%s is not one of our request pads",
GST_DEBUG_PAD_NAME (pad));
return;
}
}