diff --git a/configure.ac b/configure.ac index 3b596021cf..c4ca1ec719 100644 --- a/configure.ac +++ b/configure.ac @@ -475,6 +475,7 @@ AG_GST_CHECK_PLUGIN(proxy) AG_GST_CHECK_PLUGIN(rawparse) AG_GST_CHECK_PLUGIN(removesilence) AG_GST_CHECK_PLUGIN(rist) +AG_GST_CHECK_PLUGIN(rtp) AG_GST_CHECK_PLUGIN(sdp) AG_GST_CHECK_PLUGIN(segmentclip) AG_GST_CHECK_PLUGIN(siren) @@ -2554,6 +2555,7 @@ gst/proxy/Makefile gst/rawparse/Makefile gst/removesilence/Makefile gst/rist/Makefile +gst/rtp/Makefile gst/sdp/Makefile gst/segmentclip/Makefile gst/siren/Makefile diff --git a/gst/meson.build b/gst/meson.build index f6b306c1ed..3266c6e203 100644 --- a/gst/meson.build +++ b/gst/meson.build @@ -8,7 +8,7 @@ foreach plugin : ['accurip', 'adpcmdec', 'adpcmenc', 'aiff', 'asfmux', 'ivfparse', 'ivtc', 'jp2kdecimator', 'jpegformat', 'librfb', 'midi', 'mpegdemux', 'mpegpsmux', 'mpegtsdemux', 'mpegtsmux', 'mxf', 'netsim', 'onvif', 'pcapparse', 'pnm', 'proxy', - 'rawparse', 'removesilence', 'rist', 'sdp', 'segmentclip', + 'rawparse', 'removesilence', 'rist', 'rtp', 'sdp', 'segmentclip', 'siren', 'smooth', 'speed', 'subenc', 'timecode', 'videofilters', 'videoframe_audiolevel', 'videoparsers', 'videosignal', 'vmnc', 'y4m', 'yadif'] diff --git a/gst/rtp/Makefile.am b/gst/rtp/Makefile.am new file mode 100644 index 0000000000..2d8dd1bdd6 --- /dev/null +++ b/gst/rtp/Makefile.am @@ -0,0 +1,17 @@ +plugin_LTLIBRARIES = libgstrtpmanagerbad.la + +libgstrtpmanagerbad_la_SOURCES = \ + gstrtp-utils.c \ + gstrtpsink.c \ + gstrtpsrc.c \ + plugin.c + +libgstrtpmanagerbad_la_CFLAGS = $(GST_BASE_CFLAGS) $(GST_CFLAGS) +libgstrtpmanagerbad_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) +libgstrtpmanagerbad_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +noinst_HEADERS = \ + gstrtp-utils.h \ + gstrtpcaps.h \ + gstrtpsink.h \ + gstrtpsrc.h diff --git a/gst/rtp/gstrtp-utils.c b/gst/rtp/gstrtp-utils.c new file mode 100644 index 0000000000..fc06bba798 --- /dev/null +++ b/gst/rtp/gstrtp-utils.c @@ -0,0 +1,39 @@ +/* + * See: https://bugzilla.gnome.org/show_bug.cgi?id=779765 + */ + +#include "gstrtp-utils.h" + +static void +gst_rtp_utils_uri_query_foreach (const gchar * key, const gchar * value, + GObject * src) +{ + if (key == NULL) { + GST_WARNING_OBJECT (src, "Refusing to use empty key."); + return; + } + + if (value == NULL) { + GST_WARNING_OBJECT (src, "Refusing to use NULL for key %s.", key); + return; + } + + GST_DEBUG_OBJECT (src, "Setting property '%s' to '%s'", key, value); + gst_util_set_object_arg (src, key, value); +} + +void +gst_rtp_utils_set_properties_from_uri_query (GObject * obj, const GstUri * uri) +{ + GHashTable *hash_table; + + g_return_if_fail (uri != NULL); + hash_table = gst_uri_get_query_table (uri); + + if (hash_table) { + g_hash_table_foreach (hash_table, + (GHFunc) gst_rtp_utils_uri_query_foreach, obj); + + g_hash_table_unref (hash_table); + } +} diff --git a/gst/rtp/gstrtp-utils.h b/gst/rtp/gstrtp-utils.h new file mode 100644 index 0000000000..62ec2aafad --- /dev/null +++ b/gst/rtp/gstrtp-utils.h @@ -0,0 +1,8 @@ +#ifndef __GST_RTP_UTILS_H__ +#define __GST_RTP_UTILS_H__ + +#include + +void gst_rtp_utils_set_properties_from_uri_query (GObject * obj, const GstUri * uri); + +#endif diff --git a/gst/rtp/gstrtpsink.c b/gst/rtp/gstrtpsink.c new file mode 100644 index 0000000000..23b6df9599 --- /dev/null +++ b/gst/rtp/gstrtpsink.c @@ -0,0 +1,581 @@ +/* GStreamer + * Copyright (C) <2018> Marc Leeman + * + * 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: gstrtsinkp + * @title: GstRtpSink + * @short description: element with Uri interface to stream RTP data to + * the network. + * + * RTP (RFC 3550) is a protocol to stream media over the network while + * retaining the timing information and providing enough information to + * reconstruct the correct timing domain by the receiver. + * + * The RTP data port should be even, while the RTCP port should be + * odd. The URI that is entered defines the data port, the RTCP port will + * be allocated to the next port. + * + * This element hooks up the correct sockets to support both RTP as the + * accompanying RTCP layer. + * + * This Bin handles streaming RTP payloaded data on the network. + * + * This element also implements the URI scheme `rtp://` allowing to send + * data on the network by bins that allow use the URI to determine the sink. + * The RTP URI handler also allows setting properties through the URI query. + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gstrtpsink.h" +#include "gstrtp-utils.h" + +GST_DEBUG_CATEGORY_STATIC (gst_rtp_sink_debug); +#define GST_CAT_DEFAULT gst_rtp_sink_debug + +#define DEFAULT_PROP_URI "rtp://0.0.0.0:5004" +#define DEFAULT_PROP_TTL 64 +#define DEFAULT_PROP_TTL_MC 1 + +enum +{ + PROP_0, + + PROP_URI, + PROP_TTL, + PROP_TTL_MC, + + PROP_LAST +}; + +static void gst_rtp_sink_uri_handler_init (gpointer g_iface, + gpointer iface_data); + +#define gst_rtp_sink_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstRtpSink, gst_rtp_sink, GST_TYPE_BIN, + G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_rtp_sink_uri_handler_init); + GST_DEBUG_CATEGORY_INIT (gst_rtp_sink_debug, "rtpsink", 0, "RTP Sink")); + +#define GST_RTP_SINK_GET_LOCK(obj) (&((GstRtpSink*)(obj))->lock) +#define GST_RTP_SINK_LOCK(obj) (g_mutex_lock (GST_RTP_SINK_GET_LOCK(obj))) +#define GST_RTP_SINK_UNLOCK(obj) (g_mutex_unlock (GST_RTP_SINK_GET_LOCK(obj))) + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS ("application/x-rtp")); + +static GstStateChangeReturn +gst_rtp_sink_change_state (GstElement * element, GstStateChange transition); + +static void +gst_rtp_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRtpSink *self = GST_RTP_SINK (object); + + switch (prop_id) { + case PROP_URI:{ + GstUri *uri = NULL; + + GST_RTP_SINK_LOCK (object); + uri = gst_uri_from_string (g_value_get_string (value)); + if (uri == NULL) + break; + + if (self->uri) + gst_uri_unref (self->uri); + self->uri = uri; + /* RTP data ports should be even according to RFC 3550, while the + * RTCP is sent on odd ports. Just warn if there is a mismatch. */ + if (gst_uri_get_port (self->uri) % 2) + GST_WARNING_OBJECT (self, + "Port %u is not even, this is not standard (see RFC 3550).", + gst_uri_get_port (self->uri)); + + gst_rtp_utils_set_properties_from_uri_query (G_OBJECT (self), self->uri); + GST_RTP_SINK_UNLOCK (object); + break; + } + case PROP_TTL: + self->ttl = g_value_get_int (value); + break; + case PROP_TTL_MC: + self->ttl_mc = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtp_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstRtpSink *self = GST_RTP_SINK (object); + + switch (prop_id) { + case PROP_URI: + GST_RTP_SINK_LOCK (object); + if (self->uri) + g_value_take_string (value, gst_uri_to_string (self->uri)); + else + g_value_set_string (value, NULL); + GST_RTP_SINK_UNLOCK (object); + break; + case PROP_TTL: + g_value_set_int (value, self->ttl); + break; + case PROP_TTL_MC: + g_value_set_int (value, self->ttl_mc); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtp_sink_finalize (GObject * gobject) +{ + GstRtpSink *self = GST_RTP_SINK (gobject); + + if (self->uri) + gst_uri_unref (self->uri); + + g_mutex_clear (&self->lock); + G_OBJECT_CLASS (parent_class)->finalize (gobject); +} + +static gboolean +gst_rtp_sink_setup_elements (GstRtpSink * self) +{ + /*GstPad *pad; */ + GSocket *socket; + GInetAddress *addr; + gchar name[48]; + GstCaps *caps; + + /* Should not be NULL */ + g_return_val_if_fail (self->uri != NULL, FALSE); + + /* if not already configured */ + if (self->funnel_rtp == NULL) { + self->funnel_rtp = gst_element_factory_make ("funnel", NULL); + if (self->funnel_rtp == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "funnel_rtp element is not available")); + return FALSE; + } + + self->funnel_rtcp = gst_element_factory_make ("funnel", NULL); + if (self->funnel_rtcp == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "funnel_rtcp element is not available")); + return FALSE; + } + + self->rtp_sink = gst_element_factory_make ("udpsink", NULL); + if (self->rtp_sink == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "rtp_sink element is not available")); + return FALSE; + } + + self->rtcp_src = gst_element_factory_make ("udpsrc", NULL); + if (self->rtcp_src == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "rtcp_src element is not available")); + return FALSE; + } + + self->rtcp_sink = gst_element_factory_make ("udpsink", NULL); + if (self->rtcp_sink == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "rtcp_sink element is not available")); + return FALSE; + } + + gst_bin_add (GST_BIN (self), self->funnel_rtp); + gst_bin_add (GST_BIN (self), self->funnel_rtcp); + + /* Add elements as needed, since udpsrc/udpsink for RTCP share a socket, + * not all at the same moment */ + g_object_set (self->rtp_sink, + "host", gst_uri_get_host (self->uri), + "port", gst_uri_get_port (self->uri), + "ttl", self->ttl, "ttl-mc", self->ttl_mc, NULL); + + gst_bin_add (GST_BIN (self), self->rtp_sink); + + g_object_set (self->rtcp_sink, + "host", gst_uri_get_host (self->uri), + "port", gst_uri_get_port (self->uri) + 1, + "ttl", self->ttl, "ttl-mc", self->ttl_mc, + /* Set false since we're reusing a socket */ + "auto-multicast", FALSE, NULL); + + gst_bin_add (GST_BIN (self), self->rtcp_sink); + + /* no need to set address if unicast */ + caps = gst_caps_new_empty_simple ("application/x-rtcp"); + g_object_set (self->rtcp_src, + "port", gst_uri_get_port (self->uri) + 1, "caps", caps, NULL); + gst_caps_unref (caps); + + addr = g_inet_address_new_from_string (gst_uri_get_host (self->uri)); + if (g_inet_address_get_is_multicast (addr)) { + g_object_set (self->rtcp_src, "address", gst_uri_get_host (self->uri), + NULL); + } + g_object_unref (addr); + + gst_bin_add (GST_BIN (self), self->rtcp_src); + + gst_element_link (self->funnel_rtp, self->rtp_sink); + gst_element_link (self->funnel_rtcp, self->rtcp_sink); + + gst_element_sync_state_with_parent (self->funnel_rtp); + gst_element_sync_state_with_parent (self->funnel_rtcp); + gst_element_sync_state_with_parent (self->rtp_sink); + gst_element_sync_state_with_parent (self->rtcp_src); + + g_object_get (G_OBJECT (self->rtcp_src), "used-socket", &socket, NULL); + g_object_set (G_OBJECT (self->rtcp_sink), "socket", socket, NULL); + + gst_element_sync_state_with_parent (self->rtcp_sink); + + } + + /* pads are all named */ + g_snprintf (name, 48, "send_rtp_src_%u", GST_ELEMENT (self)->numpads); + gst_element_link_pads (self->rtpbin, name, self->funnel_rtp, "sink_%u"); + + g_snprintf (name, 48, "send_rtcp_src_%u", GST_ELEMENT (self)->numpads); + gst_element_link_pads (self->rtpbin, name, self->funnel_rtcp, "sink_%u"); + + g_snprintf (name, 48, "recv_rtcp_sink_%u", GST_ELEMENT (self)->numpads); + gst_element_link_pads (self->rtcp_src, "src", self->rtpbin, name); + + return TRUE; +} + +static GstPad * +gst_rtp_sink_request_new_pad (GstElement * element, + GstPadTemplate * templ, const gchar * name, const GstCaps * caps) +{ + GstRtpSink *self = GST_RTP_SINK (element); + GstPad *pad = NULL; + + if (self->rtpbin == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "rtpbin element is not available")); + return NULL; + } + + if (gst_rtp_sink_setup_elements (self) == FALSE) + return NULL; + + GST_RTP_SINK_LOCK (self); + + pad = gst_element_get_request_pad (self->rtpbin, "send_rtp_sink_%u"); + g_return_val_if_fail (pad != NULL, NULL); + + GST_RTP_SINK_UNLOCK (self); + + return pad; +} + +static void +gst_rtp_sink_release_pad (GstElement * element, GstPad * pad) +{ + GstRtpSink *self = GST_RTP_SINK (element); + GstPad *rpad = gst_ghost_pad_get_target (GST_GHOST_PAD (pad)); + + GST_RTP_SINK_LOCK (self); + gst_element_release_request_pad (self->rtpbin, rpad); + gst_object_unref (rpad); + + gst_pad_set_active (pad, FALSE); + gst_element_remove_pad (GST_ELEMENT (self), pad); + + GST_RTP_SINK_UNLOCK (self); +} + +static void +gst_rtp_sink_class_init (GstRtpSinkClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + gobject_class->set_property = gst_rtp_sink_set_property; + gobject_class->get_property = gst_rtp_sink_get_property; + gobject_class->finalize = gst_rtp_sink_finalize; + gstelement_class->change_state = gst_rtp_sink_change_state; + + gstelement_class->request_new_pad = + GST_DEBUG_FUNCPTR (gst_rtp_sink_request_new_pad); + gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_rtp_sink_release_pad); + + /** + * GstRtpSink:uri: + * + * uri to stream RTP to. All GStreamer parameters can be + * encoded in the URI, this URI format is RFC compliant. + */ + g_object_class_install_property (gobject_class, PROP_URI, + g_param_spec_string ("uri", "URI", + "URI in the form of rtp://host:port?query", DEFAULT_PROP_URI, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRtpSink:ttl: + * + * Set the unicast TTL parameter. + */ + g_object_class_install_property (gobject_class, PROP_TTL, + g_param_spec_int ("ttl", "Unicast TTL", + "Used for setting the unicast TTL parameter", + 0, 255, DEFAULT_PROP_TTL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRtpSink:ttl-mc: + * + * Set the multicast TTL parameter. + */ + g_object_class_install_property (gobject_class, PROP_TTL_MC, + g_param_spec_int ("ttl-mc", "Multicast TTL", + "Used for setting the multicast TTL parameter", 0, 255, + DEFAULT_PROP_TTL_MC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sink_template)); + + gst_element_class_set_static_metadata (gstelement_class, + "RTP Sink element", + "Generic/Bin/Sink", + "Simple RTP sink", "Marc Leeman "); +} + +static void +gst_rtp_sink_rtpbin_element_added_cb (GstBin * element, + GstElement * new_element, gpointer data) +{ + GstRtpSink *self = GST_RTP_SINK (data); + GST_INFO_OBJECT (self, + "Element %" GST_PTR_FORMAT " added element %" GST_PTR_FORMAT ".", element, + new_element); +} + +static void +gst_rtp_sink_rtpbin_pad_added_cb (GstElement * element, GstPad * pad, + gpointer data) +{ + GstRtpSink *self = GST_RTP_SINK (data); + GstCaps *caps = gst_pad_query_caps (pad, NULL); + GstPad *upad; + + /* Expose RTP data pad only */ + GST_INFO_OBJECT (self, + "Element %" GST_PTR_FORMAT " added pad %" GST_PTR_FORMAT "with caps %" + GST_PTR_FORMAT ".", element, pad, caps); + + /* Sanity checks */ + if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK) { + /* Src pad, do not expose */ + gst_caps_unref (caps); + return; + } + + if (G_LIKELY (caps)) { + GstCaps *ref_caps = gst_caps_new_empty_simple ("application/x-rtcp"); + + if (gst_caps_can_intersect (caps, ref_caps)) { + /* SRC RTCP caps, do not expose */ + gst_caps_unref (ref_caps); + gst_caps_unref (caps); + + return; + } + gst_caps_unref (ref_caps); + } else { + GST_ERROR_OBJECT (self, "Pad with no caps detected."); + gst_caps_unref (caps); + + return; + } + gst_caps_unref (caps); + + upad = gst_element_get_compatible_pad (self->funnel_rtp, pad, NULL); + if (upad == NULL) { + GST_ERROR_OBJECT (self, "No compatible pad found to link pad."); + gst_caps_unref (caps); + + return; + } + GST_INFO_OBJECT (self, "Linking with pad %" GST_PTR_FORMAT ".", upad); + gst_pad_link (pad, upad); + gst_object_unref (upad); +} + +static void +gst_rtp_sink_rtpbin_pad_removed_cb (GstElement * element, GstPad * pad, + gpointer data) +{ + GstRtpSink *self = GST_RTP_SINK (data); + GST_INFO_OBJECT (self, + "Element %" GST_PTR_FORMAT " removed pad %" GST_PTR_FORMAT ".", element, + pad); +} + +static gboolean +gst_rtp_sink_setup_rtpbin (GstRtpSink * self) +{ + self->rtpbin = gst_element_factory_make ("rtpbin", NULL); + if (self->rtpbin == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "rtpbin element is not available")); + return FALSE; + } + + /* Add rtpbin callbacks to monitor the operation of rtpbin */ + g_signal_connect (self->rtpbin, "element-added", + G_CALLBACK (gst_rtp_sink_rtpbin_element_added_cb), self); + g_signal_connect (self->rtpbin, "pad-added", + G_CALLBACK (gst_rtp_sink_rtpbin_pad_added_cb), self); + g_signal_connect (self->rtpbin, "pad-removed", + G_CALLBACK (gst_rtp_sink_rtpbin_pad_removed_cb), self); + + gst_bin_add (GST_BIN (self), self->rtpbin); + + gst_element_sync_state_with_parent (self->rtpbin); + + return TRUE; +} + +static GstStateChangeReturn +gst_rtp_sink_change_state (GstElement * element, GstStateChange transition) +{ + GstRtpSink *self = GST_RTP_SINK (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (self, "changing state: %s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + default: + break; + } + + return ret; +} + + +static void +gst_rtp_sink_init (GstRtpSink * self) +{ + self->rtpbin = NULL; + self->funnel_rtp = NULL; + self->funnel_rtcp = NULL; + self->rtp_sink = NULL; + self->rtcp_src = NULL; + self->rtcp_sink = NULL; + + self->uri = gst_uri_from_string (DEFAULT_PROP_URI); + self->ttl = DEFAULT_PROP_TTL; + self->ttl_mc = DEFAULT_PROP_TTL_MC; + + if (gst_rtp_sink_setup_rtpbin (self) == FALSE) + return; + + GST_OBJECT_FLAG_SET (GST_OBJECT (self), GST_ELEMENT_FLAG_SINK); + gst_bin_set_suppressed_flags (GST_BIN (self), + GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK); + + g_mutex_init (&self->lock); +} + +static guint +gst_rtp_sink_uri_get_type (GType type) +{ + return GST_URI_SINK; +} + +static const gchar *const * +gst_rtp_sink_uri_get_protocols (GType type) +{ + static const gchar *protocols[] = { (char *) "rtp", NULL }; + + return protocols; +} + +static gchar * +gst_rtp_sink_uri_get_uri (GstURIHandler * handler) +{ + GstRtpSink *self = (GstRtpSink *) handler; + + return gst_uri_to_string (self->uri); +} + +static gboolean +gst_rtp_sink_uri_set_uri (GstURIHandler * handler, const gchar * uri, + GError ** error) +{ + GstRtpSink *self = (GstRtpSink *) handler; + + g_object_set (G_OBJECT (self), "uri", uri, NULL); + + return TRUE; +} + +static void +gst_rtp_sink_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + iface->get_type = gst_rtp_sink_uri_get_type; + iface->get_protocols = gst_rtp_sink_uri_get_protocols; + iface->get_uri = gst_rtp_sink_uri_get_uri; + iface->set_uri = gst_rtp_sink_uri_set_uri; +} + +/* ex: set tabstop=2 shiftwidth=2 expandtab: */ diff --git a/gst/rtp/gstrtpsink.h b/gst/rtp/gstrtpsink.h new file mode 100644 index 0000000000..6f3fec0ac5 --- /dev/null +++ b/gst/rtp/gstrtpsink.h @@ -0,0 +1,72 @@ +/* GStreamer + * Copyright (C) 2019 Marc Leeman + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_RTP_SINK_H__ +#define __GST_RTP_SINK_H__ + +#include + +G_BEGIN_DECLS +#define GST_TYPE_RTP_SINK \ + (gst_rtp_sink_get_type()) +#define GST_RTP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTP_SINK, GstRtpSink)) +#define GST_RTP_SINK_CAST(obj) \ + ((GstRtpSink *) obj) +#define GST_RTP_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTP_SINK, GstRtpSinkClass)) +#define GST_IS_RTP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTP_SINK)) +#define GST_IS_RTP_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTP_SINK)) + +typedef struct _GstRtpSink GstRtpSink; +typedef struct _GstRtpSinkClass GstRtpSinkClass; + +struct _GstRtpSink +{ + GstBin parent; + + GstBin parent_instance; + + /* Properties */ + GstUri *uri; + gint ttl; + gint ttl_mc; + + /* Internal elements */ + GstElement *rtpbin; + GstElement *funnel_rtp; + GstElement *funnel_rtcp; + GstElement *rtp_sink; + GstElement *rtcp_src; + GstElement *rtcp_sink; + + GMutex lock; +}; + +struct _GstRtpSinkClass +{ + GstBinClass parent; +}; + +GType gst_rtp_sink_get_type (void); + +G_END_DECLS +#endif /* __GST_RTP_SINK_H__ */ diff --git a/gst/rtp/gstrtpsrc.c b/gst/rtp/gstrtpsrc.c new file mode 100644 index 0000000000..bf958603bd --- /dev/null +++ b/gst/rtp/gstrtpsrc.c @@ -0,0 +1,731 @@ +/* GStreamer + * Copyright (C) <2018> Marc Leeman + * + * 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: gstrtpsrc + * @title: GstRtpSrc + * @short description: element with Uri interface to get RTP data from + * the network. + * + * RTP (RFC 3550) is a protocol to stream media over the network while + * retaining the timing information and providing enough information to + * reconstruct the correct timing domain by the receiver. + * + * The RTP data port should be even, while the RTCP port should be + * odd. The URI that is entered defines the data port, the RTCP port will + * be allocated to the next port. + * + * This element hooks up the correct sockets to support both RTP as the + * accompanying RTCP layer. + * + * This Bin handles taking in of data from the network and provides the + * RTP payloaded data. + * + * This element also implements the URI scheme `rtp://` allowing to render + * RTP streams in GStreamer based media players. The RTP URI handler also + * allows setting properties through the URI query. + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "gstrtpsrc.h" +#include "gstrtp-utils.h" + +GST_DEBUG_CATEGORY_STATIC (gst_rtp_src_debug); +#define GST_CAT_DEFAULT gst_rtp_src_debug + +#define DEFAULT_PROP_TTL 64 +#define DEFAULT_PROP_TTL_MC 1 +#define DEFAULT_PROP_ENCODING_NAME NULL +#define DEFAULT_PROP_LATENCY 200 + +#define DEFAULT_PROP_URI "rtp://0.0.0.0:5004" + +enum +{ + PROP_0, + + PROP_URI, + PROP_TTL, + PROP_TTL_MC, + PROP_ENCODING_NAME, + PROP_LATENCY, + + PROP_LAST +}; + +static void gst_rtp_src_uri_handler_init (gpointer g_iface, + gpointer iface_data); + +#define gst_rtp_src_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstRtpSrc, gst_rtp_src, GST_TYPE_BIN, + G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_rtp_src_uri_handler_init); + GST_DEBUG_CATEGORY_INIT (gst_rtp_src_debug, "rtpsrc", 0, "RTP Source")); + +#define GST_RTP_SRC_GET_LOCK(obj) (&((GstRtpSrc*)(obj))->lock) +#define GST_RTP_SRC_LOCK(obj) (g_mutex_lock (GST_RTP_SRC_GET_LOCK(obj))) +#define GST_RTP_SRC_UNLOCK(obj) (g_mutex_unlock (GST_RTP_SRC_GET_LOCK(obj))) + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS ("application/x-rtp")); + +static GstStateChangeReturn +gst_rtp_src_change_state (GstElement * element, GstStateChange transition); + +/** + * gst_rtp_src_rtpbin_erquest_pt_map_cb: + * @self: The current #GstRtpSrc object + * + * #GstRtpBin callback to map a pt on RTP caps. + * + * Returns: (transfer none): the guess on the RTP caps based on the PT + * and caps. + */ +static GstCaps * +gst_rtp_src_rtpbin_request_pt_map_cb (GstElement * rtpbin, guint session_id, + guint pt, gpointer data) +{ + GstRtpSrc *self = GST_RTP_SRC (data); + const GstRTPPayloadInfo *p = NULL; + + GST_DEBUG_OBJECT (self, + "Requesting caps for session-id 0x%x and pt %u.", session_id, pt); + + /* the encoding-name has more relevant information */ + if (self->encoding_name != NULL) { + /* Unfortunately, the media needs to be passed in the function. Since + * it is not known, try for video if video not found. */ + p = gst_rtp_payload_info_for_name ("video", self->encoding_name); + if (p == NULL) + p = gst_rtp_payload_info_for_name ("audio", self->encoding_name); + + } + + /* Static payload types, this is a simple lookup */ + if (!GST_RTP_PAYLOAD_IS_DYNAMIC (pt)) { + p = gst_rtp_payload_info_for_pt (pt); + } + + if (p != NULL) { + GstCaps *ret = gst_caps_new_simple ("application/x-rtp", + "encoding-name", G_TYPE_STRING, p->encoding_name, + "clock-rate", G_TYPE_INT, p->clock_rate, + "media", G_TYPE_STRING, p->media, NULL); + + GST_DEBUG_OBJECT (self, "Decided on caps %" GST_PTR_FORMAT, ret); + + return ret; + } + + GST_DEBUG_OBJECT (self, "Could not determine caps based on pt and" + " the encoding-name was not set."); + return NULL; +} + +static void +gst_rtp_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstRtpSrc *self = GST_RTP_SRC (object); + GstCaps *caps; + + switch (prop_id) { + case PROP_URI:{ + GstUri *uri = NULL; + + GST_RTP_SRC_LOCK (object); + uri = gst_uri_from_string (g_value_get_string (value)); + if (uri == NULL) + break; + + if (self->uri) + gst_uri_unref (self->uri); + self->uri = uri; + if (gst_uri_get_port (self->uri) % 2) + GST_WARNING_OBJECT (self, + "Port %u is not even, this is not standard (see RFC 3550).", + gst_uri_get_port (self->uri)); + gst_rtp_utils_set_properties_from_uri_query (G_OBJECT (self), self->uri); + GST_RTP_SRC_UNLOCK (object); + break; + } + case PROP_TTL: + self->ttl = g_value_get_int (value); + break; + case PROP_TTL_MC: + self->ttl_mc = g_value_get_int (value); + break; + case PROP_ENCODING_NAME: + g_free (self->encoding_name); + self->encoding_name = g_value_dup_string (value); + if (self->rtp_src) { + caps = gst_rtp_src_rtpbin_request_pt_map_cb (NULL, 0, 96, self); + g_object_set (G_OBJECT (self->rtp_src), "caps", caps, NULL); + gst_caps_unref (caps); + } + break; + case PROP_LATENCY: + self->latency = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtp_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstRtpSrc *self = GST_RTP_SRC (object); + + switch (prop_id) { + case PROP_URI: + GST_RTP_SRC_LOCK (object); + if (self->uri) + g_value_take_string (value, gst_uri_to_string (self->uri)); + else + g_value_set_string (value, NULL); + GST_RTP_SRC_UNLOCK (object); + break; + case PROP_TTL: + g_value_set_int (value, self->ttl); + break; + case PROP_TTL_MC: + g_value_set_int (value, self->ttl_mc); + break; + case PROP_ENCODING_NAME: + g_value_set_string (value, self->encoding_name); + break; + case PROP_LATENCY: + g_value_set_uint (value, self->latency); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_rtp_src_finalize (GObject * gobject) +{ + GstRtpSrc *self = GST_RTP_SRC (gobject); + + if (self->uri) + gst_uri_unref (self->uri); + g_free (self->encoding_name); + + g_mutex_clear (&self->lock); + G_OBJECT_CLASS (parent_class)->finalize (gobject); +} + +static void +gst_rtp_src_class_init (GstRtpSrcClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + gobject_class->set_property = gst_rtp_src_set_property; + gobject_class->get_property = gst_rtp_src_get_property; + gobject_class->finalize = gst_rtp_src_finalize; + gstelement_class->change_state = gst_rtp_src_change_state; + + /** + * GstRtpSrc:uri: + * + * uri to an RTP from. All GStreamer parameters can be + * encoded in the URI, this URI format is RFC compliant. + */ + g_object_class_install_property (gobject_class, PROP_URI, + g_param_spec_string ("uri", "URI", + "URI in the form of rtp://host:port?query", DEFAULT_PROP_URI, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRtpSrc:ttl: + * + * Set the unicast TTL parameter. In RTP this of importance for RTCP. + */ + g_object_class_install_property (gobject_class, PROP_TTL, + g_param_spec_int ("ttl", "Unicast TTL", + "Used for setting the unicast TTL parameter", + 0, 255, DEFAULT_PROP_TTL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRtpSrc:ttl-mc: + * + * Set the multicast TTL parameter. In RTP this of importance for RTCP. + */ + g_object_class_install_property (gobject_class, PROP_TTL_MC, + g_param_spec_int ("ttl-mc", "Multicast TTL", + "Used for setting the multicast TTL parameter", 0, 255, + DEFAULT_PROP_TTL_MC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRtpSrc:encoding-name: + * + * Set the encoding name of the stream to use. This is a short-hand for + * the full caps and maps typically to the encoding-name in the RTP caps. + */ + g_object_class_install_property (gobject_class, PROP_ENCODING_NAME, + g_param_spec_string ("encoding-name", "Caps encoding name", + "Encoding name use to determine caps parameters", + DEFAULT_PROP_ENCODING_NAME, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstRtpSrc:latency: + * + * Set the size of the latency buffer in the + * GstRtpBin/GstRtpJitterBuffer to compensate for network jitter. + */ + 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_PROP_LATENCY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&src_template)); + + gst_element_class_set_static_metadata (gstelement_class, + "RTP Source element", + "Generic/Bin/Src", + "Simple RTP src", "Marc Leeman "); +} + +static void +gst_rtp_src_rtpbin_pad_added_cb (GstElement * element, GstPad * pad, + gpointer data) +{ + GstRtpSrc *self = GST_RTP_SRC (data); + GstCaps *caps = gst_pad_query_caps (pad, NULL); + GstPad *upad; + gchar name[48]; + + /* Expose RTP data pad only */ + GST_INFO_OBJECT (self, + "Element %" GST_PTR_FORMAT " added pad %" GST_PTR_FORMAT "with caps %" + GST_PTR_FORMAT ".", element, pad, caps); + + /* Sanity checks */ + if (GST_PAD_DIRECTION (pad) == GST_PAD_SINK) { + /* Sink pad, do not expose */ + gst_caps_unref (caps); + return; + } + + if (G_LIKELY (caps)) { + GstCaps *ref_caps = gst_caps_new_empty_simple ("application/x-rtcp"); + + if (gst_caps_can_intersect (caps, ref_caps)) { + /* SRC RTCP caps, do not expose */ + gst_caps_unref (ref_caps); + gst_caps_unref (caps); + + return; + } + gst_caps_unref (ref_caps); + } else { + GST_ERROR_OBJECT (self, "Pad with no caps detected."); + gst_caps_unref (caps); + + return; + } + gst_caps_unref (caps); + + GST_RTP_SRC_LOCK (self); + g_snprintf (name, 48, "src_%u", GST_ELEMENT (self)->numpads); + upad = gst_ghost_pad_new (name, pad); + + gst_pad_set_active (upad, TRUE); + gst_element_add_pad (GST_ELEMENT (self), upad); + + GST_RTP_SRC_UNLOCK (self); +} + +static void +gst_rtp_src_rtpbin_pad_removed_cb (GstElement * element, GstPad * pad, + gpointer data) +{ + GstRtpSrc *self = GST_RTP_SRC (data); + GST_INFO_OBJECT (self, + "Element %" GST_PTR_FORMAT " removed pad %" GST_PTR_FORMAT ".", element, + pad); +} + +static void +gst_rtp_src_rtpbin_on_ssrc_collision_cb (GstElement * rtpbin, guint session_id, + guint ssrc, gpointer data) +{ + GstRtpSrc *self = GST_RTP_SRC (data); + + GST_INFO_OBJECT (self, + "Dectected an SSRC collision: session-id 0x%x, ssrc 0x%x.", session_id, + ssrc); +} + +static void +gst_rtp_src_rtpbin_on_new_ssrc_cb (GstElement * rtpbin, guint session_id, + guint ssrc, gpointer data) +{ + GstRtpSrc *self = GST_RTP_SRC (data); + + GST_INFO_OBJECT (self, "Dectected a new SSRC: session-id 0x%x, ssrc 0x%x.", + session_id, ssrc); +} + +static GstPadProbeReturn +gst_rtp_src_on_recv_rtcp (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstRtpSrc *self = GST_RTP_SRC (user_data); + GstBuffer *buffer; + GstNetAddressMeta *meta; + + if (info->type == GST_PAD_PROBE_TYPE_BUFFER_LIST) { + GstBufferList *buffer_list = info->data; + buffer = gst_buffer_list_get (buffer_list, 0); + } else { + buffer = info->data; + } + + meta = gst_buffer_get_net_address_meta (buffer); + + GST_OBJECT_LOCK (self); + g_clear_object (&self->rtcp_send_addr); + self->rtcp_send_addr = g_object_ref (meta->addr); + GST_OBJECT_UNLOCK (self); + + return GST_PAD_PROBE_OK; +} + +static inline void +gst_rtp_src_attach_net_address_meta (GstRtpSrc * self, GstBuffer * buffer) +{ + GST_OBJECT_LOCK (self); + if (self->rtcp_send_addr) + gst_buffer_add_net_address_meta (buffer, self->rtcp_send_addr); + GST_OBJECT_UNLOCK (self); +} + +static GstPadProbeReturn +gst_rtp_src_on_send_rtcp (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstRtpSrc *self = GST_RTP_SRC (user_data); + + if (info->type == GST_PAD_PROBE_TYPE_BUFFER_LIST) { + GstBufferList *buffer_list = info->data; + GstBuffer *buffer; + gint i; + + info->data = buffer_list = gst_buffer_list_make_writable (buffer_list); + for (i = 0; i < gst_buffer_list_length (buffer_list); i++) { + buffer = gst_buffer_list_get (buffer_list, i); + gst_rtp_src_attach_net_address_meta (self, buffer); + } + } else { + GstBuffer *buffer = info->data; + info->data = buffer = gst_buffer_make_writable (buffer); + gst_rtp_src_attach_net_address_meta (self, buffer); + } + + return GST_PAD_PROBE_OK; +} + +static gboolean +gst_rtp_src_setup_elements (GstRtpSrc * self) +{ + GstPad *pad; + GSocket *socket; + GInetAddress *addr; + gchar name[48]; + GstCaps *caps; + gchar *address; + guint rtcp_port; + + /* Construct the RTP receiver pipeline. + * + * udpsrc -> [recv_rtp_sink_%u] -------- [recv_rtp_src_%u_%u_%u] + * | rtpbin | + * udpsrc -> [recv_rtcp_sink_%u] -------- [send_rtcp_src_%u] -> udpsink + * + * This pipeline is fixed for now, note that optionally an FEC stream could + * be added later. + */ + + /* Should not be NULL */ + g_return_val_if_fail (self->uri != NULL, FALSE); + + self->rtpbin = gst_element_factory_make ("rtpbin", NULL); + if (self->rtpbin == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "rtpbin element is not available")); + return FALSE; + } + + self->rtp_src = gst_element_factory_make ("udpsrc", NULL); + if (self->rtp_src == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "rtp_src element is not available")); + return FALSE; + } + + self->rtcp_src = gst_element_factory_make ("udpsrc", NULL); + if (self->rtcp_src == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "rtcp_src element is not available")); + return FALSE; + } + + self->rtcp_sink = gst_element_factory_make ("dynudpsink", NULL); + if (self->rtcp_sink == NULL) { + GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, (NULL), + ("%s", "rtcp_sink element is not available")); + return FALSE; + } + + /* Add rtpbin callbacks to monitor the operation of rtpbin */ + g_signal_connect (self->rtpbin, "pad-added", + G_CALLBACK (gst_rtp_src_rtpbin_pad_added_cb), self); + g_signal_connect (self->rtpbin, "pad-removed", + G_CALLBACK (gst_rtp_src_rtpbin_pad_removed_cb), self); + g_signal_connect (self->rtpbin, "request-pt-map", + G_CALLBACK (gst_rtp_src_rtpbin_request_pt_map_cb), self); + g_signal_connect (self->rtpbin, "on-new-ssrc", + G_CALLBACK (gst_rtp_src_rtpbin_on_new_ssrc_cb), self); + g_signal_connect (self->rtpbin, "on-ssrc-collision", + G_CALLBACK (gst_rtp_src_rtpbin_on_ssrc_collision_cb), self); + + g_object_set (self->rtpbin, "latency", self->latency, NULL); + + /* Add elements as needed, since udpsrc/udpsink for RTCP share a socket, + * not all at the same moment */ + gst_bin_add (GST_BIN (self), self->rtpbin); + gst_bin_add (GST_BIN (self), self->rtp_src); + + g_object_set (self->rtp_src, + "address", gst_uri_get_host (self->uri), + "port", gst_uri_get_port (self->uri), NULL); + + gst_bin_add (GST_BIN (self), self->rtcp_sink); + + /* no need to set address if unicast */ + caps = gst_caps_new_empty_simple ("application/x-rtcp"); + g_object_set (self->rtcp_src, + "port", gst_uri_get_port (self->uri) + 1, "caps", caps, NULL); + gst_caps_unref (caps); + + addr = g_inet_address_new_from_string (gst_uri_get_host (self->uri)); + if (g_inet_address_get_is_multicast (addr)) { + g_object_set (self->rtcp_src, "address", gst_uri_get_host (self->uri), + NULL); + } + g_object_unref (addr); + + g_object_set (self->rtcp_sink, + "host", gst_uri_get_host (self->uri), + "port", gst_uri_get_port (self->uri) + 1, + "ttl", self->ttl, "ttl-mc", self->ttl_mc, + /* Set false since we're reusing a socket */ + "auto-multicast", FALSE, NULL); + + gst_bin_add (GST_BIN (self), self->rtcp_src); + + /* share the socket created by the source */ + g_object_get (G_OBJECT (self->rtcp_src), "used-socket", &socket, + "address", &address, "port", &rtcp_port, NULL); + + addr = g_inet_address_new_from_string (address); + g_free (address); + + if (g_inet_address_get_is_multicast (addr)) { + /* mc-ttl is not supported by dynudpsink */ + g_socket_set_multicast_ttl (socket, self->ttl_mc); + /* In multicast, send RTCP to the multicast group */ + self->rtcp_send_addr = g_inet_socket_address_new (addr, rtcp_port); + } else { + /* In unicast, send RTCP to the detected sender address */ + pad = gst_element_get_static_pad (self->rtcp_src, "src"); + self->rtcp_recv_probe = gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST, + gst_rtp_src_on_recv_rtcp, self, NULL); + gst_object_unref (pad); + } + g_object_unref (addr); + + pad = gst_element_get_static_pad (self->rtcp_sink, "sink"); + self->rtcp_send_probe = gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST, + gst_rtp_src_on_send_rtcp, self, NULL); + gst_object_unref (pad); + + g_object_set (G_OBJECT (self->rtcp_sink), "socket", socket, NULL); + + /* pads are all named */ + g_snprintf (name, 48, "recv_rtp_sink_%u", GST_ELEMENT (self)->numpads); + gst_element_link_pads (self->rtp_src, "src", self->rtpbin, name); + + g_snprintf (name, 48, "recv_rtcp_sink_%u", GST_ELEMENT (self)->numpads); + gst_element_link_pads (self->rtcp_src, "src", self->rtpbin, name); + + gst_element_sync_state_with_parent (self->rtpbin); + gst_element_sync_state_with_parent (self->rtp_src); + gst_element_sync_state_with_parent (self->rtcp_sink); + + g_snprintf (name, 48, "send_rtcp_src_%u", GST_ELEMENT (self)->numpads); + gst_element_link_pads (self->rtpbin, name, self->rtcp_sink, "sink"); + + gst_element_sync_state_with_parent (self->rtcp_src); + + return TRUE; +} + +static void +gst_rtp_src_stop (GstRtpSrc * self) +{ + GstPad *pad; + + if (self->rtcp_recv_probe) { + pad = gst_element_get_static_pad (self->rtcp_src, "src"); + gst_pad_remove_probe (pad, self->rtcp_recv_probe); + self->rtcp_recv_probe = 0; + gst_object_unref (pad); + } + + pad = gst_element_get_static_pad (self->rtcp_sink, "sink"); + gst_pad_remove_probe (pad, self->rtcp_send_probe); + self->rtcp_send_probe = 0; + gst_object_unref (pad); +} + +static GstStateChangeReturn +gst_rtp_src_change_state (GstElement * element, GstStateChange transition) +{ + GstRtpSrc *self = GST_RTP_SRC (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (self, "Changing state: %s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (gst_rtp_src_setup_elements (self) == FALSE) + return GST_STATE_CHANGE_FAILURE; + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + ret = GST_STATE_CHANGE_NO_PREROLL; + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + ret = GST_STATE_CHANGE_NO_PREROLL; + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_rtp_src_stop (self); + break; + default: + break; + } + + return ret; +} + +static void +gst_rtp_src_init (GstRtpSrc * self) +{ + self->rtpbin = NULL; + self->rtp_src = NULL; + self->rtcp_src = NULL; + self->rtcp_sink = NULL; + + self->uri = gst_uri_from_string (DEFAULT_PROP_URI); + self->ttl = DEFAULT_PROP_TTL; + self->ttl_mc = DEFAULT_PROP_TTL_MC; + self->encoding_name = DEFAULT_PROP_ENCODING_NAME; + self->latency = DEFAULT_PROP_LATENCY; + + GST_OBJECT_FLAG_SET (GST_OBJECT (self), GST_ELEMENT_FLAG_SOURCE); + gst_bin_set_suppressed_flags (GST_BIN (self), + GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK); + + g_mutex_init (&self->lock); +} + +static guint +gst_rtp_src_uri_get_type (GType type) +{ + return GST_URI_SRC; +} + +static const gchar *const * +gst_rtp_src_uri_get_protocols (GType type) +{ + static const gchar *protocols[] = { (char *) "rtp", NULL }; + + return protocols; +} + +static gchar * +gst_rtp_src_uri_get_uri (GstURIHandler * handler) +{ + GstRtpSrc *self = (GstRtpSrc *) handler; + + return gst_uri_to_string (self->uri); +} + +static gboolean +gst_rtp_src_uri_set_uri (GstURIHandler * handler, const gchar * uri, + GError ** error) +{ + GstRtpSrc *self = (GstRtpSrc *) handler; + + g_object_set (G_OBJECT (self), "uri", uri, NULL); + + return TRUE; +} + +static void +gst_rtp_src_uri_handler_init (gpointer g_iface, gpointer iface_data) +{ + GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; + + iface->get_type = gst_rtp_src_uri_get_type; + iface->get_protocols = gst_rtp_src_uri_get_protocols; + iface->get_uri = gst_rtp_src_uri_get_uri; + iface->set_uri = gst_rtp_src_uri_set_uri; +} + +/* ex: set tabstop=2 shiftwidth=2 expandtab: */ diff --git a/gst/rtp/gstrtpsrc.h b/gst/rtp/gstrtpsrc.h new file mode 100644 index 0000000000..4bc3535ef4 --- /dev/null +++ b/gst/rtp/gstrtpsrc.h @@ -0,0 +1,76 @@ +/* GStreamer + * Copyright (C) 2019 Marc Leeman + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_RTP_SRC_H__ +#define __GST_RTP_SRC_H__ + +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_RTP_SRC \ + (gst_rtp_src_get_type()) +#define GST_RTP_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_RTP_SRC, GstRtpSrc)) +#define GST_RTP_SRC_CAST(obj) \ + ((GstRtpSrc *) obj) +#define GST_RTP_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_RTP_SRC, GstRtpSrcClass)) +#define GST_IS_RTP_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_RTP_SRC)) +#define GST_IS_RTP_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_RTP_SRC)) + +typedef struct _GstRtpSrc GstRtpSrc; +typedef struct _GstRtpSrcClass GstRtpSrcClass; + +struct _GstRtpSrc +{ + GstBin parent; + + /* Properties */ + GstUri *uri; + gint ttl; + gint ttl_mc; + gint latency; + gchar *encoding_name; + guint latency_ms; + + /* Internal elements */ + GstElement *rtpbin; + GstElement *rtp_src; + GstElement *rtcp_src; + GstElement *rtcp_sink; + + gulong rtcp_recv_probe; + gulong rtcp_send_probe; + GSocketAddress *rtcp_send_addr; + + GMutex lock; +}; + +struct _GstRtpSrcClass +{ + GstBinClass parent; +}; + +GType gst_rtp_src_get_type (void); + +G_END_DECLS +#endif /* __GST_RTP_SRC_H__ */ diff --git a/gst/rtp/meson.build b/gst/rtp/meson.build new file mode 100644 index 0000000000..bb21dae5ba --- /dev/null +++ b/gst/rtp/meson.build @@ -0,0 +1,15 @@ +gst_plugins_rtp_sources = [ + 'plugin.c', + 'gstrtpsink.c', + 'gstrtpsrc.c', + 'gstrtp-utils.c', +] + +gstrtp = library('gstrtpmanagerbad', + gst_plugins_rtp_sources, + dependencies: [gio_dep, gst_dep, gstbase_dep, gstrtp_dep, gstnet_dep, gstcontroller_dep], + include_directories: [configinc], + install: true, + c_args: gst_plugins_bad_args, + install_dir: plugins_install_dir, +) diff --git a/gst/rtp/plugin.c b/gst/rtp/plugin.c new file mode 100644 index 0000000000..8c1d71f8b6 --- /dev/null +++ b/gst/rtp/plugin.c @@ -0,0 +1,28 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstrtpsink.h" +#include "gstrtpsrc.h" + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + + gboolean ret = FALSE; + + ret |= gst_element_register (plugin, "rtpsrc", + GST_RANK_PRIMARY + 1, GST_TYPE_RTP_SRC); + + ret |= gst_element_register (plugin, "rtpsink", + GST_RANK_PRIMARY + 1, GST_TYPE_RTP_SINK); + + return ret; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + rtpmanagerbad, + "GStreamer RTP Plugins", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/meson_options.txt b/meson_options.txt index 111679ec9a..0c30c253d6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -49,6 +49,7 @@ option('proxy', type : 'feature', value : 'auto') option('rawparse', type : 'feature', value : 'auto') option('removesilence', type : 'feature', value : 'auto') option('rist', type : 'feature', value : 'auto') +option('rtp', type : 'feature', value : 'auto') option('sdp', type : 'feature', value : 'auto') option('segmentclip', type : 'feature', value : 'auto') option('siren', type : 'feature', value : 'auto') diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index a05bc5792a..dbc746fd76 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -290,6 +290,8 @@ check_PROGRAMS = \ elements/pnm \ elements/rtponvifparse \ elements/rtponviftimestamp \ + elements/rtpsrc \ + elements/rtpsink \ elements/id3mux \ pipelines/mxf \ libs/isoff \ @@ -578,6 +580,12 @@ elements_rtponvifparse_LDADD = $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) $(GST_L elements_rtponviftimestamp_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(AM_CFLAGS) elements_rtponviftimestamp_LDADD = $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) $(GST_LIBS) -lgstrtp-$(GST_API_VERSION) $(LDADD) +elements_rtpsrc_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(AM_CFLAGS) +elements_rtpsrc_LDADD = $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) $(GST_LIBS) -lgstrtp-$(GST_API_VERSION) $(LDADD) + +elements_rtpsink_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(AM_CFLAGS) +elements_rtpsink_LDADD = $(GST_PLUGINS_BASE_LIBS) $(GST_BASE_LIBS) $(GST_LIBS) -lgstrtp-$(GST_API_VERSION) $(LDADD) + EXTRA_DIST = gst-plugins-bad.supp $(uvch264_dist_data) orc_bayer_CFLAGS = $(ORC_CFLAGS) diff --git a/tests/check/elements/.gitignore b/tests/check/elements/.gitignore index b7e836f4af..ca99cecfa9 100644 --- a/tests/check/elements/.gitignore +++ b/tests/check/elements/.gitignore @@ -49,6 +49,8 @@ pcapparse pnm rtponvifparse rtponviftimestamp +rtpsrc +rtpsink shm srtp templatematch diff --git a/tests/check/elements/rtpsink.c b/tests/check/elements/rtpsink.c new file mode 100644 index 0000000000..bb6c5b30df --- /dev/null +++ b/tests/check/elements/rtpsink.c @@ -0,0 +1,56 @@ +/* GStreamer + * Copyright (C) <2018> Marc Leeman + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +GST_START_TEST (test_uri_to_properties) +{ + GstElement *rtpsink; + + gint ttl, ttl_mc; + + rtpsink = gst_element_factory_make ("rtpsink", NULL); + + /* Sets properties to non-default values (make sure this stays in sync) */ + g_object_set (rtpsink, "uri", "rtp://1.230.1.2?" "ttl=8" "&ttl-mc=9", NULL); + + g_object_get (rtpsink, "ttl", &ttl, "ttl_mc", &ttl_mc, NULL); + + /* Make sure these values are in sync with the one from the URI. */ + g_assert_cmpint (ttl, ==, 8); + g_assert_cmpint (ttl_mc, ==, 9); + + gst_object_unref (rtpsink); +} + +GST_END_TEST; + +static Suite * +rtpsink_suite (void) +{ + Suite *s = suite_create ("rtpsink"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_uri_to_properties); + + return s; +} + +GST_CHECK_MAIN (rtpsink); diff --git a/tests/check/elements/rtpsrc.c b/tests/check/elements/rtpsrc.c new file mode 100644 index 0000000000..6e1f1327c7 --- /dev/null +++ b/tests/check/elements/rtpsrc.c @@ -0,0 +1,58 @@ +/* GStreamer + * Copyright (C) <2018> Marc Leeman + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +GST_START_TEST (test_uri_to_properties) +{ + GstElement *rtpsrc; + guint latency, ttl, ttl_mc; + + rtpsrc = gst_element_factory_make ("rtpsrc", NULL); + + /* Sets properties to non-default values (make sure this stays in sync) */ + g_object_set (rtpsrc, "uri", "rtp://1.230.1.2?" + "latency=300" "&ttl=8" "&ttl-mc=9", NULL); + + g_object_get (rtpsrc, + "latency", &latency, "ttl-mc", &ttl_mc, "ttl", &ttl, NULL); + + /* Make sure these values are in sync with the one from the URI. */ + g_assert_cmpuint (latency, ==, 300); + g_assert_cmpint (ttl, ==, 8); + g_assert_cmpint (ttl_mc, ==, 9); + + gst_object_unref (rtpsrc); +} + +GST_END_TEST; + +static Suite * +rtpsrc_suite (void) +{ + Suite *s = suite_create ("rtpsrc"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_uri_to_properties); + + return s; +} + +GST_CHECK_MAIN (rtpsrc); diff --git a/tests/check/meson.build b/tests/check/meson.build index cd77fb4b39..5eb401d425 100644 --- a/tests/check/meson.build +++ b/tests/check/meson.build @@ -47,6 +47,8 @@ base_tests = [ [['elements/pnm.c']], [['elements/rtponvifparse.c']], [['elements/rtponviftimestamp.c']], + [['elements/rtpsrc.c']], + [['elements/rtpsink.c']], [['elements/videoframe-audiolevel.c']], [['elements/viewfinderbin.c']], [['libs/h264parser.c'], false, [gstcodecparsers_dep]],