| /* GStreamer |
| * Copyright (C) <2005,2006> Wim Taymans <wim at fluendo dot com> |
| * <2006> Lutz Mueller <lutz at topfrose dot de> |
| * |
| * 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. |
| * |
| * <refsect2> |
| * <title>Example launch line</title> |
| * |[ |
| * 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. |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif /* HAVE_UNISTD_H */ |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| |
| #include <gst/net/gstnet.h> |
| #include <gst/sdp/gstsdpmessage.h> |
| #include <gst/sdp/gstmikey.h> |
| #include <gst/rtp/rtp.h> |
| |
| #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 AES_128_KEY_LEN 16 |
| #define AES_256_KEY_LEN 32 |
| |
| #define HMAC_32_KEY_LEN 4 |
| #define HMAC_80_KEY_LEN 10 |
| |
| #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 |
| |
| 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 |
| }; |
| |
| #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 void gst_rtspsrc_sdp_attributes_to_caps (GArray * attributes, |
| GstCaps * caps); |
| |
| static gboolean gst_rtspsrc_set_proxy (GstRTSPSrc * rtsp, const gchar * proxy); |
| static void gst_rtspsrc_set_tcp_timeout (GstRTSPSrc * rtspsrc, guint64 timeout); |
| |
| static GstCaps *gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media); |
| |
| 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); |
| |
| 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)); |
| |
| /** |
| * 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_pad_template (gstelement_class, |
| gst_static_pad_template_get (&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 <wim@fluendo.com>, " |
| "Thijs Vermeir <thijs.vermeir@barco.com>, " |
| "Lutz Mueller <lutz@topfrose.de>"); |
| |
| 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); |
| |
| /* 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: |
| if (rtspsrc->prop_proxy_id) |
| g_free (rtspsrc->prop_proxy_id); |
| rtspsrc->prop_proxy_id = g_value_dup_string (value); |
| break; |
| case PROP_PROXY_PW: |
| if (rtspsrc->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: |
| if (rtspsrc->user_id) |
| g_free (rtspsrc->user_id); |
| rtspsrc->user_id = g_value_dup_string (value); |
| break; |
| case PROP_USER_PW: |
| if (rtspsrc->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 (str) { |
| sscanf (str, "%u-%u", |
| &rtspsrc->client_port_range.min, &rtspsrc->client_port_range.max); |
| } else { |
| 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; |
| 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 = 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; |
| 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=<media> <UDP port> RTP/AVP <payload> |
| */ |
| static void |
| gst_rtspsrc_collect_payloads (GstRTSPSrc * src, const GstSDPMessage * sdp, |
| const GstSDPMedia * media, GstRTSPStream * stream) |
| { |
| guint i, len; |
| const gchar *proto; |
| |
| /* 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; |
| |
| len = gst_sdp_media_formats_len (media); |
| for (i = 0; i < len; i++) { |
| gint pt; |
| GstCaps *caps; |
| 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_rtspsrc_media_to_caps (pt, media); |
| 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; |
| } |
| GST_DEBUG ("mapping sdp session level attributes to caps"); |
| gst_rtspsrc_sdp_attributes_to_caps (sdp->attributes, caps); |
| GST_DEBUG ("mapping sdp media level attributes to caps"); |
| gst_rtspsrc_sdp_attributes_to_caps (media->attributes, caps); |
| |
| /* the first pt will be the default */ |
| if (stream->ptmap->len == 0) |
| stream->default_pt = pt; |
| |
| item.pt = pt; |
| item.caps = caps; |
| g_array_append_val (stream->ptmap, item); |
| } |
| 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)); |
| 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->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; |
| } |
| } |
| |
| #define PARSE_INT(p, del, res) \ |
| G_STMT_START { \ |
| gchar *t = p; \ |
| p = strstr (p, del); \ |
| if (p == NULL) \ |
| res = -1; \ |
| else { \ |
| *p = '\0'; \ |
| p++; \ |
| res = atoi (t); \ |
| } \ |
| } G_STMT_END |
| |
| #define PARSE_STRING(p, del, res) \ |
| G_STMT_START { \ |
| gchar *t = p; \ |
| p = strstr (p, del); \ |
| if (p == NULL) { \ |
| res = NULL; \ |
| p = t; \ |
| } \ |
| else { \ |
| *p = '\0'; \ |
| p++; \ |
| res = t; \ |
| } \ |
| } G_STMT_END |
| |
| #define SKIP_SPACES(p) \ |
| while (*p && g_ascii_isspace (*p)) \ |
| p++; |
| |
| /* rtpmap contains: |
| * |
| * <payload> <encoding_name>/<clock_rate>[/<encoding_params>] |
| */ |
| static gboolean |
| gst_rtspsrc_parse_rtpmap (const gchar * rtpmap, gint * payload, gchar ** name, |
| gint * rate, gchar ** params) |
| { |
| gchar *p, *t; |
| |
| p = (gchar *) rtpmap; |
| |
| PARSE_INT (p, " ", *payload); |
| if (*payload == -1) |
| return FALSE; |
| |
| SKIP_SPACES (p); |
| if (*p == '\0') |
| return FALSE; |
| |
| PARSE_STRING (p, "/", *name); |
| if (*name == NULL) { |
| GST_DEBUG ("no rate, name %s", p); |
| /* no rate, assume -1 then, this is not supposed to happen but RealMedia |
| * streams seem to omit the rate. */ |
| *name = p; |
| *rate = -1; |
| return TRUE; |
| } |
| |
| t = p; |
| p = strstr (p, "/"); |
| if (p == NULL) { |
| *rate = atoi (t); |
| return TRUE; |
| } |
| *p = '\0'; |
| p++; |
| *rate = atoi (t); |
| |
| t = p; |
| if (*p == '\0') |
| return TRUE; |
| *params = t; |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| parse_keymgmt (const gchar * keymgmt, GstCaps * caps) |
| { |
| gboolean res = FALSE; |
| gchar *p, *kmpid; |
| gsize size; |
| guchar *data; |
| GstMIKEYMessage *msg; |
| const GstMIKEYPayload *payload; |
| const gchar *srtp_cipher; |
| const gchar *srtp_auth; |
| |
| p = (gchar *) keymgmt; |
| |
| SKIP_SPACES (p); |
| if (*p == '\0') |
| return FALSE; |
| |
| PARSE_STRING (p, " ", kmpid); |
| if (!g_str_equal (kmpid, "mikey")) |
| return FALSE; |
| |
| data = g_base64_decode (p, &size); |
| if (data == NULL) |
| return FALSE; |
| |
| msg = gst_mikey_message_new_from_data (data, size, NULL, NULL); |
| g_free (data); |
| if (msg == NULL) |
| return FALSE; |
| |
| srtp_cipher = "aes-128-icm"; |
| srtp_auth = "hmac-sha1-80"; |
| |
| /* check the Security policy if any */ |
| if ((payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_SP, 0))) { |
| GstMIKEYPayloadSP *p = (GstMIKEYPayloadSP *) payload; |
| guint len, i; |
| |
| if (p->proto != GST_MIKEY_SEC_PROTO_SRTP) |
| goto done; |
| |
| len = gst_mikey_payload_sp_get_n_params (payload); |
| for (i = 0; i < len; i++) { |
| const GstMIKEYPayloadSPParam *param = |
| gst_mikey_payload_sp_get_param (payload, i); |
| |
| switch (param->type) { |
| case GST_MIKEY_SP_SRTP_ENC_ALG: |
| switch (param->val[0]) { |
| case 0: |
| srtp_cipher = "null"; |
| break; |
| case 2: |
| case 1: |
| srtp_cipher = "aes-128-icm"; |
| break; |
| default: |
| break; |
| } |
| break; |
| case GST_MIKEY_SP_SRTP_ENC_KEY_LEN: |
| switch (param->val[0]) { |
| case AES_128_KEY_LEN: |
| srtp_cipher = "aes-128-icm"; |
| break; |
| case AES_256_KEY_LEN: |
| srtp_cipher = "aes-256-icm"; |
| break; |
| default: |
| break; |
| } |
| break; |
| case GST_MIKEY_SP_SRTP_AUTH_ALG: |
| switch (param->val[0]) { |
| case 0: |
| srtp_auth = "null"; |
| break; |
| case 2: |
| case 1: |
| srtp_auth = "hmac-sha1-80"; |
| break; |
| default: |
| break; |
| } |
| break; |
| case GST_MIKEY_SP_SRTP_AUTH_KEY_LEN: |
| switch (param->val[0]) { |
| case HMAC_32_KEY_LEN: |
| srtp_auth = "hmac-sha1-32"; |
| break; |
| case HMAC_80_KEY_LEN: |
| srtp_auth = "hmac-sha1-80"; |
| break; |
| default: |
| break; |
| } |
| break; |
| case GST_MIKEY_SP_SRTP_SRTP_ENC: |
| break; |
| case GST_MIKEY_SP_SRTP_SRTCP_ENC: |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| if (!(payload = gst_mikey_message_find_payload (msg, GST_MIKEY_PT_KEMAC, 0))) |
| goto done; |
| else { |
| GstMIKEYPayloadKEMAC *p = (GstMIKEYPayloadKEMAC *) payload; |
| const GstMIKEYPayload *sub; |
| GstMIKEYPayloadKeyData *pkd; |
| GstBuffer *buf; |
| |
| if (p->enc_alg != GST_MIKEY_ENC_NULL || p->mac_alg != GST_MIKEY_MAC_NULL) |
| goto done; |
| |
| if (!(sub = gst_mikey_payload_kemac_get_sub (payload, 0))) |
| goto done; |
| |
| if (sub->type != GST_MIKEY_PT_KEY_DATA) |
| goto done; |
| |
| pkd = (GstMIKEYPayloadKeyData *) sub; |
| buf = |
| gst_buffer_new_wrapped (g_memdup (pkd->key_data, pkd->key_len), |
| pkd->key_len); |
| gst_caps_set_simple (caps, "srtp-key", GST_TYPE_BUFFER, buf, NULL); |
| } |
| |
| gst_caps_set_simple (caps, |
| "srtp-cipher", G_TYPE_STRING, srtp_cipher, |
| "srtp-auth", G_TYPE_STRING, srtp_auth, |
| "srtcp-cipher", G_TYPE_STRING, srtp_cipher, |
| "srtcp-auth", G_TYPE_STRING, srtp_auth, NULL); |
| |
| res = TRUE; |
| done: |
| gst_mikey_message_unref (msg); |
| |
| return res; |
| } |
| |
| /* |
| * Mapping SDP attributes to caps |
| * |
| * prepend 'a-' to IANA registered sdp attributes names |
| * (ie: not prefixed with 'x-') in order to avoid |
| * collision with gstreamer standard caps properties names |
| */ |
| static void |
| gst_rtspsrc_sdp_attributes_to_caps (GArray * attributes, GstCaps * caps) |
| { |
| if (attributes->len > 0) { |
| GstStructure *s; |
| guint i; |
| |
| s = gst_caps_get_structure (caps, 0); |
| |
| for (i = 0; i < attributes->len; i++) { |
| GstSDPAttribute *attr = &g_array_index (attributes, GstSDPAttribute, i); |
| gchar *tofree, *key; |
| |
| key = attr->key; |
| |
| /* skip some of the attribute we already handle */ |
| if (!strcmp (key, "fmtp")) |
| continue; |
| if (!strcmp (key, "rtpmap")) |
| continue; |
| if (!strcmp (key, "control")) |
| continue; |
| if (!strcmp (key, "range")) |
| continue; |
| if (!strcmp (key, "framesize")) |
| continue; |
| if (g_str_equal (key, "key-mgmt")) { |
| parse_keymgmt (attr->value, caps); |
| continue; |
| } |
| |
| /* string must be valid UTF8 */ |
| if (!g_utf8_validate (attr->value, -1, NULL)) |
| continue; |
| |
| if (!g_str_has_prefix (key, "x-")) |
| tofree = key = g_strdup_printf ("a-%s", key); |
| else |
| tofree = NULL; |
| |
| GST_DEBUG ("adding caps: %s=%s", key, attr->value); |
| gst_structure_set (s, key, G_TYPE_STRING, attr->value, NULL); |
| g_free (tofree); |
| } |
| } |
| } |
| |
| static const gchar * |
| rtsp_get_attribute_for_pt (const GstSDPMedia * media, const gchar * name, |
| gint pt) |
| { |
| guint i; |
| |
| for (i = 0;; i++) { |
| const gchar *attr; |
| gint val; |
| |
| if ((attr = gst_sdp_media_get_attribute_val_n (media, name, i)) == NULL) |
| break; |
| |
| if (sscanf (attr, "%d ", &val) != 1) |
| continue; |
| |
| if (val == pt) |
| return attr; |
| } |
| return NULL; |
| } |
| |
| /* |
| * Mapping of caps to and from SDP fields: |
| * |
| * a=rtpmap:<payload> <encoding_name>/<clock_rate>[/<encoding_params>] |
| * a=framesize:<payload> <width>-<height> |
| * a=fmtp:<payload> <param>[=<value>];... |
| */ |
| static GstCaps * |
| gst_rtspsrc_media_to_caps (gint pt, const GstSDPMedia * media) |
| { |
| GstCaps *caps; |
| const gchar *rtpmap; |
| const gchar *fmtp; |
| const gchar *framesize; |
| gchar *name = NULL; |
| gint rate = -1; |
| gchar *params = NULL; |
| gchar *tmp; |
| GstStructure *s; |
| gint payload = 0; |
| gboolean ret; |
| |
| /* get and parse rtpmap */ |
| rtpmap = rtsp_get_attribute_for_pt (media, "rtpmap", pt); |
| |
| if (rtpmap) { |
| ret = gst_rtspsrc_parse_rtpmap (rtpmap, &payload, &name, &rate, ¶ms); |
| if (!ret) { |
| g_warning ("error parsing rtpmap, ignoring"); |
| rtpmap = NULL; |
| } |
| } |
| /* dynamic payloads need rtpmap or we fail */ |
| if (rtpmap == NULL && pt >= 96) |
| goto no_rtpmap; |
| |
| /* check if we have a rate, if not, we need to look up the rate from the |
| * default rates based on the payload types. */ |
| if (rate == -1) { |
| const GstRTPPayloadInfo *info; |
| |
| if (GST_RTP_PAYLOAD_IS_DYNAMIC (pt)) { |
| /* dynamic types, use media and encoding_name */ |
| tmp = g_ascii_strdown (media->media, -1); |
| info = gst_rtp_payload_info_for_name (tmp, name); |
| g_free (tmp); |
| } else { |
| /* static types, use payload type */ |
| info = gst_rtp_payload_info_for_pt (pt); |
| } |
| |
| if (info) { |
| if ((rate = info->clock_rate) == 0) |
| rate = -1; |
| } |
| /* we fail if we cannot find one */ |
| if (rate == -1) |
| goto no_rate; |
| } |
| |
| tmp = g_ascii_strdown (media->media, -1); |
| caps = gst_caps_new_simple ("application/x-unknown", |
| "media", G_TYPE_STRING, tmp, "payload", G_TYPE_INT, pt, NULL); |
| g_free (tmp); |
| s = gst_caps_get_structure (caps, 0); |
| |
| gst_structure_set (s, "clock-rate", G_TYPE_INT, rate, NULL); |
| |
| /* encoding name must be upper case */ |
| if (name != NULL) { |
| tmp = g_ascii_strup (name, -1); |
| gst_structure_set (s, "encoding-name", G_TYPE_STRING, tmp, NULL); |
| g_free (tmp); |
| } |
| |
| /* params must be lower case */ |
| if (params != NULL) { |
| tmp = g_ascii_strdown (params, -1); |
| gst_structure_set (s, "encoding-params", G_TYPE_STRING, tmp, NULL); |
| g_free (tmp); |
| } |
| |
| /* parse optional fmtp: field */ |
| if ((fmtp = rtsp_get_attribute_for_pt (media, "fmtp", pt))) { |
| gchar *p; |
| gint payload = 0; |
| |
| p = (gchar *) fmtp; |
| |
| /* p is now of the format <payload> <param>[=<value>];... */ |
| PARSE_INT (p, " ", payload); |
| if (payload != -1 && payload == pt) { |
| gchar **pairs; |
| gint i; |
| |
| /* <param>[=<value>] are separated with ';' */ |
| pairs = g_strsplit (p, ";", 0); |
| for (i = 0; pairs[i]; i++) { |
| gchar *valpos; |
| const gchar *val, *key; |
| gint j; |
| const gchar *reserved_keys[] = |
| { "media", "payload", "clock-rate", "encoding-name", |
| "encoding-params" |
| }; |
| |
| /* the key may not have a '=', the value can have other '='s */ |
| valpos = strstr (pairs[i], "="); |
| if (valpos) { |
| /* we have a '=' and thus a value, remove the '=' with \0 */ |
| *valpos = '\0'; |
| /* value is everything between '=' and ';'. We split the pairs at ; |
| * boundaries so we can take the remainder of the value. Some servers |
| * put spaces around the value which we strip off here. Alternatively |
| * we could strip those spaces in the depayloaders should these spaces |
| * actually carry any meaning in the future. */ |
| val = g_strstrip (valpos + 1); |
| } else { |
| /* simple <param>;.. is translated into <param>=1;... */ |
| val = "1"; |
| } |
| /* strip the key of spaces, convert key to lowercase but not the value. */ |
| key = g_strstrip (pairs[i]); |
| |
| /* skip keys from the fmtp, which we already use ourselves for the |
| * caps. Some software is adding random things like clock-rate into |
| * the fmtp, and we would otherwise here set a string-typed clock-rate |
| * in the caps... and thus fail to create valid RTP caps |
| */ |
| for (j = 0; j < G_N_ELEMENTS (reserved_keys); j++) { |
| if (g_ascii_strcasecmp (reserved_keys[j], key) == 0) { |
| key = ""; |
| break; |
| } |
| } |
| |
| if (strlen (key) > 1) { |
| tmp = g_ascii_strdown (key, -1); |
| gst_structure_set (s, tmp, G_TYPE_STRING, val, NULL); |
| g_free (tmp); |
| } |
| } |
| g_strfreev (pairs); |
| } |
| } |
| |
| /* parse framesize: field */ |
| if ((framesize = gst_sdp_media_get_attribute_val (media, "framesize"))) { |
| gchar *p; |
| |
| /* p is now of the format <payload> <width>-<height> */ |
| p = (gchar *) framesize; |
| |
| PARSE_INT (p, " ", payload); |
| if (payload != -1 && payload == pt) { |
| gst_structure_set (s, "a-framesize", G_TYPE_STRING, p, NULL); |
| } |
| } |
| return caps; |
| |
| /* ERRORS */ |
| no_rtpmap: |
| { |
| g_warning ("rtpmap type not given for dynamic payload %d", pt); |
| return NULL; |
| } |
| no_rate: |
| { |
| g_warning ("rate unknown for payload type %d", pt); |
| return 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; |
| |
| playing = (src->state == GST_RTSP_STATE_PLAYING); |
| |
| /* if we were playing, pause first */ |
| 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; |
| |
| /* and continue playing */ |
| if (playing) |
| gst_rtspsrc_play (src, &seeksegment, FALSE); |
| |
| /* 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; |
| } |
| |
| 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) |
| { |
| GST_DEBUG ("request key %u", ssrc); |
| return gst_caps_ref (stream_get_caps_for_pt (stream, stream->default_pt)); |
| } |
| |
| 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), "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 (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); |
| } |
| |
| /* 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); |
| 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); |
| 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_DEBUG_OBJECT (src, "no destination address specified"); |
| return FALSE; |
| } |
| no_sink_element: |
| { |
| GST_DEBUG_OBJECT (src, "no UDP sink element found"); |
| return FALSE; |
| } |
| no_fakesrc_element: |
| { |
| GST_DEBUG_OBJECT (src, "no fakesrc element found"); |
| 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 && stream->udpsrc[0]) { |
| g_object_set (stream->udpsrc[0], "caps", caps, NULL); |
| } |
| } |
| } |
| 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; |
| |
| 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 (info->url_str) |
| 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); |
| if ((res = |
| gst_rtsp_connection_connect (info->connection, |
| src->ptcp_timeout)) < 0) |
| goto could_not_connect; |
| |
| info->connected = TRUE; |
| } |
| return GST_RTSP_OK; |
| |
| /* ERRORS */ |
| parse_error: |
| { |
| GST_ERROR_OBJECT (src, "No valid RTSP URL was provided"); |
| return res; |
| } |
| could_not_create: |
| { |
| gchar *str = gst_rtsp_strresult (res); |
| GST_ERROR_OBJECT (src, "Could not create connection. (%s)", str); |
| g_free (str); |
| 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); |
| 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)); |
| } |
| |
| 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->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; |
| |
| /* we can try only TCP now */ |
| src->cur_protocols = GST_RTSP_LOWER_TRANS_TCP; |
| |
| /* close and cleanup our state */ |
| if ((res = gst_rtspsrc_close (src, async, FALSE)) < 0) |
| goto done; |
| |
| /* see if we have TCP left to try. Also don't try TCP when we were configured |
| * with an SDP. */ |
| if (!(src->protocols & GST_RTSP_LOWER_TRANS_TCP) || src->from_sdp) |
| goto no_protocols; |
| |
| /* 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))); |
| |
| /* 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_ERROR (src, STREAM, FAILED, |
| ("Internal data flow error."), |
| ("streaming task paused, reason %s (%d)", reason, 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: |
| 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 guint8 |
| enc_key_length_from_cipher_name (const gchar * cipher) |
| { |
| if (g_strcmp0 (cipher, "aes-128-icm") == 0) |
| return AES_128_KEY_LEN; |
| else if (g_strcmp0 (cipher, "aes-256-icm") == 0) |
| return AES_256_KEY_LEN; |
| else { |
| GST_ERROR ("encryption algorithm '%s' not supported", cipher); |
| return 0; |
| } |
| } |
| |
| static guint8 |
| auth_key_length_from_auth_name (const gchar * auth) |
| { |
| if (g_strcmp0 (auth, "hmac-sha1-32") == 0) |
| return HMAC_32_KEY_LEN; |
| else if (g_strcmp0 (auth, "hmac-sha1-80") == 0) |
| return HMAC_80_KEY_LEN; |
| else { |
| GST_ERROR ("authentication algorithm '%s' not supported", auth); |
| return 0; |
| } |
| } |
| |
| 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 |
| |
| /* create a random key */ |
| key_data = g_malloc (KEY_SIZE); |
| for (i = 0; i < KEY_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-srtp", |
| "srtp-key", GST_TYPE_BUFFER, buf, |
| "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) |
| { |
| GBytes *bytes; |
| gchar *result, *base64; |
| const guint8 *data; |
| gsize size; |
| GstMIKEYMessage *msg; |
| GstMIKEYPayload *payload, *pkd; |
| guint8 byte; |
| GstStructure *s; |
| GstMapInfo info; |
| GstBuffer *srtpkey; |
| const GValue *val; |
| const gchar *srtcpcipher, *srtcpauth; |
| |
| stream->srtcpparams = signal_get_srtcp_params (src, stream); |
| if (stream->srtcpparams == NULL) |
| stream->srtcpparams = default_srtcp_params (); |
| |
| s = gst_caps_get_structure (stream->srtcpparams, 0); |
| |
| srtcpcipher = gst_structure_get_string (s, "srtcp-cipher"); |
| srtcpauth = gst_structure_get_string (s, "srtcp-auth"); |
| val = gst_structure_get_value (s, "srtp-key"); |
| |
| if (srtcpcipher == NULL || srtcpauth == NULL || val == NULL) { |
| GST_ERROR_OBJECT (src, "could not find the right SRTP parameters in caps"); |
| return NULL; |
| } |
| |
| srtpkey = gst_value_get_buffer (val); |
| |
| msg = gst_mikey_message_new (); |
| /* unencrypted MIKEY message, we send this over TLS so this is allowed */ |
| gst_mikey_message_set_info (msg, GST_MIKEY_VERSION, GST_MIKEY_TYPE_PSK_INIT, |
| FALSE, GST_MIKEY_PRF_MIKEY_1, g_random_int (), GST_MIKEY_MAP_TYPE_SRTP); |
| /* add policy '0' for our SSRC */ |
| gst_mikey_message_add_cs_srtp (msg, 0, stream->send_ssrc, 0); |
| /* timestamp is now */ |
| gst_mikey_message_add_t_now_ntp_utc (msg); |
| /* add some random data */ |
| gst_mikey_message_add_rand_len (msg, 16); |
| |
| /* the policy '0' is SRTP */ |
| payload = gst_mikey_payload_new (GST_MIKEY_PT_SP); |
| gst_mikey_payload_sp_set (payload, 0, GST_MIKEY_SEC_PROTO_SRTP); |
| |
| /* only AES-CM is supported */ |
| byte = 1; |
| gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_ALG, 1, &byte); |
| /* encryption key length */ |
| byte = enc_key_length_from_cipher_name (srtcpcipher); |
| gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_ENC_KEY_LEN, 1, |
| &byte); |
| /* only HMAC-SHA1 */ |
| gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_ALG, 1, |
| &byte); |
| /* authentication key length */ |
| byte = auth_key_length_from_auth_name (srtcpauth); |
| gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_AUTH_KEY_LEN, 1, |
| &byte); |
| /* we enable encryption on RTP and RTCP */ |
| gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_ENC, 1, |
| &byte); |
| gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTCP_ENC, 1, |
| &byte); |
| /* we enable authentication on RTP and RTCP */ |
| gst_mikey_payload_sp_add_param (payload, GST_MIKEY_SP_SRTP_SRTP_AUTH, 1, |
| &byte); |
| gst_mikey_message_add_payload (msg, payload); |
| |
| /* make unencrypted KEMAC */ |
| payload = gst_mikey_payload_new (GST_MIKEY_PT_KEMAC); |
| gst_mikey_payload_kemac_set (payload, GST_MIKEY_ENC_NULL, GST_MIKEY_MAC_NULL); |
| /* add the key in KEMAC */ |
| pkd = gst_mikey_payload_new (GST_MIKEY_PT_KEY_DATA); |
| gst_buffer_map (srtpkey, &info, GST_MAP_READ); |
| gst_mikey_payload_key_data_set_key (pkd, GST_MIKEY_KD_TEK, info.size, |
| info.data); |
| gst_buffer_unmap (srtpkey, &info); |
| gst_mikey_payload_kemac_add_sub (payload, pkd); |
| gst_mikey_message_add_payload (msg, payload); |
| |
| /* now serialize this to bytes */ |
| bytes = gst_mikey_message_to_bytes (msg, NULL, NULL); |
| gst_mikey_message_unref (msg); |
| /* and make it into base64 */ |
| data = g_bytes_get_data (bytes, &size); |
| base64 = g_base64_encode (data, size); |
| g_bytes_unref (bytes); |
| |
| result = g_strdup_printf ("prot=mikey;uri=\"%s\";data=\"%s\"", |
| 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 <wrapped-clock> <server-IP:port> <clock-time>" |
| */ |
| 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) { |
| 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=<URL>;[seq=<seqbase>;rtptime=<timebase>] [, 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); |
| } |
| } |
| |
| 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: |
| 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; |
| } |