gstreamer/gst/rtpmanager/gstrtpbin.c
Wim Taymans e779adca69 gst/rtpmanager/: Also keep track of the first buffer timestamp together with the first
Original commit message from CVS:
* gst/rtpmanager/gstrtpbin.c: (gst_rtp_bin_associate),
(gst_rtp_bin_sync_chain):
* gst/rtpmanager/rtpsession.c: (update_arrival_stats),
(rtp_session_process_sr), (rtp_session_on_timeout):
* gst/rtpmanager/rtpsource.c: (rtp_source_init),
(calculate_jitter):
* gst/rtpmanager/rtpsource.h:
* gst/rtpmanager/rtpstats.h:
Also keep track of the first buffer timestamp together with the first
RTP timestamp as they both are needed to construct the timing of
outgoing packets in the jitterbuffer and are therefore also needed to
manage lip-sync. This fixes lip-sync if the first RTP packets arrive
with a wildly different gap.
2009-08-11 02:30:34 +01:00

2262 lines
67 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:element-gstrtpbin
* @short_description: handle media from one RTP bin
* @see_also: gstrtpjitterbuffer, gstrtpsession, gstrtpptdemux, gstrtpssrcdemux
*
* <refsect2>
* <para>
* 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.
* </para>
* <para>
* gstrtpbin is configured with a number of request pads that define the
* functionality that is activated, similar to the gstrtpsession element.
* </para>
* <para>
* To use gstrtpbin as an RTP receiver, request a recv_rtp_sink_%%d pad. The session
* number must be specified in the pad name.
* Data received on the recv_rtp_sink_%%d pad will be processed in the gstrtpsession
* manager and after being validated forwarded on gstrtpssrcdemuxer 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
* gstrtpptdemuxer element. The gstrtpptdemuxer element will demux the packets based
* on the payload type and will create a unique pad recv_rtp_src_%%d_%%d_%%d on
* gstrtpbin with the session number, SSRC and payload type respectively as the pad
* name.
* </para>
* <para>
* To also use gstrtpbin as an RTCP receiver, request a recv_rtcp_sink_%%d pad. The
* session number must be specified in the pad name.
* </para>
* <para>
* If you want the session manager to generate and send RTCP packets, request
* the send_rtcp_src_%%d 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.
* </para>
* <para>
* To use gstrtpbin as a sender, request a send_rtp_sink_%%d pad, which will
* automatically create a send_rtp_src_%%d pad. If the session number is not provided,
* the pad from the lowest available session will be returned. The session manager will modify the
* SSRC in the RTP packets to its own SSRC and wil forward the packets on the
* send_rtp_src_%%d pad after updating its internal state.
* </para>
* <para>
* 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.
* </para>
* <title>Example pipelines</title>
* <para>
* <programlisting>
* gst-launch udpsrc port=5000 caps="application/x-rtp, ..." ! .recv_rtp_sink_0 \
* gstrtpbin ! rtptheoradepay ! theoradec ! xvimagesink
* </programlisting>
* Receive RTP data from port 5000 and send to the session 0 in gstrtpbin.
* </para>
* <para>
* <programlisting>
* gst-launch gstrtpbin name=rtpbin \
* v4l2src ! ffmpegcolorspace ! 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
* </programlisting>
* 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
* </para>
* <para>
* <programlisting>
* gst-launch -v gstrtpbin 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
* </programlisting>
* 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.
* </para>
* </refsect2>
*
* Last reviewed on 2007-08-30 (0.10.6)
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <gst/rtp/gstrtpbuffer.h>
#include <gst/rtp/gstrtcpbuffer.h>
#include "gstrtpbin-marshal.h"
#include "gstrtpbin.h"
GST_DEBUG_CATEGORY_STATIC (gst_rtp_bin_debug);
#define GST_CAT_DEFAULT gst_rtp_bin_debug
/* elementfactory information */
static const GstElementDetails rtpbin_details = GST_ELEMENT_DETAILS ("RTP Bin",
"Filter/Network/RTP",
"Implement an RTP bin",
"Wim Taymans <wim.taymans@gmail.com>");
/* sink pads */
static GstStaticPadTemplate rtpbin_recv_rtp_sink_template =
GST_STATIC_PAD_TEMPLATE ("recv_rtp_sink_%d",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("application/x-rtp")
);
static GstStaticPadTemplate rtpbin_recv_rtcp_sink_template =
GST_STATIC_PAD_TEMPLATE ("recv_rtcp_sink_%d",
GST_PAD_SINK,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("application/x-rtcp")
);
static GstStaticPadTemplate rtpbin_send_rtp_sink_template =
GST_STATIC_PAD_TEMPLATE ("send_rtp_sink_%d",
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_%d_%d_%d",
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_%d",
GST_PAD_SRC,
GST_PAD_REQUEST,
GST_STATIC_CAPS ("application/x-rtcp")
);
static GstStaticPadTemplate rtpbin_send_rtp_src_template =
GST_STATIC_PAD_TEMPLATE ("send_rtp_src_%d",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("application/x-rtp")
);
/* padtemplate for the internal pad */
static GstStaticPadTemplate rtpbin_sync_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink_%d",
GST_PAD_SINK,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("application/x-rtcp")
);
#define GST_RTP_BIN_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTP_BIN, GstRtpBinPrivate))
#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)
struct _GstRtpBinPrivate
{
GMutex *bin_lock;
GstClockTime ntp_ns_base;
};
/* signals and args */
enum
{
SIGNAL_REQUEST_PT_MAP,
SIGNAL_CLEAR_PT_MAP,
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,
LAST_SIGNAL
};
#define DEFAULT_LATENCY_MS 200
#define DEFAULT_SDES_CNAME NULL
#define DEFAULT_SDES_NAME NULL
#define DEFAULT_SDES_EMAIL NULL
#define DEFAULT_SDES_PHONE NULL
#define DEFAULT_SDES_LOCATION NULL
#define DEFAULT_SDES_TOOL NULL
#define DEFAULT_SDES_NOTE NULL
enum
{
PROP_0,
PROP_LATENCY,
PROP_SDES_CNAME,
PROP_SDES_NAME,
PROP_SDES_EMAIL,
PROP_SDES_PHONE,
PROP_SDES_LOCATION,
PROP_SDES_TOOL,
PROP_SDES_NOTE,
PROP_LAST
};
/* 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 const gchar *sdes_type_to_name (GstRTCPSDESType type);
static void gst_rtp_bin_set_sdes_string (GstRtpBin * bin,
GstRTCPSDESType type, const gchar * data);
static void free_stream (GstRtpBinStream * stream);
/* Manages the RTP stream for one SSRC.
*
* We pipe the stream (comming 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;
/* the PT demuxer of the SSRC */
GstElement *demux;
gulong demux_newpad_sig;
gulong demux_ptreq_sig;
gulong demux_pt_change_sig;
/* the internal pad we use to get RTCP sync messages */
GstPad *sync_pad;
gboolean have_sync;
guint64 last_unix;
guint64 last_extrtptime;
/* mapping to local RTP and NTP time */
guint64 local_rtp;
guint64 local_unix;
gint64 unix_delta;
/* for lip-sync */
guint64 clock_base;
guint64 clock_base_time;
gint clock_rate;
gint64 ts_offset;
gint64 prev_ts_offset;
gint last_pt;
};
#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).
*/
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;
GMutex *lock;
/* list of GstRtpBinStream */
GSList *streams;
/* mapping of payload type to caps */
GHashTable *ptmap;
/* the pads of the session */
GstPad *recv_rtp_sink;
GstPad *recv_rtp_src;
GstPad *recv_rtcp_sink;
GstPad *sync_src;
GstPad *send_rtp_sink;
GstPad *send_rtp_src;
GstPad *send_rtcp_src;
};
/* 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;
gint64 min_delta;
};
/* 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 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);
}
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);
}
/* 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;
gint i;
if (!(session = gst_element_factory_make ("gstrtpsession", NULL)))
goto no_session;
if (!(demux = gst_element_factory_make ("gstrtpssrcdemux", NULL)))
goto no_demux;
sess = g_new0 (GstRtpBinSession, 1);
sess->lock = g_mutex_new ();
sess->id = id;
sess->bin = rtpbin;
sess->session = session;
sess->demux = demux;
sess->ptmap = g_hash_table_new_full (NULL, NULL, NULL,
(GDestroyNotify) gst_caps_unref);
rtpbin->sessions = g_slist_prepend (rtpbin->sessions, sess);
/* set NTP base or new session */
g_object_set (session, "ntp-ns-base", rtpbin->priv->ntp_ns_base, NULL);
/* configure SDES items */
GST_OBJECT_LOCK (rtpbin);
for (i = GST_RTCP_SDES_CNAME; i < GST_RTCP_SDES_PRIV; i++) {
g_object_set (session, sdes_type_to_name (i), rtpbin->sdes[i], 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);
/* FIXME, change state only to what's needed */
gst_bin_add (GST_BIN_CAST (rtpbin), session);
gst_element_set_state (session, GST_STATE_PLAYING);
gst_bin_add (GST_BIN_CAST (rtpbin), demux);
gst_element_set_state (demux, GST_STATE_PLAYING);
return sess;
/* ERRORS */
no_session:
{
g_warning ("gstrtpbin: could not create gstrtpsession element");
return NULL;
}
no_demux:
{
gst_object_unref (session);
g_warning ("gstrtpbin: could not create gstrtpssrcdemux element");
return NULL;
}
}
static void
free_session (GstRtpBinSession * sess)
{
GstRtpBin *bin;
bin = sess->bin;
gst_element_set_state (sess->session, GST_STATE_NULL);
gst_element_set_state (sess->demux, GST_STATE_NULL);
if (sess->recv_rtp_sink != NULL)
gst_element_release_request_pad (sess->session, sess->recv_rtp_sink);
if (sess->recv_rtp_src != NULL)
gst_object_unref (sess->recv_rtp_src);
if (sess->recv_rtcp_sink != NULL)
gst_element_release_request_pad (sess->session, sess->recv_rtcp_sink);
if (sess->sync_src != NULL)
gst_object_unref (sess->sync_src);
if (sess->send_rtp_sink != NULL)
gst_element_release_request_pad (sess->session, sess->send_rtp_sink);
if (sess->send_rtp_src != NULL)
gst_object_unref (sess->send_rtp_src);
if (sess->send_rtcp_src != NULL)
gst_element_release_request_pad (sess->session, sess->send_rtcp_src);
gst_bin_remove (GST_BIN_CAST (bin), sess->session);
gst_bin_remove (GST_BIN_CAST (bin), sess->demux);
g_slist_foreach (sess->streams, (GFunc) free_stream, NULL);
g_slist_free (sess->streams);
g_mutex_free (sess->lock);
g_hash_table_destroy (sess->ptmap);
bin->sessions = g_slist_remove (bin->sessions, sess);
g_free (sess);
}
#if 0
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;
}
#endif
/* 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 %d 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 ("emiting signal for pt %d in session %d", 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);
g_signal_emitv (args, gst_rtp_bin_signals[SIGNAL_REQUEST_PT_MAP], 0, &ret);
g_value_unset (&args[0]);
g_value_unset (&args[1]);
g_value_unset (&args[2]);
caps = (GstCaps *) g_value_dup_boxed (&ret);
g_value_unset (&ret);
if (!caps)
goto no_caps;
GST_DEBUG ("caching pt %d 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_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);
g_signal_emit_by_name (stream->buffer, "clear-pt-map", NULL);
g_signal_emit_by_name (stream->demux, "clear-pt-map", NULL);
}
GST_RTP_SESSION_UNLOCK (session);
}
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, guint8 * data, gboolean * created)
{
GstRtpBinClient *result = NULL;
GSList *walk;
for (walk = bin->clients; walk; walk = g_slist_next (walk)) {
GstRtpBinClient *client = (GstRtpBinClient *) walk->data;
if (len != client->cname_len)
continue;
if (!strncmp ((gchar *) data, client->cname, client->cname_len)) {
GST_DEBUG_OBJECT (bin, "found existing client %p with CNAME %s", client,
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;
result->min_delta = G_MAXINT64;
bin->clients = g_slist_prepend (bin->clients, result);
GST_DEBUG_OBJECT (bin, "created new client %p with CNAME %s", result,
result->cname);
}
return result;
}
static void
free_client (GstRtpBinClient * client)
{
g_slist_free (client->streams);
g_free (client->cname);
g_free (client);
}
/* associate a stream to the given CNAME. This will make sure all streams for
* that CNAME are synchronized together. */
static void
gst_rtp_bin_associate (GstRtpBin * bin, GstRtpBinStream * stream, guint8 len,
guint8 * data)
{
GstRtpBinClient *client;
gboolean created;
GSList *walk;
/* first find or create the CNAME */
GST_RTP_BIN_LOCK (bin);
client = get_client (bin, len, data, &created);
/* find stream in the client */
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, 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, client->cname);
}
/* we can only continue if we know the local clock-base and clock-rate */
if (stream->clock_base == -1)
goto no_clock_base;
if (stream->clock_rate <= 0) {
gint pt = -1;
GstCaps *caps = NULL;
GstStructure *s = NULL;
GST_RTP_SESSION_LOCK (stream->session);
pt = stream->last_pt;
GST_RTP_SESSION_UNLOCK (stream->session);
if (pt < 0)
goto no_clock_rate;
caps = get_pt_map (stream->session, pt);
if (!caps)
goto no_clock_rate;
s = gst_caps_get_structure (caps, 0);
gst_structure_get_int (s, "clock-rate", &stream->clock_rate);
gst_caps_unref (caps);
if (stream->clock_rate <= 0)
goto no_clock_rate;
}
/* map last RTP time to local timeline using our clock-base */
stream->local_rtp = stream->last_extrtptime - stream->clock_base;
GST_DEBUG_OBJECT (bin,
"base %" G_GUINT64_FORMAT ", extrtptime %" G_GUINT64_FORMAT
", local RTP %" G_GUINT64_FORMAT ", clock-rate %d", stream->clock_base,
stream->last_extrtptime, stream->local_rtp, stream->clock_rate);
/* calculate local NTP time in gstreamer timestamp */
stream->local_unix =
gst_util_uint64_scale_int (stream->local_rtp, GST_SECOND,
stream->clock_rate);
stream->local_unix += stream->clock_base_time;
/* calculate delta between server and receiver */
stream->unix_delta = stream->last_unix - stream->local_unix;
GST_DEBUG_OBJECT (bin,
"local UNIX %" G_GUINT64_FORMAT ", remote UNIX %" G_GUINT64_FORMAT
", delta %" G_GINT64_FORMAT, stream->local_unix, stream->last_unix,
stream->unix_delta);
/* recalc inter stream playout offset, but only if there are more than one
* stream. */
if (client->nstreams > 1) {
gint64 min;
/* calculate the min of all deltas */
min = G_MAXINT64;
for (walk = client->streams; walk; walk = g_slist_next (walk)) {
GstRtpBinStream *ostream = (GstRtpBinStream *) walk->data;
if (ostream->unix_delta && ostream->unix_delta < min)
min = ostream->unix_delta;
}
GST_DEBUG_OBJECT (bin, "client %p min delta %" G_GINT64_FORMAT, client,
min);
/* calculate offsets for each stream */
for (walk = client->streams; walk; walk = g_slist_next (walk)) {
GstRtpBinStream *ostream = (GstRtpBinStream *) walk->data;
if (ostream->unix_delta == 0)
continue;
ostream->ts_offset = ostream->unix_delta - min;
/* delta changed, see how much */
if (ostream->prev_ts_offset != ostream->ts_offset) {
gint64 diff;
if (ostream->prev_ts_offset > ostream->ts_offset)
diff = ostream->prev_ts_offset - ostream->ts_offset;
else
diff = ostream->ts_offset - ostream->prev_ts_offset;
/* only change diff when it changed more than 1 millisecond. This
* compensates for rounding errors in NTP to RTP timestamp
* conversions */
if (diff > GST_MSECOND)
g_object_set (ostream->buffer, "ts-offset", ostream->ts_offset, NULL);
ostream->prev_ts_offset = ostream->ts_offset;
}
GST_DEBUG_OBJECT (bin, "stream SSRC %08x, delta %" G_GINT64_FORMAT,
ostream->ssrc, ostream->ts_offset);
}
}
GST_RTP_BIN_UNLOCK (bin);
return;
no_clock_base:
{
GST_WARNING_OBJECT (bin, "we have no clock-base");
GST_RTP_BIN_UNLOCK (bin);
return;
}
no_clock_rate:
{
GST_WARNING_OBJECT (bin, "we have no clock-rate");
GST_RTP_BIN_UNLOCK (bin);
return;
}
}
#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 GstFlowReturn
gst_rtp_bin_sync_chain (GstPad * pad, GstBuffer * buffer)
{
GstFlowReturn ret = GST_FLOW_OK;
GstRtpBinStream *stream;
GstRtpBin *bin;
GstRTCPPacket packet;
guint32 ssrc;
guint64 ntptime;
guint32 rtptime;
gboolean have_sr, have_sdes;
gboolean more;
stream = gst_pad_get_element_private (pad);
bin = stream->bin;
GST_DEBUG_OBJECT (bin, "received sync packet");
if (!gst_rtcp_buffer_validate (buffer))
goto invalid_rtcp;
have_sr = FALSE;
have_sdes = FALSE;
GST_RTCP_BUFFER_FOR_PACKETS (more, buffer, &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 */
if (have_sr)
break;
/* get NTP and RTP times */
gst_rtcp_packet_sr_get_sender_info (&packet, &ssrc, &ntptime, &rtptime,
NULL, NULL);
GST_DEBUG_OBJECT (bin, "received sync packet from SSRC %08x", ssrc);
/* ignore SR that is not ours */
if (ssrc != stream->ssrc)
continue;
have_sr = TRUE;
/* store values in the stream */
stream->have_sync = TRUE;
stream->last_unix = gst_rtcp_ntp_to_unix (ntptime);
/* use extended timestamp */
gst_rtp_buffer_ext_timestamp (&stream->last_extrtptime, rtptime);
break;
case GST_RTCP_TYPE_SDES:
{
gboolean more_items, more_entries;
/* only deal with first SDES, there is only supposed to be one SDES in
* the RTCP packet but we deal with bad packets gracefully. Also bail
* out if we have not seen an SR item yet. */
if (have_sdes || !have_sr)
break;
GST_RTCP_SDES_FOR_ITEMS (more_items, &packet) {
/* 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;
guint8 *data;
gst_rtcp_packet_sdes_get_entry (&packet, &type, &len, &data);
if (type == GST_RTCP_SDES_CNAME) {
stream->clock_base = GST_BUFFER_OFFSET (buffer);
stream->clock_base_time = GST_BUFFER_OFFSET_END (buffer);
/* associate the stream to CNAME */
gst_rtp_bin_associate (bin, stream, len, data);
}
}
}
have_sdes = TRUE;
break;
}
default:
/* we can ignore these packets */
break;
}
}
gst_buffer_unref (buffer);
return ret;
/* ERRORS */
invalid_rtcp:
{
/* this is fatal and should be filtered earlier */
GST_ELEMENT_ERROR (bin, STREAM, DECODE, (NULL),
("invalid RTCP packet received"));
gst_buffer_unref (buffer);
return GST_FLOW_ERROR;
}
}
/* 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;
GstRtpBinStream *stream;
GstPadTemplate *templ;
gchar *padname;
if (!(buffer = gst_element_factory_make ("gstrtpjitterbuffer", NULL)))
goto no_jitterbuffer;
if (!(demux = gst_element_factory_make ("gstrtpptdemux", NULL)))
goto no_demux;
stream = g_new0 (GstRtpBinStream, 1);
stream->ssrc = ssrc;
stream->bin = session->bin;
stream->session = session;
stream->buffer = buffer;
stream->demux = demux;
stream->last_extrtptime = -1;
stream->last_pt = -1;
stream->have_sync = FALSE;
session->streams = g_slist_prepend (session->streams, stream);
/* make an internal sinkpad for RTCP sync packets. Take ownership of the
* pad. We will link this pad later. */
padname = g_strdup_printf ("sync_%d", ssrc);
templ = gst_static_pad_template_get (&rtpbin_sync_sink_template);
stream->sync_pad = gst_pad_new_from_template (templ, padname);
gst_object_unref (templ);
g_free (padname);
gst_object_ref (stream->sync_pad);
gst_object_sink (stream->sync_pad);
gst_pad_set_element_private (stream->sync_pad, stream);
gst_pad_set_chain_function (stream->sync_pad, gst_rtp_bin_sync_chain);
gst_pad_set_active (stream->sync_pad, TRUE);
/* provide clock_rate to the jitterbuffer when needed */
g_signal_connect (buffer, "request-pt-map",
(GCallback) pt_map_requested, session);
/* configure latency */
g_object_set (buffer, "latency", session->bin->latency, NULL);
gst_bin_add (GST_BIN_CAST (session->bin), buffer);
gst_element_set_state (buffer, GST_STATE_PLAYING);
gst_bin_add (GST_BIN_CAST (session->bin), demux);
gst_element_set_state (demux, GST_STATE_PLAYING);
/* link stuff */
gst_element_link (buffer, demux);
return stream;
/* ERRORS */
no_jitterbuffer:
{
g_warning ("gstrtpbin: could not create gstrtpjitterbuffer element");
return NULL;
}
no_demux:
{
gst_object_unref (buffer);
g_warning ("gstrtpbin: could not create gstrtpptdemux element");
return NULL;
}
}
static void
free_stream (GstRtpBinStream * stream)
{
GstRtpBinSession *session;
session = stream->session;
gst_element_set_state (stream->buffer, GST_STATE_NULL);
gst_element_set_state (stream->demux, GST_STATE_NULL);
gst_bin_remove (GST_BIN_CAST (session->bin), stream->buffer);
gst_bin_remove (GST_BIN_CAST (session->bin), stream->demux);
gst_object_unref (stream->sync_pad);
session->streams = g_slist_remove (session->streams, stream);
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 GstClock *gst_rtp_bin_provide_clock (GstElement * element);
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);
static void gst_rtp_bin_release_pad (GstElement * element, GstPad * pad);
static void gst_rtp_bin_handle_message (GstBin * bin, GstMessage * message);
static void gst_rtp_bin_clear_pt_map (GstRtpBin * bin);
GST_BOILERPLATE (GstRtpBin, gst_rtp_bin, GstBin, GST_TYPE_BIN);
static void
gst_rtp_bin_base_init (gpointer klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
/* sink pads */
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&rtpbin_recv_rtp_sink_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&rtpbin_recv_rtcp_sink_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&rtpbin_send_rtp_sink_template));
/* src pads */
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&rtpbin_recv_rtp_src_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&rtpbin_send_rtcp_src_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&rtpbin_send_rtp_src_template));
gst_element_class_set_details (element_class, &rtpbin_details);
}
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;
g_type_class_add_private (klass, sizeof (GstRtpBinPrivate));
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));
/**
* 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),
NULL, NULL, gst_rtp_bin_marshal_BOXED__UINT_UINT, GST_TYPE_CAPS, 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, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
0, G_TYPE_NONE);
/**
* 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, gst_rtp_bin_marshal_VOID__UINT_UINT, 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, gst_rtp_bin_marshal_VOID__UINT_UINT, 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, gst_rtp_bin_marshal_VOID__UINT_UINT, 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, gst_rtp_bin_marshal_VOID__UINT_UINT, 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, gst_rtp_bin_marshal_VOID__UINT_UINT, 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, gst_rtp_bin_marshal_VOID__UINT_UINT, 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, gst_rtp_bin_marshal_VOID__UINT_UINT, 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, gst_rtp_bin_marshal_VOID__UINT_UINT, G_TYPE_NONE, 2,
G_TYPE_UINT, G_TYPE_UINT);
g_object_class_install_property (gobject_class, PROP_SDES_CNAME,
g_param_spec_string ("sdes-cname", "SDES CNAME",
"The CNAME to put in SDES messages of this session",
DEFAULT_SDES_CNAME, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_SDES_NAME,
g_param_spec_string ("sdes-name", "SDES NAME",
"The NAME to put in SDES messages of this session",
DEFAULT_SDES_NAME, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_SDES_EMAIL,
g_param_spec_string ("sdes-email", "SDES EMAIL",
"The EMAIL to put in SDES messages of this session",
DEFAULT_SDES_EMAIL, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_SDES_PHONE,
g_param_spec_string ("sdes-phone", "SDES PHONE",
"The PHONE to put in SDES messages of this session",
DEFAULT_SDES_PHONE, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_SDES_LOCATION,
g_param_spec_string ("sdes-location", "SDES LOCATION",
"The LOCATION to put in SDES messages of this session",
DEFAULT_SDES_LOCATION, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_SDES_TOOL,
g_param_spec_string ("sdes-tool", "SDES TOOL",
"The TOOL to put in SDES messages of this session",
DEFAULT_SDES_TOOL, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_SDES_NOTE,
g_param_spec_string ("sdes-note", "SDES NOTE",
"The NOTE to put in SDES messages of this session",
DEFAULT_SDES_NOTE, G_PARAM_READWRITE));
gstelement_class->provide_clock =
GST_DEBUG_FUNCPTR (gst_rtp_bin_provide_clock);
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);
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);
GST_DEBUG_CATEGORY_INIT (gst_rtp_bin_debug, "rtpbin", 0, "RTP bin");
}
static void
gst_rtp_bin_init (GstRtpBin * rtpbin, GstRtpBinClass * klass)
{
gchar *str;
rtpbin->priv = GST_RTP_BIN_GET_PRIVATE (rtpbin);
rtpbin->priv->bin_lock = g_mutex_new ();
rtpbin->provided_clock = gst_system_clock_obtain ();
rtpbin->latency = DEFAULT_LATENCY_MS;
/* some default SDES entries */
str = g_strdup_printf ("%s@%s", g_get_user_name (), g_get_host_name ());
gst_rtp_bin_set_sdes_string (rtpbin, GST_RTCP_SDES_CNAME, str);
g_free (str);
gst_rtp_bin_set_sdes_string (rtpbin, GST_RTCP_SDES_NAME, g_get_real_name ());
gst_rtp_bin_set_sdes_string (rtpbin, GST_RTCP_SDES_TOOL, "GStreamer");
}
static void
gst_rtp_bin_dispose (GObject * object)
{
GstRtpBin *rtpbin;
rtpbin = GST_RTP_BIN (object);
g_slist_foreach (rtpbin->sessions, (GFunc) free_session, NULL);
g_slist_free (rtpbin->sessions);
rtpbin->sessions = NULL;
g_slist_foreach (rtpbin->clients, (GFunc) free_client, NULL);
g_slist_free (rtpbin->clients);
rtpbin->clients = NULL;
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_rtp_bin_finalize (GObject * object)
{
GstRtpBin *rtpbin;
gint i;
rtpbin = GST_RTP_BIN (object);
for (i = 0; i < 9; i++)
g_free (rtpbin->sdes[i]);
g_mutex_free (rtpbin->priv->bin_lock);
gst_object_unref (rtpbin->provided_clock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static const gchar *
sdes_type_to_name (GstRTCPSDESType type)
{
const gchar *result;
switch (type) {
case GST_RTCP_SDES_CNAME:
result = "sdes-cname";
break;
case GST_RTCP_SDES_NAME:
result = "sdes-name";
break;
case GST_RTCP_SDES_EMAIL:
result = "sdes-email";
break;
case GST_RTCP_SDES_PHONE:
result = "sdes-phone";
break;
case GST_RTCP_SDES_LOC:
result = "sdes-location";
break;
case GST_RTCP_SDES_TOOL:
result = "sdes-tool";
break;
case GST_RTCP_SDES_NOTE:
result = "sdes-note";
break;
case GST_RTCP_SDES_PRIV:
result = "sdes-priv";
break;
default:
result = NULL;
break;
}
return result;
}
static void
gst_rtp_bin_set_sdes_string (GstRtpBin * bin, GstRTCPSDESType type,
const gchar * data)
{
GSList *item;
const gchar *name;
if (type < 0 || type > 8)
return;
GST_OBJECT_LOCK (bin);
g_free (bin->sdes[type]);
bin->sdes[type] = g_strdup (data);
name = sdes_type_to_name (type);
/* store in all sessions */
for (item = bin->sessions; item; item = g_slist_next (item))
g_object_set (item->data, name, bin->sdes[type], NULL);
GST_OBJECT_UNLOCK (bin);
}
static gchar *
gst_rtp_bin_get_sdes_string (GstRtpBin * bin, GstRTCPSDESType type)
{
gchar *result;
if (type < 0 || type > 8)
return NULL;
GST_OBJECT_LOCK (bin);
result = g_strdup (bin->sdes[type]);
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 = g_value_get_uint (value);
GST_RTP_BIN_UNLOCK (rtpbin);
break;
case PROP_SDES_CNAME:
gst_rtp_bin_set_sdes_string (rtpbin, GST_RTCP_SDES_CNAME,
g_value_get_string (value));
break;
case PROP_SDES_NAME:
gst_rtp_bin_set_sdes_string (rtpbin, GST_RTCP_SDES_NAME,
g_value_get_string (value));
break;
case PROP_SDES_EMAIL:
gst_rtp_bin_set_sdes_string (rtpbin, GST_RTCP_SDES_EMAIL,
g_value_get_string (value));
break;
case PROP_SDES_PHONE:
gst_rtp_bin_set_sdes_string (rtpbin, GST_RTCP_SDES_PHONE,
g_value_get_string (value));
break;
case PROP_SDES_LOCATION:
gst_rtp_bin_set_sdes_string (rtpbin, GST_RTCP_SDES_LOC,
g_value_get_string (value));
break;
case PROP_SDES_TOOL:
gst_rtp_bin_set_sdes_string (rtpbin, GST_RTCP_SDES_TOOL,
g_value_get_string (value));
break;
case PROP_SDES_NOTE:
gst_rtp_bin_set_sdes_string (rtpbin, GST_RTCP_SDES_NOTE,
g_value_get_string (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);
GST_RTP_BIN_UNLOCK (rtpbin);
break;
case PROP_SDES_CNAME:
g_value_take_string (value, gst_rtp_bin_get_sdes_string (rtpbin,
GST_RTCP_SDES_CNAME));
break;
case PROP_SDES_NAME:
g_value_take_string (value, gst_rtp_bin_get_sdes_string (rtpbin,
GST_RTCP_SDES_NAME));
break;
case PROP_SDES_EMAIL:
g_value_take_string (value, gst_rtp_bin_get_sdes_string (rtpbin,
GST_RTCP_SDES_EMAIL));
break;
case PROP_SDES_PHONE:
g_value_take_string (value, gst_rtp_bin_get_sdes_string (rtpbin,
GST_RTCP_SDES_PHONE));
break;
case PROP_SDES_LOCATION:
g_value_take_string (value, gst_rtp_bin_get_sdes_string (rtpbin,
GST_RTCP_SDES_LOC));
break;
case PROP_SDES_TOOL:
g_value_take_string (value, gst_rtp_bin_get_sdes_string (rtpbin,
GST_RTCP_SDES_TOOL));
break;
case PROP_SDES_NOTE:
g_value_take_string (value, gst_rtp_bin_get_sdes_string (rtpbin,
GST_RTCP_SDES_NOTE));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstClock *
gst_rtp_bin_provide_clock (GstElement * element)
{
GstRtpBin *rtpbin;
rtpbin = GST_RTP_BIN (element);
return GST_CLOCK_CAST (gst_object_ref (rtpbin->provided_clock));
}
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, "GstRTPSessionSDES")) {
GSList *walk;
/* find the session, the message source has it */
for (walk = rtpbin->sessions; walk; walk = g_slist_next (walk)) {
GstRtpBinSession *sess = (GstRtpBinSession *) walk->data;
/* if we found the session, change message. else we exit the loop and
* leave the message unchanged */
if (GST_OBJECT_CAST (sess->session) == GST_MESSAGE_SRC (message)) {
message = gst_message_make_writable (message);
s = gst_message_get_structure (message);
gst_structure_set_name ((GstStructure *) s, "GstRTPBinSDES");
gst_structure_set ((GstStructure *) s, "session", G_TYPE_UINT,
sess->id, NULL);
break;
}
}
}
/* fallthrough to forward the modified message to the parent */
}
default:
{
GST_BIN_CLASS (parent_class)->handle_message (bin, message);
break;
}
}
}
static void
calc_ntp_ns_base (GstRtpBin * bin)
{
GstClockTime now;
GTimeVal current;
GSList *walk;
/* get the current time and convert it to NTP time in nanoseconds */
g_get_current_time (&current);
now = GST_TIMEVAL_TO_TIME (current);
now += (2208988800LL * GST_SECOND);
GST_RTP_BIN_LOCK (bin);
bin->priv->ntp_ns_base = now;
for (walk = bin->sessions; walk; walk = g_slist_next (walk)) {
GstRtpBinSession *session = (GstRtpBinSession *) walk->data;
g_object_set (session->session, "ntp-ns-base", now, NULL);
}
GST_RTP_BIN_UNLOCK (bin);
return;
}
static GstStateChangeReturn
gst_rtp_bin_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn res;
GstRtpBin *rtpbin;
rtpbin = GST_RTP_BIN (element);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
calc_ntp_ns_base (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;
}
/* a new pad (SSRC) was created in @session */
static void
new_payload_found (GstElement * element, guint pt, GstPad * pad,
GstRtpBinStream * stream)
{
GstRtpBin *rtpbin;
GstElementClass *klass;
GstPadTemplate *templ;
gchar *padname;
GstPad *gpad;
rtpbin = stream->bin;
GST_DEBUG ("new payload pad %d", pt);
/* ghost the pad to the parent */
klass = GST_ELEMENT_GET_CLASS (rtpbin);
templ = gst_element_class_get_pad_template (klass, "recv_rtp_src_%d_%d_%d");
padname = g_strdup_printf ("recv_rtp_src_%d_%u_%d",
stream->session->id, stream->ssrc, pt);
gpad = gst_ghost_pad_new_from_template (padname, pad, templ);
g_free (padname);
gst_pad_set_caps (gpad, GST_PAD_CAPS (pad));
gst_pad_set_active (gpad, TRUE);
gst_element_add_pad (GST_ELEMENT_CAST (rtpbin), gpad);
}
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 %d in session %d", 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;
}
}
/* emited 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))
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);
}
/* Stores the last payload type received on a particular stream */
static void
payload_type_change (GstElement * element, guint pt, GstRtpBinStream * stream)
{
GST_RTP_SESSION_LOCK (stream->session);
stream->last_pt = pt;
GST_RTP_SESSION_UNLOCK (stream->session);
}
/* a new pad (SSRC) was created in @session */
static void
new_ssrc_pad_found (GstElement * element, guint ssrc, GstPad * pad,
GstRtpBinSession * session)
{
GstRtpBinStream *stream;
GstPad *sinkpad, *srcpad;
gchar *padname;
GstCaps *caps;
GST_DEBUG_OBJECT (session->bin, "new SSRC pad %08x", ssrc);
GST_RTP_SESSION_LOCK (session);
/* create new stream */
stream = create_stream (session, ssrc);
if (!stream)
goto no_stream;
/* get the caps of the pad, we need the clock-rate and base_time if any. */
if ((caps = gst_pad_get_caps (pad))) {
const GstStructure *s;
guint val;
GST_DEBUG_OBJECT (session->bin, "pad has caps %" GST_PTR_FORMAT, caps);
s = gst_caps_get_structure (caps, 0);
if (!gst_structure_get_int (s, "clock-rate", &stream->clock_rate)) {
stream->clock_rate = -1;
GST_WARNING_OBJECT (session->bin,
"Caps have no clock rate %s from pad %s:%s",
gst_caps_to_string (caps), GST_DEBUG_PAD_NAME (pad));
}
if (gst_structure_get_uint (s, "clock-base", &val))
stream->clock_base = val;
else
stream->clock_base = -1;
gst_caps_unref (caps);
}
/* get pad and link */
GST_DEBUG_OBJECT (session->bin, "linking jitterbuffer");
padname = g_strdup_printf ("src_%d", ssrc);
srcpad = gst_element_get_static_pad (element, padname);
g_free (padname);
sinkpad = gst_element_get_static_pad (stream->buffer, "sink");
gst_pad_link (srcpad, sinkpad);
gst_object_unref (sinkpad);
gst_object_unref (srcpad);
/* get the RTCP sync pad */
GST_DEBUG_OBJECT (session->bin, "linking sync pad");
padname = g_strdup_printf ("rtcp_src_%d", ssrc);
srcpad = gst_element_get_static_pad (element, padname);
g_free (padname);
gst_pad_link (srcpad, stream->sync_pad);
gst_object_unref (srcpad);
/* 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);
/* connect to the request-pt-map signal. This signal will be emited 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) pt_map_requested, session);
/* connect to the payload-type-change signal so that we can know which
* PT is the current PT so that the jitterbuffer can be matched to the right
* offset. */
stream->demux_pt_change_sig = g_signal_connect (stream->demux,
"payload-type-change", (GCallback) payload_type_change, stream);
GST_RTP_SESSION_UNLOCK (session);
return;
/* ERRORS */
no_stream:
{
GST_RTP_SESSION_UNLOCK (session);
GST_DEBUG_OBJECT (session->bin, "could not create stream");
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)
{
GstPad *result, *sinkdpad;
guint sessid;
GstRtpBinSession *session;
GstPadLinkReturn lres;
/* first get the session number */
if (name == NULL || sscanf (name, "recv_rtp_sink_%d", &sessid) != 1)
goto no_name;
GST_DEBUG_OBJECT (rtpbin, "finding session %d", sessid);
/* get or create session */
session = find_session_by_id (rtpbin, sessid);
if (!session) {
GST_DEBUG_OBJECT (rtpbin, "creating session %d", 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 != NULL)
goto existed;
GST_DEBUG_OBJECT (rtpbin, "getting RTP sink pad");
/* get recv_rtp pad and store */
session->recv_rtp_sink =
gst_element_get_request_pad (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, "getting RTP src pad");
/* get srcpad, link to SSRCDemux */
session->recv_rtp_src =
gst_element_get_static_pad (session->session, "recv_rtp_src");
if (session->recv_rtp_src == NULL)
goto pad_failed;
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");
lres = gst_pad_link (session->recv_rtp_src, sinkdpad);
gst_object_unref (sinkdpad);
if (lres != GST_PAD_LINK_OK)
goto link_failed;
/* 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);
GST_DEBUG_OBJECT (rtpbin, "ghosting session sink pad");
result =
gst_ghost_pad_new_from_template (name, session->recv_rtp_sink, templ);
gst_pad_set_active (result, TRUE);
gst_element_add_pad (GST_ELEMENT_CAST (rtpbin), result);
return result;
/* ERRORS */
no_name:
{
g_warning ("gstrtpbin: invalid name given");
return NULL;
}
create_error:
{
/* create_session already warned */
return NULL;
}
existed:
{
g_warning ("gstrtpbin: recv_rtp pad already requested for session %d",
sessid);
return NULL;
}
pad_failed:
{
g_warning ("gstrtpbin: failed to get session pad");
return NULL;
}
link_failed:
{
g_warning ("gstrtpbin: failed to link pads");
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)
{
GstPad *result;
guint sessid;
GstRtpBinSession *session;
GstPad *sinkdpad;
GstPadLinkReturn lres;
/* first get the session number */
if (name == NULL || sscanf (name, "recv_rtcp_sink_%d", &sessid) != 1)
goto no_name;
GST_DEBUG_OBJECT (rtpbin, "finding session %d", sessid);
/* get or create the session */
session = find_session_by_id (rtpbin, sessid);
if (!session) {
GST_DEBUG_OBJECT (rtpbin, "creating session %d", 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 != NULL)
goto existed;
/* get recv_rtp pad and store */
GST_DEBUG_OBJECT (rtpbin, "getting RTCP sink pad");
session->recv_rtcp_sink =
gst_element_get_request_pad (session->session, "recv_rtcp_sink");
if (session->recv_rtcp_sink == NULL)
goto pad_failed;
/* 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 pad_failed;
GST_DEBUG_OBJECT (rtpbin, "getting demuxer RTCP sink pad");
sinkdpad = gst_element_get_static_pad (session->demux, "rtcp_sink");
lres = gst_pad_link (session->sync_src, sinkdpad);
gst_object_unref (sinkdpad);
if (lres != GST_PAD_LINK_OK)
goto link_failed;
result =
gst_ghost_pad_new_from_template (name, session->recv_rtcp_sink, templ);
gst_pad_set_active (result, TRUE);
gst_element_add_pad (GST_ELEMENT_CAST (rtpbin), result);
return result;
/* ERRORS */
no_name:
{
g_warning ("gstrtpbin: invalid name given");
return NULL;
}
create_error:
{
/* create_session already warned */
return NULL;
}
existed:
{
g_warning ("gstrtpbin: recv_rtcp pad already requested for session %d",
sessid);
return NULL;
}
pad_failed:
{
g_warning ("gstrtpbin: failed to get session pad");
return NULL;
}
link_failed:
{
g_warning ("gstrtpbin: failed to link pads");
return NULL;
}
}
/* 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)
{
GstPad *result, *srcghost;
gchar *gname;
guint sessid;
GstRtpBinSession *session;
GstElementClass *klass;
/* first get the session number */
if (name == NULL || sscanf (name, "send_rtp_sink_%d", &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 != NULL)
goto existed;
/* get send_rtp pad and store */
session->send_rtp_sink =
gst_element_get_request_pad (session->session, "send_rtp_sink");
if (session->send_rtp_sink == NULL)
goto pad_failed;
result =
gst_ghost_pad_new_from_template (name, session->send_rtp_sink, templ);
gst_pad_set_active (result, TRUE);
gst_element_add_pad (GST_ELEMENT_CAST (rtpbin), result);
/* get srcpad */
session->send_rtp_src =
gst_element_get_static_pad (session->session, "send_rtp_src");
if (session->send_rtp_src == NULL)
goto no_srcpad;
/* ghost the new source pad */
klass = GST_ELEMENT_GET_CLASS (rtpbin);
gname = g_strdup_printf ("send_rtp_src_%d", sessid);
templ = gst_element_class_get_pad_template (klass, "send_rtp_src_%d");
srcghost =
gst_ghost_pad_new_from_template (gname, session->send_rtp_src, templ);
gst_pad_set_active (srcghost, TRUE);
gst_element_add_pad (GST_ELEMENT_CAST (rtpbin), srcghost);
g_free (gname);
return result;
/* ERRORS */
no_name:
{
g_warning ("gstrtpbin: invalid name given");
return NULL;
}
create_error:
{
/* create_session already warned */
return NULL;
}
existed:
{
g_warning ("gstrtpbin: send_rtp pad already requested for session %d",
sessid);
return NULL;
}
pad_failed:
{
g_warning ("gstrtpbin: failed to get session pad for session %d", sessid);
return NULL;
}
no_srcpad:
{
g_warning ("gstrtpbin: failed to get rtp source pad for session %d",
sessid);
return NULL;
}
}
/* Create a pad for sending RTCP for the session in @name. Must be called with
* RTP_BIN_LOCK.
*/
static GstPad *
create_rtcp (GstRtpBin * rtpbin, GstPadTemplate * templ, const gchar * name)
{
GstPad *result;
guint sessid;
GstRtpBinSession *session;
/* first get the session number */
if (name == NULL || sscanf (name, "send_rtcp_src_%d", &sessid) != 1)
goto no_name;
/* get or create session */
session = find_session_by_id (rtpbin, sessid);
if (!session)
goto no_session;
/* check if pad was requested */
if (session->send_rtcp_src != NULL)
goto existed;
/* get rtcp_src pad and store */
session->send_rtcp_src =
gst_element_get_request_pad (session->session, "send_rtcp_src");
if (session->send_rtcp_src == NULL)
goto pad_failed;
result =
gst_ghost_pad_new_from_template (name, session->send_rtcp_src, templ);
gst_pad_set_active (result, TRUE);
gst_element_add_pad (GST_ELEMENT_CAST (rtpbin), result);
return result;
/* ERRORS */
no_name:
{
g_warning ("gstrtpbin: invalid name given");
return NULL;
}
no_session:
{
g_warning ("gstrtpbin: session with id %d does not exist", sessid);
return NULL;
}
existed:
{
g_warning ("gstrtpbin: send_rtcp_src pad already requested for session %d",
sessid);
return NULL;
}
pad_failed:
{
g_warning ("gstrtpbin: failed to get rtcp pad for session %d", sessid);
return NULL;
}
}
/* If the requested name is NULL we should create a name with
* the session number assuming we want the lowest posible 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;
GstPad *pad = NULL;
GstIterator *pad_it = NULL;
gchar *pad_name = NULL;
GST_DEBUG_OBJECT (element, "find a free pad name for template");
while (!name_found) {
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 (gst_iterator_next (pad_it, (gpointer) & pad) == GST_ITERATOR_OK) {
gchar *name;
name = gst_pad_get_name (pad);
if (strcmp (name, pad_name) == 0)
name_found = FALSE;
g_free (name);
}
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)
{
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 ("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_%d")) {
result = create_recv_rtp (rtpbin, templ, pad_name);
} else if (templ == gst_element_class_get_pad_template (klass,
"recv_rtcp_sink_%d")) {
result = create_recv_rtcp (rtpbin, templ, pad_name);
} else if (templ == gst_element_class_get_pad_template (klass,
"send_rtp_sink_%d")) {
result = create_send_rtp (rtpbin, templ, pad_name);
} else if (templ == gst_element_class_get_pad_template (klass,
"send_rtcp_src_%d")) {
result = create_rtcp (rtpbin, templ, pad_name);
} else
goto wrong_template;
g_free (pad_name);
GST_RTP_BIN_UNLOCK (rtpbin);
return result;
/* ERRORS */
wrong_template:
{
g_free (pad_name);
GST_RTP_BIN_UNLOCK (rtpbin);
g_warning ("gstrtpbin: this is not our template");
return NULL;
}
}
static void
gst_rtp_bin_release_pad (GstElement * element, GstPad * pad)
{
}