/* GStreamer * Copyright (C) <2005,2006> Wim Taymans * <2006> Lutz Mueller * * 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. */ /* * Unless otherwise indicated, Source Code is licensed under MIT license. * See further explanation attached in License Statement (distributed in the file * LICENSE). * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * SECTION:element-rtspsrc * * Makes a connection to an RTSP server and read the data. * rtspsrc strictly follows RFC 2326 and therefore does not (yet) support * RealMedia/Quicktime/Microsoft extensions. * * RTSP supports transport over TCP or UDP in unicast or multicast mode. By * default rtspsrc will negotiate a connection in the following order: * UDP unicast/UDP multicast/TCP. The order cannot be changed but the allowed * protocols can be controlled with the #GstRTSPSrc:protocols property. * * rtspsrc currently understands SDP as the format of the session description. * For each stream listed in the SDP a new rtp_stream\%d pad will be created * with caps derived from the SDP media description. This is a caps of mime type * "application/x-rtp" that can be connected to any available RTP depayloader * element. * * rtspsrc will internally instantiate an RTP session manager element * that will handle the RTCP messages to and from the server, jitter removal, * packet reordering along with providing a clock for the pipeline. * This feature is implemented using the gstrtpbin element. * * rtspsrc acts like a live source and will therefore only generate data in the * PLAYING state. * * * Example launch line * |[ * gst-launch-1.0 rtspsrc location=rtsp://some.server/url ! fakesink * ]| Establish a connection to an RTSP server and send the raw RTP packets to a * fakesink. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_UNISTD_H #include #endif /* HAVE_UNISTD_H */ #include #include #include #include #include #include #include #include #include "gst/gst-i18n-plugin.h" #include "gstrtspsrc.h" GST_DEBUG_CATEGORY_STATIC (rtspsrc_debug); #define GST_CAT_DEFAULT (rtspsrc_debug) static GstStaticPadTemplate rtptemplate = GST_STATIC_PAD_TEMPLATE ("stream_%u", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS ("application/x-rtp; application/x-rdt")); /* templates used internally */ static GstStaticPadTemplate anysrctemplate = GST_STATIC_PAD_TEMPLATE ("internalsrc_%u", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate anysinktemplate = GST_STATIC_PAD_TEMPLATE ("internalsink_%u", GST_PAD_SINK, GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); enum { SIGNAL_HANDLE_REQUEST, SIGNAL_ON_SDP, SIGNAL_SELECT_STREAM, SIGNAL_NEW_MANAGER, SIGNAL_REQUEST_RTCP_KEY, LAST_SIGNAL }; enum _GstRtspSrcRtcpSyncMode { RTCP_SYNC_ALWAYS, RTCP_SYNC_INITIAL, RTCP_SYNC_RTP }; enum _GstRtspSrcBufferMode { BUFFER_MODE_NONE, BUFFER_MODE_SLAVE, BUFFER_MODE_BUFFER, BUFFER_MODE_AUTO, BUFFER_MODE_SYNCED }; #define GST_TYPE_RTSP_SRC_BUFFER_MODE (gst_rtsp_src_buffer_mode_get_type()) static GType gst_rtsp_src_buffer_mode_get_type (void) { static GType buffer_mode_type = 0; static const GEnumValue buffer_modes[] = { {BUFFER_MODE_NONE, "Only use RTP timestamps", "none"}, {BUFFER_MODE_SLAVE, "Slave receiver to sender clock", "slave"}, {BUFFER_MODE_BUFFER, "Do low/high watermark buffering", "buffer"}, {BUFFER_MODE_AUTO, "Choose mode depending on stream live", "auto"}, {BUFFER_MODE_SYNCED, "Synchronized sender and receiver clocks", "synced"}, {0, NULL, NULL}, }; if (!buffer_mode_type) { buffer_mode_type = g_enum_register_static ("GstRTSPSrcBufferMode", buffer_modes); } return buffer_mode_type; } enum _GstRtspSrcNtpTimeSource { NTP_TIME_SOURCE_NTP, NTP_TIME_SOURCE_UNIX, NTP_TIME_SOURCE_RUNNING_TIME, NTP_TIME_SOURCE_CLOCK_TIME }; #define GST_TYPE_RTSP_SRC_NTP_TIME_SOURCE (gst_rtsp_src_ntp_time_source_get_type()) static GType gst_rtsp_src_ntp_time_source_get_type (void) { static GType ntp_time_source_type = 0; static const GEnumValue ntp_time_source_values[] = { {NTP_TIME_SOURCE_NTP, "NTP time based on realtime clock", "ntp"}, {NTP_TIME_SOURCE_UNIX, "UNIX time based on realtime clock", "unix"}, {NTP_TIME_SOURCE_RUNNING_TIME, "Running time based on pipeline clock", "running-time"}, {NTP_TIME_SOURCE_CLOCK_TIME, "Pipeline clock time", "clock-time"}, {0, NULL, NULL}, }; if (!ntp_time_source_type) { ntp_time_source_type = g_enum_register_static ("GstRTSPSrcNtpTimeSource", ntp_time_source_values); } return ntp_time_source_type; } #define DEFAULT_LOCATION NULL #define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP #define DEFAULT_DEBUG FALSE #define DEFAULT_RETRY 20 #define DEFAULT_TIMEOUT 5000000 #define DEFAULT_UDP_BUFFER_SIZE 0x80000 #define DEFAULT_TCP_TIMEOUT 20000000 #define DEFAULT_LATENCY_MS 2000 #define DEFAULT_DROP_ON_LATENCY FALSE #define DEFAULT_CONNECTION_SPEED 0 #define DEFAULT_NAT_METHOD GST_RTSP_NAT_DUMMY #define DEFAULT_DO_RTCP TRUE #define DEFAULT_DO_RTSP_KEEP_ALIVE TRUE #define DEFAULT_PROXY NULL #define DEFAULT_RTP_BLOCKSIZE 0 #define DEFAULT_USER_ID NULL #define DEFAULT_USER_PW NULL #define DEFAULT_BUFFER_MODE BUFFER_MODE_AUTO #define DEFAULT_PORT_RANGE NULL #define DEFAULT_SHORT_HEADER FALSE #define DEFAULT_PROBATION 2 #define DEFAULT_UDP_RECONNECT TRUE #define DEFAULT_MULTICAST_IFACE NULL #define DEFAULT_NTP_SYNC FALSE #define DEFAULT_USE_PIPELINE_CLOCK FALSE #define DEFAULT_TLS_VALIDATION_FLAGS G_TLS_CERTIFICATE_VALIDATE_ALL #define DEFAULT_TLS_DATABASE NULL #define DEFAULT_TLS_INTERACTION NULL #define DEFAULT_DO_RETRANSMISSION TRUE #define DEFAULT_NTP_TIME_SOURCE NTP_TIME_SOURCE_NTP #define DEFAULT_USER_AGENT "GStreamer/" PACKAGE_VERSION #define DEFAULT_MAX_RTCP_RTP_TIME_DIFF 1000 #define DEFAULT_RFC7273_SYNC FALSE enum { PROP_0, PROP_LOCATION, PROP_PROTOCOLS, PROP_DEBUG, PROP_RETRY, PROP_TIMEOUT, PROP_TCP_TIMEOUT, PROP_LATENCY, PROP_DROP_ON_LATENCY, PROP_CONNECTION_SPEED, PROP_NAT_METHOD, PROP_DO_RTCP, PROP_DO_RTSP_KEEP_ALIVE, PROP_PROXY, PROP_PROXY_ID, PROP_PROXY_PW, PROP_RTP_BLOCKSIZE, PROP_USER_ID, PROP_USER_PW, PROP_BUFFER_MODE, PROP_PORT_RANGE, PROP_UDP_BUFFER_SIZE, PROP_SHORT_HEADER, PROP_PROBATION, PROP_UDP_RECONNECT, PROP_MULTICAST_IFACE, PROP_NTP_SYNC, PROP_USE_PIPELINE_CLOCK, PROP_SDES, PROP_TLS_VALIDATION_FLAGS, PROP_TLS_DATABASE, PROP_TLS_INTERACTION, PROP_DO_RETRANSMISSION, PROP_NTP_TIME_SOURCE, PROP_USER_AGENT, PROP_MAX_RTCP_RTP_TIME_DIFF, PROP_RFC7273_SYNC }; #define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type()) static GType gst_rtsp_nat_method_get_type (void) { static GType rtsp_nat_method_type = 0; static const GEnumValue rtsp_nat_method[] = { {GST_RTSP_NAT_NONE, "None", "none"}, {GST_RTSP_NAT_DUMMY, "Send Dummy packets", "dummy"}, {0, NULL, NULL}, }; if (!rtsp_nat_method_type) { rtsp_nat_method_type = g_enum_register_static ("GstRTSPNatMethod", rtsp_nat_method); } return rtsp_nat_method_type; } static void gst_rtspsrc_finalize (GObject * object); static void gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static GstClock *gst_rtspsrc_provide_clock (GstElement * element); static void gst_rtspsrc_uri_handler_init (gpointer g_iface, gpointer iface_data); static gboolean gst_rtspsrc_set_proxy (GstRTSPSrc * rtsp, const gchar * proxy); static void gst_rtspsrc_set_tcp_timeout (GstRTSPSrc * rtspsrc, guint64 timeout); static GstStateChangeReturn gst_rtspsrc_change_state (GstElement * element, GstStateChange transition); static gboolean gst_rtspsrc_send_event (GstElement * element, GstEvent * event); static void gst_rtspsrc_handle_message (GstBin * bin, GstMessage * message); static gboolean gst_rtspsrc_setup_auth (GstRTSPSrc * src, GstRTSPMessage * response); static gboolean gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd, gint mask); static GstRTSPResult gst_rtspsrc_send_cb (GstRTSPExtension * ext, GstRTSPMessage * request, GstRTSPMessage * response, GstRTSPSrc * src); static GstRTSPResult gst_rtspsrc_open (GstRTSPSrc * src, gboolean async); static GstRTSPResult gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async); static GstRTSPResult gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async); static GstRTSPResult gst_rtspsrc_close (GstRTSPSrc * src, gboolean async, gboolean only_close); static gboolean gst_rtspsrc_uri_set_uri (GstURIHandler * handler, const gchar * uri, GError ** error); static gchar *gst_rtspsrc_uri_get_uri (GstURIHandler * handler); static gboolean gst_rtspsrc_activate_streams (GstRTSPSrc * src); static gboolean gst_rtspsrc_loop (GstRTSPSrc * src); static gboolean gst_rtspsrc_stream_push_event (GstRTSPSrc * src, GstRTSPStream * stream, GstEvent * event); static gboolean gst_rtspsrc_push_event (GstRTSPSrc * src, GstEvent * event); static void gst_rtspsrc_connection_flush (GstRTSPSrc * src, gboolean flush); static GstRTSPResult gst_rtsp_conninfo_close (GstRTSPSrc * src, GstRTSPConnInfo * info, gboolean free); typedef struct { guint8 pt; GstCaps *caps; } PtMapItem; /* commands we send to out loop to notify it of events */ #define CMD_OPEN (1 << 0) #define CMD_PLAY (1 << 1) #define CMD_PAUSE (1 << 2) #define CMD_CLOSE (1 << 3) #define CMD_WAIT (1 << 4) #define CMD_RECONNECT (1 << 5) #define CMD_LOOP (1 << 6) /* mask for all commands */ #define CMD_ALL ((CMD_LOOP << 1) - 1) #define GST_ELEMENT_PROGRESS(el, type, code, text) \ G_STMT_START { \ gchar *__txt = _gst_element_error_printf text; \ gst_element_post_message (GST_ELEMENT_CAST (el), \ gst_message_new_progress (GST_OBJECT_CAST (el), \ GST_PROGRESS_TYPE_ ##type, code, __txt)); \ g_free (__txt); \ } G_STMT_END static guint gst_rtspsrc_signals[LAST_SIGNAL] = { 0 }; #define gst_rtspsrc_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstRTSPSrc, gst_rtspsrc, GST_TYPE_BIN, G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_rtspsrc_uri_handler_init)); #ifndef GST_DISABLE_GST_DEBUG static inline const char * cmd_to_string (guint cmd) { switch (cmd) { case CMD_OPEN: return "OPEN"; case CMD_PLAY: return "PLAY"; case CMD_PAUSE: return "PAUSE"; case CMD_CLOSE: return "CLOSE"; case CMD_WAIT: return "WAIT"; case CMD_RECONNECT: return "RECONNECT"; case CMD_LOOP: return "LOOP"; } return "unknown"; } #endif static gboolean default_select_stream (GstRTSPSrc * src, guint id, GstCaps * caps) { GST_DEBUG_OBJECT (src, "default handler"); return TRUE; } static gboolean select_stream_accum (GSignalInvocationHint * ihint, GValue * return_accu, const GValue * handler_return, gpointer data) { gboolean myboolean; myboolean = g_value_get_boolean (handler_return); GST_DEBUG ("accum %d", myboolean); g_value_set_boolean (return_accu, myboolean); /* stop emission if FALSE */ return myboolean; } static void gst_rtspsrc_class_init (GstRTSPSrcClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; GstBinClass *gstbin_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gstbin_class = (GstBinClass *) klass; GST_DEBUG_CATEGORY_INIT (rtspsrc_debug, "rtspsrc", 0, "RTSP src"); gobject_class->set_property = gst_rtspsrc_set_property; gobject_class->get_property = gst_rtspsrc_get_property; gobject_class->finalize = gst_rtspsrc_finalize; g_object_class_install_property (gobject_class, PROP_LOCATION, g_param_spec_string ("location", "RTSP Location", "Location of the RTSP url to read", DEFAULT_LOCATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PROTOCOLS, g_param_spec_flags ("protocols", "Protocols", "Allowed lower transport protocols", GST_TYPE_RTSP_LOWER_TRANS, DEFAULT_PROTOCOLS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_DEBUG, g_param_spec_boolean ("debug", "Debug", "Dump request and response messages to stdout", DEFAULT_DEBUG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_RETRY, g_param_spec_uint ("retry", "Retry", "Max number of retries when allocating RTP ports.", 0, G_MAXUINT16, DEFAULT_RETRY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_TIMEOUT, g_param_spec_uint64 ("timeout", "Timeout", "Retry TCP transport after UDP timeout microseconds (0 = disabled)", 0, G_MAXUINT64, DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_TCP_TIMEOUT, g_param_spec_uint64 ("tcp-timeout", "TCP Timeout", "Fail after timeout microseconds on TCP connections (0 = disabled)", 0, G_MAXUINT64, DEFAULT_TCP_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_LATENCY, g_param_spec_uint ("latency", "Buffer latency in ms", "Amount of ms to buffer", 0, G_MAXUINT, DEFAULT_LATENCY_MS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_DROP_ON_LATENCY, g_param_spec_boolean ("drop-on-latency", "Drop buffers when maximum latency is reached", "Tells the jitterbuffer to never exceed the given latency in size", DEFAULT_DROP_ON_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED, g_param_spec_uint64 ("connection-speed", "Connection Speed", "Network connection speed in kbps (0 = unknown)", 0, G_MAXUINT64 / 1000, DEFAULT_CONNECTION_SPEED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_NAT_METHOD, g_param_spec_enum ("nat-method", "NAT Method", "Method to use for traversing firewalls and NAT", GST_TYPE_RTSP_NAT_METHOD, DEFAULT_NAT_METHOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc:do-rtcp: * * Enable RTCP support. Some old server don't like RTCP and then this property * needs to be set to FALSE. */ g_object_class_install_property (gobject_class, PROP_DO_RTCP, g_param_spec_boolean ("do-rtcp", "Do RTCP", "Send RTCP packets, disable for old incompatible server.", DEFAULT_DO_RTCP, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc:do-rtsp-keep-alive: * * Enable RTSP keep alive support. Some old server don't like RTSP * keep alive and then this property needs to be set to FALSE. */ g_object_class_install_property (gobject_class, PROP_DO_RTSP_KEEP_ALIVE, g_param_spec_boolean ("do-rtsp-keep-alive", "Do RTSP Keep Alive", "Send RTSP keep alive packets, disable for old incompatible server.", DEFAULT_DO_RTSP_KEEP_ALIVE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc:proxy: * * Set the proxy parameters. This has to be a string of the format * [http://][user:passwd@]host[:port]. */ g_object_class_install_property (gobject_class, PROP_PROXY, g_param_spec_string ("proxy", "Proxy", "Proxy settings for HTTP tunneling. Format: [http://][user:passwd@]host[:port]", DEFAULT_PROXY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc:proxy-id: * * Sets the proxy URI user id for authentication. If the URI set via the * "proxy" property contains a user-id already, that will take precedence. * * Since: 1.2 */ g_object_class_install_property (gobject_class, PROP_PROXY_ID, g_param_spec_string ("proxy-id", "proxy-id", "HTTP proxy URI user id for authentication", "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc:proxy-pw: * * Sets the proxy URI password for authentication. If the URI set via the * "proxy" property contains a password already, that will take precedence. * * Since: 1.2 */ g_object_class_install_property (gobject_class, PROP_PROXY_PW, g_param_spec_string ("proxy-pw", "proxy-pw", "HTTP proxy URI user password for authentication", "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc:rtp-blocksize: * * RTP package size to suggest to server. */ g_object_class_install_property (gobject_class, PROP_RTP_BLOCKSIZE, g_param_spec_uint ("rtp-blocksize", "RTP Blocksize", "RTP package size to suggest to server (0 = disabled)", 0, 65536, DEFAULT_RTP_BLOCKSIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_USER_ID, g_param_spec_string ("user-id", "user-id", "RTSP location URI user id for authentication", DEFAULT_USER_ID, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_USER_PW, g_param_spec_string ("user-pw", "user-pw", "RTSP location URI user password for authentication", DEFAULT_USER_PW, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc:buffer-mode: * * Control the buffering and timestamping mode used by the jitterbuffer. */ g_object_class_install_property (gobject_class, PROP_BUFFER_MODE, g_param_spec_enum ("buffer-mode", "Buffer Mode", "Control the buffering algorithm in use", GST_TYPE_RTSP_SRC_BUFFER_MODE, DEFAULT_BUFFER_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc:port-range: * * Configure the client port numbers that can be used to recieve RTP and * RTCP. */ g_object_class_install_property (gobject_class, PROP_PORT_RANGE, g_param_spec_string ("port-range", "Port range", "Client port range that can be used to receive RTP and RTCP data, " "eg. 3000-3005 (NULL = no restrictions)", DEFAULT_PORT_RANGE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc:udp-buffer-size: * * Size of the kernel UDP receive buffer in bytes. */ g_object_class_install_property (gobject_class, PROP_UDP_BUFFER_SIZE, g_param_spec_int ("udp-buffer-size", "UDP Buffer Size", "Size of the kernel UDP receive buffer in bytes, 0=default", 0, G_MAXINT, DEFAULT_UDP_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc:short-header: * * Only send the basic RTSP headers for broken encoders. */ g_object_class_install_property (gobject_class, PROP_SHORT_HEADER, g_param_spec_boolean ("short-header", "Short Header", "Only send the basic RTSP headers for broken encoders", DEFAULT_SHORT_HEADER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PROBATION, g_param_spec_uint ("probation", "Number of probations", "Consecutive packet sequence numbers to accept the source", 0, G_MAXUINT, DEFAULT_PROBATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_UDP_RECONNECT, g_param_spec_boolean ("udp-reconnect", "Reconnect to the server", "Reconnect to the server if RTSP connection is closed when doing UDP", DEFAULT_UDP_RECONNECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_MULTICAST_IFACE, g_param_spec_string ("multicast-iface", "Multicast Interface", "The network interface on which to join the multicast group", DEFAULT_MULTICAST_IFACE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_NTP_SYNC, g_param_spec_boolean ("ntp-sync", "Sync on NTP clock", "Synchronize received streams to the NTP clock", DEFAULT_NTP_SYNC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_USE_PIPELINE_CLOCK, g_param_spec_boolean ("use-pipeline-clock", "Use pipeline clock", "Use the pipeline running-time to set the NTP time in the RTCP SR messages" "(DEPRECATED: Use ntp-time-source property)", DEFAULT_USE_PIPELINE_CLOCK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); g_object_class_install_property (gobject_class, PROP_SDES, g_param_spec_boxed ("sdes", "SDES", "The SDES items of this session", GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc::tls-validation-flags: * * TLS certificate validation flags used to validate server * certificate. * * Since: 1.2.1 */ g_object_class_install_property (gobject_class, PROP_TLS_VALIDATION_FLAGS, g_param_spec_flags ("tls-validation-flags", "TLS validation flags", "TLS certificate validation flags used to validate the server certificate", G_TYPE_TLS_CERTIFICATE_FLAGS, DEFAULT_TLS_VALIDATION_FLAGS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc::tls-database: * * TLS database with anchor certificate authorities used to validate * the server certificate. * * Since: 1.4 */ g_object_class_install_property (gobject_class, PROP_TLS_DATABASE, g_param_spec_object ("tls-database", "TLS database", "TLS database with anchor certificate authorities used to validate the server certificate", G_TYPE_TLS_DATABASE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc::tls-interaction: * * A #GTlsInteraction object to be used when the connection or certificate * database need to interact with the user. This will be used to prompt the * user for passwords where necessary. * * Since: 1.6 */ g_object_class_install_property (gobject_class, PROP_TLS_INTERACTION, g_param_spec_object ("tls-interaction", "TLS interaction", "A GTlsInteraction object to promt the user for password or certificate", G_TYPE_TLS_INTERACTION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc::do-retransmission: * * Attempt to ask the server to retransmit lost packets according to RFC4588. * * Note: currently only works with SSRC-multiplexed retransmission streams * * Since: 1.6 */ g_object_class_install_property (gobject_class, PROP_DO_RETRANSMISSION, g_param_spec_boolean ("do-retransmission", "Retransmission", "Ask the server to retransmit lost packets", DEFAULT_DO_RETRANSMISSION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc::ntp-time-source: * * allows to select the time source that should be used * for the NTP time in RTCP packets * * Since: 1.6 */ g_object_class_install_property (gobject_class, PROP_NTP_TIME_SOURCE, g_param_spec_enum ("ntp-time-source", "NTP Time Source", "NTP time source for RTCP packets", GST_TYPE_RTSP_SRC_NTP_TIME_SOURCE, DEFAULT_NTP_TIME_SOURCE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc::user-agent: * * The string to set in the User-Agent header. * * Since: 1.6 */ g_object_class_install_property (gobject_class, PROP_USER_AGENT, g_param_spec_string ("user-agent", "User Agent", "The User-Agent string to send to the server", DEFAULT_USER_AGENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_MAX_RTCP_RTP_TIME_DIFF, g_param_spec_int ("max-rtcp-rtp-time-diff", "Max RTCP RTP Time Diff", "Maximum amount of time in ms that the RTP time in RTCP SRs " "is allowed to be ahead (-1 disabled)", -1, G_MAXINT, DEFAULT_MAX_RTCP_RTP_TIME_DIFF, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_RFC7273_SYNC, g_param_spec_boolean ("rfc7273-sync", "Sync on RFC7273 clock", "Synchronize received streams to the RFC7273 clock " "(requires clock and offset to be provided)", DEFAULT_RFC7273_SYNC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRTSPSrc::handle-request: * @rtspsrc: a #GstRTSPSrc * @request: a #GstRTSPMessage * @response: a #GstRTSPMessage * * Handle a server request in @request and prepare @response. * * This signal is called from the streaming thread, you should therefore not * do any state changes on @rtspsrc because this might deadlock. If you want * to modify the state as a result of this signal, post a * #GST_MESSAGE_REQUEST_STATE message on the bus or signal the main thread * in some other way. * * Since: 1.2 */ gst_rtspsrc_signals[SIGNAL_HANDLE_REQUEST] = g_signal_new ("handle-request", G_TYPE_FROM_CLASS (klass), 0, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER); /** * GstRTSPSrc::on-sdp: * @rtspsrc: a #GstRTSPSrc * @sdp: a #GstSDPMessage * * Emited when the client has retrieved the SDP and before it configures the * streams in the SDP. @sdp can be inspected and modified. * * This signal is called from the streaming thread, you should therefore not * do any state changes on @rtspsrc because this might deadlock. If you want * to modify the state as a result of this signal, post a * #GST_MESSAGE_REQUEST_STATE message on the bus or signal the main thread * in some other way. * * Since: 1.2 */ gst_rtspsrc_signals[SIGNAL_ON_SDP] = g_signal_new ("on-sdp", G_TYPE_FROM_CLASS (klass), 0, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_SDP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE); /** * GstRTSPSrc::select-stream: * @rtspsrc: a #GstRTSPSrc * @num: the stream number * @caps: the stream caps * * Emited before the client decides to configure the stream @num with * @caps. * * Returns: %TRUE when the stream should be selected, %FALSE when the stream * is to be ignored. * * Since: 1.2 */ gst_rtspsrc_signals[SIGNAL_SELECT_STREAM] = g_signal_new_class_handler ("select-stream", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_CLEANUP, (GCallback) default_select_stream, select_stream_accum, NULL, g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 2, G_TYPE_UINT, GST_TYPE_CAPS); /** * GstRTSPSrc::new-manager: * @rtspsrc: a #GstRTSPSrc * @manager: a #GstElement * * Emited after a new manager (like rtpbin) was created and the default * properties were configured. * * Since: 1.4 */ gst_rtspsrc_signals[SIGNAL_NEW_MANAGER] = g_signal_new_class_handler ("new-manager", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_CLEANUP, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, GST_TYPE_ELEMENT); /** * GstRTSPSrc::request-rtcp-key: * @rtspsrc: a #GstRTSPSrc * @num: the stream number * * Signal emited to get the crypto parameters relevant to the RTCP * stream. User should provide the key and the RTCP encryption ciphers * and authentication, and return them wrapped in a GstCaps. * * Since: 1.4 */ gst_rtspsrc_signals[SIGNAL_REQUEST_RTCP_KEY] = g_signal_new ("request-rtcp-key", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT); gstelement_class->send_event = gst_rtspsrc_send_event; gstelement_class->provide_clock = gst_rtspsrc_provide_clock; gstelement_class->change_state = gst_rtspsrc_change_state; gst_element_class_add_static_pad_template (gstelement_class, &rtptemplate); gst_element_class_set_static_metadata (gstelement_class, "RTSP packet receiver", "Source/Network", "Receive data over the network via RTSP (RFC 2326)", "Wim Taymans , " "Thijs Vermeir , " "Lutz Mueller "); gstbin_class->handle_message = gst_rtspsrc_handle_message; gst_rtsp_ext_list_init (); } static void gst_rtspsrc_init (GstRTSPSrc * src) { src->conninfo.location = g_strdup (DEFAULT_LOCATION); src->protocols = DEFAULT_PROTOCOLS; src->debug = DEFAULT_DEBUG; src->retry = DEFAULT_RETRY; src->udp_timeout = DEFAULT_TIMEOUT; gst_rtspsrc_set_tcp_timeout (src, DEFAULT_TCP_TIMEOUT); src->latency = DEFAULT_LATENCY_MS; src->drop_on_latency = DEFAULT_DROP_ON_LATENCY; src->connection_speed = DEFAULT_CONNECTION_SPEED; src->nat_method = DEFAULT_NAT_METHOD; src->do_rtcp = DEFAULT_DO_RTCP; src->do_rtsp_keep_alive = DEFAULT_DO_RTSP_KEEP_ALIVE; gst_rtspsrc_set_proxy (src, DEFAULT_PROXY); src->rtp_blocksize = DEFAULT_RTP_BLOCKSIZE; src->user_id = g_strdup (DEFAULT_USER_ID); src->user_pw = g_strdup (DEFAULT_USER_PW); src->buffer_mode = DEFAULT_BUFFER_MODE; src->client_port_range.min = 0; src->client_port_range.max = 0; src->udp_buffer_size = DEFAULT_UDP_BUFFER_SIZE; src->short_header = DEFAULT_SHORT_HEADER; src->probation = DEFAULT_PROBATION; src->udp_reconnect = DEFAULT_UDP_RECONNECT; src->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE); src->ntp_sync = DEFAULT_NTP_SYNC; src->use_pipeline_clock = DEFAULT_USE_PIPELINE_CLOCK; src->sdes = NULL; src->tls_validation_flags = DEFAULT_TLS_VALIDATION_FLAGS; src->tls_database = DEFAULT_TLS_DATABASE; src->tls_interaction = DEFAULT_TLS_INTERACTION; src->do_retransmission = DEFAULT_DO_RETRANSMISSION; src->ntp_time_source = DEFAULT_NTP_TIME_SOURCE; src->user_agent = g_strdup (DEFAULT_USER_AGENT); src->max_rtcp_rtp_time_diff = DEFAULT_MAX_RTCP_RTP_TIME_DIFF; src->rfc7273_sync = DEFAULT_RFC7273_SYNC; /* get a list of all extensions */ src->extensions = gst_rtsp_ext_list_get (); /* connect to send signal */ gst_rtsp_ext_list_connect (src->extensions, "send", (GCallback) gst_rtspsrc_send_cb, src); /* protects the streaming thread in interleaved mode or the polling * thread in UDP mode. */ g_rec_mutex_init (&src->stream_rec_lock); /* protects our state changes from multiple invocations */ g_rec_mutex_init (&src->state_rec_lock); src->state = GST_RTSP_STATE_INVALID; GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_SOURCE); } static void gst_rtspsrc_finalize (GObject * object) { GstRTSPSrc *rtspsrc; rtspsrc = GST_RTSPSRC (object); gst_rtsp_ext_list_free (rtspsrc->extensions); g_free (rtspsrc->conninfo.location); gst_rtsp_url_free (rtspsrc->conninfo.url); g_free (rtspsrc->conninfo.url_str); g_free (rtspsrc->user_id); g_free (rtspsrc->user_pw); g_free (rtspsrc->multi_iface); g_free (rtspsrc->user_agent); if (rtspsrc->sdp) { gst_sdp_message_free (rtspsrc->sdp); rtspsrc->sdp = NULL; } if (rtspsrc->provided_clock) gst_object_unref (rtspsrc->provided_clock); if (rtspsrc->sdes) gst_structure_free (rtspsrc->sdes); if (rtspsrc->tls_database) g_object_unref (rtspsrc->tls_database); if (rtspsrc->tls_interaction) g_object_unref (rtspsrc->tls_interaction); /* free locks */ g_rec_mutex_clear (&rtspsrc->stream_rec_lock); g_rec_mutex_clear (&rtspsrc->state_rec_lock); G_OBJECT_CLASS (parent_class)->finalize (object); } static GstClock * gst_rtspsrc_provide_clock (GstElement * element) { GstRTSPSrc *src = GST_RTSPSRC (element); GstClock *clock; if ((clock = src->provided_clock) != NULL) gst_object_ref (clock); return clock; } /* a proxy string of the format [user:passwd@]host[:port] */ static gboolean gst_rtspsrc_set_proxy (GstRTSPSrc * rtsp, const gchar * proxy) { gchar *p, *at, *col; g_free (rtsp->proxy_user); rtsp->proxy_user = NULL; g_free (rtsp->proxy_passwd); rtsp->proxy_passwd = NULL; g_free (rtsp->proxy_host); rtsp->proxy_host = NULL; rtsp->proxy_port = 0; p = (gchar *) proxy; if (p == NULL) return TRUE; /* we allow http:// in front but ignore it */ if (g_str_has_prefix (p, "http://")) p += 7; at = strchr (p, '@'); if (at) { /* look for user:passwd */ col = strchr (proxy, ':'); if (col == NULL || col > at) return FALSE; rtsp->proxy_user = g_strndup (p, col - p); col++; rtsp->proxy_passwd = g_strndup (col, at - col); /* move to host */ p = at + 1; } else { if (rtsp->prop_proxy_id != NULL && *rtsp->prop_proxy_id != '\0') rtsp->proxy_user = g_strdup (rtsp->prop_proxy_id); if (rtsp->prop_proxy_pw != NULL && *rtsp->prop_proxy_pw != '\0') rtsp->proxy_passwd = g_strdup (rtsp->prop_proxy_pw); if (rtsp->proxy_user != NULL || rtsp->proxy_passwd != NULL) { GST_LOG_OBJECT (rtsp, "set proxy user/pw from properties: %s:%s", GST_STR_NULL (rtsp->proxy_user), GST_STR_NULL (rtsp->proxy_passwd)); } } col = strchr (p, ':'); if (col) { /* everything before the colon is the hostname */ rtsp->proxy_host = g_strndup (p, col - p); p = col + 1; rtsp->proxy_port = strtoul (p, (char **) &p, 10); } else { rtsp->proxy_host = g_strdup (p); rtsp->proxy_port = 8080; } return TRUE; } static void gst_rtspsrc_set_tcp_timeout (GstRTSPSrc * rtspsrc, guint64 timeout) { rtspsrc->tcp_timeout.tv_sec = timeout / G_USEC_PER_SEC; rtspsrc->tcp_timeout.tv_usec = timeout % G_USEC_PER_SEC; if (timeout != 0) rtspsrc->ptcp_timeout = &rtspsrc->tcp_timeout; else rtspsrc->ptcp_timeout = NULL; } static void gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstRTSPSrc *rtspsrc; rtspsrc = GST_RTSPSRC (object); switch (prop_id) { case PROP_LOCATION: gst_rtspsrc_uri_set_uri (GST_URI_HANDLER (rtspsrc), g_value_get_string (value), NULL); break; case PROP_PROTOCOLS: rtspsrc->protocols = g_value_get_flags (value); break; case PROP_DEBUG: rtspsrc->debug = g_value_get_boolean (value); break; case PROP_RETRY: rtspsrc->retry = g_value_get_uint (value); break; case PROP_TIMEOUT: rtspsrc->udp_timeout = g_value_get_uint64 (value); break; case PROP_TCP_TIMEOUT: gst_rtspsrc_set_tcp_timeout (rtspsrc, g_value_get_uint64 (value)); break; case PROP_LATENCY: rtspsrc->latency = g_value_get_uint (value); break; case PROP_DROP_ON_LATENCY: rtspsrc->drop_on_latency = g_value_get_boolean (value); break; case PROP_CONNECTION_SPEED: rtspsrc->connection_speed = g_value_get_uint64 (value); break; case PROP_NAT_METHOD: rtspsrc->nat_method = g_value_get_enum (value); break; case PROP_DO_RTCP: rtspsrc->do_rtcp = g_value_get_boolean (value); break; case PROP_DO_RTSP_KEEP_ALIVE: rtspsrc->do_rtsp_keep_alive = g_value_get_boolean (value); break; case PROP_PROXY: gst_rtspsrc_set_proxy (rtspsrc, g_value_get_string (value)); break; case PROP_PROXY_ID: g_free (rtspsrc->prop_proxy_id); rtspsrc->prop_proxy_id = g_value_dup_string (value); break; case PROP_PROXY_PW: g_free (rtspsrc->prop_proxy_pw); rtspsrc->prop_proxy_pw = g_value_dup_string (value); break; case PROP_RTP_BLOCKSIZE: rtspsrc->rtp_blocksize = g_value_get_uint (value); break; case PROP_USER_ID: g_free (rtspsrc->user_id); rtspsrc->user_id = g_value_dup_string (value); break; case PROP_USER_PW: g_free (rtspsrc->user_pw); rtspsrc->user_pw = g_value_dup_string (value); break; case PROP_BUFFER_MODE: rtspsrc->buffer_mode = g_value_get_enum (value); break; case PROP_PORT_RANGE: { const gchar *str; str = g_value_get_string (value); if (sscanf (str, "%u-%u", &rtspsrc->client_port_range.min, &rtspsrc->client_port_range.max) != 2) { rtspsrc->client_port_range.min = 0; rtspsrc->client_port_range.max = 0; } break; } case PROP_UDP_BUFFER_SIZE: rtspsrc->udp_buffer_size = g_value_get_int (value); break; case PROP_SHORT_HEADER: rtspsrc->short_header = g_value_get_boolean (value); break; case PROP_PROBATION: rtspsrc->probation = g_value_get_uint (value); break; case PROP_UDP_RECONNECT: rtspsrc->udp_reconnect = g_value_get_boolean (value); break; case PROP_MULTICAST_IFACE: g_free (rtspsrc->multi_iface); if (g_value_get_string (value) == NULL) rtspsrc->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE); else rtspsrc->multi_iface = g_value_dup_string (value); break; case PROP_NTP_SYNC: rtspsrc->ntp_sync = g_value_get_boolean (value); break; case PROP_USE_PIPELINE_CLOCK: rtspsrc->use_pipeline_clock = g_value_get_boolean (value); break; case PROP_SDES: rtspsrc->sdes = g_value_dup_boxed (value); break; case PROP_TLS_VALIDATION_FLAGS: rtspsrc->tls_validation_flags = g_value_get_flags (value); break; case PROP_TLS_DATABASE: g_clear_object (&rtspsrc->tls_database); rtspsrc->tls_database = g_value_dup_object (value); break; case PROP_TLS_INTERACTION: g_clear_object (&rtspsrc->tls_interaction); rtspsrc->tls_interaction = g_value_dup_object (value); break; case PROP_DO_RETRANSMISSION: rtspsrc->do_retransmission = g_value_get_boolean (value); break; case PROP_NTP_TIME_SOURCE: rtspsrc->ntp_time_source = g_value_get_enum (value); break; case PROP_USER_AGENT: g_free (rtspsrc->user_agent); rtspsrc->user_agent = g_value_dup_string (value); break; case PROP_MAX_RTCP_RTP_TIME_DIFF: rtspsrc->max_rtcp_rtp_time_diff = g_value_get_int (value); break; case PROP_RFC7273_SYNC: rtspsrc->rfc7273_sync = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstRTSPSrc *rtspsrc; rtspsrc = GST_RTSPSRC (object); switch (prop_id) { case PROP_LOCATION: g_value_set_string (value, rtspsrc->conninfo.location); break; case PROP_PROTOCOLS: g_value_set_flags (value, rtspsrc->protocols); break; case PROP_DEBUG: g_value_set_boolean (value, rtspsrc->debug); break; case PROP_RETRY: g_value_set_uint (value, rtspsrc->retry); break; case PROP_TIMEOUT: g_value_set_uint64 (value, rtspsrc->udp_timeout); break; case PROP_TCP_TIMEOUT: { guint64 timeout; timeout = ((guint64) rtspsrc->tcp_timeout.tv_sec) * G_USEC_PER_SEC + rtspsrc->tcp_timeout.tv_usec; g_value_set_uint64 (value, timeout); break; } case PROP_LATENCY: g_value_set_uint (value, rtspsrc->latency); break; case PROP_DROP_ON_LATENCY: g_value_set_boolean (value, rtspsrc->drop_on_latency); break; case PROP_CONNECTION_SPEED: g_value_set_uint64 (value, rtspsrc->connection_speed); break; case PROP_NAT_METHOD: g_value_set_enum (value, rtspsrc->nat_method); break; case PROP_DO_RTCP: g_value_set_boolean (value, rtspsrc->do_rtcp); break; case PROP_DO_RTSP_KEEP_ALIVE: g_value_set_boolean (value, rtspsrc->do_rtsp_keep_alive); break; case PROP_PROXY: { gchar *str; if (rtspsrc->proxy_host) { str = g_strdup_printf ("%s:%d", rtspsrc->proxy_host, rtspsrc->proxy_port); } else { str = NULL; } g_value_take_string (value, str); break; } case PROP_PROXY_ID: g_value_set_string (value, rtspsrc->prop_proxy_id); break; case PROP_PROXY_PW: g_value_set_string (value, rtspsrc->prop_proxy_pw); break; case PROP_RTP_BLOCKSIZE: g_value_set_uint (value, rtspsrc->rtp_blocksize); break; case PROP_USER_ID: g_value_set_string (value, rtspsrc->user_id); break; case PROP_USER_PW: g_value_set_string (value, rtspsrc->user_pw); break; case PROP_BUFFER_MODE: g_value_set_enum (value, rtspsrc->buffer_mode); break; case PROP_PORT_RANGE: { gchar *str; if (rtspsrc->client_port_range.min != 0) { str = g_strdup_printf ("%u-%u", rtspsrc->client_port_range.min, rtspsrc->client_port_range.max); } else { str = NULL; } g_value_take_string (value, str); break; } case PROP_UDP_BUFFER_SIZE: g_value_set_int (value, rtspsrc->udp_buffer_size); break; case PROP_SHORT_HEADER: g_value_set_boolean (value, rtspsrc->short_header); break; case PROP_PROBATION: g_value_set_uint (value, rtspsrc->probation); break; case PROP_UDP_RECONNECT: g_value_set_boolean (value, rtspsrc->udp_reconnect); break; case PROP_MULTICAST_IFACE: g_value_set_string (value, rtspsrc->multi_iface); break; case PROP_NTP_SYNC: g_value_set_boolean (value, rtspsrc->ntp_sync); break; case PROP_USE_PIPELINE_CLOCK: g_value_set_boolean (value, rtspsrc->use_pipeline_clock); break; case PROP_SDES: g_value_set_boxed (value, rtspsrc->sdes); break; case PROP_TLS_VALIDATION_FLAGS: g_value_set_flags (value, rtspsrc->tls_validation_flags); break; case PROP_TLS_DATABASE: g_value_set_object (value, rtspsrc->tls_database); break; case PROP_TLS_INTERACTION: g_value_set_object (value, rtspsrc->tls_interaction); break; case PROP_DO_RETRANSMISSION: g_value_set_boolean (value, rtspsrc->do_retransmission); break; case PROP_NTP_TIME_SOURCE: g_value_set_enum (value, rtspsrc->ntp_time_source); break; case PROP_USER_AGENT: g_value_set_string (value, rtspsrc->user_agent); break; case PROP_MAX_RTCP_RTP_TIME_DIFF: g_value_set_int (value, rtspsrc->max_rtcp_rtp_time_diff); break; case PROP_RFC7273_SYNC: g_value_set_boolean (value, rtspsrc->rfc7273_sync); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gint find_stream_by_id (GstRTSPStream * stream, gint * id) { if (stream->id == *id) return 0; return -1; } static gint find_stream_by_channel (GstRTSPStream * stream, gint * channel) { if (stream->channel[0] == *channel || stream->channel[1] == *channel) return 0; return -1; } static gint find_stream_by_udpsrc (GstRTSPStream * stream, gconstpointer a) { GstElement *src = (GstElement *) a; if (stream->udpsrc[0] == src) return 0; if (stream->udpsrc[1] == src) return 0; return -1; } static gint find_stream_by_setup (GstRTSPStream * stream, gconstpointer a) { if (stream->conninfo.location) { /* check qualified setup_url */ if (!strcmp (stream->conninfo.location, (gchar *) a)) return 0; } if (stream->control_url) { /* check original control_url */ if (!strcmp (stream->control_url, (gchar *) a)) return 0; /* check if qualified setup_url ends with string */ if (g_str_has_suffix (stream->control_url, (gchar *) a)) return 0; } return -1; } static GstRTSPStream * find_stream (GstRTSPSrc * src, gconstpointer data, gconstpointer func) { GList *lstream; /* find and get stream */ if ((lstream = g_list_find_custom (src->streams, data, (GCompareFunc) func))) return (GstRTSPStream *) lstream->data; return NULL; } static const GstSDPBandwidth * gst_rtspsrc_get_bandwidth (GstRTSPSrc * src, const GstSDPMessage * sdp, const GstSDPMedia * media, const gchar * type) { guint i, len; /* first look in the media specific section */ len = gst_sdp_media_bandwidths_len (media); for (i = 0; i < len; i++) { const GstSDPBandwidth *bw = gst_sdp_media_get_bandwidth (media, i); if (strcmp (bw->bwtype, type) == 0) return bw; } /* then look in the message specific section */ len = gst_sdp_message_bandwidths_len (sdp); for (i = 0; i < len; i++) { const GstSDPBandwidth *bw = gst_sdp_message_get_bandwidth (sdp, i); if (strcmp (bw->bwtype, type) == 0) return bw; } return NULL; } static void gst_rtspsrc_collect_bandwidth (GstRTSPSrc * src, const GstSDPMessage * sdp, const GstSDPMedia * media, GstRTSPStream * stream) { const GstSDPBandwidth *bw; if ((bw = gst_rtspsrc_get_bandwidth (src, sdp, media, GST_SDP_BWTYPE_AS))) stream->as_bandwidth = bw->bandwidth; else stream->as_bandwidth = -1; if ((bw = gst_rtspsrc_get_bandwidth (src, sdp, media, GST_SDP_BWTYPE_RR))) stream->rr_bandwidth = bw->bandwidth; else stream->rr_bandwidth = -1; if ((bw = gst_rtspsrc_get_bandwidth (src, sdp, media, GST_SDP_BWTYPE_RS))) stream->rs_bandwidth = bw->bandwidth; else stream->rs_bandwidth = -1; } static void gst_rtspsrc_do_stream_connection (GstRTSPSrc * src, GstRTSPStream * stream, const GstSDPConnection * conn) { if (conn->nettype == NULL || strcmp (conn->nettype, "IN") != 0) return; if (conn->addrtype == NULL) return; /* check for IPV6 */ if (strcmp (conn->addrtype, "IP4") == 0) stream->is_ipv6 = FALSE; else if (strcmp (conn->addrtype, "IP6") == 0) stream->is_ipv6 = TRUE; else return; /* save address */ g_free (stream->destination); stream->destination = g_strdup (conn->address); /* check for multicast */ stream->is_multicast = gst_sdp_address_is_multicast (conn->nettype, conn->addrtype, conn->address); stream->ttl = conn->ttl; } /* Go over the connections for a stream. * - If we are dealing with IPV6, we will setup IPV6 sockets for sending and * receiving. * - If we are dealing with a localhost address, we disable multicast */ static void gst_rtspsrc_collect_connections (GstRTSPSrc * src, const GstSDPMessage * sdp, const GstSDPMedia * media, GstRTSPStream * stream) { const GstSDPConnection *conn; guint i, len; /* first look in the media specific section */ len = gst_sdp_media_connections_len (media); for (i = 0; i < len; i++) { conn = gst_sdp_media_get_connection (media, i); gst_rtspsrc_do_stream_connection (src, stream, conn); } /* then look in the message specific section */ if ((conn = gst_sdp_message_get_connection (sdp))) { gst_rtspsrc_do_stream_connection (src, stream, conn); } } /* m= RTP/AVP */ static void gst_rtspsrc_collect_payloads (GstRTSPSrc * src, const GstSDPMessage * sdp, const GstSDPMedia * media, GstRTSPStream * stream) { guint i, len; const gchar *proto; GstCaps *global_caps; /* get proto */ proto = gst_sdp_media_get_proto (media); if (proto == NULL) goto no_proto; if (g_str_equal (proto, "RTP/AVP")) stream->profile = GST_RTSP_PROFILE_AVP; else if (g_str_equal (proto, "RTP/SAVP")) stream->profile = GST_RTSP_PROFILE_SAVP; else if (g_str_equal (proto, "RTP/AVPF")) stream->profile = GST_RTSP_PROFILE_AVPF; else if (g_str_equal (proto, "RTP/SAVPF")) stream->profile = GST_RTSP_PROFILE_SAVPF; else goto unknown_proto; /* Parse global SDP attributes once */ global_caps = gst_caps_new_empty_simple ("application/x-unknown"); GST_DEBUG ("mapping sdp session level attributes to caps"); gst_sdp_message_attributes_to_caps (sdp, global_caps); GST_DEBUG ("mapping sdp media level attributes to caps"); gst_sdp_media_attributes_to_caps (media, global_caps); /* Keep a copy of the SDP key management */ gst_sdp_media_parse_keymgmt (media, &stream->mikey); if (stream->mikey == NULL) gst_sdp_message_parse_keymgmt (sdp, &stream->mikey); len = gst_sdp_media_formats_len (media); for (i = 0; i < len; i++) { gint pt; GstCaps *caps, *outcaps; GstStructure *s; const gchar *enc; PtMapItem item; pt = atoi (gst_sdp_media_get_format (media, i)); GST_DEBUG_OBJECT (src, " looking at %d pt: %d", i, pt); /* convert caps */ caps = gst_sdp_media_get_caps_from_media (media, pt); if (caps == NULL) { GST_WARNING_OBJECT (src, " skipping pt %d without caps", pt); continue; } /* do some tweaks */ s = gst_caps_get_structure (caps, 0); if ((enc = gst_structure_get_string (s, "encoding-name"))) { stream->is_real = (strstr (enc, "-REAL") != NULL); if (strcmp (enc, "X-ASF-PF") == 0) stream->container = TRUE; } /* Merge in global caps */ /* Intersect will merge in missing fields to the current caps */ outcaps = gst_caps_intersect (caps, global_caps); gst_caps_unref (caps); /* the first pt will be the default */ if (stream->ptmap->len == 0) stream->default_pt = pt; item.pt = pt; item.caps = outcaps; g_array_append_val (stream->ptmap, item); } gst_caps_unref (global_caps); return; no_proto: { GST_ERROR_OBJECT (src, "can't find proto in media"); return; } unknown_proto: { GST_ERROR_OBJECT (src, "unknown proto in media %s", proto); return; } } static const gchar * get_aggregate_control (GstRTSPSrc * src) { const gchar *base; if (src->control) base = src->control; else if (src->content_base) base = src->content_base; else if (src->conninfo.url_str) base = src->conninfo.url_str; else base = "/"; return base; } static void clear_ptmap_item (PtMapItem * item) { if (item->caps) gst_caps_unref (item->caps); } static GstRTSPStream * gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) { GstRTSPStream *stream; const gchar *control_url; const GstSDPMedia *media; /* get media, should not return NULL */ media = gst_sdp_message_get_media (sdp, idx); if (media == NULL) return NULL; stream = g_new0 (GstRTSPStream, 1); stream->parent = src; /* we mark the pad as not linked, we will mark it as OK when we add the pad to * the element. */ stream->last_ret = GST_FLOW_NOT_LINKED; stream->added = FALSE; stream->setup = FALSE; stream->skipped = FALSE; stream->id = idx; stream->eos = FALSE; stream->discont = TRUE; stream->seqbase = -1; stream->timebase = -1; stream->send_ssrc = g_random_int (); stream->profile = GST_RTSP_PROFILE_AVP; stream->ptmap = g_array_new (FALSE, FALSE, sizeof (PtMapItem)); stream->mikey = NULL; g_array_set_clear_func (stream->ptmap, (GDestroyNotify) clear_ptmap_item); /* collect bandwidth information for this steam. FIXME, configure in the RTP * session manager to scale RTCP. */ gst_rtspsrc_collect_bandwidth (src, sdp, media, stream); /* collect connection info */ gst_rtspsrc_collect_connections (src, sdp, media, stream); /* make the payload type map */ gst_rtspsrc_collect_payloads (src, sdp, media, stream); /* collect port number */ stream->port = gst_sdp_media_get_port (media); /* get control url to construct the setup url. The setup url is used to * configure the transport of the stream and is used to identity the stream in * the RTP-Info header field returned from PLAY. */ control_url = gst_sdp_media_get_attribute_val (media, "control"); if (control_url == NULL) control_url = gst_sdp_message_get_attribute_val_n (sdp, "control", 0); GST_DEBUG_OBJECT (src, "stream %d, (%p)", stream->id, stream); GST_DEBUG_OBJECT (src, " port: %d", stream->port); GST_DEBUG_OBJECT (src, " container: %d", stream->container); GST_DEBUG_OBJECT (src, " control: %s", GST_STR_NULL (control_url)); if (control_url != NULL) { stream->control_url = g_strdup (control_url); /* Build a fully qualified url using the content_base if any or by prefixing * the original request. * If the control_url starts with a '/' or a non rtsp: protocol we will most * likely build a URL that the server will fail to understand, this is ok, * we will fail then. */ if (g_str_has_prefix (control_url, "rtsp://")) stream->conninfo.location = g_strdup (control_url); else { const gchar *base; gboolean has_slash; if (g_strcmp0 (control_url, "*") == 0) control_url = ""; base = get_aggregate_control (src); /* check if the base ends or control starts with / */ has_slash = g_str_has_prefix (control_url, "/"); has_slash = has_slash || g_str_has_suffix (base, "/"); /* concatenate the two strings, insert / when not present */ stream->conninfo.location = g_strdup_printf ("%s%s%s", base, has_slash ? "" : "/", control_url); } } GST_DEBUG_OBJECT (src, " setup: %s", GST_STR_NULL (stream->conninfo.location)); /* we keep track of all streams */ src->streams = g_list_append (src->streams, stream); return stream; /* ERRORS */ } static void gst_rtspsrc_stream_free (GstRTSPSrc * src, GstRTSPStream * stream) { gint i; GST_DEBUG_OBJECT (src, "free stream %p", stream); g_array_free (stream->ptmap, TRUE); g_free (stream->destination); g_free (stream->control_url); g_free (stream->conninfo.location); for (i = 0; i < 2; i++) { if (stream->udpsrc[i]) { gst_element_set_state (stream->udpsrc[i], GST_STATE_NULL); gst_bin_remove (GST_BIN_CAST (src), stream->udpsrc[i]); gst_object_unref (stream->udpsrc[i]); } if (stream->channelpad[i]) gst_object_unref (stream->channelpad[i]); if (stream->udpsink[i]) { gst_element_set_state (stream->udpsink[i], GST_STATE_NULL); gst_bin_remove (GST_BIN_CAST (src), stream->udpsink[i]); gst_object_unref (stream->udpsink[i]); } } if (stream->fakesrc) { gst_element_set_state (stream->fakesrc, GST_STATE_NULL); gst_bin_remove (GST_BIN_CAST (src), stream->fakesrc); gst_object_unref (stream->fakesrc); } if (stream->srcpad) { gst_pad_set_active (stream->srcpad, FALSE); if (stream->added) gst_element_remove_pad (GST_ELEMENT_CAST (src), stream->srcpad); } if (stream->srtpenc) gst_object_unref (stream->srtpenc); if (stream->srtpdec) gst_object_unref (stream->srtpdec); if (stream->srtcpparams) gst_caps_unref (stream->srtcpparams); if (stream->mikey) gst_mikey_message_unref (stream->mikey); if (stream->rtcppad) gst_object_unref (stream->rtcppad); if (stream->session) g_object_unref (stream->session); if (stream->rtx_pt_map) gst_structure_free (stream->rtx_pt_map); g_free (stream); } static void gst_rtspsrc_cleanup (GstRTSPSrc * src) { GList *walk; GST_DEBUG_OBJECT (src, "cleanup"); for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; gst_rtspsrc_stream_free (src, stream); } g_list_free (src->streams); src->streams = NULL; if (src->manager) { if (src->manager_sig_id) { g_signal_handler_disconnect (src->manager, src->manager_sig_id); src->manager_sig_id = 0; } gst_element_set_state (src->manager, GST_STATE_NULL); gst_bin_remove (GST_BIN_CAST (src), src->manager); src->manager = NULL; } if (src->props) gst_structure_free (src->props); src->props = NULL; g_free (src->content_base); src->content_base = NULL; g_free (src->control); src->control = NULL; if (src->range) gst_rtsp_range_free (src->range); src->range = NULL; /* don't clear the SDP when it was used in the url */ if (src->sdp && !src->from_sdp) { gst_sdp_message_free (src->sdp); src->sdp = NULL; } src->need_segment = FALSE; if (src->provided_clock) { gst_object_unref (src->provided_clock); src->provided_clock = NULL; } } static gboolean gst_rtspsrc_alloc_udp_ports (GstRTSPStream * stream, gint * rtpport, gint * rtcpport) { GstRTSPSrc *src; GstStateChangeReturn ret; GstElement *udpsrc0, *udpsrc1; gint tmp_rtp, tmp_rtcp; guint count; const gchar *host; src = stream->parent; udpsrc0 = NULL; udpsrc1 = NULL; count = 0; /* Start at next port */ tmp_rtp = src->next_port_num; if (stream->is_ipv6) host = "udp://[::0]"; else host = "udp://0.0.0.0"; /* try to allocate 2 UDP ports, the RTP port should be an even * number and the RTCP port should be the next (uneven) port */ again: if (tmp_rtp != 0 && src->client_port_range.max > 0 && tmp_rtp >= src->client_port_range.max) goto no_ports; udpsrc0 = gst_element_make_from_uri (GST_URI_SRC, host, NULL, NULL); if (udpsrc0 == NULL) goto no_udp_protocol; g_object_set (G_OBJECT (udpsrc0), "port", tmp_rtp, "reuse", FALSE, NULL); if (src->udp_buffer_size != 0) g_object_set (G_OBJECT (udpsrc0), "buffer-size", src->udp_buffer_size, NULL); ret = gst_element_set_state (udpsrc0, GST_STATE_READY); if (ret == GST_STATE_CHANGE_FAILURE) { if (tmp_rtp != 0) { GST_DEBUG_OBJECT (src, "Unable to make udpsrc from RTP port %d", tmp_rtp); tmp_rtp += 2; if (++count > src->retry) goto no_ports; GST_DEBUG_OBJECT (src, "free RTP udpsrc"); gst_element_set_state (udpsrc0, GST_STATE_NULL); gst_object_unref (udpsrc0); udpsrc0 = NULL; GST_DEBUG_OBJECT (src, "retry %d", count); goto again; } goto no_udp_protocol; } g_object_get (G_OBJECT (udpsrc0), "port", &tmp_rtp, NULL); GST_DEBUG_OBJECT (src, "got RTP port %d", tmp_rtp); /* check if port is even */ if ((tmp_rtp & 0x01) != 0) { /* port not even, close and allocate another */ if (++count > src->retry) goto no_ports; GST_DEBUG_OBJECT (src, "RTP port not even"); GST_DEBUG_OBJECT (src, "free RTP udpsrc"); gst_element_set_state (udpsrc0, GST_STATE_NULL); gst_object_unref (udpsrc0); udpsrc0 = NULL; GST_DEBUG_OBJECT (src, "retry %d", count); tmp_rtp++; goto again; } /* allocate port+1 for RTCP now */ udpsrc1 = gst_element_make_from_uri (GST_URI_SRC, host, NULL, NULL); if (udpsrc1 == NULL) goto no_udp_rtcp_protocol; /* set port */ tmp_rtcp = tmp_rtp + 1; if (src->client_port_range.max > 0 && tmp_rtcp > src->client_port_range.max) goto no_ports; g_object_set (G_OBJECT (udpsrc1), "port", tmp_rtcp, "reuse", FALSE, NULL); GST_DEBUG_OBJECT (src, "starting RTCP on port %d", tmp_rtcp); ret = gst_element_set_state (udpsrc1, GST_STATE_READY); /* tmp_rtcp port is busy already : retry to make rtp/rtcp pair */ if (ret == GST_STATE_CHANGE_FAILURE) { GST_DEBUG_OBJECT (src, "Unable to make udpsrc from RTCP port %d", tmp_rtcp); if (++count > src->retry) goto no_ports; GST_DEBUG_OBJECT (src, "free RTP udpsrc"); gst_element_set_state (udpsrc0, GST_STATE_NULL); gst_object_unref (udpsrc0); udpsrc0 = NULL; GST_DEBUG_OBJECT (src, "free RTCP udpsrc"); gst_element_set_state (udpsrc1, GST_STATE_NULL); gst_object_unref (udpsrc1); udpsrc1 = NULL; tmp_rtp += 2; GST_DEBUG_OBJECT (src, "retry %d", count); goto again; } /* all fine, do port check */ g_object_get (G_OBJECT (udpsrc0), "port", rtpport, NULL); g_object_get (G_OBJECT (udpsrc1), "port", rtcpport, NULL); /* this should not happen... */ if (*rtpport != tmp_rtp || *rtcpport != tmp_rtcp) goto port_error; /* we keep these elements, we configure all in configure_transport when the * server told us to really use the UDP ports. */ stream->udpsrc[0] = gst_object_ref_sink (udpsrc0); stream->udpsrc[1] = gst_object_ref_sink (udpsrc1); gst_element_set_locked_state (stream->udpsrc[0], TRUE); gst_element_set_locked_state (stream->udpsrc[1], TRUE); /* keep track of next available port number when we have a range * configured */ if (src->next_port_num != 0) src->next_port_num = tmp_rtcp + 1; return TRUE; /* ERRORS */ no_udp_protocol: { GST_DEBUG_OBJECT (src, "could not get UDP source"); goto cleanup; } no_ports: { GST_DEBUG_OBJECT (src, "could not allocate UDP port pair after %d retries", count); goto cleanup; } no_udp_rtcp_protocol: { GST_DEBUG_OBJECT (src, "could not get UDP source for RTCP"); goto cleanup; } port_error: { GST_DEBUG_OBJECT (src, "ports don't match rtp: %d<->%d, rtcp: %d<->%d", tmp_rtp, *rtpport, tmp_rtcp, *rtcpport); goto cleanup; } cleanup: { if (udpsrc0) { gst_element_set_state (udpsrc0, GST_STATE_NULL); gst_object_unref (udpsrc0); } if (udpsrc1) { gst_element_set_state (udpsrc1, GST_STATE_NULL); gst_object_unref (udpsrc1); } return FALSE; } } static void gst_rtspsrc_set_state (GstRTSPSrc * src, GstState state) { GList *walk; if (src->manager) gst_element_set_state (GST_ELEMENT_CAST (src->manager), state); for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; gint i; for (i = 0; i < 2; i++) { if (stream->udpsrc[i]) gst_element_set_state (stream->udpsrc[i], state); } } } static void gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing) { GstEvent *event; gint cmd; GstState state; if (flush) { event = gst_event_new_flush_start (); GST_DEBUG_OBJECT (src, "start flush"); cmd = CMD_WAIT; state = GST_STATE_PAUSED; } else { event = gst_event_new_flush_stop (FALSE); GST_DEBUG_OBJECT (src, "stop flush; playing %d", playing); cmd = CMD_LOOP; if (playing) state = GST_STATE_PLAYING; else state = GST_STATE_PAUSED; } gst_rtspsrc_push_event (src, event); gst_rtspsrc_loop_send_cmd (src, cmd, CMD_LOOP); gst_rtspsrc_set_state (src, state); } static GstRTSPResult gst_rtspsrc_connection_send (GstRTSPSrc * src, GstRTSPConnection * conn, GstRTSPMessage * message, GTimeVal * timeout) { GstRTSPResult ret; if (conn) ret = gst_rtsp_connection_send (conn, message, timeout); else ret = GST_RTSP_ERROR; return ret; } static GstRTSPResult gst_rtspsrc_connection_receive (GstRTSPSrc * src, GstRTSPConnection * conn, GstRTSPMessage * message, GTimeVal * timeout) { GstRTSPResult ret; if (conn) ret = gst_rtsp_connection_receive (conn, message, timeout); else ret = GST_RTSP_ERROR; return ret; } static void gst_rtspsrc_get_position (GstRTSPSrc * src) { GstQuery *query; GList *walk; query = gst_query_new_position (GST_FORMAT_TIME); /* should be known somewhere down the stream (e.g. jitterbuffer) */ for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; GstFormat fmt; gint64 pos; if (stream->srcpad) { if (gst_pad_query (stream->srcpad, query)) { gst_query_parse_position (query, &fmt, &pos); GST_DEBUG_OBJECT (src, "retaining position %" GST_TIME_FORMAT, GST_TIME_ARGS (pos)); src->last_pos = pos; goto out; } } } src->last_pos = 0; out: gst_query_unref (query); } static gboolean gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) { gdouble rate; GstFormat format; GstSeekFlags flags; GstSeekType cur_type = GST_SEEK_TYPE_NONE, stop_type; gint64 cur, stop; gboolean flush, skip; gboolean update; gboolean playing; GstSegment seeksegment = { 0, }; GList *walk; if (event) { GST_DEBUG_OBJECT (src, "doing seek with event"); gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur, &stop_type, &stop); /* no negative rates yet */ if (rate < 0.0) goto negative_rate; /* we need TIME format */ if (format != src->segment.format) goto no_format; } else { GST_DEBUG_OBJECT (src, "doing seek without event"); flags = 0; cur_type = GST_SEEK_TYPE_SET; stop_type = GST_SEEK_TYPE_SET; } /* get flush flag */ flush = flags & GST_SEEK_FLAG_FLUSH; skip = flags & GST_SEEK_FLAG_SKIP; /* now we need to make sure the streaming thread is stopped. We do this by * either sending a FLUSH_START event downstream which will cause the * streaming thread to stop with a WRONG_STATE. * For a non-flushing seek we simply pause the task, which will happen as soon * as it completes one iteration (and thus might block when the sink is * blocking in preroll). */ if (flush) { GST_DEBUG_OBJECT (src, "starting flush"); gst_rtspsrc_flush (src, TRUE, FALSE); } else { if (src->task) { gst_task_pause (src->task); } } /* we should now be able to grab the streaming thread because we stopped it * with the above flush/pause code */ GST_RTSP_STREAM_LOCK (src); GST_DEBUG_OBJECT (src, "stopped streaming"); /* stop flushing the rtsp connection so we can send PAUSE/PLAY below */ gst_rtspsrc_connection_flush (src, FALSE); /* copy segment, we need this because we still need the old * segment when we close the current segment. */ memcpy (&seeksegment, &src->segment, sizeof (GstSegment)); /* configure the seek parameters in the seeksegment. We will then have the * right values in the segment to perform the seek */ if (event) { GST_DEBUG_OBJECT (src, "configuring seek"); gst_segment_do_seek (&seeksegment, rate, format, flags, cur_type, cur, stop_type, stop, &update); } /* figure out the last position we need to play. If it's configured (stop != * -1), use that, else we play until the total duration of the file */ if ((stop = seeksegment.stop) == -1) stop = seeksegment.duration; /* if we were playing, pause first */ playing = (src->state == GST_RTSP_STATE_PLAYING); if (playing) { /* obtain current position in case seek fails */ gst_rtspsrc_get_position (src); gst_rtspsrc_pause (src, FALSE); } src->skip = skip; src->state = GST_RTSP_STATE_SEEKING; /* PLAY will add the range header now. */ src->need_range = TRUE; /* prepare for streaming again */ if (flush) { /* if we started flush, we stop now */ GST_DEBUG_OBJECT (src, "stopping flush"); gst_rtspsrc_flush (src, FALSE, playing); } /* now we did the seek and can activate the new segment values */ memcpy (&src->segment, &seeksegment, sizeof (GstSegment)); /* if we're doing a segment seek, post a SEGMENT_START message */ if (src->segment.flags & GST_SEEK_FLAG_SEGMENT) { gst_element_post_message (GST_ELEMENT_CAST (src), gst_message_new_segment_start (GST_OBJECT_CAST (src), src->segment.format, src->segment.position)); } /* now create the newsegment */ GST_DEBUG_OBJECT (src, "Creating newsegment from %" G_GINT64_FORMAT " to %" G_GINT64_FORMAT, src->segment.position, stop); /* mark discont */ GST_DEBUG_OBJECT (src, "mark DISCONT, we did a seek to another position"); for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; stream->discont = TRUE; } /* and continue playing if needed */ GST_OBJECT_LOCK (src); playing = (GST_STATE_PENDING (src) == GST_STATE_VOID_PENDING && GST_STATE (src) == GST_STATE_PLAYING) || (GST_STATE_PENDING (src) == GST_STATE_PLAYING); GST_OBJECT_UNLOCK (src); if (playing) gst_rtspsrc_play (src, &seeksegment, FALSE); GST_RTSP_STREAM_UNLOCK (src); return TRUE; /* ERRORS */ negative_rate: { GST_DEBUG_OBJECT (src, "negative playback rates are not supported yet."); return FALSE; } no_format: { GST_DEBUG_OBJECT (src, "unsupported format given, seek aborted."); return FALSE; } } static gboolean gst_rtspsrc_handle_src_event (GstPad * pad, GstObject * parent, GstEvent * event) { GstRTSPSrc *src; gboolean res = TRUE; gboolean forward; src = GST_RTSPSRC_CAST (parent); GST_DEBUG_OBJECT (src, "pad %s:%s received event %s", GST_DEBUG_PAD_NAME (pad), GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: res = gst_rtspsrc_perform_seek (src, event); forward = FALSE; break; case GST_EVENT_QOS: case GST_EVENT_NAVIGATION: case GST_EVENT_LATENCY: default: forward = TRUE; break; } if (forward) { GstPad *target; if ((target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (pad)))) { res = gst_pad_send_event (target, event); gst_object_unref (target); } else { gst_event_unref (event); } } else { gst_event_unref (event); } return res; } /* this is the final event function we receive on the internal source pad when * we deal with TCP connections */ static gboolean gst_rtspsrc_handle_internal_src_event (GstPad * pad, GstObject * parent, GstEvent * event) { gboolean res; GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: case GST_EVENT_QOS: case GST_EVENT_NAVIGATION: case GST_EVENT_LATENCY: default: gst_event_unref (event); res = TRUE; break; } return res; } /* this is the final query function we receive on the internal source pad when * we deal with TCP connections */ static gboolean gst_rtspsrc_handle_internal_src_query (GstPad * pad, GstObject * parent, GstQuery * query) { GstRTSPSrc *src; gboolean res = TRUE; src = GST_RTSPSRC_CAST (gst_pad_get_element_private (pad)); GST_DEBUG_OBJECT (src, "pad %s:%s received query %s", GST_DEBUG_PAD_NAME (pad), GST_QUERY_TYPE_NAME (query)); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_POSITION: { /* no idea */ break; } case GST_QUERY_DURATION: { GstFormat format; gst_query_parse_duration (query, &format, NULL); switch (format) { case GST_FORMAT_TIME: gst_query_set_duration (query, format, src->segment.duration); break; default: res = FALSE; break; } break; } case GST_QUERY_LATENCY: { /* we are live with a min latency of 0 and unlimited max latency, this * result will be updated by the session manager if there is any. */ gst_query_set_latency (query, TRUE, 0, -1); break; } default: break; } return res; } /* this query is executed on the ghost source pad exposed on rtspsrc. */ static gboolean gst_rtspsrc_handle_src_query (GstPad * pad, GstObject * parent, GstQuery * query) { GstRTSPSrc *src; gboolean res = FALSE; src = GST_RTSPSRC_CAST (parent); GST_DEBUG_OBJECT (src, "pad %s:%s received query %s", GST_DEBUG_PAD_NAME (pad), GST_QUERY_TYPE_NAME (query)); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_DURATION: { GstFormat format; gst_query_parse_duration (query, &format, NULL); switch (format) { case GST_FORMAT_TIME: gst_query_set_duration (query, format, src->segment.duration); res = TRUE; break; default: break; } break; } case GST_QUERY_SEEKING: { GstFormat format; gst_query_parse_seeking (query, &format, NULL, NULL, NULL); if (format == GST_FORMAT_TIME) { gboolean seekable = src->cur_protocols != GST_RTSP_LOWER_TRANS_UDP_MCAST; /* seeking without duration is unlikely */ seekable = seekable && src->seekable && src->segment.duration && GST_CLOCK_TIME_IS_VALID (src->segment.duration); gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, src->segment.duration); res = TRUE; } break; } case GST_QUERY_URI: { gchar *uri; uri = gst_rtspsrc_uri_get_uri (GST_URI_HANDLER (src)); if (uri != NULL) { gst_query_set_uri (query, uri); g_free (uri); res = TRUE; } break; } default: { GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (pad)); /* forward the query to the proxy target pad */ if (target) { res = gst_pad_query (target, query); gst_object_unref (target); } break; } } return res; } /* callback for RTCP messages to be sent to the server when operating in TCP * mode. */ static GstFlowReturn gst_rtspsrc_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) { GstRTSPSrc *src; GstRTSPStream *stream; GstFlowReturn res = GST_FLOW_OK; GstMapInfo map; guint8 *data; guint size; GstRTSPResult ret; GstRTSPMessage message = { 0 }; GstRTSPConnection *conn; stream = (GstRTSPStream *) gst_pad_get_element_private (pad); src = stream->parent; gst_buffer_map (buffer, &map, GST_MAP_READ); size = map.size; data = map.data; gst_rtsp_message_init_data (&message, stream->channel[1]); /* lend the body data to the message */ gst_rtsp_message_take_body (&message, data, size); if (stream->conninfo.connection) conn = stream->conninfo.connection; else conn = src->conninfo.connection; GST_DEBUG_OBJECT (src, "sending %u bytes RTCP", size); ret = gst_rtspsrc_connection_send (src, conn, &message, NULL); GST_DEBUG_OBJECT (src, "sent RTCP, %d", ret); /* and steal it away again because we will free it when unreffing the * buffer */ gst_rtsp_message_steal_body (&message, &data, &size); gst_rtsp_message_unset (&message); gst_buffer_unmap (buffer, &map); gst_buffer_unref (buffer); return res; } static GstPadProbeReturn pad_blocked (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) { GstRTSPSrc *src = user_data; GST_DEBUG_OBJECT (src, "pad %s:%s blocked, activating streams", GST_DEBUG_PAD_NAME (pad)); /* activate the streams */ GST_OBJECT_LOCK (src); if (!src->need_activate) goto was_ok; src->need_activate = FALSE; GST_OBJECT_UNLOCK (src); gst_rtspsrc_activate_streams (src); return GST_PAD_PROBE_OK; was_ok: { GST_OBJECT_UNLOCK (src); return GST_PAD_PROBE_OK; } } static gboolean copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data) { GstPad *gpad = GST_PAD_CAST (user_data); GST_DEBUG_OBJECT (gpad, "store sticky event %" GST_PTR_FORMAT, *event); gst_pad_store_sticky_event (gpad, *event); return TRUE; } /* this callback is called when the session manager generated a new src pad with * payloaded RTP packets. We simply ghost the pad here. */ static void new_manager_pad (GstElement * manager, GstPad * pad, GstRTSPSrc * src) { gchar *name; GstPadTemplate *template; gint id, ssrc, pt; GList *ostreams; GstRTSPStream *stream; gboolean all_added; GST_DEBUG_OBJECT (src, "got new manager pad %" GST_PTR_FORMAT, pad); GST_RTSP_STATE_LOCK (src); /* find stream */ name = gst_object_get_name (GST_OBJECT_CAST (pad)); if (sscanf (name, "recv_rtp_src_%u_%u_%u", &id, &ssrc, &pt) != 3) goto unknown_stream; GST_DEBUG_OBJECT (src, "stream: %u, SSRC %08x, PT %d", id, ssrc, pt); stream = find_stream (src, &id, (gpointer) find_stream_by_id); if (stream == NULL) goto unknown_stream; /* save SSRC */ stream->ssrc = ssrc; /* we'll add it later see below */ stream->added = TRUE; /* check if we added all streams */ all_added = TRUE; for (ostreams = src->streams; ostreams; ostreams = g_list_next (ostreams)) { GstRTSPStream *ostream = (GstRTSPStream *) ostreams->data; GST_DEBUG_OBJECT (src, "stream %p, container %d, added %d, setup %d", ostream, ostream->container, ostream->added, ostream->setup); /* if we find a stream for which we did a setup that is not added, we * need to wait some more */ if (ostream->setup && !ostream->added) { all_added = FALSE; break; } } GST_RTSP_STATE_UNLOCK (src); /* create a new pad we will use to stream to */ template = gst_static_pad_template_get (&rtptemplate); stream->srcpad = gst_ghost_pad_new_from_template (name, pad, template); gst_object_unref (template); g_free (name); gst_pad_set_event_function (stream->srcpad, gst_rtspsrc_handle_src_event); gst_pad_set_query_function (stream->srcpad, gst_rtspsrc_handle_src_query); gst_pad_set_active (stream->srcpad, TRUE); gst_pad_sticky_events_foreach (pad, copy_sticky_events, stream->srcpad); gst_element_add_pad (GST_ELEMENT_CAST (src), stream->srcpad); if (all_added) { GST_DEBUG_OBJECT (src, "We added all streams"); /* when we get here, all stream are added and we can fire the no-more-pads * signal. */ gst_element_no_more_pads (GST_ELEMENT_CAST (src)); } return; /* ERRORS */ unknown_stream: { GST_DEBUG_OBJECT (src, "ignoring unknown stream"); GST_RTSP_STATE_UNLOCK (src); g_free (name); return; } } static GstCaps * stream_get_caps_for_pt (GstRTSPStream * stream, guint pt) { guint i, len; len = stream->ptmap->len; for (i = 0; i < len; i++) { PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); if (item->pt == pt) return item->caps; } return NULL; } static GstCaps * request_pt_map (GstElement * manager, guint session, guint pt, GstRTSPSrc * src) { GstRTSPStream *stream; GstCaps *caps; GST_DEBUG_OBJECT (src, "getting pt map for pt %d in session %d", pt, session); GST_RTSP_STATE_LOCK (src); stream = find_stream (src, &session, (gpointer) find_stream_by_id); if (!stream) goto unknown_stream; if ((caps = stream_get_caps_for_pt (stream, pt))) gst_caps_ref (caps); GST_RTSP_STATE_UNLOCK (src); return caps; unknown_stream: { GST_DEBUG_OBJECT (src, "unknown stream %d", session); GST_RTSP_STATE_UNLOCK (src); return NULL; } } static void gst_rtspsrc_do_stream_eos (GstRTSPSrc * src, GstRTSPStream * stream) { GST_DEBUG_OBJECT (src, "setting stream for session %u to EOS", stream->id); if (stream->eos) goto was_eos; stream->eos = TRUE; gst_rtspsrc_stream_push_event (src, stream, gst_event_new_eos ()); return; /* ERRORS */ was_eos: { GST_DEBUG_OBJECT (src, "stream for session %u was already EOS", stream->id); return; } } static void on_bye_ssrc (GObject * session, GObject * source, GstRTSPStream * stream) { GstRTSPSrc *src = stream->parent; guint ssrc; g_object_get (source, "ssrc", &ssrc, NULL); GST_DEBUG_OBJECT (src, "source %08x, stream %08x, session %u received BYE", ssrc, stream->ssrc, stream->id); if (ssrc == stream->ssrc) gst_rtspsrc_do_stream_eos (src, stream); } static void on_timeout (GObject * session, GObject * source, GstRTSPStream * stream) { GstRTSPSrc *src = stream->parent; guint ssrc; g_object_get (source, "ssrc", &ssrc, NULL); GST_WARNING_OBJECT (src, "source %08x, stream %08x in session %u timed out", ssrc, stream->ssrc, stream->id); if (ssrc == stream->ssrc) gst_rtspsrc_do_stream_eos (src, stream); } static void on_npt_stop (GstElement * rtpbin, guint session, guint ssrc, GstRTSPSrc * src) { GstRTSPStream *stream; GST_DEBUG_OBJECT (src, "source in session %u reached NPT stop", session); /* get stream for session */ stream = find_stream (src, &session, (gpointer) find_stream_by_id); if (stream) { gst_rtspsrc_do_stream_eos (src, stream); } } static void on_ssrc_active (GObject * session, GObject * source, GstRTSPStream * stream) { GST_DEBUG_OBJECT (stream->parent, "source in session %u is active", stream->id); } static void set_manager_buffer_mode (GstRTSPSrc * src) { GObjectClass *klass; if (src->manager == NULL) return; klass = G_OBJECT_GET_CLASS (G_OBJECT (src->manager)); if (!g_object_class_find_property (klass, "buffer-mode")) return; if (src->buffer_mode != BUFFER_MODE_AUTO) { g_object_set (src->manager, "buffer-mode", src->buffer_mode, NULL); return; } GST_DEBUG_OBJECT (src, "auto buffering mode, have clock %" GST_PTR_FORMAT, src->provided_clock); if (src->provided_clock) { GstClock *clock = gst_element_get_clock (GST_ELEMENT_CAST (src)); if (clock == src->provided_clock) { GST_DEBUG_OBJECT (src, "selected synced"); g_object_set (src->manager, "buffer-mode", BUFFER_MODE_SYNCED, NULL); if (clock) gst_object_unref (clock); return; } /* Otherwise fall-through and use another buffer mode */ if (clock) gst_object_unref (clock); } GST_DEBUG_OBJECT (src, "auto buffering mode"); if (src->use_buffering) { GST_DEBUG_OBJECT (src, "selected buffer"); g_object_set (src->manager, "buffer-mode", BUFFER_MODE_BUFFER, NULL); } else { GST_DEBUG_OBJECT (src, "selected slave"); g_object_set (src->manager, "buffer-mode", BUFFER_MODE_SLAVE, NULL); } } static GstCaps * request_key (GstElement * srtpdec, guint ssrc, GstRTSPStream * stream) { guint i; GstCaps *caps; GstMIKEYMessage *msg = stream->mikey; GST_DEBUG ("request key SSRC %u", ssrc); caps = gst_caps_ref (stream_get_caps_for_pt (stream, stream->default_pt)); caps = gst_caps_make_writable (caps); /* parse crypto sessions and look for the SSRC rollover counter */ msg = stream->mikey; for (i = 0; msg && i < gst_mikey_message_get_n_cs (msg); i++) { const GstMIKEYMapSRTP *map = gst_mikey_message_get_cs_srtp (msg, i); if (ssrc == map->ssrc) { gst_caps_set_simple (caps, "roc", G_TYPE_UINT, map->roc, NULL); break; } } return caps; } static GstElement * request_rtp_decoder (GstElement * rtpbin, guint session, GstRTSPStream * stream) { GST_DEBUG ("decoder session %u, stream %p, %d", session, stream, stream->id); if (stream->id != session) return NULL; if (stream->profile != GST_RTSP_PROFILE_SAVP && stream->profile != GST_RTSP_PROFILE_SAVPF) return NULL; if (stream->srtpdec == NULL) { gchar *name; name = g_strdup_printf ("srtpdec_%u", session); stream->srtpdec = gst_element_factory_make ("srtpdec", name); g_free (name); g_signal_connect (stream->srtpdec, "request-key", (GCallback) request_key, stream); } return gst_object_ref (stream->srtpdec); } static GstElement * request_rtcp_encoder (GstElement * rtpbin, guint session, GstRTSPStream * stream) { gchar *name; GstPad *pad; GST_DEBUG ("decoder session %u, stream %p, %d", session, stream, stream->id); if (stream->id != session) return NULL; if (stream->profile != GST_RTSP_PROFILE_SAVP && stream->profile != GST_RTSP_PROFILE_SAVPF) return NULL; if (stream->srtpenc == NULL) { GstStructure *s; name = g_strdup_printf ("srtpenc_%u", session); stream->srtpenc = gst_element_factory_make ("srtpenc", name); g_free (name); /* get RTCP crypto parameters from caps */ s = gst_caps_get_structure (stream->srtcpparams, 0); if (s) { GstBuffer *buf; const gchar *str; GType ciphertype, authtype; GValue rtcp_cipher = G_VALUE_INIT, rtcp_auth = G_VALUE_INIT; ciphertype = g_type_from_name ("GstSrtpCipherType"); authtype = g_type_from_name ("GstSrtpAuthType"); g_value_init (&rtcp_cipher, ciphertype); g_value_init (&rtcp_auth, authtype); str = gst_structure_get_string (s, "srtcp-cipher"); gst_value_deserialize (&rtcp_cipher, str); str = gst_structure_get_string (s, "srtcp-auth"); gst_value_deserialize (&rtcp_auth, str); gst_structure_get (s, "srtp-key", GST_TYPE_BUFFER, &buf, NULL); g_object_set_property (G_OBJECT (stream->srtpenc), "rtp-cipher", &rtcp_cipher); g_object_set_property (G_OBJECT (stream->srtpenc), "rtp-auth", &rtcp_auth); g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-cipher", &rtcp_cipher); g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-auth", &rtcp_auth); g_object_set (stream->srtpenc, "key", buf, NULL); g_value_unset (&rtcp_cipher); g_value_unset (&rtcp_auth); gst_buffer_unref (buf); } } name = g_strdup_printf ("rtcp_sink_%d", session); pad = gst_element_get_request_pad (stream->srtpenc, name); g_free (name); gst_object_unref (pad); return gst_object_ref (stream->srtpenc); } static GstElement * request_aux_receiver (GstElement * rtpbin, guint sessid, GstRTSPSrc * src) { GstElement *rtx, *bin; GstPad *pad; gchar *name; GstRTSPStream *stream; stream = find_stream (src, &sessid, (gpointer) find_stream_by_id); if (!stream) { GST_WARNING_OBJECT (src, "Stream %u not found", sessid); return NULL; } GST_INFO_OBJECT (src, "creating retransmision receiver for session %u " "with map %" GST_PTR_FORMAT, sessid, stream->rtx_pt_map); bin = gst_bin_new (NULL); rtx = gst_element_factory_make ("rtprtxreceive", NULL); g_object_set (rtx, "payload-type-map", stream->rtx_pt_map, NULL); gst_bin_add (GST_BIN (bin), rtx); pad = gst_element_get_static_pad (rtx, "src"); name = g_strdup_printf ("src_%u", sessid); gst_element_add_pad (bin, gst_ghost_pad_new (name, pad)); g_free (name); gst_object_unref (pad); pad = gst_element_get_static_pad (rtx, "sink"); name = g_strdup_printf ("sink_%u", sessid); gst_element_add_pad (bin, gst_ghost_pad_new (name, pad)); g_free (name); gst_object_unref (pad); return bin; } static void add_retransmission (GstRTSPSrc * src, GstRTSPTransport * transport) { GList *walk; guint signal_id; gboolean do_retransmission = FALSE; if (transport->trans != GST_RTSP_TRANS_RTP) return; if (transport->profile != GST_RTSP_PROFILE_AVPF && transport->profile != GST_RTSP_PROFILE_SAVPF) return; signal_id = g_signal_lookup ("request-aux-receiver", G_OBJECT_TYPE (src->manager)); /* there's already something connected */ if (g_signal_handler_find (src->manager, G_SIGNAL_MATCH_ID, signal_id, 0, NULL, NULL, NULL) != 0) { GST_DEBUG_OBJECT (src, "Not adding RTX AUX element as " "\"request-aux-receiver\" signal is " "already used by the application"); return; } /* build the retransmission payload type map */ for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; gboolean do_retransmission_stream = FALSE; int i; if (stream->rtx_pt_map) gst_structure_free (stream->rtx_pt_map); stream->rtx_pt_map = gst_structure_new_empty ("application/x-rtp-pt-map"); for (i = 0; i < stream->ptmap->len; i++) { PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); GstStructure *s = gst_caps_get_structure (item->caps, 0); const gchar *encoding; /* we only care about RTX streams */ if ((encoding = gst_structure_get_string (s, "encoding-name")) && g_strcmp0 (encoding, "RTX") == 0) { const gchar *stream_pt_s; gint rtx_pt; if (gst_structure_get_int (s, "payload", &rtx_pt) && (stream_pt_s = gst_structure_get_string (s, "apt"))) { if (rtx_pt != 0) { gst_structure_set (stream->rtx_pt_map, stream_pt_s, G_TYPE_UINT, rtx_pt, NULL); do_retransmission_stream = TRUE; } } } } if (do_retransmission_stream) { GST_DEBUG_OBJECT (src, "built retransmission payload map for stream " "id %i: %" GST_PTR_FORMAT, stream->id, stream->rtx_pt_map); do_retransmission = TRUE; } else { GST_DEBUG_OBJECT (src, "no retransmission payload map for stream " "id %i", stream->id); gst_structure_free (stream->rtx_pt_map); stream->rtx_pt_map = NULL; } } if (do_retransmission) { GST_DEBUG_OBJECT (src, "Enabling retransmissions"); g_object_set (src->manager, "do-retransmission", TRUE, NULL); /* enable RFC4588 retransmission handling by setting rtprtxreceive * as the "aux" element of rtpbin */ g_signal_connect (src->manager, "request-aux-receiver", (GCallback) request_aux_receiver, src); } else { GST_DEBUG_OBJECT (src, "Not enabling retransmissions as no stream had a retransmission payload map"); } } /* try to get and configure a manager */ static gboolean gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, GstRTSPTransport * transport) { const gchar *manager; gchar *name; GstStateChangeReturn ret; /* find a manager */ if (gst_rtsp_transport_get_manager (transport->trans, &manager, 0) < 0) goto no_manager; if (manager) { GST_DEBUG_OBJECT (src, "using manager %s", manager); /* configure the manager */ if (src->manager == NULL) { GObjectClass *klass; if (!(src->manager = gst_element_factory_make (manager, "manager"))) { /* fallback */ if (gst_rtsp_transport_get_manager (transport->trans, &manager, 1) < 0) goto no_manager; if (!manager) goto use_no_manager; if (!(src->manager = gst_element_factory_make (manager, "manager"))) goto manager_failed; } /* we manage this element */ gst_element_set_locked_state (src->manager, TRUE); gst_bin_add (GST_BIN_CAST (src), src->manager); ret = gst_element_set_state (src->manager, GST_STATE_PAUSED); if (ret == GST_STATE_CHANGE_FAILURE) goto start_manager_failure; g_object_set (src->manager, "latency", src->latency, NULL); klass = G_OBJECT_GET_CLASS (G_OBJECT (src->manager)); if (g_object_class_find_property (klass, "ntp-sync")) { g_object_set (src->manager, "ntp-sync", src->ntp_sync, NULL); } if (g_object_class_find_property (klass, "rfc7273-sync")) { g_object_set (src->manager, "rfc7273-sync", src->rfc7273_sync, NULL); } if (src->use_pipeline_clock) { if (g_object_class_find_property (klass, "use-pipeline-clock")) { g_object_set (src->manager, "use-pipeline-clock", TRUE, NULL); } } else { if (g_object_class_find_property (klass, "ntp-time-source")) { g_object_set (src->manager, "ntp-time-source", src->ntp_time_source, NULL); } } if (src->sdes && g_object_class_find_property (klass, "sdes")) { g_object_set (src->manager, "sdes", src->sdes, NULL); } if (g_object_class_find_property (klass, "drop-on-latency")) { g_object_set (src->manager, "drop-on-latency", src->drop_on_latency, NULL); } if (g_object_class_find_property (klass, "max-rtcp-rtp-time-diff")) { g_object_set (src->manager, "max-rtcp-rtp-time-diff", src->max_rtcp_rtp_time_diff, NULL); } /* buffer mode pauses are handled by adding offsets to buffer times, * but some depayloaders may have a hard time syncing output times * with such input times, e.g. container ones, most notably ASF */ /* TODO alternatives are having an event that indicates these shifts, * or having rtsp extensions provide suggestion on buffer mode */ /* valid duration implies not likely live pipeline, * so slaving in jitterbuffer does not make much sense * (and might mess things up due to bursts) */ if (GST_CLOCK_TIME_IS_VALID (src->segment.duration) && src->segment.duration && stream->container) { src->use_buffering = TRUE; } else { src->use_buffering = FALSE; } set_manager_buffer_mode (src); /* connect to signals */ GST_DEBUG_OBJECT (src, "connect to signals on session manager, stream %p", stream); src->manager_sig_id = g_signal_connect (src->manager, "pad-added", (GCallback) new_manager_pad, src); src->manager_ptmap_id = g_signal_connect (src->manager, "request-pt-map", (GCallback) request_pt_map, src); g_signal_connect (src->manager, "on-npt-stop", (GCallback) on_npt_stop, src); g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_NEW_MANAGER], 0, src->manager); if (src->do_retransmission) add_retransmission (src, transport); } g_signal_connect (src->manager, "request-rtp-decoder", (GCallback) request_rtp_decoder, stream); g_signal_connect (src->manager, "request-rtcp-decoder", (GCallback) request_rtp_decoder, stream); g_signal_connect (src->manager, "request-rtcp-encoder", (GCallback) request_rtcp_encoder, stream); /* we stream directly to the manager, get some pads. Each RTSP stream goes * into a separate RTP session. */ name = g_strdup_printf ("recv_rtp_sink_%u", stream->id); stream->channelpad[0] = gst_element_get_request_pad (src->manager, name); g_free (name); name = g_strdup_printf ("recv_rtcp_sink_%u", stream->id); stream->channelpad[1] = gst_element_get_request_pad (src->manager, name); g_free (name); /* now configure the bandwidth in the manager */ if (g_signal_lookup ("get-internal-session", G_OBJECT_TYPE (src->manager)) != 0) { GObject *rtpsession; g_signal_emit_by_name (src->manager, "get-internal-session", stream->id, &rtpsession); if (rtpsession) { GstRTPProfile rtp_profile; GST_INFO_OBJECT (src, "configure bandwidth in session %p", rtpsession); stream->session = rtpsession; if (stream->as_bandwidth != -1) { GST_INFO_OBJECT (src, "setting AS: %f", (gdouble) (stream->as_bandwidth * 1000)); g_object_set (rtpsession, "bandwidth", (gdouble) (stream->as_bandwidth * 1000), NULL); } if (stream->rr_bandwidth != -1) { GST_INFO_OBJECT (src, "setting RR: %u", stream->rr_bandwidth); g_object_set (rtpsession, "rtcp-rr-bandwidth", stream->rr_bandwidth, NULL); } if (stream->rs_bandwidth != -1) { GST_INFO_OBJECT (src, "setting RS: %u", stream->rs_bandwidth); g_object_set (rtpsession, "rtcp-rs-bandwidth", stream->rs_bandwidth, NULL); } switch (stream->profile) { case GST_RTSP_PROFILE_AVPF: rtp_profile = GST_RTP_PROFILE_AVPF; break; case GST_RTSP_PROFILE_SAVP: rtp_profile = GST_RTP_PROFILE_SAVP; break; case GST_RTSP_PROFILE_SAVPF: rtp_profile = GST_RTP_PROFILE_SAVPF; break; case GST_RTSP_PROFILE_AVP: default: rtp_profile = GST_RTP_PROFILE_AVP; break; } g_object_set (rtpsession, "rtp-profile", rtp_profile, NULL); g_object_set (rtpsession, "probation", src->probation, NULL); g_object_set (rtpsession, "internal-ssrc", stream->send_ssrc, NULL); g_signal_connect (rtpsession, "on-bye-ssrc", (GCallback) on_bye_ssrc, stream); g_signal_connect (rtpsession, "on-bye-timeout", (GCallback) on_timeout, stream); g_signal_connect (rtpsession, "on-timeout", (GCallback) on_timeout, stream); g_signal_connect (rtpsession, "on-ssrc-active", (GCallback) on_ssrc_active, stream); } } } use_no_manager: return TRUE; /* ERRORS */ no_manager: { GST_DEBUG_OBJECT (src, "cannot get a session manager"); return FALSE; } manager_failed: { GST_DEBUG_OBJECT (src, "no session manager element %s found", manager); return FALSE; } start_manager_failure: { GST_DEBUG_OBJECT (src, "could not start session manager"); return FALSE; } } /* free the UDP sources allocated when negotiating a transport. * This function is called when the server negotiated to a transport where the * UDP sources are not needed anymore, such as TCP or multicast. */ static void gst_rtspsrc_stream_free_udp (GstRTSPStream * stream) { gint i; for (i = 0; i < 2; i++) { if (stream->udpsrc[i]) { GST_DEBUG ("free UDP source %d for stream %p", i, stream); gst_element_set_state (stream->udpsrc[i], GST_STATE_NULL); gst_object_unref (stream->udpsrc[i]); stream->udpsrc[i] = NULL; } } } /* for TCP, create pads to send and receive data to and from the manager and to * intercept various events and queries */ static gboolean gst_rtspsrc_stream_configure_tcp (GstRTSPSrc * src, GstRTSPStream * stream, GstRTSPTransport * transport, GstPad ** outpad) { gchar *name; GstPadTemplate *template; GstPad *pad0, *pad1; /* configure for interleaved delivery, nothing needs to be done * here, the loop function will call the chain functions of the * session manager. */ stream->channel[0] = transport->interleaved.min; stream->channel[1] = transport->interleaved.max; GST_DEBUG_OBJECT (src, "stream %p on channels %d-%d", stream, stream->channel[0], stream->channel[1]); /* we can remove the allocated UDP ports now */ gst_rtspsrc_stream_free_udp (stream); /* no session manager, send data to srcpad directly */ if (!stream->channelpad[0]) { GST_DEBUG_OBJECT (src, "no manager, creating pad"); /* create a new pad we will use to stream to */ name = g_strdup_printf ("stream_%u", stream->id); template = gst_static_pad_template_get (&rtptemplate); stream->channelpad[0] = gst_pad_new_from_template (template, name); gst_object_unref (template); g_free (name); /* set caps and activate */ gst_pad_use_fixed_caps (stream->channelpad[0]); gst_pad_set_active (stream->channelpad[0], TRUE); *outpad = gst_object_ref (stream->channelpad[0]); } else { GST_DEBUG_OBJECT (src, "using manager source pad"); template = gst_static_pad_template_get (&anysrctemplate); /* allocate pads for sending the channel data into the manager */ pad0 = gst_pad_new_from_template (template, "internalsrc_0"); gst_pad_link_full (pad0, stream->channelpad[0], GST_PAD_LINK_CHECK_NOTHING); gst_object_unref (stream->channelpad[0]); stream->channelpad[0] = pad0; gst_pad_set_event_function (pad0, gst_rtspsrc_handle_internal_src_event); gst_pad_set_query_function (pad0, gst_rtspsrc_handle_internal_src_query); gst_pad_set_element_private (pad0, src); gst_pad_set_active (pad0, TRUE); if (stream->channelpad[1]) { /* if we have a sinkpad for the other channel, create a pad and link to the * manager. */ pad1 = gst_pad_new_from_template (template, "internalsrc_1"); gst_pad_set_event_function (pad1, gst_rtspsrc_handle_internal_src_event); gst_pad_link_full (pad1, stream->channelpad[1], GST_PAD_LINK_CHECK_NOTHING); gst_object_unref (stream->channelpad[1]); stream->channelpad[1] = pad1; gst_pad_set_active (pad1, TRUE); } gst_object_unref (template); } /* setup RTCP transport back to the server if we have to. */ if (src->manager && src->do_rtcp) { GstPad *pad; template = gst_static_pad_template_get (&anysinktemplate); stream->rtcppad = gst_pad_new_from_template (template, "internalsink_0"); gst_pad_set_chain_function (stream->rtcppad, gst_rtspsrc_sink_chain); gst_pad_set_element_private (stream->rtcppad, stream); gst_pad_set_active (stream->rtcppad, TRUE); /* get session RTCP pad */ name = g_strdup_printf ("send_rtcp_src_%u", stream->id); pad = gst_element_get_request_pad (src->manager, name); g_free (name); /* and link */ if (pad) { gst_pad_link_full (pad, stream->rtcppad, GST_PAD_LINK_CHECK_NOTHING); gst_object_unref (pad); } gst_object_unref (template); } return TRUE; } static void gst_rtspsrc_get_transport_info (GstRTSPSrc * src, GstRTSPStream * stream, GstRTSPTransport * transport, const gchar ** destination, gint * min, gint * max, guint * ttl) { if (transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP_MCAST) { if (destination) { if (!(*destination = transport->destination)) *destination = stream->destination; } if (min && max) { /* transport first */ *min = transport->port.min; *max = transport->port.max; if (*min == -1 && *max == -1) { /* then try from SDP */ if (stream->port != 0) { *min = stream->port; *max = stream->port + 1; } } } if (ttl) { if (!(*ttl = transport->ttl)) *ttl = stream->ttl; } } else { if (destination) { /* first take the source, then the endpoint to figure out where to send * the RTCP. */ if (!(*destination = transport->source)) { if (src->conninfo.connection) *destination = gst_rtsp_connection_get_ip (src->conninfo.connection); else if (stream->conninfo.connection) *destination = gst_rtsp_connection_get_ip (stream->conninfo.connection); } } if (min && max) { /* for unicast we only expect the ports here */ *min = transport->server_port.min; *max = transport->server_port.max; } } } /* For multicast create UDP sources and join the multicast group. */ static gboolean gst_rtspsrc_stream_configure_mcast (GstRTSPSrc * src, GstRTSPStream * stream, GstRTSPTransport * transport, GstPad ** outpad) { gchar *uri; const gchar *destination; gint min, max; GST_DEBUG_OBJECT (src, "creating UDP sources for multicast"); /* we can remove the allocated UDP ports now */ gst_rtspsrc_stream_free_udp (stream); gst_rtspsrc_get_transport_info (src, stream, transport, &destination, &min, &max, NULL); /* we need a destination now */ if (destination == NULL) goto no_destination; /* we really need ports now or we won't be able to receive anything at all */ if (min == -1 && max == -1) goto no_ports; GST_DEBUG_OBJECT (src, "have destination '%s' and ports (%d)-(%d)", destination, min, max); /* creating UDP source for RTP */ if (min != -1) { uri = g_strdup_printf ("udp://%s:%d", destination, min); stream->udpsrc[0] = gst_element_make_from_uri (GST_URI_SRC, uri, NULL, NULL); g_free (uri); if (stream->udpsrc[0] == NULL) goto no_element; /* take ownership */ gst_object_ref_sink (stream->udpsrc[0]); if (src->udp_buffer_size != 0) g_object_set (G_OBJECT (stream->udpsrc[0]), "buffer-size", src->udp_buffer_size, NULL); if (src->multi_iface != NULL) g_object_set (G_OBJECT (stream->udpsrc[0]), "multicast-iface", src->multi_iface, NULL); /* change state */ gst_element_set_locked_state (stream->udpsrc[0], TRUE); gst_element_set_state (stream->udpsrc[0], GST_STATE_READY); } /* creating another UDP source for RTCP */ if (max != -1) { GstCaps *caps; uri = g_strdup_printf ("udp://%s:%d", destination, max); stream->udpsrc[1] = gst_element_make_from_uri (GST_URI_SRC, uri, NULL, NULL); g_free (uri); if (stream->udpsrc[1] == NULL) goto no_element; if (stream->profile == GST_RTSP_PROFILE_SAVP || stream->profile == GST_RTSP_PROFILE_SAVPF) caps = gst_caps_new_empty_simple ("application/x-srtcp"); else caps = gst_caps_new_empty_simple ("application/x-rtcp"); g_object_set (stream->udpsrc[1], "caps", caps, NULL); gst_caps_unref (caps); /* take ownership */ gst_object_ref_sink (stream->udpsrc[1]); if (src->multi_iface != NULL) g_object_set (G_OBJECT (stream->udpsrc[0]), "multicast-iface", src->multi_iface, NULL); gst_element_set_state (stream->udpsrc[1], GST_STATE_READY); } return TRUE; /* ERRORS */ no_element: { GST_DEBUG_OBJECT (src, "no UDP source element found"); return FALSE; } no_destination: { GST_DEBUG_OBJECT (src, "no destination found"); return FALSE; } no_ports: { GST_DEBUG_OBJECT (src, "no ports found"); return FALSE; } } /* configure the remainder of the UDP ports */ static gboolean gst_rtspsrc_stream_configure_udp (GstRTSPSrc * src, GstRTSPStream * stream, GstRTSPTransport * transport, GstPad ** outpad) { /* we manage the UDP elements now. For unicast, the UDP sources where * allocated in the stream when we suggested a transport. */ if (stream->udpsrc[0]) { GstCaps *caps; gst_element_set_locked_state (stream->udpsrc[0], TRUE); gst_bin_add (GST_BIN_CAST (src), stream->udpsrc[0]); GST_DEBUG_OBJECT (src, "setting up UDP source"); /* configure a timeout on the UDP port. When the timeout message is * posted, we assume UDP transport is not possible. We reconnect using TCP * if we can. */ g_object_set (G_OBJECT (stream->udpsrc[0]), "timeout", src->udp_timeout * 1000, NULL); if ((caps = stream_get_caps_for_pt (stream, stream->default_pt))) g_object_set (stream->udpsrc[0], "caps", caps, NULL); /* get output pad of the UDP source. */ *outpad = gst_element_get_static_pad (stream->udpsrc[0], "src"); /* save it so we can unblock */ stream->blockedpad = *outpad; /* configure pad block on the pad. As soon as there is dataflow on the * UDP source, we know that UDP is not blocked by a firewall and we can * configure all the streams to let the application autoplug decoders. */ stream->blockid = gst_pad_add_probe (stream->blockedpad, GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST, pad_blocked, src, NULL); if (stream->channelpad[0]) { GST_DEBUG_OBJECT (src, "connecting UDP source 0 to manager"); /* configure for UDP delivery, we need to connect the UDP pads to * the session plugin. */ gst_pad_link_full (*outpad, stream->channelpad[0], GST_PAD_LINK_CHECK_NOTHING); gst_object_unref (*outpad); *outpad = NULL; /* we connected to pad-added signal to get pads from the manager */ } else { GST_DEBUG_OBJECT (src, "using UDP src pad as output"); } } /* RTCP port */ if (stream->udpsrc[1]) { GstCaps *caps; gst_element_set_locked_state (stream->udpsrc[1], TRUE); gst_bin_add (GST_BIN_CAST (src), stream->udpsrc[1]); if (stream->profile == GST_RTSP_PROFILE_SAVP || stream->profile == GST_RTSP_PROFILE_SAVPF) caps = gst_caps_new_empty_simple ("application/x-srtcp"); else caps = gst_caps_new_empty_simple ("application/x-rtcp"); g_object_set (stream->udpsrc[1], "caps", caps, NULL); gst_caps_unref (caps); if (stream->channelpad[1]) { GstPad *pad; GST_DEBUG_OBJECT (src, "connecting UDP source 1 to manager"); pad = gst_element_get_static_pad (stream->udpsrc[1], "src"); gst_pad_link_full (pad, stream->channelpad[1], GST_PAD_LINK_CHECK_NOTHING); gst_object_unref (pad); } else { /* leave unlinked */ } } return TRUE; } /* configure the UDP sink back to the server for status reports */ static gboolean gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src, GstRTSPStream * stream, GstRTSPTransport * transport) { GstPad *pad; gint rtp_port, rtcp_port; gboolean do_rtp, do_rtcp; const gchar *destination; gchar *uri, *name; guint ttl = 0; GSocket *socket; /* get transport info */ gst_rtspsrc_get_transport_info (src, stream, transport, &destination, &rtp_port, &rtcp_port, &ttl); /* see what we need to do */ do_rtp = (rtp_port != -1); /* it's possible that the server does not want us to send RTCP in which case * the port is -1 */ do_rtcp = (rtcp_port != -1 && src->manager != NULL && src->do_rtcp); /* we need a destination when we have RTP or RTCP ports */ if (destination == NULL && (do_rtp || do_rtcp)) goto no_destination; /* try to construct the fakesrc to the RTP port of the server to open up any * NAT firewalls */ if (do_rtp) { GST_DEBUG_OBJECT (src, "configure RTP UDP sink for %s:%d", destination, rtp_port); uri = g_strdup_printf ("udp://%s:%d", destination, rtp_port); stream->udpsink[0] = gst_element_make_from_uri (GST_URI_SINK, uri, NULL, NULL); g_free (uri); if (stream->udpsink[0] == NULL) goto no_sink_element; /* don't join multicast group, we will have the source socket do that */ /* no sync or async state changes needed */ g_object_set (G_OBJECT (stream->udpsink[0]), "auto-multicast", FALSE, "loop", FALSE, "sync", FALSE, "async", FALSE, NULL); if (ttl > 0) g_object_set (G_OBJECT (stream->udpsink[0]), "ttl", ttl, NULL); if (stream->udpsrc[0]) { /* configure socket, we give it the same UDP socket as the udpsrc for RTP * so that NAT firewalls will open a hole for us */ g_object_get (G_OBJECT (stream->udpsrc[0]), "used-socket", &socket, NULL); if (!socket) goto no_socket; GST_DEBUG_OBJECT (src, "RTP UDP src has sock %p", socket); /* configure socket and make sure udpsink does not close it when shutting * down, it belongs to udpsrc after all. */ g_object_set (G_OBJECT (stream->udpsink[0]), "socket", socket, "close-socket", FALSE, NULL); g_object_unref (socket); } /* the source for the dummy packets to open up NAT */ stream->fakesrc = gst_element_factory_make ("fakesrc", NULL); if (stream->fakesrc == NULL) goto no_fakesrc_element; /* random data in 5 buffers, a size of 200 bytes should be fine */ g_object_set (G_OBJECT (stream->fakesrc), "filltype", 3, "num-buffers", 5, "sizetype", 2, "sizemax", 200, "silent", TRUE, NULL); /* we don't want to consider this a sink */ GST_OBJECT_FLAG_UNSET (stream->udpsink[0], GST_ELEMENT_FLAG_SINK); /* keep everything locked */ gst_element_set_locked_state (stream->udpsink[0], TRUE); gst_element_set_locked_state (stream->fakesrc, TRUE); gst_object_ref (stream->udpsink[0]); gst_bin_add (GST_BIN_CAST (src), stream->udpsink[0]); gst_object_ref (stream->fakesrc); gst_bin_add (GST_BIN_CAST (src), stream->fakesrc); gst_element_link_pads_full (stream->fakesrc, "src", stream->udpsink[0], "sink", GST_PAD_LINK_CHECK_NOTHING); } if (do_rtcp) { GST_DEBUG_OBJECT (src, "configure RTCP UDP sink for %s:%d", destination, rtcp_port); uri = g_strdup_printf ("udp://%s:%d", destination, rtcp_port); stream->udpsink[1] = gst_element_make_from_uri (GST_URI_SINK, uri, NULL, NULL); g_free (uri); if (stream->udpsink[1] == NULL) goto no_sink_element; /* don't join multicast group, we will have the source socket do that */ /* no sync or async state changes needed */ g_object_set (G_OBJECT (stream->udpsink[1]), "auto-multicast", FALSE, "loop", FALSE, "sync", FALSE, "async", FALSE, NULL); if (ttl > 0) g_object_set (G_OBJECT (stream->udpsink[0]), "ttl", ttl, NULL); if (stream->udpsrc[1]) { /* configure socket, we give it the same UDP socket as the udpsrc for RTCP * because some servers check the port number of where it sends RTCP to identify * the RTCP packets it receives */ g_object_get (G_OBJECT (stream->udpsrc[1]), "used-socket", &socket, NULL); if (!socket) goto no_socket; GST_DEBUG_OBJECT (src, "RTCP UDP src has sock %p", socket); /* configure socket and make sure udpsink does not close it when shutting * down, it belongs to udpsrc after all. */ g_object_set (G_OBJECT (stream->udpsink[1]), "socket", socket, "close-socket", FALSE, NULL); g_object_unref (socket); } /* we don't want to consider this a sink */ GST_OBJECT_FLAG_UNSET (stream->udpsink[1], GST_ELEMENT_FLAG_SINK); /* we keep this playing always */ gst_element_set_locked_state (stream->udpsink[1], TRUE); gst_element_set_state (stream->udpsink[1], GST_STATE_PLAYING); gst_object_ref (stream->udpsink[1]); gst_bin_add (GST_BIN_CAST (src), stream->udpsink[1]); stream->rtcppad = gst_element_get_static_pad (stream->udpsink[1], "sink"); /* get session RTCP pad */ name = g_strdup_printf ("send_rtcp_src_%u", stream->id); pad = gst_element_get_request_pad (src->manager, name); g_free (name); /* and link */ if (pad) { gst_pad_link_full (pad, stream->rtcppad, GST_PAD_LINK_CHECK_NOTHING); gst_object_unref (pad); } } return TRUE; /* ERRORS */ no_destination: { GST_ERROR_OBJECT (src, "no destination address specified"); return FALSE; } no_sink_element: { GST_ERROR_OBJECT (src, "no UDP sink element found"); return FALSE; } no_fakesrc_element: { GST_ERROR_OBJECT (src, "no fakesrc element found"); return FALSE; } no_socket: { GST_ERROR_OBJECT (src, "failed to create socket"); return FALSE; } } /* sets up all elements needed for streaming over the specified transport. * Does not yet expose the element pads, this will be done when there is actuall * dataflow detected, which might never happen when UDP is blocked in a * firewall, for example. */ static gboolean gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, GstRTSPTransport * transport) { GstRTSPSrc *src; GstPad *outpad = NULL; GstPadTemplate *template; gchar *name; const gchar *media_type; guint i, len; src = stream->parent; GST_DEBUG_OBJECT (src, "configuring transport for stream %p", stream); /* get the proper media type for this stream now */ if (gst_rtsp_transport_get_media_type (transport, &media_type) < 0) goto unknown_transport; if (!media_type) goto unknown_transport; /* configure the final media type */ GST_DEBUG_OBJECT (src, "setting media type to %s", media_type); len = stream->ptmap->len; for (i = 0; i < len; i++) { GstStructure *s; PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); if (item->caps == NULL) continue; s = gst_caps_get_structure (item->caps, 0); gst_structure_set_name (s, media_type); /* set ssrc if known */ if (transport->ssrc) gst_structure_set (s, "ssrc", G_TYPE_UINT, transport->ssrc, NULL); } /* try to get and configure a manager, channelpad[0-1] will be configured with * the pads for the manager, or NULL when no manager is needed. */ if (!gst_rtspsrc_stream_configure_manager (src, stream, transport)) goto no_manager; switch (transport->lower_transport) { case GST_RTSP_LOWER_TRANS_TCP: if (!gst_rtspsrc_stream_configure_tcp (src, stream, transport, &outpad)) goto transport_failed; break; case GST_RTSP_LOWER_TRANS_UDP_MCAST: if (!gst_rtspsrc_stream_configure_mcast (src, stream, transport, &outpad)) goto transport_failed; /* fallthrough, the rest is the same for UDP and MCAST */ case GST_RTSP_LOWER_TRANS_UDP: if (!gst_rtspsrc_stream_configure_udp (src, stream, transport, &outpad)) goto transport_failed; /* configure udpsinks back to the server for RTCP messages and for the * dummy RTP messages to open NAT. */ if (!gst_rtspsrc_stream_configure_udp_sinks (src, stream, transport)) goto transport_failed; break; default: goto unknown_transport; } if (outpad) { GST_DEBUG_OBJECT (src, "creating ghostpad"); gst_pad_use_fixed_caps (outpad); /* create ghostpad, don't add just yet, this will be done when we activate * the stream. */ name = g_strdup_printf ("stream_%u", stream->id); template = gst_static_pad_template_get (&rtptemplate); stream->srcpad = gst_ghost_pad_new_from_template (name, outpad, template); gst_pad_set_event_function (stream->srcpad, gst_rtspsrc_handle_src_event); gst_pad_set_query_function (stream->srcpad, gst_rtspsrc_handle_src_query); gst_object_unref (template); g_free (name); gst_object_unref (outpad); } /* mark pad as ok */ stream->last_ret = GST_FLOW_OK; return TRUE; /* ERRORS */ transport_failed: { GST_DEBUG_OBJECT (src, "failed to configure transport"); return FALSE; } unknown_transport: { GST_DEBUG_OBJECT (src, "unknown transport"); return FALSE; } no_manager: { GST_DEBUG_OBJECT (src, "cannot get a session manager"); return FALSE; } } /* send a couple of dummy random packets on the receiver RTP port to the server, * this should make a firewall think we initiated the data transfer and * hopefully allow packets to go from the sender port to our RTP receiver port */ static gboolean gst_rtspsrc_send_dummy_packets (GstRTSPSrc * src) { GList *walk; if (src->nat_method != GST_RTSP_NAT_DUMMY) return TRUE; for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; if (stream->fakesrc && stream->udpsink[0]) { GST_DEBUG_OBJECT (src, "sending dummy packet to stream %p", stream); gst_element_set_state (stream->udpsink[0], GST_STATE_NULL); gst_element_set_state (stream->fakesrc, GST_STATE_NULL); gst_element_set_state (stream->udpsink[0], GST_STATE_PLAYING); gst_element_set_state (stream->fakesrc, GST_STATE_PLAYING); } } return TRUE; } /* Adds the source pads of all configured streams to the element. * This code is performed when we detected dataflow. * * We detect dataflow from either the _loop function or with pad probes on the * udp sources. */ static gboolean gst_rtspsrc_activate_streams (GstRTSPSrc * src) { GList *walk; GST_DEBUG_OBJECT (src, "activating streams"); for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; if (stream->udpsrc[0]) { /* remove timeout, we are streaming now and timeouts will be handled by * the session manager and jitter buffer */ g_object_set (G_OBJECT (stream->udpsrc[0]), "timeout", (guint64) 0, NULL); } if (stream->srcpad) { GST_DEBUG_OBJECT (src, "activating stream pad %p", stream); gst_pad_set_active (stream->srcpad, TRUE); /* if we don't have a session manager, set the caps now. If we have a * session, we will get a notification of the pad and the caps. */ if (!src->manager) { GstCaps *caps; caps = stream_get_caps_for_pt (stream, stream->default_pt); GST_DEBUG_OBJECT (src, "setting pad caps for stream %p", stream); gst_pad_set_caps (stream->srcpad, caps); } /* add the pad */ if (!stream->added) { GST_DEBUG_OBJECT (src, "adding stream pad %p", stream); gst_element_add_pad (GST_ELEMENT_CAST (src), stream->srcpad); stream->added = TRUE; } } } /* unblock all pads */ for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; if (stream->blockid) { GST_DEBUG_OBJECT (src, "unblocking stream pad %p", stream); gst_pad_remove_probe (stream->blockedpad, stream->blockid); stream->blockid = 0; } } return TRUE; } static void gst_rtspsrc_configure_caps (GstRTSPSrc * src, GstSegment * segment, gboolean reset_manager) { GList *walk; guint64 start, stop; gdouble play_speed, play_scale; GST_DEBUG_OBJECT (src, "configuring stream caps"); start = segment->position; stop = segment->duration; play_speed = segment->rate; play_scale = segment->applied_rate; for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; guint j, len; if (!stream->setup) continue; len = stream->ptmap->len; for (j = 0; j < len; j++) { GstCaps *caps; PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, j); if (item->caps == NULL) continue; caps = gst_caps_make_writable (item->caps); /* update caps */ if (stream->timebase != -1) gst_caps_set_simple (caps, "clock-base", G_TYPE_UINT, (guint) stream->timebase, NULL); if (stream->seqbase != -1) gst_caps_set_simple (caps, "seqnum-base", G_TYPE_UINT, (guint) stream->seqbase, NULL); gst_caps_set_simple (caps, "npt-start", G_TYPE_UINT64, start, NULL); if (stop != -1) gst_caps_set_simple (caps, "npt-stop", G_TYPE_UINT64, stop, NULL); gst_caps_set_simple (caps, "play-speed", G_TYPE_DOUBLE, play_speed, NULL); gst_caps_set_simple (caps, "play-scale", G_TYPE_DOUBLE, play_scale, NULL); item->caps = caps; GST_DEBUG_OBJECT (src, "stream %p, pt %d, caps %" GST_PTR_FORMAT, stream, item->pt, caps); if (item->pt == stream->default_pt) { if (stream->udpsrc[0]) g_object_set (stream->udpsrc[0], "caps", caps, NULL); stream->need_caps = TRUE; } } } if (reset_manager && src->manager) { GST_DEBUG_OBJECT (src, "clear session"); g_signal_emit_by_name (src->manager, "clear-pt-map", NULL); } } static GstFlowReturn gst_rtspsrc_combine_flows (GstRTSPSrc * src, GstRTSPStream * stream, GstFlowReturn ret) { GList *streams; /* store the value */ stream->last_ret = ret; /* if it's success we can return the value right away */ if (ret == GST_FLOW_OK) goto done; /* any other error that is not-linked can be returned right * away */ if (ret != GST_FLOW_NOT_LINKED) goto done; /* only return NOT_LINKED if all other pads returned NOT_LINKED */ for (streams = src->streams; streams; streams = g_list_next (streams)) { GstRTSPStream *ostream = (GstRTSPStream *) streams->data; ret = ostream->last_ret; /* some other return value (must be SUCCESS but we can return * other values as well) */ if (ret != GST_FLOW_NOT_LINKED) goto done; } /* if we get here, all other pads were unlinked and we return * NOT_LINKED then */ done: return ret; } static gboolean gst_rtspsrc_stream_push_event (GstRTSPSrc * src, GstRTSPStream * stream, GstEvent * event) { gboolean res = TRUE; /* only streams that have a connection to the outside world */ if (!stream->setup) goto done; if (stream->udpsrc[0]) { gst_event_ref (event); res = gst_element_send_event (stream->udpsrc[0], event); } else if (stream->channelpad[0]) { gst_event_ref (event); if (GST_PAD_IS_SRC (stream->channelpad[0])) res = gst_pad_push_event (stream->channelpad[0], event); else res = gst_pad_send_event (stream->channelpad[0], event); } if (stream->udpsrc[1]) { gst_event_ref (event); res &= gst_element_send_event (stream->udpsrc[1], event); } else if (stream->channelpad[1]) { gst_event_ref (event); if (GST_PAD_IS_SRC (stream->channelpad[1])) res &= gst_pad_push_event (stream->channelpad[1], event); else res &= gst_pad_send_event (stream->channelpad[1], event); } done: gst_event_unref (event); return res; } static gboolean gst_rtspsrc_push_event (GstRTSPSrc * src, GstEvent * event) { GList *streams; gboolean res = TRUE; for (streams = src->streams; streams; streams = g_list_next (streams)) { GstRTSPStream *ostream = (GstRTSPStream *) streams->data; gst_event_ref (event); res &= gst_rtspsrc_stream_push_event (src, ostream, event); } gst_event_unref (event); return res; } static GstRTSPResult gst_rtsp_conninfo_connect (GstRTSPSrc * src, GstRTSPConnInfo * info, gboolean async) { GstRTSPResult res; GstRTSPMessage response; gboolean retry = FALSE; memset (&response, 0, sizeof (response)); gst_rtsp_message_init (&response); do { if (info->connection == NULL) { if (info->url == NULL) { GST_DEBUG_OBJECT (src, "parsing uri (%s)...", info->location); if ((res = gst_rtsp_url_parse (info->location, &info->url)) < 0) goto parse_error; } /* create connection */ GST_DEBUG_OBJECT (src, "creating connection (%s)...", info->location); if ((res = gst_rtsp_connection_create (info->url, &info->connection)) < 0) goto could_not_create; if (retry) { gst_rtspsrc_setup_auth (src, &response); } g_free (info->url_str); info->url_str = gst_rtsp_url_get_request_uri (info->url); GST_DEBUG_OBJECT (src, "sanitized uri %s", info->url_str); if (info->url->transports & GST_RTSP_LOWER_TRANS_TLS) { if (!gst_rtsp_connection_set_tls_validation_flags (info->connection, src->tls_validation_flags)) GST_WARNING_OBJECT (src, "Unable to set TLS validation flags"); if (src->tls_database) gst_rtsp_connection_set_tls_database (info->connection, src->tls_database); if (src->tls_interaction) gst_rtsp_connection_set_tls_interaction (info->connection, src->tls_interaction); } if (info->url->transports & GST_RTSP_LOWER_TRANS_HTTP) gst_rtsp_connection_set_tunneled (info->connection, TRUE); if (src->proxy_host) { GST_DEBUG_OBJECT (src, "setting proxy %s:%d", src->proxy_host, src->proxy_port); gst_rtsp_connection_set_proxy (info->connection, src->proxy_host, src->proxy_port); } } if (!info->connected) { /* connect */ if (async) GST_ELEMENT_PROGRESS (src, CONTINUE, "connect", ("Connecting to %s", info->location)); GST_DEBUG_OBJECT (src, "connecting (%s)...", info->location); res = gst_rtsp_connection_connect_with_response (info->connection, src->ptcp_timeout, &response); if (response.type == GST_RTSP_MESSAGE_HTTP_RESPONSE && response.type_data.response.code == GST_RTSP_STS_UNAUTHORIZED) { gst_rtsp_conninfo_close (src, info, TRUE); if (!retry) retry = TRUE; else retry = FALSE; // we should not retry more than once } else { retry = FALSE; } if (res == GST_RTSP_OK) info->connected = TRUE; else if (!retry) goto could_not_connect; } } while (!info->connected && retry); gst_rtsp_message_unset (&response); return GST_RTSP_OK; /* ERRORS */ parse_error: { GST_ERROR_OBJECT (src, "No valid RTSP URL was provided"); gst_rtsp_message_unset (&response); return res; } could_not_create: { gchar *str = gst_rtsp_strresult (res); GST_ERROR_OBJECT (src, "Could not create connection. (%s)", str); g_free (str); gst_rtsp_message_unset (&response); return res; } could_not_connect: { gchar *str = gst_rtsp_strresult (res); GST_ERROR_OBJECT (src, "Could not connect to server. (%s)", str); g_free (str); gst_rtsp_message_unset (&response); return res; } } static GstRTSPResult gst_rtsp_conninfo_close (GstRTSPSrc * src, GstRTSPConnInfo * info, gboolean free) { GST_RTSP_STATE_LOCK (src); if (info->connected) { GST_DEBUG_OBJECT (src, "closing connection..."); gst_rtsp_connection_close (info->connection); info->connected = FALSE; } if (free && info->connection) { /* free connection */ GST_DEBUG_OBJECT (src, "freeing connection..."); gst_rtsp_connection_free (info->connection); info->connection = NULL; } GST_RTSP_STATE_UNLOCK (src); return GST_RTSP_OK; } static GstRTSPResult gst_rtsp_conninfo_reconnect (GstRTSPSrc * src, GstRTSPConnInfo * info, gboolean async) { GstRTSPResult res; GST_DEBUG_OBJECT (src, "reconnecting connection..."); gst_rtsp_conninfo_close (src, info, FALSE); res = gst_rtsp_conninfo_connect (src, info, async); return res; } static void gst_rtspsrc_connection_flush (GstRTSPSrc * src, gboolean flush) { GList *walk; GST_DEBUG_OBJECT (src, "set flushing %d", flush); GST_RTSP_STATE_LOCK (src); if (src->conninfo.connection && src->conninfo.flushing != flush) { GST_DEBUG_OBJECT (src, "connection flush"); gst_rtsp_connection_flush (src->conninfo.connection, flush); src->conninfo.flushing = flush; } for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; if (stream->conninfo.connection && stream->conninfo.flushing != flush) { GST_DEBUG_OBJECT (src, "stream %p flush", stream); gst_rtsp_connection_flush (stream->conninfo.connection, flush); stream->conninfo.flushing = flush; } } GST_RTSP_STATE_UNLOCK (src); } static GstRTSPResult gst_rtspsrc_init_request (GstRTSPSrc * src, GstRTSPMessage * msg, GstRTSPMethod method, const gchar * uri) { GstRTSPResult res; res = gst_rtsp_message_init_request (msg, method, uri); if (res < 0) return res; /* set user-agent */ if (src->user_agent) gst_rtsp_message_add_header (msg, GST_RTSP_HDR_USER_AGENT, src->user_agent); return res; } /* FIXME, handle server request, reply with OK, for now */ static GstRTSPResult gst_rtspsrc_handle_request (GstRTSPSrc * src, GstRTSPConnection * conn, GstRTSPMessage * request) { GstRTSPMessage response = { 0 }; GstRTSPResult res; GST_DEBUG_OBJECT (src, "got server request message"); if (src->debug) gst_rtsp_message_dump (request); res = gst_rtsp_ext_list_receive_request (src->extensions, request); if (res == GST_RTSP_ENOTIMPL) { /* default implementation, send OK */ GST_DEBUG_OBJECT (src, "prepare OK reply"); res = gst_rtsp_message_init_response (&response, GST_RTSP_STS_OK, "OK", request); if (res < 0) goto send_error; /* let app parse and reply */ g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_HANDLE_REQUEST], 0, request, &response); if (src->debug) gst_rtsp_message_dump (&response); res = gst_rtspsrc_connection_send (src, conn, &response, NULL); if (res < 0) goto send_error; gst_rtsp_message_unset (&response); } else if (res == GST_RTSP_EEOF) return res; return GST_RTSP_OK; /* ERRORS */ send_error: { gst_rtsp_message_unset (&response); return res; } } /* send server keep-alive */ static GstRTSPResult gst_rtspsrc_send_keep_alive (GstRTSPSrc * src) { GstRTSPMessage request = { 0 }; GstRTSPResult res; GstRTSPMethod method; const gchar *control; if (src->do_rtsp_keep_alive == FALSE) { GST_DEBUG_OBJECT (src, "do-rtsp-keep-alive is FALSE, not sending."); gst_rtsp_connection_reset_timeout (src->conninfo.connection); return GST_RTSP_OK; } GST_DEBUG_OBJECT (src, "creating server keep-alive"); /* find a method to use for keep-alive */ if (src->methods & GST_RTSP_GET_PARAMETER) method = GST_RTSP_GET_PARAMETER; else method = GST_RTSP_OPTIONS; control = get_aggregate_control (src); if (control == NULL) goto no_control; res = gst_rtspsrc_init_request (src, &request, method, control); if (res < 0) goto send_error; if (src->debug) gst_rtsp_message_dump (&request); res = gst_rtspsrc_connection_send (src, src->conninfo.connection, &request, NULL); if (res < 0) goto send_error; gst_rtsp_connection_reset_timeout (src->conninfo.connection); gst_rtsp_message_unset (&request); return GST_RTSP_OK; /* ERRORS */ no_control: { GST_WARNING_OBJECT (src, "no control url to send keepalive"); return GST_RTSP_OK; } send_error: { gchar *str = gst_rtsp_strresult (res); gst_rtsp_message_unset (&request); GST_ELEMENT_WARNING (src, RESOURCE, WRITE, (NULL), ("Could not send keep-alive. (%s)", str)); g_free (str); return res; } } static GstFlowReturn gst_rtspsrc_handle_data (GstRTSPSrc * src, GstRTSPMessage * message) { GstFlowReturn ret = GST_FLOW_OK; gint channel; GstRTSPStream *stream; GstPad *outpad = NULL; guint8 *data; guint size; GstBuffer *buf; gboolean is_rtcp; channel = message->type_data.data.channel; stream = find_stream (src, &channel, (gpointer) find_stream_by_channel); if (!stream) goto unknown_stream; if (channel == stream->channel[0]) { outpad = stream->channelpad[0]; is_rtcp = FALSE; } else if (channel == stream->channel[1]) { outpad = stream->channelpad[1]; is_rtcp = TRUE; } else { is_rtcp = FALSE; } /* take a look at the body to figure out what we have */ gst_rtsp_message_get_body (message, &data, &size); if (size < 2) goto invalid_length; /* channels are not correct on some servers, do extra check */ if (data[1] >= 200 && data[1] <= 204) { /* hmm RTCP message switch to the RTCP pad of the same stream. */ outpad = stream->channelpad[1]; is_rtcp = TRUE; } /* we have no clue what this is, just ignore then. */ if (outpad == NULL) goto unknown_stream; /* take the message body for further processing */ gst_rtsp_message_steal_body (message, &data, &size); /* strip the trailing \0 */ size -= 1; buf = gst_buffer_new (); gst_buffer_append_memory (buf, gst_memory_new_wrapped (0, data, size, 0, size, data, g_free)); /* don't need message anymore */ gst_rtsp_message_unset (message); GST_DEBUG_OBJECT (src, "pushing data of size %d on channel %d", size, channel); if (src->need_activate) { gchar *stream_id; GstEvent *event; GChecksum *cs; gchar *uri; GList *streams; guint group_id = gst_util_group_id_next (); /* generate an SHA256 sum of the URI */ cs = g_checksum_new (G_CHECKSUM_SHA256); uri = src->conninfo.location; g_checksum_update (cs, (const guchar *) uri, strlen (uri)); for (streams = src->streams; streams; streams = g_list_next (streams)) { GstRTSPStream *ostream = (GstRTSPStream *) streams->data; GstCaps *caps; stream_id = g_strdup_printf ("%s/%d", g_checksum_get_string (cs), ostream->id); event = gst_event_new_stream_start (stream_id); gst_event_set_group_id (event, group_id); g_free (stream_id); gst_rtspsrc_stream_push_event (src, ostream, event); if ((caps = stream_get_caps_for_pt (ostream, ostream->default_pt))) { /* only streams that have a connection to the outside world */ if (ostream->setup) { if (ostream->udpsrc[0]) { gst_element_send_event (ostream->udpsrc[0], gst_event_new_caps (caps)); } else if (ostream->channelpad[0]) { if (GST_PAD_IS_SRC (ostream->channelpad[0])) gst_pad_push_event (ostream->channelpad[0], gst_event_new_caps (caps)); else gst_pad_send_event (ostream->channelpad[0], gst_event_new_caps (caps)); } ostream->need_caps = FALSE; if (ostream->profile == GST_RTSP_PROFILE_SAVP || ostream->profile == GST_RTSP_PROFILE_SAVPF) caps = gst_caps_new_empty_simple ("application/x-srtcp"); else caps = gst_caps_new_empty_simple ("application/x-rtcp"); if (ostream->udpsrc[1]) { gst_element_send_event (ostream->udpsrc[1], gst_event_new_caps (caps)); } else if (ostream->channelpad[1]) { if (GST_PAD_IS_SRC (ostream->channelpad[1])) gst_pad_push_event (ostream->channelpad[1], gst_event_new_caps (caps)); else gst_pad_send_event (ostream->channelpad[1], gst_event_new_caps (caps)); } gst_caps_unref (caps); } } } g_checksum_free (cs); gst_rtspsrc_activate_streams (src); src->need_activate = FALSE; src->need_segment = TRUE; } if (src->base_time == -1) { /* Take current running_time. This timestamp will be put on * the first buffer of each stream because we are a live source and so we * timestamp with the running_time. When we are dealing with TCP, we also * only timestamp the first buffer (using the DISCONT flag) because a server * typically bursts data, for which we don't want to compensate by speeding * up the media. The other timestamps will be interpollated from this one * using the RTP timestamps. */ GST_OBJECT_LOCK (src); if (GST_ELEMENT_CLOCK (src)) { GstClockTime now; GstClockTime base_time; now = gst_clock_get_time (GST_ELEMENT_CLOCK (src)); base_time = GST_ELEMENT_CAST (src)->base_time; src->base_time = now - base_time; GST_DEBUG_OBJECT (src, "first buffer at time %" GST_TIME_FORMAT ", base %" GST_TIME_FORMAT, GST_TIME_ARGS (now), GST_TIME_ARGS (base_time)); } GST_OBJECT_UNLOCK (src); } /* If needed send a new segment, don't forget we are live and buffer are * timestamped with running time */ if (src->need_segment) { GstSegment segment; src->need_segment = FALSE; gst_segment_init (&segment, GST_FORMAT_TIME); gst_rtspsrc_push_event (src, gst_event_new_segment (&segment)); } if (stream->need_caps) { GstCaps *caps; if ((caps = stream_get_caps_for_pt (stream, stream->default_pt))) { /* only streams that have a connection to the outside world */ if (stream->setup) { /* Only need to update the TCP caps here, UDP is already handled */ if (stream->channelpad[0]) { if (GST_PAD_IS_SRC (stream->channelpad[0])) gst_pad_push_event (stream->channelpad[0], gst_event_new_caps (caps)); else gst_pad_send_event (stream->channelpad[0], gst_event_new_caps (caps)); } stream->need_caps = FALSE; } } stream->need_caps = FALSE; } if (stream->discont && !is_rtcp) { /* mark first RTP buffer as discont */ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); stream->discont = FALSE; /* first buffer gets the timestamp, other buffers are not timestamped and * their presentation time will be interpollated from the rtp timestamps. */ GST_DEBUG_OBJECT (src, "setting timestamp %" GST_TIME_FORMAT, GST_TIME_ARGS (src->base_time)); GST_BUFFER_TIMESTAMP (buf) = src->base_time; } /* chain to the peer pad */ if (GST_PAD_IS_SINK (outpad)) ret = gst_pad_chain (outpad, buf); else ret = gst_pad_push (outpad, buf); if (!is_rtcp) { /* combine all stream flows for the data transport */ ret = gst_rtspsrc_combine_flows (src, stream, ret); } return ret; /* ERRORS */ unknown_stream: { GST_DEBUG_OBJECT (src, "unknown stream on channel %d, ignored", channel); gst_rtsp_message_unset (message); return GST_FLOW_OK; } invalid_length: { GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), ("Short message received, ignoring.")); gst_rtsp_message_unset (message); return GST_FLOW_OK; } } static GstFlowReturn gst_rtspsrc_loop_interleaved (GstRTSPSrc * src) { GstRTSPMessage message = { 0 }; GstRTSPResult res; GstFlowReturn ret = GST_FLOW_OK; GTimeVal tv_timeout; while (TRUE) { /* get the next timeout interval */ gst_rtsp_connection_next_timeout (src->conninfo.connection, &tv_timeout); /* see if the timeout period expired */ if ((tv_timeout.tv_sec | tv_timeout.tv_usec) == 0) { GST_DEBUG_OBJECT (src, "timout, sending keep-alive"); /* send keep-alive, only act on interrupt, a warning will be posted for * other errors. */ if ((res = gst_rtspsrc_send_keep_alive (src)) == GST_RTSP_EINTR) goto interrupt; /* get new timeout */ gst_rtsp_connection_next_timeout (src->conninfo.connection, &tv_timeout); } GST_DEBUG_OBJECT (src, "doing receive with timeout %ld seconds, %ld usec", tv_timeout.tv_sec, tv_timeout.tv_usec); /* protect the connection with the connection lock so that we can see when * we are finished doing server communication */ res = gst_rtspsrc_connection_receive (src, src->conninfo.connection, &message, src->ptcp_timeout); switch (res) { case GST_RTSP_OK: GST_DEBUG_OBJECT (src, "we received a server message"); break; case GST_RTSP_EINTR: /* we got interrupted this means we need to stop */ goto interrupt; case GST_RTSP_ETIMEOUT: /* no reply, send keep alive */ GST_DEBUG_OBJECT (src, "timeout, sending keep-alive"); if ((res = gst_rtspsrc_send_keep_alive (src)) == GST_RTSP_EINTR) goto interrupt; continue; case GST_RTSP_EEOF: /* go EOS when the server closed the connection */ goto server_eof; default: goto receive_error; } switch (message.type) { case GST_RTSP_MESSAGE_REQUEST: /* server sends us a request message, handle it */ res = gst_rtspsrc_handle_request (src, src->conninfo.connection, &message); if (res == GST_RTSP_EEOF) goto server_eof; else if (res < 0) goto handle_request_failed; break; case GST_RTSP_MESSAGE_RESPONSE: /* we ignore response messages */ GST_DEBUG_OBJECT (src, "ignoring response message"); if (src->debug) gst_rtsp_message_dump (&message); break; case GST_RTSP_MESSAGE_DATA: GST_DEBUG_OBJECT (src, "got data message"); ret = gst_rtspsrc_handle_data (src, &message); if (ret != GST_FLOW_OK) goto handle_data_failed; break; default: GST_WARNING_OBJECT (src, "ignoring unknown message type %d", message.type); break; } } g_assert_not_reached (); /* ERRORS */ server_eof: { GST_DEBUG_OBJECT (src, "we got an eof from the server"); GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), ("The server closed the connection.")); src->conninfo.connected = FALSE; gst_rtsp_message_unset (&message); return GST_FLOW_EOS; } interrupt: { gst_rtsp_message_unset (&message); GST_DEBUG_OBJECT (src, "got interrupted"); return GST_FLOW_FLUSHING; } receive_error: { gchar *str = gst_rtsp_strresult (res); GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Could not receive message. (%s)", str)); g_free (str); gst_rtsp_message_unset (&message); return GST_FLOW_ERROR; } handle_request_failed: { gchar *str = gst_rtsp_strresult (res); GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not handle server message. (%s)", str)); g_free (str); gst_rtsp_message_unset (&message); return GST_FLOW_ERROR; } handle_data_failed: { GST_DEBUG_OBJECT (src, "could no handle data message"); return ret; } } static GstFlowReturn gst_rtspsrc_loop_udp (GstRTSPSrc * src) { GstRTSPResult res; GstRTSPMessage message = { 0 }; gint retry = 0; while (TRUE) { GTimeVal tv_timeout; /* get the next timeout interval */ gst_rtsp_connection_next_timeout (src->conninfo.connection, &tv_timeout); GST_DEBUG_OBJECT (src, "doing receive with timeout %d seconds", (gint) tv_timeout.tv_sec); gst_rtsp_message_unset (&message); /* we should continue reading the TCP socket because the server might * send us requests. When the session timeout expires, we need to send a * keep-alive request to keep the session open. */ res = gst_rtspsrc_connection_receive (src, src->conninfo.connection, &message, &tv_timeout); switch (res) { case GST_RTSP_OK: GST_DEBUG_OBJECT (src, "we received a server message"); break; case GST_RTSP_EINTR: /* we got interrupted, see what we have to do */ goto interrupt; case GST_RTSP_ETIMEOUT: /* send keep-alive, ignore the result, a warning will be posted. */ GST_DEBUG_OBJECT (src, "timeout, sending keep-alive"); if ((res = gst_rtspsrc_send_keep_alive (src)) == GST_RTSP_EINTR) goto interrupt; continue; case GST_RTSP_EEOF: /* server closed the connection. not very fatal for UDP, reconnect and * see what happens. */ GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), ("The server closed the connection.")); if (src->udp_reconnect) { if ((res = gst_rtsp_conninfo_reconnect (src, &src->conninfo, FALSE)) < 0) goto connect_error; } else { goto server_eof; } continue; case GST_RTSP_ENET: GST_DEBUG_OBJECT (src, "An ethernet problem occured."); default: GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), ("Unhandled return value %d.", res)); goto receive_error; } switch (message.type) { case GST_RTSP_MESSAGE_REQUEST: /* server sends us a request message, handle it */ res = gst_rtspsrc_handle_request (src, src->conninfo.connection, &message); if (res == GST_RTSP_EEOF) goto server_eof; else if (res < 0) goto handle_request_failed; break; case GST_RTSP_MESSAGE_RESPONSE: /* we ignore response and data messages */ GST_DEBUG_OBJECT (src, "ignoring response message"); if (src->debug) gst_rtsp_message_dump (&message); if (message.type_data.response.code == GST_RTSP_STS_UNAUTHORIZED) { GST_DEBUG_OBJECT (src, "but is Unauthorized response ..."); if (gst_rtspsrc_setup_auth (src, &message) && !(retry++)) { GST_DEBUG_OBJECT (src, "so retrying keep-alive"); if ((res = gst_rtspsrc_send_keep_alive (src)) == GST_RTSP_EINTR) goto interrupt; } } else { retry = 0; } break; case GST_RTSP_MESSAGE_DATA: /* we ignore response and data messages */ GST_DEBUG_OBJECT (src, "ignoring data message"); break; default: GST_WARNING_OBJECT (src, "ignoring unknown message type %d", message.type); break; } } g_assert_not_reached (); /* we get here when the connection got interrupted */ interrupt: { gst_rtsp_message_unset (&message); GST_DEBUG_OBJECT (src, "got interrupted"); return GST_FLOW_FLUSHING; } connect_error: { gchar *str = gst_rtsp_strresult (res); GstFlowReturn ret; src->conninfo.connected = FALSE; if (res != GST_RTSP_EINTR) { GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, (NULL), ("Could not connect to server. (%s)", str)); g_free (str); ret = GST_FLOW_ERROR; } else { ret = GST_FLOW_FLUSHING; } return ret; } receive_error: { gchar *str = gst_rtsp_strresult (res); GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Could not receive message. (%s)", str)); g_free (str); return GST_FLOW_ERROR; } handle_request_failed: { gchar *str = gst_rtsp_strresult (res); GstFlowReturn ret; gst_rtsp_message_unset (&message); if (res != GST_RTSP_EINTR) { GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not handle server message. (%s)", str)); g_free (str); ret = GST_FLOW_ERROR; } else { ret = GST_FLOW_FLUSHING; } return ret; } server_eof: { GST_DEBUG_OBJECT (src, "we got an eof from the server"); GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), ("The server closed the connection.")); src->conninfo.connected = FALSE; gst_rtsp_message_unset (&message); return GST_FLOW_EOS; } } static GstRTSPResult gst_rtspsrc_reconnect (GstRTSPSrc * src, gboolean async) { GstRTSPResult res = GST_RTSP_OK; gboolean restart; GST_DEBUG_OBJECT (src, "doing reconnect"); GST_OBJECT_LOCK (src); /* only restart when the pads were not yet activated, else we were * streaming over UDP */ restart = src->need_activate; GST_OBJECT_UNLOCK (src); /* no need to restart, we're done */ if (!restart) goto done; /* unless redirect, try tcp */ if (!src->need_redirect) src->cur_protocols = GST_RTSP_LOWER_TRANS_TCP; /* close and cleanup our state */ if ((res = gst_rtspsrc_close (src, async, FALSE)) < 0) goto done; /* unless redirect, see if we have TCP left to try. Also don't * try TCP when we were configured with an SDP. */ if (!src->need_redirect && (!(src->protocols & GST_RTSP_LOWER_TRANS_TCP) || src->from_sdp)) goto no_protocols; if (!src->need_redirect) { /* We post a warning message now to inform the user * that nothing happened. It's most likely a firewall thing. */ GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), ("Could not receive any UDP packets for %.4f seconds, maybe your " "firewall is blocking it. Retrying using a tcp connection.", gst_guint64_to_gdouble (src->udp_timeout) / 1000000.0)); } /* unless redirect, open new connection using tcp */ if (gst_rtspsrc_open (src, async) < 0) goto open_failed; /* start playback */ if (gst_rtspsrc_play (src, &src->segment, async) < 0) goto play_failed; done: return res; /* ERRORS */ no_protocols: { src->cur_protocols = 0; /* no transport possible, post an error and stop */ GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Could not receive any UDP packets for %.4f seconds, maybe your " "firewall is blocking it. No other protocols to try.", gst_guint64_to_gdouble (src->udp_timeout) / 1000000.0)); return GST_RTSP_ERROR; } open_failed: { GST_DEBUG_OBJECT (src, "open failed"); return GST_RTSP_OK; } play_failed: { GST_DEBUG_OBJECT (src, "play failed"); return GST_RTSP_OK; } } static void gst_rtspsrc_loop_start_cmd (GstRTSPSrc * src, gint cmd) { switch (cmd) { case CMD_OPEN: GST_ELEMENT_PROGRESS (src, START, "open", ("Opening Stream")); break; case CMD_PLAY: GST_ELEMENT_PROGRESS (src, START, "request", ("Sending PLAY request")); break; case CMD_PAUSE: GST_ELEMENT_PROGRESS (src, START, "request", ("Sending PAUSE request")); break; case CMD_CLOSE: GST_ELEMENT_PROGRESS (src, START, "close", ("Closing Stream")); break; default: break; } } static void gst_rtspsrc_loop_complete_cmd (GstRTSPSrc * src, gint cmd) { switch (cmd) { case CMD_OPEN: GST_ELEMENT_PROGRESS (src, COMPLETE, "open", ("Opened Stream")); break; case CMD_PLAY: GST_ELEMENT_PROGRESS (src, COMPLETE, "request", ("Sent PLAY request")); break; case CMD_PAUSE: GST_ELEMENT_PROGRESS (src, COMPLETE, "request", ("Sent PAUSE request")); break; case CMD_CLOSE: GST_ELEMENT_PROGRESS (src, COMPLETE, "close", ("Closed Stream")); break; default: break; } } static void gst_rtspsrc_loop_cancel_cmd (GstRTSPSrc * src, gint cmd) { switch (cmd) { case CMD_OPEN: GST_ELEMENT_PROGRESS (src, CANCELED, "open", ("Open canceled")); break; case CMD_PLAY: GST_ELEMENT_PROGRESS (src, CANCELED, "request", ("PLAY canceled")); break; case CMD_PAUSE: GST_ELEMENT_PROGRESS (src, CANCELED, "request", ("PAUSE canceled")); break; case CMD_CLOSE: GST_ELEMENT_PROGRESS (src, CANCELED, "close", ("Close canceled")); break; default: break; } } static void gst_rtspsrc_loop_error_cmd (GstRTSPSrc * src, gint cmd) { switch (cmd) { case CMD_OPEN: GST_ELEMENT_PROGRESS (src, ERROR, "open", ("Open failed")); break; case CMD_PLAY: GST_ELEMENT_PROGRESS (src, ERROR, "request", ("PLAY failed")); break; case CMD_PAUSE: GST_ELEMENT_PROGRESS (src, ERROR, "request", ("PAUSE failed")); break; case CMD_CLOSE: GST_ELEMENT_PROGRESS (src, ERROR, "close", ("Close failed")); break; default: break; } } static void gst_rtspsrc_loop_end_cmd (GstRTSPSrc * src, gint cmd, GstRTSPResult ret) { if (ret == GST_RTSP_OK) gst_rtspsrc_loop_complete_cmd (src, cmd); else if (ret == GST_RTSP_EINTR) gst_rtspsrc_loop_cancel_cmd (src, cmd); else gst_rtspsrc_loop_error_cmd (src, cmd); } static gboolean gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd, gint mask) { gint old; gboolean flushed = FALSE; /* start new request */ gst_rtspsrc_loop_start_cmd (src, cmd); GST_DEBUG_OBJECT (src, "sending cmd %s", cmd_to_string (cmd)); GST_OBJECT_LOCK (src); old = src->pending_cmd; if (old == CMD_RECONNECT) { GST_DEBUG_OBJECT (src, "ignore, we were reconnecting"); cmd = CMD_RECONNECT; } if (old != CMD_WAIT) { src->pending_cmd = CMD_WAIT; GST_OBJECT_UNLOCK (src); /* cancel previous request */ GST_DEBUG_OBJECT (src, "cancel previous request %s", cmd_to_string (old)); gst_rtspsrc_loop_cancel_cmd (src, old); GST_OBJECT_LOCK (src); } src->pending_cmd = cmd; /* interrupt if allowed */ if (src->busy_cmd & mask) { GST_DEBUG_OBJECT (src, "connection flush busy %s", cmd_to_string (src->busy_cmd)); gst_rtspsrc_connection_flush (src, TRUE); flushed = TRUE; } else { GST_DEBUG_OBJECT (src, "not interrupting busy cmd %s", cmd_to_string (src->busy_cmd)); } if (src->task) gst_task_start (src->task); GST_OBJECT_UNLOCK (src); return flushed; } static gboolean gst_rtspsrc_loop (GstRTSPSrc * src) { GstFlowReturn ret; if (!src->conninfo.connection || !src->conninfo.connected) goto no_connection; if (src->interleaved) ret = gst_rtspsrc_loop_interleaved (src); else ret = gst_rtspsrc_loop_udp (src); if (ret != GST_FLOW_OK) goto pause; return TRUE; /* ERRORS */ no_connection: { GST_WARNING_OBJECT (src, "we are not connected"); ret = GST_FLOW_FLUSHING; goto pause; } pause: { const gchar *reason = gst_flow_get_name (ret); GST_DEBUG_OBJECT (src, "pausing task, reason %s", reason); src->running = FALSE; if (ret == GST_FLOW_EOS) { /* perform EOS logic */ if (src->segment.flags & GST_SEEK_FLAG_SEGMENT) { gst_element_post_message (GST_ELEMENT_CAST (src), gst_message_new_segment_done (GST_OBJECT_CAST (src), src->segment.format, src->segment.position)); gst_rtspsrc_push_event (src, gst_event_new_segment_done (src->segment.format, src->segment.position)); } else { gst_rtspsrc_push_event (src, gst_event_new_eos ()); } } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) { /* for fatal errors we post an error message, post the error before the * EOS so the app knows about the error first. */ GST_ELEMENT_FLOW_ERROR (src, ret); gst_rtspsrc_push_event (src, gst_event_new_eos ()); } gst_rtspsrc_loop_send_cmd (src, CMD_WAIT, CMD_LOOP); return FALSE; } } #ifndef GST_DISABLE_GST_DEBUG static const gchar * gst_rtsp_auth_method_to_string (GstRTSPAuthMethod method) { gint index = 0; while (method != 0) { index++; method >>= 1; } switch (index) { case 0: return "None"; case 1: return "Basic"; case 2: return "Digest"; } return "Unknown"; } #endif static const gchar * gst_rtspsrc_skip_lws (const gchar * s) { while (g_ascii_isspace (*s)) s++; return s; } static const gchar * gst_rtspsrc_unskip_lws (const gchar * s, const gchar * start) { while (s > start && g_ascii_isspace (*(s - 1))) s--; return s; } static const gchar * gst_rtspsrc_skip_commas (const gchar * s) { /* The grammar allows for multiple commas */ while (g_ascii_isspace (*s) || *s == ',') s++; return s; } static const gchar * gst_rtspsrc_skip_item (const gchar * s) { gboolean quoted = FALSE; const gchar *start = s; /* A list item ends at the last non-whitespace character * before a comma which is not inside a quoted-string. Or at * the end of the string. */ while (*s) { if (*s == '"') quoted = !quoted; else if (quoted) { if (*s == '\\' && *(s + 1)) s++; } else { if (*s == ',') break; } s++; } return gst_rtspsrc_unskip_lws (s, start); } static void gst_rtsp_decode_quoted_string (gchar * quoted_string) { gchar *src, *dst; src = quoted_string + 1; dst = quoted_string; while (*src && *src != '"') { if (*src == '\\' && *(src + 1)) src++; *dst++ = *src++; } *dst = '\0'; } /* Extract the authentication tokens that the server provided for each method * into an array of structures and give those to the connection object. */ static void gst_rtspsrc_parse_digest_challenge (GstRTSPConnection * conn, const gchar * header, gboolean * stale) { GSList *list = NULL, *iter; const gchar *end; gchar *item, *eq, *name_end, *value; g_return_if_fail (stale != NULL); gst_rtsp_connection_clear_auth_params (conn); *stale = FALSE; /* Parse a header whose content is described by RFC2616 as * "#something", where "something" does not itself contain commas, * except as part of quoted-strings, into a list of allocated strings. */ header = gst_rtspsrc_skip_commas (header); while (*header) { end = gst_rtspsrc_skip_item (header); list = g_slist_prepend (list, g_strndup (header, end - header)); header = gst_rtspsrc_skip_commas (end); } if (!list) return; list = g_slist_reverse (list); for (iter = list; iter; iter = iter->next) { item = iter->data; eq = strchr (item, '='); if (eq) { name_end = (gchar *) gst_rtspsrc_unskip_lws (eq, item); if (name_end == item) { /* That's no good... */ g_free (item); continue; } *name_end = '\0'; value = (gchar *) gst_rtspsrc_skip_lws (eq + 1); if (*value == '"') gst_rtsp_decode_quoted_string (value); } else value = NULL; if (value && strcmp (item, "stale") == 0 && strcmp (value, "TRUE") == 0) *stale = TRUE; gst_rtsp_connection_set_auth_param (conn, item, value); g_free (item); } g_slist_free (list); } /* Parse a WWW-Authenticate Response header and determine the * available authentication methods * * This code should also cope with the fact that each WWW-Authenticate * header can contain multiple challenge methods + tokens * * At the moment, for Basic auth, we just do a minimal check and don't * even parse out the realm */ static void gst_rtspsrc_parse_auth_hdr (gchar * hdr, GstRTSPAuthMethod * methods, GstRTSPConnection * conn, gboolean * stale) { gchar *start; g_return_if_fail (hdr != NULL); g_return_if_fail (methods != NULL); g_return_if_fail (stale != NULL); /* Skip whitespace at the start of the string */ for (start = hdr; start[0] != '\0' && g_ascii_isspace (start[0]); start++); if (g_ascii_strncasecmp (start, "basic", 5) == 0) *methods |= GST_RTSP_AUTH_BASIC; else if (g_ascii_strncasecmp (start, "digest ", 7) == 0) { *methods |= GST_RTSP_AUTH_DIGEST; gst_rtspsrc_parse_digest_challenge (conn, &start[7], stale); } } /** * gst_rtspsrc_setup_auth: * @src: the rtsp source * * Configure a username and password and auth method on the * connection object based on a response we received from the * peer. * * Currently, this requires that a username and password were supplied * in the uri. In the future, they may be requested on demand by sending * a message up the bus. * * Returns: TRUE if authentication information could be set up correctly. */ static gboolean gst_rtspsrc_setup_auth (GstRTSPSrc * src, GstRTSPMessage * response) { gchar *user = NULL; gchar *pass = NULL; GstRTSPAuthMethod avail_methods = GST_RTSP_AUTH_NONE; GstRTSPAuthMethod method; GstRTSPResult auth_result; GstRTSPUrl *url; GstRTSPConnection *conn; gchar *hdr; gboolean stale = FALSE; conn = src->conninfo.connection; /* Identify the available auth methods and see if any are supported */ if (gst_rtsp_message_get_header (response, GST_RTSP_HDR_WWW_AUTHENTICATE, &hdr, 0) == GST_RTSP_OK) { gst_rtspsrc_parse_auth_hdr (hdr, &avail_methods, conn, &stale); } if (avail_methods == GST_RTSP_AUTH_NONE) goto no_auth_available; /* For digest auth, if the response indicates that the session * data are stale, we just update them in the connection object and * return TRUE to retry the request */ if (stale) src->tried_url_auth = FALSE; url = gst_rtsp_connection_get_url (conn); /* Do we have username and password available? */ if (url != NULL && !src->tried_url_auth && url->user != NULL && url->passwd != NULL) { user = url->user; pass = url->passwd; src->tried_url_auth = TRUE; GST_DEBUG_OBJECT (src, "Attempting authentication using credentials from the URL"); } else { user = src->user_id; pass = src->user_pw; GST_DEBUG_OBJECT (src, "Attempting authentication using credentials from the properties"); } /* FIXME: If the url didn't contain username and password or we tried them * already, request a username and passwd from the application via some kind * of credentials request message */ /* If we don't have a username and passwd at this point, bail out. */ if (user == NULL || pass == NULL) goto no_user_pass; /* Try to configure for each available authentication method, strongest to * weakest */ for (method = GST_RTSP_AUTH_MAX; method != GST_RTSP_AUTH_NONE; method >>= 1) { /* Check if this method is available on the server */ if ((method & avail_methods) == 0) continue; /* Pass the credentials to the connection to try on the next request */ auth_result = gst_rtsp_connection_set_auth (conn, method, user, pass); /* INVAL indicates an invalid username/passwd were supplied, so we'll just * ignore it and end up retrying later */ if (auth_result == GST_RTSP_OK || auth_result == GST_RTSP_EINVAL) { GST_DEBUG_OBJECT (src, "Attempting %s authentication", gst_rtsp_auth_method_to_string (method)); break; } } if (method == GST_RTSP_AUTH_NONE) goto no_auth_available; return TRUE; no_auth_available: { /* Output an error indicating that we couldn't connect because there were * no supported authentication protocols */ GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), ("No supported authentication protocol was found")); return FALSE; } no_user_pass: { /* We don't fire an error message, we just return FALSE and let the * normal NOT_AUTHORIZED error be propagated */ return FALSE; } } static GstRTSPResult gst_rtspsrc_try_send (GstRTSPSrc * src, GstRTSPConnection * conn, GstRTSPMessage * request, GstRTSPMessage * response, GstRTSPStatusCode * code) { GstRTSPResult res; GstRTSPStatusCode thecode; gchar *content_base = NULL; gint try = 0; again: if (!src->short_header) gst_rtsp_ext_list_before_send (src->extensions, request); GST_DEBUG_OBJECT (src, "sending message"); if (src->debug) gst_rtsp_message_dump (request); res = gst_rtspsrc_connection_send (src, conn, request, src->ptcp_timeout); if (res < 0) goto send_error; gst_rtsp_connection_reset_timeout (conn); next: res = gst_rtspsrc_connection_receive (src, conn, response, src->ptcp_timeout); if (res < 0) goto receive_error; if (src->debug) gst_rtsp_message_dump (response); switch (response->type) { case GST_RTSP_MESSAGE_REQUEST: res = gst_rtspsrc_handle_request (src, conn, response); if (res == GST_RTSP_EEOF) goto server_eof; else if (res < 0) goto handle_request_failed; goto next; case GST_RTSP_MESSAGE_RESPONSE: /* ok, a response is good */ GST_DEBUG_OBJECT (src, "received response message"); break; case GST_RTSP_MESSAGE_DATA: /* get next response */ GST_DEBUG_OBJECT (src, "handle data response message"); gst_rtspsrc_handle_data (src, response); goto next; default: GST_WARNING_OBJECT (src, "ignoring unknown message type %d", response->type); goto next; } thecode = response->type_data.response.code; GST_DEBUG_OBJECT (src, "got response message %d", thecode); /* if the caller wanted the result code, we store it. */ if (code) *code = thecode; /* If the request didn't succeed, bail out before doing any more */ if (thecode != GST_RTSP_STS_OK) return GST_RTSP_OK; /* store new content base if any */ gst_rtsp_message_get_header (response, GST_RTSP_HDR_CONTENT_BASE, &content_base, 0); if (content_base) { g_free (src->content_base); src->content_base = g_strdup (content_base); } gst_rtsp_ext_list_after_send (src->extensions, request, response); return GST_RTSP_OK; /* ERRORS */ send_error: { gchar *str = gst_rtsp_strresult (res); if (res != GST_RTSP_EINTR) { GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not send message. (%s)", str)); } else { GST_WARNING_OBJECT (src, "send interrupted"); } g_free (str); return res; } receive_error: { switch (res) { case GST_RTSP_EEOF: GST_WARNING_OBJECT (src, "server closed connection"); if ((try == 0) && !src->interleaved && src->udp_reconnect) { try++; /* if reconnect succeeds, try again */ if ((res = gst_rtsp_conninfo_reconnect (src, &src->conninfo, FALSE)) == 0) goto again; } /* only try once after reconnect, then fallthrough and error out */ default: { gchar *str = gst_rtsp_strresult (res); if (res != GST_RTSP_EINTR) { GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Could not receive message. (%s)", str)); } else { GST_WARNING_OBJECT (src, "receive interrupted"); } g_free (str); break; } } return res; } handle_request_failed: { /* ERROR was posted */ gst_rtsp_message_unset (response); return res; } server_eof: { GST_DEBUG_OBJECT (src, "we got an eof from the server"); GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), ("The server closed the connection.")); gst_rtsp_message_unset (response); return res; } } /** * gst_rtspsrc_send: * @src: the rtsp source * @conn: the connection to send on * @request: must point to a valid request * @response: must point to an empty #GstRTSPMessage * @code: an optional code result * * send @request and retrieve the response in @response. optionally @code can be * non-NULL in which case it will contain the status code of the response. * * If This function returns #GST_RTSP_OK, @response will contain a valid response * message that should be cleaned with gst_rtsp_message_unset() after usage. * * If @code is NULL, this function will return #GST_RTSP_ERROR (with an invalid * @response message) if the response code was not 200 (OK). * * If the attempt results in an authentication failure, then this will attempt * to retrieve authentication credentials via gst_rtspsrc_setup_auth and retry * the request. * * Returns: #GST_RTSP_OK if the processing was successful. */ static GstRTSPResult gst_rtspsrc_send (GstRTSPSrc * src, GstRTSPConnection * conn, GstRTSPMessage * request, GstRTSPMessage * response, GstRTSPStatusCode * code) { GstRTSPStatusCode int_code = GST_RTSP_STS_OK; GstRTSPResult res = GST_RTSP_ERROR; gint count; gboolean retry; GstRTSPMethod method = GST_RTSP_INVALID; count = 0; do { retry = FALSE; /* make sure we don't loop forever */ if (count++ > 8) break; /* save method so we can disable it when the server complains */ method = request->type_data.request.method; if ((res = gst_rtspsrc_try_send (src, conn, request, response, &int_code)) < 0) goto error; switch (int_code) { case GST_RTSP_STS_UNAUTHORIZED: case GST_RTSP_STS_NOT_FOUND: if (gst_rtspsrc_setup_auth (src, response)) { /* Try the request/response again after configuring the auth info * and loop again */ retry = TRUE; } break; default: break; } } while (retry == TRUE); /* If the user requested the code, let them handle errors, otherwise * post an error below */ if (code != NULL) *code = int_code; else if (int_code != GST_RTSP_STS_OK) goto error_response; return res; /* ERRORS */ error: { GST_DEBUG_OBJECT (src, "got error %d", res); return res; } error_response: { res = GST_RTSP_ERROR; switch (response->type_data.response.code) { case GST_RTSP_STS_NOT_FOUND: GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), ("%s", response->type_data.response.reason)); break; case GST_RTSP_STS_UNAUTHORIZED: GST_ELEMENT_ERROR (src, RESOURCE, NOT_AUTHORIZED, (NULL), ("%s", response->type_data.response.reason)); break; case GST_RTSP_STS_MOVED_PERMANENTLY: case GST_RTSP_STS_MOVE_TEMPORARILY: { gchar *new_location; GstRTSPLowerTrans transports; GST_DEBUG_OBJECT (src, "got redirection"); /* if we don't have a Location Header, we must error */ if (gst_rtsp_message_get_header (response, GST_RTSP_HDR_LOCATION, &new_location, 0) < 0) break; /* When we receive a redirect result, we go back to the INIT state after * parsing the new URI. The caller should do the needed steps to issue * a new setup when it detects this state change. */ GST_DEBUG_OBJECT (src, "redirection to %s", new_location); /* save current transports */ if (src->conninfo.url) transports = src->conninfo.url->transports; else transports = GST_RTSP_LOWER_TRANS_UNKNOWN; gst_rtspsrc_uri_set_uri (GST_URI_HANDLER (src), new_location, NULL); /* set old transports */ if (src->conninfo.url && transports != GST_RTSP_LOWER_TRANS_UNKNOWN) src->conninfo.url->transports = transports; src->need_redirect = TRUE; src->state = GST_RTSP_STATE_INIT; res = GST_RTSP_OK; break; } case GST_RTSP_STS_NOT_ACCEPTABLE: case GST_RTSP_STS_NOT_IMPLEMENTED: case GST_RTSP_STS_METHOD_NOT_ALLOWED: GST_WARNING_OBJECT (src, "got NOT IMPLEMENTED, disable method %s", gst_rtsp_method_as_text (method)); src->methods &= ~method; res = GST_RTSP_OK; break; default: GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Got error response: %d (%s).", response->type_data.response.code, response->type_data.response.reason)); break; } /* if we return ERROR we should unset the response ourselves */ if (res == GST_RTSP_ERROR) gst_rtsp_message_unset (response); return res; } } static GstRTSPResult gst_rtspsrc_send_cb (GstRTSPExtension * ext, GstRTSPMessage * request, GstRTSPMessage * response, GstRTSPSrc * src) { return gst_rtspsrc_send (src, src->conninfo.connection, request, response, NULL); } /* parse the response and collect all the supported methods. We need this * information so that we don't try to send an unsupported request to the * server. */ static gboolean gst_rtspsrc_parse_methods (GstRTSPSrc * src, GstRTSPMessage * response) { GstRTSPHeaderField field; gchar *respoptions; gint indx = 0; /* reset supported methods */ src->methods = 0; /* Try Allow Header first */ field = GST_RTSP_HDR_ALLOW; while (TRUE) { respoptions = NULL; gst_rtsp_message_get_header (response, field, &respoptions, indx); if (indx == 0 && !respoptions) { /* if no Allow header was found then try the Public header... */ field = GST_RTSP_HDR_PUBLIC; gst_rtsp_message_get_header (response, field, &respoptions, indx); } if (!respoptions) break; src->methods |= gst_rtsp_options_from_text (respoptions); indx++; } if (src->methods == 0) { /* neither Allow nor Public are required, assume the server supports * at least DESCRIBE, SETUP, we always assume it supports PLAY as * well. */ GST_DEBUG_OBJECT (src, "could not get OPTIONS"); src->methods = GST_RTSP_DESCRIBE | GST_RTSP_SETUP; } /* always assume PLAY, FIXME, extensions should be able to override * this */ src->methods |= GST_RTSP_PLAY; /* also assume it will support Range */ src->seekable = TRUE; /* we need describe and setup */ if (!(src->methods & GST_RTSP_DESCRIBE)) goto no_describe; if (!(src->methods & GST_RTSP_SETUP)) goto no_setup; return TRUE; /* ERRORS */ no_describe: { GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), ("Server does not support DESCRIBE.")); return FALSE; } no_setup: { GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), ("Server does not support SETUP.")); return FALSE; } } /* masks to be kept in sync with the hardcoded protocol order of preference * in code below */ static const guint protocol_masks[] = { GST_RTSP_LOWER_TRANS_UDP, GST_RTSP_LOWER_TRANS_UDP_MCAST, GST_RTSP_LOWER_TRANS_TCP, 0 }; static GstRTSPResult gst_rtspsrc_create_transports_string (GstRTSPSrc * src, GstRTSPLowerTrans protocols, GstRTSPProfile profile, gchar ** transports) { GstRTSPResult res; GString *result; gboolean add_udp_str; *transports = NULL; res = gst_rtsp_ext_list_get_transports (src->extensions, protocols, transports); if (res < 0) goto failed; GST_DEBUG_OBJECT (src, "got transports %s", GST_STR_NULL (*transports)); /* extension listed transports, use those */ if (*transports != NULL) return GST_RTSP_OK; /* it's the default */ add_udp_str = FALSE; /* the default RTSP transports */ result = g_string_new ("RTP"); switch (profile) { case GST_RTSP_PROFILE_AVP: g_string_append (result, "/AVP"); break; case GST_RTSP_PROFILE_SAVP: g_string_append (result, "/SAVP"); break; case GST_RTSP_PROFILE_AVPF: g_string_append (result, "/AVPF"); break; case GST_RTSP_PROFILE_SAVPF: g_string_append (result, "/SAVPF"); break; default: break; } if (protocols & GST_RTSP_LOWER_TRANS_UDP) { GST_DEBUG_OBJECT (src, "adding UDP unicast"); if (add_udp_str) g_string_append (result, "/UDP"); g_string_append (result, ";unicast;client_port=%%u1-%%u2"); } else if (protocols & GST_RTSP_LOWER_TRANS_UDP_MCAST) { GST_DEBUG_OBJECT (src, "adding UDP multicast"); /* we don't have to allocate any UDP ports yet, if the selected transport * turns out to be multicast we can create them and join the multicast * group indicated in the transport reply */ if (add_udp_str) g_string_append (result, "/UDP"); g_string_append (result, ";multicast"); if (src->next_port_num != 0) { if (src->client_port_range.max > 0 && src->next_port_num >= src->client_port_range.max) goto no_ports; g_string_append_printf (result, ";client_port=%d-%d", src->next_port_num, src->next_port_num + 1); } } else if (protocols & GST_RTSP_LOWER_TRANS_TCP) { GST_DEBUG_OBJECT (src, "adding TCP"); g_string_append (result, "/TCP;unicast;interleaved=%%i1-%%i2"); } *transports = g_string_free (result, FALSE); GST_DEBUG_OBJECT (src, "prepared transports %s", GST_STR_NULL (*transports)); return GST_RTSP_OK; /* ERRORS */ failed: { GST_ERROR ("extension gave error %d", res); return res; } no_ports: { GST_ERROR ("no more ports available"); return GST_RTSP_ERROR; } } static GstRTSPResult gst_rtspsrc_prepare_transports (GstRTSPStream * stream, gchar ** transports, gint orig_rtpport, gint orig_rtcpport) { GstRTSPSrc *src; gint nr_udp, nr_int; gchar *next, *p; gint rtpport = 0, rtcpport = 0; GString *str; src = stream->parent; /* find number of placeholders first */ if (strstr (*transports, "%%i2")) nr_int = 2; else if (strstr (*transports, "%%i1")) nr_int = 1; else nr_int = 0; if (strstr (*transports, "%%u2")) nr_udp = 2; else if (strstr (*transports, "%%u1")) nr_udp = 1; else nr_udp = 0; if (nr_udp == 0 && nr_int == 0) goto done; if (nr_udp > 0) { if (!orig_rtpport || !orig_rtcpport) { if (!gst_rtspsrc_alloc_udp_ports (stream, &rtpport, &rtcpport)) goto failed; } else { rtpport = orig_rtpport; rtcpport = orig_rtcpport; } } str = g_string_new (""); p = *transports; while ((next = strstr (p, "%%"))) { g_string_append_len (str, p, next - p); if (next[2] == 'u') { if (next[3] == '1') g_string_append_printf (str, "%d", rtpport); else if (next[3] == '2') g_string_append_printf (str, "%d", rtcpport); } if (next[2] == 'i') { if (next[3] == '1') g_string_append_printf (str, "%d", src->free_channel); else if (next[3] == '2') g_string_append_printf (str, "%d", src->free_channel + 1); } p = next + 4; } /* append final part */ g_string_append (str, p); g_free (*transports); *transports = g_string_free (str, FALSE); done: return GST_RTSP_OK; /* ERRORS */ failed: { GST_ERROR ("failed to allocate udp ports"); return GST_RTSP_ERROR; } } static GstCaps * signal_get_srtcp_params (GstRTSPSrc * src, GstRTSPStream * stream) { GstCaps *caps = NULL; g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_REQUEST_RTCP_KEY], 0, stream->id, &caps); if (caps != NULL) GST_DEBUG_OBJECT (src, "SRTP parameters received"); return caps; } static GstCaps * default_srtcp_params (void) { guint i; GstCaps *caps; GstBuffer *buf; guint8 *key_data; #define KEY_SIZE 30 guint data_size = GST_ROUND_UP_4 (KEY_SIZE); /* create a random key */ key_data = g_malloc (data_size); for (i = 0; i < data_size; i += 4) GST_WRITE_UINT32_BE (key_data + i, g_random_int ()); buf = gst_buffer_new_wrapped (key_data, KEY_SIZE); caps = gst_caps_new_simple ("application/x-srtcp", "srtp-key", GST_TYPE_BUFFER, buf, "srtp-cipher", G_TYPE_STRING, "aes-128-icm", "srtp-auth", G_TYPE_STRING, "hmac-sha1-80", "srtcp-cipher", G_TYPE_STRING, "aes-128-icm", "srtcp-auth", G_TYPE_STRING, "hmac-sha1-80", NULL); gst_buffer_unref (buf); return caps; } static gchar * gst_rtspsrc_stream_make_keymgmt (GstRTSPSrc * src, GstRTSPStream * stream) { gchar *base64, *result = NULL; GstMIKEYMessage *mikey_msg; stream->srtcpparams = signal_get_srtcp_params (src, stream); if (stream->srtcpparams == NULL) stream->srtcpparams = default_srtcp_params (); mikey_msg = gst_mikey_message_new_from_caps (stream->srtcpparams); if (mikey_msg) { /* add policy '0' for our SSRC */ gst_mikey_message_add_cs_srtp (mikey_msg, 0, stream->send_ssrc, 0); base64 = gst_mikey_message_base64_encode (mikey_msg); gst_mikey_message_unref (mikey_msg); if (base64) { result = gst_sdp_make_keymgmt (stream->conninfo.location, base64); g_free (base64); } } return result; } /* Perform the SETUP request for all the streams. * * We ask the server for a specific transport, which initially includes all the * ones we can support (UDP/TCP/MULTICAST). For the UDP transport we allocate * two local UDP ports that we send to the server. * * Once the server replied with a transport, we configure the other streams * with the same transport. * * This function will also configure the stream for the selected transport, * which basically means creating the pipeline. */ static GstRTSPResult gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) { GList *walk; GstRTSPResult res = GST_RTSP_ERROR; GstRTSPMessage request = { 0 }; GstRTSPMessage response = { 0 }; GstRTSPStream *stream = NULL; GstRTSPLowerTrans protocols; GstRTSPStatusCode code; gboolean unsupported_real = FALSE; gint rtpport, rtcpport; GstRTSPUrl *url; gchar *hval; if (src->conninfo.connection) { url = gst_rtsp_connection_get_url (src->conninfo.connection); /* we initially allow all configured lower transports. based on the URL * transports and the replies from the server we narrow them down. */ protocols = url->transports & src->cur_protocols; } else { url = NULL; protocols = src->cur_protocols; } if (protocols == 0) goto no_protocols; /* reset some state */ src->free_channel = 0; src->interleaved = FALSE; src->need_activate = FALSE; /* keep track of next port number, 0 is random */ src->next_port_num = src->client_port_range.min; rtpport = rtcpport = 0; if (G_UNLIKELY (src->streams == NULL)) goto no_streams; for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPConnection *conn; gchar *transports; gint retry = 0; guint mask = 0; gboolean selected; GstCaps *caps; stream = (GstRTSPStream *) walk->data; caps = stream_get_caps_for_pt (stream, stream->default_pt); if (caps == NULL) { GST_DEBUG_OBJECT (src, "skipping stream %p, no caps", stream); continue; } if (stream->skipped) { GST_DEBUG_OBJECT (src, "skipping stream %p", stream); continue; } /* see if we need to configure this stream */ if (!gst_rtsp_ext_list_configure_stream (src->extensions, caps)) { GST_DEBUG_OBJECT (src, "skipping stream %p, disabled by extension", stream); continue; } g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_SELECT_STREAM], 0, stream->id, caps, &selected); if (!selected) { GST_DEBUG_OBJECT (src, "skipping stream %p, disabled by signal", stream); continue; } /* merge/overwrite global caps */ if (caps) { guint j, num; GstStructure *s; s = gst_caps_get_structure (caps, 0); num = gst_structure_n_fields (src->props); for (j = 0; j < num; j++) { const gchar *name; const GValue *val; name = gst_structure_nth_field_name (src->props, j); val = gst_structure_get_value (src->props, name); gst_structure_set_value (s, name, val); GST_DEBUG_OBJECT (src, "copied %s", name); } } /* skip setup if we have no URL for it */ if (stream->conninfo.location == NULL) { GST_DEBUG_OBJECT (src, "skipping stream %p, no setup", stream); continue; } if (src->conninfo.connection == NULL) { if (!gst_rtsp_conninfo_connect (src, &stream->conninfo, async)) { GST_DEBUG_OBJECT (src, "skipping stream %p, failed to connect", stream); continue; } conn = stream->conninfo.connection; } else { conn = src->conninfo.connection; } GST_DEBUG_OBJECT (src, "doing setup of stream %p with %s", stream, stream->conninfo.location); /* if we have a multicast connection, only suggest multicast from now on */ if (stream->is_multicast) protocols &= GST_RTSP_LOWER_TRANS_UDP_MCAST; next_protocol: /* first selectable protocol */ while (protocol_masks[mask] && !(protocols & protocol_masks[mask])) mask++; if (!protocol_masks[mask]) goto no_protocols; retry: GST_DEBUG_OBJECT (src, "protocols = 0x%x, protocol mask = 0x%x", protocols, protocol_masks[mask]); /* create a string with first transport in line */ transports = NULL; res = gst_rtspsrc_create_transports_string (src, protocols & protocol_masks[mask], stream->profile, &transports); if (res < 0 || transports == NULL) goto setup_transport_failed; if (strlen (transports) == 0) { g_free (transports); GST_DEBUG_OBJECT (src, "no transports found"); mask++; goto next_protocol; } GST_DEBUG_OBJECT (src, "replace ports in %s", GST_STR_NULL (transports)); /* replace placeholders with real values, this function will optionally * allocate UDP ports and other info needed to execute the setup request */ res = gst_rtspsrc_prepare_transports (stream, &transports, retry > 0 ? rtpport : 0, retry > 0 ? rtcpport : 0); if (res < 0) { g_free (transports); goto setup_transport_failed; } GST_DEBUG_OBJECT (src, "transport is now %s", GST_STR_NULL (transports)); /* create SETUP request */ res = gst_rtspsrc_init_request (src, &request, GST_RTSP_SETUP, stream->conninfo.location); if (res < 0) { g_free (transports); goto create_request_failed; } /* select transport */ gst_rtsp_message_take_header (&request, GST_RTSP_HDR_TRANSPORT, transports); /* set up keys */ if (stream->profile == GST_RTSP_PROFILE_SAVP || stream->profile == GST_RTSP_PROFILE_SAVPF) { hval = gst_rtspsrc_stream_make_keymgmt (src, stream); gst_rtsp_message_take_header (&request, GST_RTSP_HDR_KEYMGMT, hval); } /* if the user wants a non default RTP packet size we add the blocksize * parameter */ if (src->rtp_blocksize > 0) { hval = g_strdup_printf ("%d", src->rtp_blocksize); gst_rtsp_message_take_header (&request, GST_RTSP_HDR_BLOCKSIZE, hval); } if (async) GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("SETUP stream %d", stream->id)); /* handle the code ourselves */ res = gst_rtspsrc_send (src, conn, &request, &response, &code); if (res < 0) goto send_error; switch (code) { case GST_RTSP_STS_OK: break; case GST_RTSP_STS_UNSUPPORTED_TRANSPORT: gst_rtsp_message_unset (&request); gst_rtsp_message_unset (&response); /* cleanup of leftover transport */ gst_rtspsrc_stream_free_udp (stream); /* MS WMServer RTSP MUST use same UDP pair in all SETUP requests; * we might be in this case */ if (stream->container && rtpport && rtcpport && !retry) { GST_DEBUG_OBJECT (src, "retrying with original port pair %u-%u", rtpport, rtcpport); retry++; goto retry; } /* this transport did not go down well, but we may have others to try * that we did not send yet, try those and only give up then * but not without checking for lost cause/extension so we can * post a nicer/more useful error message later */ if (!unsupported_real) unsupported_real = stream->is_real; /* select next available protocol, give up on this stream if none */ mask++; while (protocol_masks[mask] && !(protocols & protocol_masks[mask])) mask++; if (!protocol_masks[mask] || unsupported_real) continue; else goto retry; default: /* cleanup of leftover transport and move to the next stream */ gst_rtspsrc_stream_free_udp (stream); goto response_error; } /* parse response transport */ { gchar *resptrans = NULL; GstRTSPTransport transport = { 0 }; gst_rtsp_message_get_header (&response, GST_RTSP_HDR_TRANSPORT, &resptrans, 0); if (!resptrans) { gst_rtspsrc_stream_free_udp (stream); goto no_transport; } /* parse transport, go to next stream on parse error */ if (gst_rtsp_transport_parse (resptrans, &transport) != GST_RTSP_OK) { GST_WARNING_OBJECT (src, "failed to parse transport %s", resptrans); goto next; } /* update allowed transports for other streams. once the transport of * one stream has been determined, we make sure that all other streams * are configured in the same way */ switch (transport.lower_transport) { case GST_RTSP_LOWER_TRANS_TCP: GST_DEBUG_OBJECT (src, "stream %p as TCP interleaved", stream); protocols = GST_RTSP_LOWER_TRANS_TCP; src->interleaved = TRUE; /* update free channels */ src->free_channel = MAX (transport.interleaved.min, src->free_channel); src->free_channel = MAX (transport.interleaved.max, src->free_channel); src->free_channel++; break; case GST_RTSP_LOWER_TRANS_UDP_MCAST: /* only allow multicast for other streams */ GST_DEBUG_OBJECT (src, "stream %p as UDP multicast", stream); protocols = GST_RTSP_LOWER_TRANS_UDP_MCAST; /* if the server selected our ports, increment our counters so that * we select a new port later */ if (src->next_port_num == transport.port.min && src->next_port_num + 1 == transport.port.max) { src->next_port_num += 2; } break; case GST_RTSP_LOWER_TRANS_UDP: /* only allow unicast for other streams */ GST_DEBUG_OBJECT (src, "stream %p as UDP unicast", stream); protocols = GST_RTSP_LOWER_TRANS_UDP; break; default: GST_DEBUG_OBJECT (src, "stream %p unknown transport %d", stream, transport.lower_transport); break; } if (!src->interleaved || !retry) { /* now configure the stream with the selected transport */ if (!gst_rtspsrc_stream_configure_transport (stream, &transport)) { GST_DEBUG_OBJECT (src, "could not configure stream %p transport, skipping stream", stream); goto next; } else if (stream->udpsrc[0] && stream->udpsrc[1]) { /* retain the first allocated UDP port pair */ g_object_get (G_OBJECT (stream->udpsrc[0]), "port", &rtpport, NULL); g_object_get (G_OBJECT (stream->udpsrc[1]), "port", &rtcpport, NULL); } } /* we need to activate at least one streams when we detect activity */ src->need_activate = TRUE; /* stream is setup now */ stream->setup = TRUE; { GList *skip = walk; while (TRUE) { GstRTSPStream *sskip; skip = g_list_next (skip); if (skip == NULL) break; sskip = (GstRTSPStream *) skip->data; /* skip all streams with the same control url */ if (g_str_equal (stream->conninfo.location, sskip->conninfo.location)) { GST_DEBUG_OBJECT (src, "found stream %p with same control %s", sskip, sskip->conninfo.location); sskip->skipped = TRUE; } } } next: /* clean up our transport struct */ gst_rtsp_transport_init (&transport); /* clean up used RTSP messages */ gst_rtsp_message_unset (&request); gst_rtsp_message_unset (&response); } } /* store the transport protocol that was configured */ src->cur_protocols = protocols; gst_rtsp_ext_list_stream_select (src->extensions, url); /* if there is nothing to activate, error out */ if (!src->need_activate) goto nothing_to_activate; return res; /* ERRORS */ no_protocols: { /* no transport possible, post an error and stop */ GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Could not connect to server, no protocols left")); return GST_RTSP_ERROR; } no_streams: { GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("SDP contains no streams")); return GST_RTSP_ERROR; } create_request_failed: { gchar *str = gst_rtsp_strresult (res); GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Could not create request. (%s)", str)); g_free (str); goto cleanup_error; } setup_transport_failed: { GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("Could not setup transport.")); res = GST_RTSP_ERROR; goto cleanup_error; } response_error: { const gchar *str = gst_rtsp_status_as_text (code); GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Error (%d): %s", code, GST_STR_NULL (str))); res = GST_RTSP_ERROR; goto cleanup_error; } send_error: { gchar *str = gst_rtsp_strresult (res); if (res != GST_RTSP_EINTR) { GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not send message. (%s)", str)); } else { GST_WARNING_OBJECT (src, "send interrupted"); } g_free (str); goto cleanup_error; } no_transport: { GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("Server did not select transport.")); res = GST_RTSP_ERROR; goto cleanup_error; } nothing_to_activate: { /* none of the available error codes is really right .. */ if (unsupported_real) { GST_ELEMENT_ERROR (src, STREAM, CODEC_NOT_FOUND, (_("No supported stream was found. You might need to install a " "GStreamer RTSP extension plugin for Real media streams.")), (NULL)); } else { GST_ELEMENT_ERROR (src, STREAM, CODEC_NOT_FOUND, (_("No supported stream was found. You might need to allow " "more transport protocols or may otherwise be missing " "the right GStreamer RTSP extension plugin.")), (NULL)); } return GST_RTSP_ERROR; } cleanup_error: { gst_rtsp_message_unset (&request); gst_rtsp_message_unset (&response); return res; } } static gboolean gst_rtspsrc_parse_range (GstRTSPSrc * src, const gchar * range, GstSegment * segment) { gint64 seconds; GstRTSPTimeRange *therange; if (src->range) gst_rtsp_range_free (src->range); if (gst_rtsp_range_parse (range, &therange) == GST_RTSP_OK) { GST_DEBUG_OBJECT (src, "parsed range %s", range); src->range = therange; } else { GST_DEBUG_OBJECT (src, "failed to parse range %s", range); src->range = NULL; gst_segment_init (segment, GST_FORMAT_TIME); return FALSE; } GST_DEBUG_OBJECT (src, "range: type %d, min %f - type %d, max %f ", therange->min.type, therange->min.seconds, therange->max.type, therange->max.seconds); if (therange->min.type == GST_RTSP_TIME_NOW) seconds = 0; else if (therange->min.type == GST_RTSP_TIME_END) seconds = 0; else seconds = therange->min.seconds * GST_SECOND; GST_DEBUG_OBJECT (src, "range: min %" GST_TIME_FORMAT, GST_TIME_ARGS (seconds)); /* we need to start playback without clipping from the position reported by * the server */ segment->start = seconds; segment->position = seconds; if (therange->max.type == GST_RTSP_TIME_NOW) seconds = -1; else if (therange->max.type == GST_RTSP_TIME_END) seconds = -1; else seconds = therange->max.seconds * GST_SECOND; GST_DEBUG_OBJECT (src, "range: max %" GST_TIME_FORMAT, GST_TIME_ARGS (seconds)); /* live (WMS) server might send overflowed large max as its idea of infinity, * compensate to prevent problems later on */ if (seconds != -1 && seconds < 0) { seconds = -1; GST_DEBUG_OBJECT (src, "insane range, set to NONE"); } /* live (WMS) might send min == max, which is not worth recording */ if (segment->duration == -1 && seconds == segment->start) seconds = -1; /* don't change duration with unknown value, we might have a valid value * there that we want to keep. */ if (seconds != -1) segment->duration = seconds; return TRUE; } /* Parse clock profived by the server with following syntax: * * "GstNetTimeProvider " */ static gboolean gst_rtspsrc_parse_gst_clock (GstRTSPSrc * src, const gchar * gstclock) { gboolean res = FALSE; if (g_str_has_prefix (gstclock, "GstNetTimeProvider ")) { gchar **fields = NULL, **parts = NULL; gchar *remote_ip, *str; gint port; GstClockTime base_time; GstClock *netclock; fields = g_strsplit (gstclock, " ", 0); /* wrapped clock, not very interesting for now */ if (fields[1] == NULL) goto cleanup; /* remote IP address and port */ if ((str = fields[2]) == NULL) goto cleanup; parts = g_strsplit (str, ":", 0); if ((remote_ip = parts[0]) == NULL) goto cleanup; if ((str = parts[1]) == NULL) goto cleanup; port = atoi (str); if (port == 0) goto cleanup; /* base-time */ if ((str = fields[3]) == NULL) goto cleanup; base_time = g_ascii_strtoull (str, NULL, 10); netclock = gst_net_client_clock_new ((gchar *) "GstRTSPClock", remote_ip, port, base_time); if (src->provided_clock) gst_object_unref (src->provided_clock); src->provided_clock = netclock; gst_element_post_message (GST_ELEMENT_CAST (src), gst_message_new_clock_provide (GST_OBJECT_CAST (src), src->provided_clock, TRUE)); res = TRUE; cleanup: g_strfreev (fields); g_strfreev (parts); } return res; } /* must be called with the RTSP state lock */ static GstRTSPResult gst_rtspsrc_open_from_sdp (GstRTSPSrc * src, GstSDPMessage * sdp, gboolean async) { GstRTSPResult res; gint i, n_streams; /* prepare global stream caps properties */ if (src->props) gst_structure_remove_all_fields (src->props); else src->props = gst_structure_new_empty ("RTSPProperties"); if (src->debug) gst_sdp_message_dump (sdp); gst_rtsp_ext_list_parse_sdp (src->extensions, sdp, src->props); /* let the app inspect and change the SDP */ g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_ON_SDP], 0, sdp); gst_segment_init (&src->segment, GST_FORMAT_TIME); /* parse range for duration reporting. */ { const gchar *range; for (i = 0;; i++) { range = gst_sdp_message_get_attribute_val_n (sdp, "range", i); if (range == NULL) break; /* keep track of the range and configure it in the segment */ if (gst_rtspsrc_parse_range (src, range, &src->segment)) break; } } /* parse clock information. This is GStreamer specific, a server can tell the * client what clock it is using and wrap that in a network clock. The * advantage of that is that we can slave to it. */ { const gchar *gstclock; for (i = 0;; i++) { gstclock = gst_sdp_message_get_attribute_val_n (sdp, "x-gst-clock", i); if (gstclock == NULL) break; /* parse the clock and expose it in the provide_clock method */ if (gst_rtspsrc_parse_gst_clock (src, gstclock)) break; } } /* try to find a global control attribute. Note that a '*' means that we should * do aggregate control with the current url (so we don't do anything and * leave the current connection as is) */ { const gchar *control; for (i = 0;; i++) { control = gst_sdp_message_get_attribute_val_n (sdp, "control", i); if (control == NULL) break; /* only take fully qualified urls */ if (g_str_has_prefix (control, "rtsp://")) break; } if (control) { g_free (src->conninfo.location); src->conninfo.location = g_strdup (control); /* make a connection for this, if there was a connection already, nothing * happens. */ if (gst_rtsp_conninfo_connect (src, &src->conninfo, async) < 0) { GST_ERROR_OBJECT (src, "could not connect"); } } /* we need to keep the control url separate from the connection url because * the rules for constructing the media control url need it */ g_free (src->control); src->control = g_strdup (control); } /* create streams */ n_streams = gst_sdp_message_medias_len (sdp); for (i = 0; i < n_streams; i++) { gst_rtspsrc_create_stream (src, sdp, i); } src->state = GST_RTSP_STATE_INIT; /* setup streams */ if ((res = gst_rtspsrc_setup_streams (src, async)) < 0) goto setup_failed; /* reset our state */ src->need_range = TRUE; src->skip = FALSE; src->state = GST_RTSP_STATE_READY; return res; /* ERRORS */ setup_failed: { GST_ERROR_OBJECT (src, "setup failed"); gst_rtspsrc_cleanup (src); return res; } } static GstRTSPResult gst_rtspsrc_retrieve_sdp (GstRTSPSrc * src, GstSDPMessage ** sdp, gboolean async) { GstRTSPResult res; GstRTSPMessage request = { 0 }; GstRTSPMessage response = { 0 }; guint8 *data; guint size; gchar *respcont = NULL; restart: src->need_redirect = FALSE; /* can't continue without a valid url */ if (G_UNLIKELY (src->conninfo.url == NULL)) { res = GST_RTSP_EINVAL; goto no_url; } src->tried_url_auth = FALSE; if ((res = gst_rtsp_conninfo_connect (src, &src->conninfo, async)) < 0) goto connect_failed; /* create OPTIONS */ GST_DEBUG_OBJECT (src, "create options..."); res = gst_rtspsrc_init_request (src, &request, GST_RTSP_OPTIONS, src->conninfo.url_str); if (res < 0) goto create_request_failed; /* send OPTIONS */ GST_DEBUG_OBJECT (src, "send options..."); if (async) GST_ELEMENT_PROGRESS (src, CONTINUE, "open", ("Retrieving server options")); if ((res = gst_rtspsrc_send (src, src->conninfo.connection, &request, &response, NULL)) < 0) goto send_error; /* parse OPTIONS */ if (!gst_rtspsrc_parse_methods (src, &response)) goto methods_error; /* create DESCRIBE */ GST_DEBUG_OBJECT (src, "create describe..."); res = gst_rtspsrc_init_request (src, &request, GST_RTSP_DESCRIBE, src->conninfo.url_str); if (res < 0) goto create_request_failed; /* we only accept SDP for now */ gst_rtsp_message_add_header (&request, GST_RTSP_HDR_ACCEPT, "application/sdp"); /* send DESCRIBE */ GST_DEBUG_OBJECT (src, "send describe..."); if (async) GST_ELEMENT_PROGRESS (src, CONTINUE, "open", ("Retrieving media info")); if ((res = gst_rtspsrc_send (src, src->conninfo.connection, &request, &response, NULL)) < 0) goto send_error; /* we only perform redirect for the describe, currently */ if (src->need_redirect) { /* close connection, we don't have to send a TEARDOWN yet, ignore the * result. */ gst_rtsp_conninfo_close (src, &src->conninfo, TRUE); gst_rtsp_message_unset (&request); gst_rtsp_message_unset (&response); /* and now retry */ goto restart; } /* it could be that the DESCRIBE method was not implemented */ if (!(src->methods & GST_RTSP_DESCRIBE)) goto no_describe; /* check if reply is SDP */ gst_rtsp_message_get_header (&response, GST_RTSP_HDR_CONTENT_TYPE, &respcont, 0); /* could not be set but since the request returned OK, we assume it * was SDP, else check it. */ if (respcont) { const gchar *props = strchr (respcont, ';'); if (props) { gchar *mimetype = g_strndup (respcont, props - respcont); mimetype = g_strstrip (mimetype); if (g_ascii_strcasecmp (mimetype, "application/sdp") != 0) { g_free (mimetype); goto wrong_content_type; } /* TODO: Check for charset property and do conversions of all messages if * needed. Some servers actually send that property */ g_free (mimetype); } else if (g_ascii_strcasecmp (respcont, "application/sdp") != 0) { goto wrong_content_type; } } /* get message body and parse as SDP */ gst_rtsp_message_get_body (&response, &data, &size); if (data == NULL || size == 0) goto no_describe; GST_DEBUG_OBJECT (src, "parse SDP..."); gst_sdp_message_new (sdp); gst_sdp_message_parse_buffer (data, size, *sdp); /* clean up any messages */ gst_rtsp_message_unset (&request); gst_rtsp_message_unset (&response); return res; /* ERRORS */ no_url: { GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), ("No valid RTSP URL was provided")); goto cleanup_error; } connect_failed: { gchar *str = gst_rtsp_strresult (res); if (res != GST_RTSP_EINTR) { GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, (NULL), ("Failed to connect. (%s)", str)); } else { GST_WARNING_OBJECT (src, "connect interrupted"); } g_free (str); goto cleanup_error; } create_request_failed: { gchar *str = gst_rtsp_strresult (res); GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Could not create request. (%s)", str)); g_free (str); goto cleanup_error; } send_error: { /* Don't post a message - the rtsp_send method will have * taken care of it because we passed NULL for the response code */ goto cleanup_error; } methods_error: { /* error was posted */ res = GST_RTSP_ERROR; goto cleanup_error; } wrong_content_type: { GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("Server does not support SDP, got %s.", respcont)); res = GST_RTSP_ERROR; goto cleanup_error; } no_describe: { GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), ("Server can not provide an SDP.")); res = GST_RTSP_ERROR; goto cleanup_error; } cleanup_error: { if (src->conninfo.connection) { GST_DEBUG_OBJECT (src, "free connection"); gst_rtsp_conninfo_close (src, &src->conninfo, TRUE); } gst_rtsp_message_unset (&request); gst_rtsp_message_unset (&response); return res; } } static GstRTSPResult gst_rtspsrc_open (GstRTSPSrc * src, gboolean async) { GstRTSPResult ret; src->methods = GST_RTSP_SETUP | GST_RTSP_PLAY | GST_RTSP_PAUSE | GST_RTSP_TEARDOWN; if (src->sdp == NULL) { if ((ret = gst_rtspsrc_retrieve_sdp (src, &src->sdp, async)) < 0) goto no_sdp; } if ((ret = gst_rtspsrc_open_from_sdp (src, src->sdp, async)) < 0) goto open_failed; done: if (async) gst_rtspsrc_loop_end_cmd (src, CMD_OPEN, ret); return ret; /* ERRORS */ no_sdp: { GST_WARNING_OBJECT (src, "can't get sdp"); src->open_error = TRUE; goto done; } open_failed: { GST_WARNING_OBJECT (src, "can't setup streaming from sdp"); src->open_error = TRUE; goto done; } } static GstRTSPResult gst_rtspsrc_close (GstRTSPSrc * src, gboolean async, gboolean only_close) { GstRTSPMessage request = { 0 }; GstRTSPMessage response = { 0 }; GstRTSPResult res = GST_RTSP_OK; GList *walk; const gchar *control; GST_DEBUG_OBJECT (src, "TEARDOWN..."); gst_rtspsrc_set_state (src, GST_STATE_READY); if (src->state < GST_RTSP_STATE_READY) { GST_DEBUG_OBJECT (src, "not ready, doing cleanup"); goto close; } if (only_close) goto close; /* construct a control url */ control = get_aggregate_control (src); if (!(src->methods & (GST_RTSP_PLAY | GST_RTSP_TEARDOWN))) goto not_supported; for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; const gchar *setup_url; GstRTSPConnInfo *info; /* try aggregate control first but do non-aggregate control otherwise */ if (control) setup_url = control; else if ((setup_url = stream->conninfo.location) == NULL) continue; if (src->conninfo.connection) { info = &src->conninfo; } else if (stream->conninfo.connection) { info = &stream->conninfo; } else { continue; } if (!info->connected) goto next; /* do TEARDOWN */ res = gst_rtspsrc_init_request (src, &request, GST_RTSP_TEARDOWN, setup_url); if (res < 0) goto create_request_failed; if (async) GST_ELEMENT_PROGRESS (src, CONTINUE, "close", ("Closing stream")); if ((res = gst_rtspsrc_send (src, info->connection, &request, &response, NULL)) < 0) goto send_error; /* FIXME, parse result? */ gst_rtsp_message_unset (&request); gst_rtsp_message_unset (&response); next: /* early exit when we did aggregate control */ if (control) break; } close: /* close connections */ GST_DEBUG_OBJECT (src, "closing connection..."); gst_rtsp_conninfo_close (src, &src->conninfo, TRUE); for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; gst_rtsp_conninfo_close (src, &stream->conninfo, TRUE); } /* cleanup */ gst_rtspsrc_cleanup (src); src->state = GST_RTSP_STATE_INVALID; if (async) gst_rtspsrc_loop_end_cmd (src, CMD_CLOSE, res); return res; /* ERRORS */ create_request_failed: { gchar *str = gst_rtsp_strresult (res); GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Could not create request. (%s)", str)); g_free (str); goto close; } send_error: { gchar *str = gst_rtsp_strresult (res); gst_rtsp_message_unset (&request); if (res != GST_RTSP_EINTR) { GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not send message. (%s)", str)); } else { GST_WARNING_OBJECT (src, "TEARDOWN interrupted"); } g_free (str); goto close; } not_supported: { GST_DEBUG_OBJECT (src, "TEARDOWN and PLAY not supported, can't do TEARDOWN"); goto close; } } /* RTP-Info is of the format: * * url=;[seq=;rtptime=] [, url=...] * * rtptime corresponds to the timestamp for the NPT time given in the header * seqbase corresponds to the next sequence number we received. This number * indicates the first seqnum after the seek and should be used to discard * packets that are from before the seek. */ static gboolean gst_rtspsrc_parse_rtpinfo (GstRTSPSrc * src, gchar * rtpinfo) { gchar **infos; gint i, j; GST_DEBUG_OBJECT (src, "parsing RTP-Info %s", rtpinfo); infos = g_strsplit (rtpinfo, ",", 0); for (i = 0; infos[i]; i++) { gchar **fields; GstRTSPStream *stream; gint32 seqbase; gint64 timebase; GST_DEBUG_OBJECT (src, "parsing info %s", infos[i]); /* init values, types of seqbase and timebase are bigger than needed so we * can store -1 as uninitialized values */ stream = NULL; seqbase = -1; timebase = -1; /* parse url, find stream for url. * parse seq and rtptime. The seq number should be configured in the rtp * depayloader or session manager to detect gaps. Same for the rtptime, it * should be used to create an initial time newsegment. */ fields = g_strsplit (infos[i], ";", 0); for (j = 0; fields[j]; j++) { GST_DEBUG_OBJECT (src, "parsing field %s", fields[j]); /* remove leading whitespace */ fields[j] = g_strchug (fields[j]); if (g_str_has_prefix (fields[j], "url=")) { /* get the url and the stream */ stream = find_stream (src, (fields[j] + 4), (gpointer) find_stream_by_setup); } else if (g_str_has_prefix (fields[j], "seq=")) { seqbase = atoi (fields[j] + 4); } else if (g_str_has_prefix (fields[j], "rtptime=")) { timebase = g_ascii_strtoll (fields[j] + 8, NULL, 10); } } g_strfreev (fields); /* now we need to store the values for the caps of the stream */ if (stream != NULL) { GST_DEBUG_OBJECT (src, "found stream %p, setting: seqbase %d, timebase %" G_GINT64_FORMAT, stream, seqbase, timebase); /* we have a stream, configure detected params */ stream->seqbase = seqbase; stream->timebase = timebase; } } g_strfreev (infos); return TRUE; } static void gst_rtspsrc_handle_rtcp_interval (GstRTSPSrc * src, gchar * rtcp) { guint64 interval; GList *walk; interval = strtoul (rtcp, NULL, 10); GST_DEBUG_OBJECT (src, "rtcp interval: %" G_GUINT64_FORMAT " ms", interval); if (!interval) return; interval *= GST_MSECOND; for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; /* already (optionally) retrieved this when configuring manager */ if (stream->session) { GObject *rtpsession = stream->session; GST_DEBUG_OBJECT (src, "configure rtcp interval in session %p", rtpsession); g_object_set (rtpsession, "rtcp-min-interval", interval, NULL); } } /* now it happens that (Xenon) server sending this may also provide bogus * RTCP SR sync data (i.e. with quite some jitter), so never mind those * and just use RTP-Info to sync */ if (src->manager) { GObjectClass *klass; klass = G_OBJECT_GET_CLASS (G_OBJECT (src->manager)); if (g_object_class_find_property (klass, "rtcp-sync")) { GST_DEBUG_OBJECT (src, "configuring rtp sync method"); g_object_set (src->manager, "rtcp-sync", RTCP_SYNC_RTP, NULL); } } } static gdouble gst_rtspsrc_get_float (const gchar * dstr) { gchar s[G_ASCII_DTOSTR_BUF_SIZE] = { 0, }; /* canonicalise floating point string so we can handle float strings * in the form "24.930" or "24,930" irrespective of the current locale */ g_strlcpy (s, dstr, sizeof (s)); g_strdelimit (s, ",", '.'); return g_ascii_strtod (s, NULL); } static gchar * gen_range_header (GstRTSPSrc * src, GstSegment * segment) { gchar val_str[G_ASCII_DTOSTR_BUF_SIZE] = { 0, }; if (src->range && src->range->min.type == GST_RTSP_TIME_NOW) { g_strlcpy (val_str, "now", sizeof (val_str)); } else { if (segment->position == 0) { g_strlcpy (val_str, "0", sizeof (val_str)); } else { g_ascii_dtostr (val_str, sizeof (val_str), ((gdouble) segment->position) / GST_SECOND); } } return g_strdup_printf ("npt=%s-", val_str); } static void clear_rtp_base (GstRTSPSrc * src, GstRTSPStream * stream) { guint i, len; stream->timebase = -1; stream->seqbase = -1; len = stream->ptmap->len; for (i = 0; i < len; i++) { PtMapItem *item = &g_array_index (stream->ptmap, PtMapItem, i); GstStructure *s; if (item->caps == NULL) continue; item->caps = gst_caps_make_writable (item->caps); s = gst_caps_get_structure (item->caps, 0); gst_structure_remove_fields (s, "clock-base", "seqnum-base", NULL); if (item->pt == stream->default_pt && stream->udpsrc[0]) g_object_set (stream->udpsrc[0], "caps", item->caps, NULL); } stream->need_caps = TRUE; } static GstRTSPResult gst_rtspsrc_ensure_open (GstRTSPSrc * src, gboolean async) { GstRTSPResult res = GST_RTSP_OK; if (src->state < GST_RTSP_STATE_READY) { res = GST_RTSP_ERROR; if (src->open_error) { GST_DEBUG_OBJECT (src, "the stream was in error"); goto done; } if (async) gst_rtspsrc_loop_start_cmd (src, CMD_OPEN); if ((res = gst_rtspsrc_open (src, async)) < 0) { GST_DEBUG_OBJECT (src, "failed to open stream"); goto done; } } done: return res; } static GstRTSPResult gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) { GstRTSPMessage request = { 0 }; GstRTSPMessage response = { 0 }; GstRTSPResult res = GST_RTSP_OK; GList *walk; gchar *hval; gint hval_idx; const gchar *control; GST_DEBUG_OBJECT (src, "PLAY..."); if ((res = gst_rtspsrc_ensure_open (src, async)) < 0) goto open_failed; if (!(src->methods & GST_RTSP_PLAY)) goto not_supported; if (src->state == GST_RTSP_STATE_PLAYING) goto was_playing; if (!src->conninfo.connection || !src->conninfo.connected) goto done; /* send some dummy packets before we activate the receive in the * udp sources */ gst_rtspsrc_send_dummy_packets (src); /* require new SR packets */ if (src->manager) g_signal_emit_by_name (src->manager, "reset-sync", NULL); /* construct a control url */ control = get_aggregate_control (src); for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; const gchar *setup_url; GstRTSPConnection *conn; /* try aggregate control first but do non-aggregate control otherwise */ if (control) setup_url = control; else if ((setup_url = stream->conninfo.location) == NULL) continue; if (src->conninfo.connection) { conn = src->conninfo.connection; } else if (stream->conninfo.connection) { conn = stream->conninfo.connection; } else { continue; } /* do play */ res = gst_rtspsrc_init_request (src, &request, GST_RTSP_PLAY, setup_url); if (res < 0) goto create_request_failed; if (src->need_range) { hval = gen_range_header (src, segment); gst_rtsp_message_take_header (&request, GST_RTSP_HDR_RANGE, hval); /* store the newsegment event so it can be sent from the streaming thread. */ src->need_segment = TRUE; } if (segment->rate != 1.0) { gchar hval[G_ASCII_DTOSTR_BUF_SIZE]; g_ascii_dtostr (hval, sizeof (hval), segment->rate); if (src->skip) gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SCALE, hval); else gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SPEED, hval); } if (async) GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("Sending PLAY request")); if ((res = gst_rtspsrc_send (src, conn, &request, &response, NULL)) < 0) goto send_error; /* seek may have silently failed as it is not supported */ if (!(src->methods & GST_RTSP_PLAY)) { GST_DEBUG_OBJECT (src, "PLAY Range not supported; re-enable PLAY"); /* obviously it is supported as we made it here */ src->methods |= GST_RTSP_PLAY; src->seekable = FALSE; /* but there is nothing to parse in the response, * so convey we have no idea and not to expect anything particular */ clear_rtp_base (src, stream); if (control) { GList *run; /* need to do for all streams */ for (run = src->streams; run; run = g_list_next (run)) clear_rtp_base (src, (GstRTSPStream *) run->data); } /* NOTE the above also disables npt based eos detection */ /* and below forces position to 0, * which is visible feedback we lost the plot */ segment->start = segment->position = src->last_pos; } gst_rtsp_message_unset (&request); /* parse RTP npt field. This is the current position in the stream (Normal * Play Time) and should be put in the NEWSEGMENT position field. */ if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RANGE, &hval, 0) == GST_RTSP_OK) gst_rtspsrc_parse_range (src, hval, segment); /* assume 1.0 rate now, overwrite when the SCALE or SPEED headers are present. */ segment->rate = 1.0; /* parse Speed header. This is the intended playback rate of the stream * and should be put in the NEWSEGMENT rate field. */ if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_SPEED, &hval, 0) == GST_RTSP_OK) { segment->rate = gst_rtspsrc_get_float (hval); } else if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_SCALE, &hval, 0) == GST_RTSP_OK) { segment->rate = gst_rtspsrc_get_float (hval); } /* parse the RTP-Info header field (if ANY) to get the base seqnum and timestamp * for the RTP packets. If this is not present, we assume all starts from 0... * This is info for the RTP session manager that we pass to it in caps. */ hval_idx = 0; while (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RTP_INFO, &hval, hval_idx++) == GST_RTSP_OK) gst_rtspsrc_parse_rtpinfo (src, hval); /* some servers indicate RTCP parameters in PLAY response, * rather than properly in SDP */ if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_RTCP_INTERVAL, &hval, 0) == GST_RTSP_OK) gst_rtspsrc_handle_rtcp_interval (src, hval); gst_rtsp_message_unset (&response); /* early exit when we did aggregate control */ if (control) break; } /* configure the caps of the streams after we parsed all headers. Only reset * the manager object when we set a new Range header (we did a seek) */ gst_rtspsrc_configure_caps (src, segment, src->need_range); /* set to PLAYING after we have configured the caps, otherwise we * might end up calling request_key (with SRTP) while caps are still * being configured. */ gst_rtspsrc_set_state (src, GST_STATE_PLAYING); /* set again when needed */ src->need_range = FALSE; src->running = TRUE; src->base_time = -1; src->state = GST_RTSP_STATE_PLAYING; /* mark discont */ GST_DEBUG_OBJECT (src, "mark DISCONT, we did a seek to another position"); for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; stream->discont = TRUE; } done: if (async) gst_rtspsrc_loop_end_cmd (src, CMD_PLAY, res); return res; /* ERRORS */ open_failed: { GST_DEBUG_OBJECT (src, "failed to open stream"); goto done; } not_supported: { GST_DEBUG_OBJECT (src, "PLAY is not supported"); goto done; } was_playing: { GST_DEBUG_OBJECT (src, "we were already PLAYING"); goto done; } create_request_failed: { gchar *str = gst_rtsp_strresult (res); GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Could not create request. (%s)", str)); g_free (str); goto done; } send_error: { gchar *str = gst_rtsp_strresult (res); gst_rtsp_message_unset (&request); if (res != GST_RTSP_EINTR) { GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not send message. (%s)", str)); } else { GST_WARNING_OBJECT (src, "PLAY interrupted"); } g_free (str); goto done; } } static GstRTSPResult gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async) { GstRTSPResult res = GST_RTSP_OK; GstRTSPMessage request = { 0 }; GstRTSPMessage response = { 0 }; GList *walk; const gchar *control; GST_DEBUG_OBJECT (src, "PAUSE..."); if ((res = gst_rtspsrc_ensure_open (src, async)) < 0) goto open_failed; if (!(src->methods & GST_RTSP_PAUSE)) goto not_supported; if (src->state == GST_RTSP_STATE_READY) goto was_paused; if (!src->conninfo.connection || !src->conninfo.connected) goto no_connection; /* construct a control url */ control = get_aggregate_control (src); /* loop over the streams. We might exit the loop early when we could do an * aggregate control */ for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; GstRTSPConnection *conn; const gchar *setup_url; /* try aggregate control first but do non-aggregate control otherwise */ if (control) setup_url = control; else if ((setup_url = stream->conninfo.location) == NULL) continue; if (src->conninfo.connection) { conn = src->conninfo.connection; } else if (stream->conninfo.connection) { conn = stream->conninfo.connection; } else { continue; } if (async) GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("Sending PAUSE request")); if ((res = gst_rtspsrc_init_request (src, &request, GST_RTSP_PAUSE, setup_url)) < 0) goto create_request_failed; if ((res = gst_rtspsrc_send (src, conn, &request, &response, NULL)) < 0) goto send_error; gst_rtsp_message_unset (&request); gst_rtsp_message_unset (&response); /* exit early when we did agregate control */ if (control) break; } /* change element states now */ gst_rtspsrc_set_state (src, GST_STATE_PAUSED); no_connection: src->state = GST_RTSP_STATE_READY; done: if (async) gst_rtspsrc_loop_end_cmd (src, CMD_PAUSE, res); return res; /* ERRORS */ open_failed: { GST_DEBUG_OBJECT (src, "failed to open stream"); goto done; } not_supported: { GST_DEBUG_OBJECT (src, "PAUSE is not supported"); goto done; } was_paused: { GST_DEBUG_OBJECT (src, "we were already PAUSED"); goto done; } create_request_failed: { gchar *str = gst_rtsp_strresult (res); GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Could not create request. (%s)", str)); g_free (str); goto done; } send_error: { gchar *str = gst_rtsp_strresult (res); gst_rtsp_message_unset (&request); if (res != GST_RTSP_EINTR) { GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), ("Could not send message. (%s)", str)); } else { GST_WARNING_OBJECT (src, "PAUSE interrupted"); } g_free (str); goto done; } } static void gst_rtspsrc_handle_message (GstBin * bin, GstMessage * message) { GstRTSPSrc *rtspsrc; rtspsrc = GST_RTSPSRC (bin); switch (GST_MESSAGE_TYPE (message)) { case GST_MESSAGE_EOS: gst_message_unref (message); break; case GST_MESSAGE_ELEMENT: { const GstStructure *s = gst_message_get_structure (message); if (gst_structure_has_name (s, "GstUDPSrcTimeout")) { gboolean ignore_timeout; GST_DEBUG_OBJECT (bin, "timeout on UDP port"); GST_OBJECT_LOCK (rtspsrc); ignore_timeout = rtspsrc->ignore_timeout; rtspsrc->ignore_timeout = TRUE; GST_OBJECT_UNLOCK (rtspsrc); /* we only act on the first udp timeout message, others are irrelevant * and can be ignored. */ if (!ignore_timeout) gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_RECONNECT, CMD_LOOP); /* eat and free */ gst_message_unref (message); return; } GST_BIN_CLASS (parent_class)->handle_message (bin, message); break; } case GST_MESSAGE_ERROR: { GstObject *udpsrc; GstRTSPStream *stream; GstFlowReturn ret; udpsrc = GST_MESSAGE_SRC (message); GST_DEBUG_OBJECT (rtspsrc, "got error from %s", GST_ELEMENT_NAME (udpsrc)); stream = find_stream (rtspsrc, udpsrc, (gpointer) find_stream_by_udpsrc); if (!stream) goto forward; /* we ignore the RTCP udpsrc */ if (stream->udpsrc[1] == GST_ELEMENT_CAST (udpsrc)) goto done; /* if we get error messages from the udp sources, that's not a problem as * long as not all of them error out. We also don't really know what the * problem is, the message does not give enough detail... */ ret = gst_rtspsrc_combine_flows (rtspsrc, stream, GST_FLOW_NOT_LINKED); GST_DEBUG_OBJECT (rtspsrc, "combined flows: %s", gst_flow_get_name (ret)); if (ret != GST_FLOW_OK) goto forward; done: gst_message_unref (message); break; forward: /* fatal but not our message, forward */ GST_BIN_CLASS (parent_class)->handle_message (bin, message); break; } default: { GST_BIN_CLASS (parent_class)->handle_message (bin, message); break; } } } /* the thread where everything happens */ static void gst_rtspsrc_thread (GstRTSPSrc * src) { gint cmd; GST_OBJECT_LOCK (src); cmd = src->pending_cmd; if (cmd == CMD_RECONNECT || cmd == CMD_PLAY || cmd == CMD_PAUSE || cmd == CMD_LOOP || cmd == CMD_OPEN) src->pending_cmd = CMD_LOOP; else src->pending_cmd = CMD_WAIT; GST_DEBUG_OBJECT (src, "got command %s", cmd_to_string (cmd)); /* we got the message command, so ensure communication is possible again */ gst_rtspsrc_connection_flush (src, FALSE); src->busy_cmd = cmd; GST_OBJECT_UNLOCK (src); switch (cmd) { case CMD_OPEN: gst_rtspsrc_open (src, TRUE); break; case CMD_PLAY: gst_rtspsrc_play (src, &src->segment, TRUE); break; case CMD_PAUSE: gst_rtspsrc_pause (src, TRUE); break; case CMD_CLOSE: gst_rtspsrc_close (src, TRUE, FALSE); break; case CMD_LOOP: gst_rtspsrc_loop (src); break; case CMD_RECONNECT: gst_rtspsrc_reconnect (src, FALSE); break; default: break; } GST_OBJECT_LOCK (src); /* and go back to sleep */ if (src->pending_cmd == CMD_WAIT) { if (src->task) gst_task_pause (src->task); } /* reset waiting */ src->busy_cmd = CMD_WAIT; GST_OBJECT_UNLOCK (src); } static gboolean gst_rtspsrc_start (GstRTSPSrc * src) { GST_DEBUG_OBJECT (src, "starting"); GST_OBJECT_LOCK (src); src->pending_cmd = CMD_WAIT; if (src->task == NULL) { src->task = gst_task_new ((GstTaskFunction) gst_rtspsrc_thread, src, NULL); if (src->task == NULL) goto task_error; gst_task_set_lock (src->task, GST_RTSP_STREAM_GET_LOCK (src)); } GST_OBJECT_UNLOCK (src); return TRUE; /* ERRORS */ task_error: { GST_OBJECT_UNLOCK (src); GST_ERROR_OBJECT (src, "failed to create task"); return FALSE; } } static gboolean gst_rtspsrc_stop (GstRTSPSrc * src) { GstTask *task; GST_DEBUG_OBJECT (src, "stopping"); /* also cancels pending task */ gst_rtspsrc_loop_send_cmd (src, CMD_WAIT, CMD_ALL); GST_OBJECT_LOCK (src); if ((task = src->task)) { src->task = NULL; GST_OBJECT_UNLOCK (src); gst_task_stop (task); /* make sure it is not running */ GST_RTSP_STREAM_LOCK (src); GST_RTSP_STREAM_UNLOCK (src); /* now wait for the task to finish */ gst_task_join (task); /* and free the task */ gst_object_unref (GST_OBJECT (task)); GST_OBJECT_LOCK (src); } GST_OBJECT_UNLOCK (src); /* ensure synchronously all is closed and clean */ gst_rtspsrc_close (src, FALSE, TRUE); return TRUE; } static GstStateChangeReturn gst_rtspsrc_change_state (GstElement * element, GstStateChange transition) { GstRTSPSrc *rtspsrc; GstStateChangeReturn ret; rtspsrc = GST_RTSPSRC (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: if (!gst_rtspsrc_start (rtspsrc)) goto start_failed; break; case GST_STATE_CHANGE_READY_TO_PAUSED: /* init some state */ rtspsrc->cur_protocols = rtspsrc->protocols; /* first attempt, don't ignore timeouts */ rtspsrc->ignore_timeout = FALSE; rtspsrc->open_error = FALSE; gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_OPEN, 0); break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: set_manager_buffer_mode (rtspsrc); /* fall-through */ case GST_STATE_CHANGE_PLAYING_TO_PAUSED: /* unblock the tcp tasks and make the loop waiting */ if (gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_WAIT, CMD_LOOP)) { /* make sure it is waiting before we send PAUSE or PLAY below */ GST_RTSP_STREAM_LOCK (rtspsrc); GST_RTSP_STREAM_UNLOCK (rtspsrc); } break; case GST_STATE_CHANGE_PAUSED_TO_READY: break; default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) goto done; switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: ret = GST_STATE_CHANGE_SUCCESS; break; case GST_STATE_CHANGE_READY_TO_PAUSED: ret = GST_STATE_CHANGE_NO_PREROLL; break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_PLAY, 0); ret = GST_STATE_CHANGE_SUCCESS; break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED: /* send pause request and keep the idle task around */ gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_PAUSE, CMD_LOOP); ret = GST_STATE_CHANGE_NO_PREROLL; break; case GST_STATE_CHANGE_PAUSED_TO_READY: gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_CLOSE, CMD_PAUSE); ret = GST_STATE_CHANGE_SUCCESS; break; case GST_STATE_CHANGE_READY_TO_NULL: gst_rtspsrc_stop (rtspsrc); ret = GST_STATE_CHANGE_SUCCESS; break; default: /* Otherwise it's success, we don't want to return spurious * NO_PREROLL or ASYNC from internal elements as we care for * state changes ourselves here * * This is to catch PAUSED->PAUSED and PLAYING->PLAYING transitions. */ if (GST_STATE_TRANSITION_NEXT (transition) == GST_STATE_PAUSED) ret = GST_STATE_CHANGE_NO_PREROLL; else ret = GST_STATE_CHANGE_SUCCESS; break; } done: return ret; start_failed: { GST_DEBUG_OBJECT (rtspsrc, "start failed"); return GST_STATE_CHANGE_FAILURE; } } static gboolean gst_rtspsrc_send_event (GstElement * element, GstEvent * event) { gboolean res; GstRTSPSrc *rtspsrc; rtspsrc = GST_RTSPSRC (element); if (GST_EVENT_IS_DOWNSTREAM (event)) { res = gst_rtspsrc_push_event (rtspsrc, event); } else { res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event); } return res; } /*** GSTURIHANDLER INTERFACE *************************************************/ static GstURIType gst_rtspsrc_uri_get_type (GType type) { return GST_URI_SRC; } static const gchar *const * gst_rtspsrc_uri_get_protocols (GType type) { static const gchar *protocols[] = { "rtsp", "rtspu", "rtspt", "rtsph", "rtsp-sdp", "rtsps", "rtspsu", "rtspst", "rtspsh", NULL }; return protocols; } static gchar * gst_rtspsrc_uri_get_uri (GstURIHandler * handler) { GstRTSPSrc *src = GST_RTSPSRC (handler); /* FIXME: make thread-safe */ return g_strdup (src->conninfo.location); } static gboolean gst_rtspsrc_uri_set_uri (GstURIHandler * handler, const gchar * uri, GError ** error) { GstRTSPSrc *src; GstRTSPResult res; GstSDPResult sres; GstRTSPUrl *newurl = NULL; GstSDPMessage *sdp = NULL; src = GST_RTSPSRC (handler); /* same URI, we're fine */ if (src->conninfo.location && uri && !strcmp (uri, src->conninfo.location)) goto was_ok; if (g_str_has_prefix (uri, "rtsp-sdp://")) { sres = gst_sdp_message_new (&sdp); if (sres < 0) goto sdp_failed; GST_DEBUG_OBJECT (src, "parsing SDP message"); sres = gst_sdp_message_parse_uri (uri, sdp); if (sres < 0) goto invalid_sdp; } else { /* try to parse */ GST_DEBUG_OBJECT (src, "parsing URI"); if ((res = gst_rtsp_url_parse (uri, &newurl)) < 0) goto parse_error; } /* if worked, free previous and store new url object along with the original * location. */ GST_DEBUG_OBJECT (src, "configuring URI"); g_free (src->conninfo.location); src->conninfo.location = g_strdup (uri); gst_rtsp_url_free (src->conninfo.url); src->conninfo.url = newurl; g_free (src->conninfo.url_str); if (newurl) src->conninfo.url_str = gst_rtsp_url_get_request_uri (src->conninfo.url); else src->conninfo.url_str = NULL; if (src->sdp) gst_sdp_message_free (src->sdp); src->sdp = sdp; src->from_sdp = sdp != NULL; GST_DEBUG_OBJECT (src, "set uri: %s", GST_STR_NULL (uri)); GST_DEBUG_OBJECT (src, "request uri is: %s", GST_STR_NULL (src->conninfo.url_str)); return TRUE; /* Special cases */ was_ok: { GST_DEBUG_OBJECT (src, "URI was ok: '%s'", GST_STR_NULL (uri)); return TRUE; } sdp_failed: { GST_ERROR_OBJECT (src, "Could not create new SDP (%d)", sres); g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, "Could not create SDP"); return FALSE; } invalid_sdp: { GST_ERROR_OBJECT (src, "Not a valid SDP (%d) '%s'", sres, GST_STR_NULL (uri)); gst_sdp_message_free (sdp); g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, "Invalid SDP"); return FALSE; } parse_error: { GST_ERROR_OBJECT (src, "Not a valid RTSP url '%s' (%d)", GST_STR_NULL (uri), res); g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, "Invalid RTSP URI"); return FALSE; } } static void gst_rtspsrc_uri_handler_init (gpointer g_iface, gpointer iface_data) { GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; iface->get_type = gst_rtspsrc_uri_get_type; iface->get_protocols = gst_rtspsrc_uri_get_protocols; iface->get_uri = gst_rtspsrc_uri_get_uri; iface->set_uri = gst_rtspsrc_uri_set_uri; }