| /* 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, |
| SIGNAL_ACCEPT_CERTIFICATE, |
| SIGNAL_BEFORE_SEND, |
| SIGNAL_PUSH_BACKCHANNEL_BUFFER, |
| 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 DEBUG_RTSP(__self,msg) gst_rtspsrc_print_rtsp_message (__self, msg) |
| #define DEBUG_SDP(__self,msg) gst_rtspsrc_print_sdp_message (__self, msg) |
| |
| #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; |
| } |
| |
| enum _GstRtspBackchannel |
| { |
| BACKCHANNEL_NONE, |
| BACKCHANNEL_ONVIF |
| }; |
| |
| #define GST_TYPE_RTSP_BACKCHANNEL (gst_rtsp_backchannel_get_type()) |
| static GType |
| gst_rtsp_backchannel_get_type (void) |
| { |
| static GType backchannel_type = 0; |
| static const GEnumValue backchannel_values[] = { |
| {BACKCHANNEL_NONE, "No backchannel", "none"}, |
| {BACKCHANNEL_ONVIF, "ONVIF audio backchannel", "onvif"}, |
| {0, NULL, NULL}, |
| }; |
| |
| if (G_UNLIKELY (backchannel_type == 0)) { |
| backchannel_type = |
| g_enum_register_static ("GstRTSPBackchannel", backchannel_values); |
| } |
| return backchannel_type; |
| } |
| |
| #define BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL "www.onvif.org/ver20/backchannel" |
| |
| #define DEFAULT_LOCATION NULL |
| #define DEFAULT_PROTOCOLS GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_UDP_MCAST | GST_RTSP_LOWER_TRANS_TCP |
| #define DEFAULT_DEBUG FALSE |
| #define DEFAULT_RETRY 20 |
| #define DEFAULT_TIMEOUT 5000000 |
| #define DEFAULT_UDP_BUFFER_SIZE 0x80000 |
| #define DEFAULT_TCP_TIMEOUT 20000000 |
| #define DEFAULT_LATENCY_MS 2000 |
| #define DEFAULT_DROP_ON_LATENCY FALSE |
| #define DEFAULT_CONNECTION_SPEED 0 |
| #define DEFAULT_NAT_METHOD GST_RTSP_NAT_DUMMY |
| #define DEFAULT_DO_RTCP TRUE |
| #define DEFAULT_DO_RTSP_KEEP_ALIVE TRUE |
| #define DEFAULT_PROXY NULL |
| #define DEFAULT_RTP_BLOCKSIZE 0 |
| #define DEFAULT_USER_ID NULL |
| #define DEFAULT_USER_PW NULL |
| #define DEFAULT_BUFFER_MODE BUFFER_MODE_AUTO |
| #define DEFAULT_PORT_RANGE NULL |
| #define DEFAULT_SHORT_HEADER FALSE |
| #define DEFAULT_PROBATION 2 |
| #define DEFAULT_UDP_RECONNECT TRUE |
| #define DEFAULT_MULTICAST_IFACE NULL |
| #define DEFAULT_NTP_SYNC FALSE |
| #define DEFAULT_USE_PIPELINE_CLOCK FALSE |
| #define DEFAULT_TLS_VALIDATION_FLAGS G_TLS_CERTIFICATE_VALIDATE_ALL |
| #define DEFAULT_TLS_DATABASE NULL |
| #define DEFAULT_TLS_INTERACTION NULL |
| #define DEFAULT_DO_RETRANSMISSION TRUE |
| #define DEFAULT_NTP_TIME_SOURCE NTP_TIME_SOURCE_NTP |
| #define DEFAULT_USER_AGENT "GStreamer/" PACKAGE_VERSION |
| #define DEFAULT_MAX_RTCP_RTP_TIME_DIFF 1000 |
| #define DEFAULT_RFC7273_SYNC FALSE |
| #define DEFAULT_MAX_TS_OFFSET_ADJUSTMENT G_GUINT64_CONSTANT(0) |
| #define DEFAULT_MAX_TS_OFFSET G_GINT64_CONSTANT(3000000000) |
| #define DEFAULT_VERSION GST_RTSP_VERSION_1_0 |
| #define DEFAULT_BACKCHANNEL GST_RTSP_BACKCHANNEL_NONE |
| |
| enum |
| { |
| PROP_0, |
| PROP_LOCATION, |
| PROP_PROTOCOLS, |
| PROP_DEBUG, |
| PROP_RETRY, |
| PROP_TIMEOUT, |
| PROP_TCP_TIMEOUT, |
| PROP_LATENCY, |
| PROP_DROP_ON_LATENCY, |
| PROP_CONNECTION_SPEED, |
| PROP_NAT_METHOD, |
| PROP_DO_RTCP, |
| PROP_DO_RTSP_KEEP_ALIVE, |
| PROP_PROXY, |
| PROP_PROXY_ID, |
| PROP_PROXY_PW, |
| PROP_RTP_BLOCKSIZE, |
| PROP_USER_ID, |
| PROP_USER_PW, |
| PROP_BUFFER_MODE, |
| PROP_PORT_RANGE, |
| PROP_UDP_BUFFER_SIZE, |
| PROP_SHORT_HEADER, |
| PROP_PROBATION, |
| PROP_UDP_RECONNECT, |
| PROP_MULTICAST_IFACE, |
| PROP_NTP_SYNC, |
| PROP_USE_PIPELINE_CLOCK, |
| PROP_SDES, |
| PROP_TLS_VALIDATION_FLAGS, |
| PROP_TLS_DATABASE, |
| PROP_TLS_INTERACTION, |
| PROP_DO_RETRANSMISSION, |
| PROP_NTP_TIME_SOURCE, |
| PROP_USER_AGENT, |
| PROP_MAX_RTCP_RTP_TIME_DIFF, |
| PROP_RFC7273_SYNC, |
| PROP_MAX_TS_OFFSET_ADJUSTMENT, |
| PROP_MAX_TS_OFFSET, |
| PROP_DEFAULT_VERSION, |
| PROP_BACKCHANNEL, |
| }; |
| |
| #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; |
| } |
| |
| #define RTSP_SRC_RESPONSE_ERROR(src, response_msg, err_cat, err_code, error_message) \ |
| do { \ |
| GST_ELEMENT_ERROR_WITH_DETAILS((src), err_cat, err_code, ("%s", error_message), \ |
| ("%s (%d)", (response_msg)->type_data.response.reason, (response_msg)->type_data.response.code), \ |
| ("rtsp-status-code", G_TYPE_UINT, (response_msg)->type_data.response.code, \ |
| "rtsp-status-reason", G_TYPE_STRING, GST_STR_NULL((response_msg)->type_data.response.reason), NULL)); \ |
| } while (0) |
| |
| static void gst_rtspsrc_finalize (GObject * object); |
| |
| static void gst_rtspsrc_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_rtspsrc_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| static GstClock *gst_rtspsrc_provide_clock (GstElement * element); |
| |
| static void gst_rtspsrc_uri_handler_init (gpointer g_iface, |
| gpointer iface_data); |
| |
| static gboolean gst_rtspsrc_set_proxy (GstRTSPSrc * rtsp, const gchar * proxy); |
| static void gst_rtspsrc_set_tcp_timeout (GstRTSPSrc * rtspsrc, guint64 timeout); |
| |
| static GstStateChangeReturn gst_rtspsrc_change_state (GstElement * element, |
| GstStateChange transition); |
| static gboolean gst_rtspsrc_send_event (GstElement * element, GstEvent * event); |
| static void gst_rtspsrc_handle_message (GstBin * bin, GstMessage * message); |
| |
| static gboolean gst_rtspsrc_setup_auth (GstRTSPSrc * src, |
| GstRTSPMessage * response); |
| |
| static gboolean gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd, |
| gint mask); |
| static GstRTSPResult gst_rtspsrc_send_cb (GstRTSPExtension * ext, |
| GstRTSPMessage * request, GstRTSPMessage * response, GstRTSPSrc * src); |
| |
| static GstRTSPResult gst_rtspsrc_open (GstRTSPSrc * src, gboolean async); |
| static GstRTSPResult gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, |
| gboolean async, const gchar * seek_style); |
| static GstRTSPResult gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async); |
| static GstRTSPResult gst_rtspsrc_close (GstRTSPSrc * src, gboolean async, |
| gboolean only_close); |
| |
| static gboolean gst_rtspsrc_uri_set_uri (GstURIHandler * handler, |
| const gchar * uri, GError ** error); |
| static gchar *gst_rtspsrc_uri_get_uri (GstURIHandler * handler); |
| |
| static gboolean gst_rtspsrc_activate_streams (GstRTSPSrc * src); |
| static gboolean gst_rtspsrc_loop (GstRTSPSrc * src); |
| static gboolean gst_rtspsrc_stream_push_event (GstRTSPSrc * src, |
| GstRTSPStream * stream, GstEvent * event); |
| static gboolean gst_rtspsrc_push_event (GstRTSPSrc * src, GstEvent * event); |
| static void gst_rtspsrc_connection_flush (GstRTSPSrc * src, gboolean flush); |
| static GstRTSPResult gst_rtsp_conninfo_close (GstRTSPSrc * src, |
| GstRTSPConnInfo * info, gboolean free); |
| static void |
| gst_rtspsrc_print_rtsp_message (GstRTSPSrc * src, const GstRTSPMessage * msg); |
| static void |
| gst_rtspsrc_print_sdp_message (GstRTSPSrc * src, const GstSDPMessage * msg); |
| |
| static GstFlowReturn gst_rtspsrc_push_backchannel_buffer (GstRTSPSrc * src, |
| guint id, GstSample * sample); |
| |
| 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 gboolean |
| default_before_send (GstRTSPSrc * src, GstRTSPMessage * msg) |
| { |
| GST_DEBUG_OBJECT (src, "default handler"); |
| return TRUE; |
| } |
| |
| static gboolean |
| before_send_accum (GSignalInvocationHint * ihint, |
| GValue * return_accu, const GValue * handler_return, gpointer data) |
| { |
| gboolean myboolean; |
| |
| myboolean = g_value_get_boolean (handler_return); |
| g_value_set_boolean (return_accu, myboolean); |
| |
| /* prevent send 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" |
| "(DEPRECATED: Printed all RTSP message to gstreamer log as 'log' level)", |
| DEFAULT_DEBUG, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); |
| |
| g_object_class_install_property (gobject_class, PROP_RETRY, |
| g_param_spec_uint ("retry", "Retry", |
| "Max number of retries when allocating RTP ports.", |
| 0, G_MAXUINT16, DEFAULT_RETRY, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_TIMEOUT, |
| g_param_spec_uint64 ("timeout", "Timeout", |
| "Retry TCP transport after UDP timeout microseconds (0 = disabled)", |
| 0, G_MAXUINT64, DEFAULT_TIMEOUT, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_TCP_TIMEOUT, |
| g_param_spec_uint64 ("tcp-timeout", "TCP Timeout", |
| "Fail after timeout microseconds on TCP connections (0 = disabled)", |
| 0, G_MAXUINT64, DEFAULT_TCP_TIMEOUT, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_LATENCY, |
| g_param_spec_uint ("latency", "Buffer latency in ms", |
| "Amount of ms to buffer", 0, G_MAXUINT, DEFAULT_LATENCY_MS, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_DROP_ON_LATENCY, |
| g_param_spec_boolean ("drop-on-latency", |
| "Drop buffers when maximum latency is reached", |
| "Tells the jitterbuffer to never exceed the given latency in size", |
| DEFAULT_DROP_ON_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED, |
| g_param_spec_uint64 ("connection-speed", "Connection Speed", |
| "Network connection speed in kbps (0 = unknown)", |
| 0, G_MAXUINT64 / 1000, DEFAULT_CONNECTION_SPEED, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_NAT_METHOD, |
| g_param_spec_enum ("nat-method", "NAT Method", |
| "Method to use for traversing firewalls and NAT", |
| GST_TYPE_RTSP_NAT_METHOD, DEFAULT_NAT_METHOD, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc:do-rtcp: |
| * |
| * Enable RTCP support. Some old server don't like RTCP and then this property |
| * needs to be set to FALSE. |
| */ |
| g_object_class_install_property (gobject_class, PROP_DO_RTCP, |
| g_param_spec_boolean ("do-rtcp", "Do RTCP", |
| "Send RTCP packets, disable for old incompatible server.", |
| DEFAULT_DO_RTCP, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc:do-rtsp-keep-alive: |
| * |
| * Enable RTSP keep alive support. Some old server don't like RTSP |
| * keep alive and then this property needs to be set to FALSE. |
| */ |
| g_object_class_install_property (gobject_class, PROP_DO_RTSP_KEEP_ALIVE, |
| g_param_spec_boolean ("do-rtsp-keep-alive", "Do RTSP Keep Alive", |
| "Send RTSP keep alive packets, disable for old incompatible server.", |
| DEFAULT_DO_RTSP_KEEP_ALIVE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc:proxy: |
| * |
| * Set the proxy parameters. This has to be a string of the format |
| * [http://][user:passwd@]host[:port]. |
| */ |
| g_object_class_install_property (gobject_class, PROP_PROXY, |
| g_param_spec_string ("proxy", "Proxy", |
| "Proxy settings for HTTP tunneling. Format: [http://][user:passwd@]host[:port]", |
| DEFAULT_PROXY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| /** |
| * GstRTSPSrc:proxy-id: |
| * |
| * Sets the proxy URI user id for authentication. If the URI set via the |
| * "proxy" property contains a user-id already, that will take precedence. |
| * |
| * Since: 1.2 |
| */ |
| g_object_class_install_property (gobject_class, PROP_PROXY_ID, |
| g_param_spec_string ("proxy-id", "proxy-id", |
| "HTTP proxy URI user id for authentication", "", |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| /** |
| * GstRTSPSrc:proxy-pw: |
| * |
| * Sets the proxy URI password for authentication. If the URI set via the |
| * "proxy" property contains a password already, that will take precedence. |
| * |
| * Since: 1.2 |
| */ |
| g_object_class_install_property (gobject_class, PROP_PROXY_PW, |
| g_param_spec_string ("proxy-pw", "proxy-pw", |
| "HTTP proxy URI user password for authentication", "", |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc:rtp-blocksize: |
| * |
| * RTP package size to suggest to server. |
| */ |
| g_object_class_install_property (gobject_class, PROP_RTP_BLOCKSIZE, |
| g_param_spec_uint ("rtp-blocksize", "RTP Blocksize", |
| "RTP package size to suggest to server (0 = disabled)", |
| 0, 65536, DEFAULT_RTP_BLOCKSIZE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, |
| PROP_USER_ID, |
| g_param_spec_string ("user-id", "user-id", |
| "RTSP location URI user id for authentication", DEFAULT_USER_ID, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_USER_PW, |
| g_param_spec_string ("user-pw", "user-pw", |
| "RTSP location URI user password for authentication", DEFAULT_USER_PW, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc:buffer-mode: |
| * |
| * Control the buffering and timestamping mode used by the jitterbuffer. |
| */ |
| g_object_class_install_property (gobject_class, PROP_BUFFER_MODE, |
| g_param_spec_enum ("buffer-mode", "Buffer Mode", |
| "Control the buffering algorithm in use", |
| GST_TYPE_RTSP_SRC_BUFFER_MODE, DEFAULT_BUFFER_MODE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc:port-range: |
| * |
| * Configure the client port numbers that can be used to recieve RTP and |
| * RTCP. |
| */ |
| g_object_class_install_property (gobject_class, PROP_PORT_RANGE, |
| g_param_spec_string ("port-range", "Port range", |
| "Client port range that can be used to receive RTP and RTCP data, " |
| "eg. 3000-3005 (NULL = no restrictions)", DEFAULT_PORT_RANGE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc:udp-buffer-size: |
| * |
| * Size of the kernel UDP receive buffer in bytes. |
| */ |
| g_object_class_install_property (gobject_class, PROP_UDP_BUFFER_SIZE, |
| g_param_spec_int ("udp-buffer-size", "UDP Buffer Size", |
| "Size of the kernel UDP receive buffer in bytes, 0=default", |
| 0, G_MAXINT, DEFAULT_UDP_BUFFER_SIZE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc:short-header: |
| * |
| * Only send the basic RTSP headers for broken encoders. |
| */ |
| g_object_class_install_property (gobject_class, PROP_SHORT_HEADER, |
| g_param_spec_boolean ("short-header", "Short Header", |
| "Only send the basic RTSP headers for broken encoders", |
| DEFAULT_SHORT_HEADER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_PROBATION, |
| g_param_spec_uint ("probation", "Number of probations", |
| "Consecutive packet sequence numbers to accept the source", |
| 0, G_MAXUINT, DEFAULT_PROBATION, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_UDP_RECONNECT, |
| g_param_spec_boolean ("udp-reconnect", "Reconnect to the server", |
| "Reconnect to the server if RTSP connection is closed when doing UDP", |
| DEFAULT_UDP_RECONNECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_MULTICAST_IFACE, |
| g_param_spec_string ("multicast-iface", "Multicast Interface", |
| "The network interface on which to join the multicast group", |
| DEFAULT_MULTICAST_IFACE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_NTP_SYNC, |
| g_param_spec_boolean ("ntp-sync", "Sync on NTP clock", |
| "Synchronize received streams to the NTP clock", DEFAULT_NTP_SYNC, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_USE_PIPELINE_CLOCK, |
| g_param_spec_boolean ("use-pipeline-clock", "Use pipeline clock", |
| "Use the pipeline running-time to set the NTP time in the RTCP SR messages" |
| "(DEPRECATED: Use ntp-time-source property)", |
| DEFAULT_USE_PIPELINE_CLOCK, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); |
| |
| g_object_class_install_property (gobject_class, PROP_SDES, |
| g_param_spec_boxed ("sdes", "SDES", |
| "The SDES items of this session", |
| GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc::tls-validation-flags: |
| * |
| * TLS certificate validation flags used to validate server |
| * certificate. |
| * |
| * Since: 1.2.1 |
| */ |
| g_object_class_install_property (gobject_class, PROP_TLS_VALIDATION_FLAGS, |
| g_param_spec_flags ("tls-validation-flags", "TLS validation flags", |
| "TLS certificate validation flags used to validate the server certificate", |
| G_TYPE_TLS_CERTIFICATE_FLAGS, DEFAULT_TLS_VALIDATION_FLAGS, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc::tls-database: |
| * |
| * TLS database with anchor certificate authorities used to validate |
| * the server certificate. |
| * |
| * Since: 1.4 |
| */ |
| g_object_class_install_property (gobject_class, PROP_TLS_DATABASE, |
| g_param_spec_object ("tls-database", "TLS database", |
| "TLS database with anchor certificate authorities used to validate the server certificate", |
| G_TYPE_TLS_DATABASE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc::tls-interaction: |
| * |
| * A #GTlsInteraction object to be used when the connection or certificate |
| * database need to interact with the user. This will be used to prompt the |
| * user for passwords where necessary. |
| * |
| * Since: 1.6 |
| */ |
| g_object_class_install_property (gobject_class, PROP_TLS_INTERACTION, |
| g_param_spec_object ("tls-interaction", "TLS interaction", |
| "A GTlsInteraction object to promt the user for password or certificate", |
| G_TYPE_TLS_INTERACTION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc::do-retransmission: |
| * |
| * Attempt to ask the server to retransmit lost packets according to RFC4588. |
| * |
| * Note: currently only works with SSRC-multiplexed retransmission streams |
| * |
| * Since: 1.6 |
| */ |
| g_object_class_install_property (gobject_class, PROP_DO_RETRANSMISSION, |
| g_param_spec_boolean ("do-retransmission", "Retransmission", |
| "Ask the server to retransmit lost packets", |
| DEFAULT_DO_RETRANSMISSION, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc::ntp-time-source: |
| * |
| * allows to select the time source that should be used |
| * for the NTP time in RTCP packets |
| * |
| * Since: 1.6 |
| */ |
| g_object_class_install_property (gobject_class, PROP_NTP_TIME_SOURCE, |
| g_param_spec_enum ("ntp-time-source", "NTP Time Source", |
| "NTP time source for RTCP packets", |
| GST_TYPE_RTSP_SRC_NTP_TIME_SOURCE, DEFAULT_NTP_TIME_SOURCE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc::user-agent: |
| * |
| * The string to set in the User-Agent header. |
| * |
| * Since: 1.6 |
| */ |
| g_object_class_install_property (gobject_class, PROP_USER_AGENT, |
| g_param_spec_string ("user-agent", "User Agent", |
| "The User-Agent string to send to the server", |
| DEFAULT_USER_AGENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_MAX_RTCP_RTP_TIME_DIFF, |
| g_param_spec_int ("max-rtcp-rtp-time-diff", "Max RTCP RTP Time Diff", |
| "Maximum amount of time in ms that the RTP time in RTCP SRs " |
| "is allowed to be ahead (-1 disabled)", -1, G_MAXINT, |
| DEFAULT_MAX_RTCP_RTP_TIME_DIFF, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_RFC7273_SYNC, |
| g_param_spec_boolean ("rfc7273-sync", "Sync on RFC7273 clock", |
| "Synchronize received streams to the RFC7273 clock " |
| "(requires clock and offset to be provided)", DEFAULT_RFC7273_SYNC, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc:default-rtsp-version: |
| * |
| * The preferred RTSP version to use while negotiating the version with the server. |
| * |
| * Since: 1.14 |
| */ |
| g_object_class_install_property (gobject_class, PROP_DEFAULT_VERSION, |
| g_param_spec_enum ("default-rtsp-version", |
| "The RTSP version to try first", |
| "The RTSP version that should be tried first when negotiating version.", |
| GST_TYPE_RTSP_VERSION, DEFAULT_VERSION, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRTSPSrc:max-ts-offset-adjustment: |
| * |
| * Syncing time stamps to NTP time adds a time offset. This parameter |
| * specifies the maximum number of nanoseconds per frame that this time offset |
| * may be adjusted with. This is used to avoid sudden large changes to time |
| * stamps. |
| */ |
| g_object_class_install_property (gobject_class, PROP_MAX_TS_OFFSET_ADJUSTMENT, |
| g_param_spec_uint64 ("max-ts-offset-adjustment", |
| "Max Timestamp Offset Adjustment", |
| "The maximum number of nanoseconds per frame that time stamp offsets " |
| "may be adjusted (0 = no limit).", 0, G_MAXUINT64, |
| DEFAULT_MAX_TS_OFFSET_ADJUSTMENT, G_PARAM_READWRITE | |
| G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRtpBin:max-ts-offset: |
| * |
| * Used to set an upper limit of how large a time offset may be. This |
| * is used to protect against unrealistic values as a result of either |
| * client,server or clock issues. |
| */ |
| g_object_class_install_property (gobject_class, PROP_MAX_TS_OFFSET, |
| g_param_spec_int64 ("max-ts-offset", "Max TS Offset", |
| "The maximum absolute value of the time offset in (nanoseconds). " |
| "Note, if the ntp-sync parameter is set the default value is " |
| "changed to 0 (no limit)", 0, G_MAXINT64, DEFAULT_MAX_TS_OFFSET, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /** |
| * GstRtpSrc:backchannel |
| * |
| * Select a type of backchannel to setup with the RTSP server. |
| * Default value is "none". Allowed values are "none" and "onvif". |
| * |
| * Since: 1.14 |
| */ |
| g_object_class_install_property (gobject_class, PROP_BACKCHANNEL, |
| g_param_spec_enum ("backchannel", "Backchannel type", |
| "The type of backchannel to setup. Default is 'none'.", |
| GST_TYPE_RTSP_BACKCHANNEL, BACKCHANNEL_NONE, |
| 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); |
| |
| /** |
| * GstRTSPSrc::accept-certificate: |
| * @rtspsrc: a #GstRTSPSrc |
| * @peer_cert: the peer's #GTlsCertificate |
| * @errors: the problems with @peer_cert |
| * @user_data: user data set when the signal handler was connected. |
| * |
| * This will directly map to #GTlsConnection 's "accept-certificate" |
| * signal and be performed after the default checks of #GstRTSPConnection |
| * (checking against the #GTlsDatabase with the given #GTlsCertificateFlags) |
| * have failed. If no #GTlsDatabase is set on this connection, only this |
| * signal will be emitted. |
| * |
| * Since: 1.14 |
| */ |
| gst_rtspsrc_signals[SIGNAL_ACCEPT_CERTIFICATE] = |
| g_signal_new ("accept-certificate", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST, 0, g_signal_accumulator_true_handled, NULL, NULL, |
| G_TYPE_BOOLEAN, 3, G_TYPE_TLS_CONNECTION, G_TYPE_TLS_CERTIFICATE, |
| G_TYPE_TLS_CERTIFICATE_FLAGS); |
| |
| /* |
| * GstRTSPSrc::before-send |
| * @rtspsrc: a #GstRTSPSrc |
| * @num: the stream number |
| * |
| * Emitted before each RTSP request is sent, in order to allow |
| * the application to modify send parameters or to skip the message entirely. |
| * This can be used, for example, to work with ONVIF Profile G servers, |
| * which need a different/additional range, rate-control, and intra/x |
| * parameters. |
| * |
| * Returns: %TRUE when the command should be sent, %FALSE when the |
| * command should be dropped. |
| * |
| * Since: 1.14 |
| */ |
| gst_rtspsrc_signals[SIGNAL_BEFORE_SEND] = |
| g_signal_new_class_handler ("before-send", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_CLEANUP, |
| (GCallback) default_before_send, before_send_accum, NULL, |
| g_cclosure_marshal_generic, G_TYPE_BOOLEAN, |
| 1, GST_TYPE_RTSP_MESSAGE | G_SIGNAL_TYPE_STATIC_SCOPE); |
| |
| /** |
| * GstRTSPSrc::push-backchannel-buffer: |
| * @rtspsrc: a #GstRTSPSrc |
| * @buffer: RTP buffer to send back |
| * |
| * |
| */ |
| gst_rtspsrc_signals[SIGNAL_PUSH_BACKCHANNEL_BUFFER] = |
| g_signal_new ("push-backchannel-buffer", G_TYPE_FROM_CLASS (klass), |
| G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRTSPSrcClass, |
| push_backchannel_buffer), NULL, NULL, NULL, GST_TYPE_FLOW_RETURN, 2, |
| G_TYPE_UINT, GST_TYPE_BUFFER); |
| |
| gstelement_class->send_event = gst_rtspsrc_send_event; |
| gstelement_class->provide_clock = gst_rtspsrc_provide_clock; |
| gstelement_class->change_state = gst_rtspsrc_change_state; |
| |
| gst_element_class_add_static_pad_template (gstelement_class, &rtptemplate); |
| |
| gst_element_class_set_static_metadata (gstelement_class, |
| "RTSP packet receiver", "Source/Network", |
| "Receive data over the network via RTSP (RFC 2326)", |
| "Wim Taymans <wim@fluendo.com>, " |
| "Thijs Vermeir <thijs.vermeir@barco.com>, " |
| "Lutz Mueller <lutz@topfrose.de>"); |
| |
| gstbin_class->handle_message = gst_rtspsrc_handle_message; |
| |
| klass->push_backchannel_buffer = gst_rtspsrc_push_backchannel_buffer; |
| |
| gst_rtsp_ext_list_init (); |
| } |
| |
| static void |
| gst_rtspsrc_init (GstRTSPSrc * src) |
| { |
| src->conninfo.location = g_strdup (DEFAULT_LOCATION); |
| src->protocols = DEFAULT_PROTOCOLS; |
| src->debug = DEFAULT_DEBUG; |
| src->retry = DEFAULT_RETRY; |
| src->udp_timeout = DEFAULT_TIMEOUT; |
| gst_rtspsrc_set_tcp_timeout (src, DEFAULT_TCP_TIMEOUT); |
| src->latency = DEFAULT_LATENCY_MS; |
| src->drop_on_latency = DEFAULT_DROP_ON_LATENCY; |
| src->connection_speed = DEFAULT_CONNECTION_SPEED; |
| src->nat_method = DEFAULT_NAT_METHOD; |
| src->do_rtcp = DEFAULT_DO_RTCP; |
| src->do_rtsp_keep_alive = DEFAULT_DO_RTSP_KEEP_ALIVE; |
| gst_rtspsrc_set_proxy (src, DEFAULT_PROXY); |
| src->rtp_blocksize = DEFAULT_RTP_BLOCKSIZE; |
| src->user_id = g_strdup (DEFAULT_USER_ID); |
| src->user_pw = g_strdup (DEFAULT_USER_PW); |
| src->buffer_mode = DEFAULT_BUFFER_MODE; |
| src->client_port_range.min = 0; |
| src->client_port_range.max = 0; |
| src->udp_buffer_size = DEFAULT_UDP_BUFFER_SIZE; |
| src->short_header = DEFAULT_SHORT_HEADER; |
| src->probation = DEFAULT_PROBATION; |
| src->udp_reconnect = DEFAULT_UDP_RECONNECT; |
| src->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE); |
| src->ntp_sync = DEFAULT_NTP_SYNC; |
| src->use_pipeline_clock = DEFAULT_USE_PIPELINE_CLOCK; |
| src->sdes = NULL; |
| src->tls_validation_flags = DEFAULT_TLS_VALIDATION_FLAGS; |
| src->tls_database = DEFAULT_TLS_DATABASE; |
| src->tls_interaction = DEFAULT_TLS_INTERACTION; |
| src->do_retransmission = DEFAULT_DO_RETRANSMISSION; |
| src->ntp_time_source = DEFAULT_NTP_TIME_SOURCE; |
| src->user_agent = g_strdup (DEFAULT_USER_AGENT); |
| src->max_rtcp_rtp_time_diff = DEFAULT_MAX_RTCP_RTP_TIME_DIFF; |
| src->rfc7273_sync = DEFAULT_RFC7273_SYNC; |
| src->max_ts_offset_adjustment = DEFAULT_MAX_TS_OFFSET_ADJUSTMENT; |
| src->max_ts_offset = DEFAULT_MAX_TS_OFFSET; |
| src->max_ts_offset_is_set = FALSE; |
| src->default_version = DEFAULT_VERSION; |
| src->version = GST_RTSP_VERSION_INVALID; |
| |
| /* 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; |
| |
| g_mutex_init (&src->conninfo.send_lock); |
| g_mutex_init (&src->conninfo.recv_lock); |
| |
| GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_SOURCE); |
| gst_bin_set_suppressed_flags (GST_BIN (src), |
| GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK); |
| } |
| |
| 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_mutex_clear (&rtspsrc->conninfo.send_lock); |
| g_mutex_clear (&rtspsrc->conninfo.recv_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) |
| return gst_object_ref (clock); |
| |
| return GST_ELEMENT_CLASS (parent_class)->provide_clock (element); |
| } |
| |
| /* a proxy string of the format [user:passwd@]host[:port] */ |
| static gboolean |
| gst_rtspsrc_set_proxy (GstRTSPSrc * rtsp, const gchar * proxy) |
| { |
| gchar *p, *at, *col; |
| |
| g_free (rtsp->proxy_user); |
| rtsp->proxy_user = NULL; |
| g_free (rtsp->proxy_passwd); |
| rtsp->proxy_passwd = NULL; |
| g_free (rtsp->proxy_host); |
| rtsp->proxy_host = NULL; |
| rtsp->proxy_port = 0; |
| |
| p = (gchar *) proxy; |
| |
| if (p == NULL) |
| return TRUE; |
| |
| /* we allow http:// in front but ignore it */ |
| if (g_str_has_prefix (p, "http://")) |
| p += 7; |
| |
| at = strchr (p, '@'); |
| if (at) { |
| /* look for user:passwd */ |
| col = strchr (proxy, ':'); |
| if (col == NULL || col > at) |
| return FALSE; |
| |
| rtsp->proxy_user = g_strndup (p, col - p); |
| col++; |
| rtsp->proxy_passwd = g_strndup (col, at - col); |
| |
| /* move to host */ |
| p = at + 1; |
| } else { |
| if (rtsp->prop_proxy_id != NULL && *rtsp->prop_proxy_id != '\0') |
| rtsp->proxy_user = g_strdup (rtsp->prop_proxy_id); |
| if (rtsp->prop_proxy_pw != NULL && *rtsp->prop_proxy_pw != '\0') |
| rtsp->proxy_passwd = g_strdup (rtsp->prop_proxy_pw); |
| if (rtsp->proxy_user != NULL || rtsp->proxy_passwd != NULL) { |
| GST_LOG_OBJECT (rtsp, "set proxy user/pw from properties: %s:%s", |
| GST_STR_NULL (rtsp->proxy_user), GST_STR_NULL (rtsp->proxy_passwd)); |
| } |
| } |
| col = strchr (p, ':'); |
| |
| if (col) { |
| /* everything before the colon is the hostname */ |
| rtsp->proxy_host = g_strndup (p, col - p); |
| p = col + 1; |
| rtsp->proxy_port = strtoul (p, (char **) &p, 10); |
| } else { |
| rtsp->proxy_host = g_strdup (p); |
| rtsp->proxy_port = 8080; |
| } |
| return TRUE; |
| } |
| |
| static void |
| gst_rtspsrc_set_tcp_timeout (GstRTSPSrc * rtspsrc, guint64 timeout) |
| { |
| rtspsrc->tcp_timeout.tv_sec = timeout / G_USEC_PER_SEC; |
| rtspsrc->tcp_timeout.tv_usec = timeout % G_USEC_PER_SEC; |
| |
| if (timeout != 0) |
| rtspsrc->ptcp_timeout = &rtspsrc->tcp_timeout; |
| else |
| rtspsrc->ptcp_timeout = NULL; |
| } |
| |
| static void |
| gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, |
| GParamSpec * pspec) |
| { |
| GstRTSPSrc *rtspsrc; |
| |
| rtspsrc = GST_RTSPSRC (object); |
| |
| switch (prop_id) { |
| case PROP_LOCATION: |
| gst_rtspsrc_uri_set_uri (GST_URI_HANDLER (rtspsrc), |
| g_value_get_string (value), NULL); |
| break; |
| case PROP_PROTOCOLS: |
| rtspsrc->protocols = g_value_get_flags (value); |
| break; |
| case PROP_DEBUG: |
| rtspsrc->debug = g_value_get_boolean (value); |
| break; |
| case PROP_RETRY: |
| rtspsrc->retry = g_value_get_uint (value); |
| break; |
| case PROP_TIMEOUT: |
| rtspsrc->udp_timeout = g_value_get_uint64 (value); |
| break; |
| case PROP_TCP_TIMEOUT: |
| gst_rtspsrc_set_tcp_timeout (rtspsrc, g_value_get_uint64 (value)); |
| break; |
| case PROP_LATENCY: |
| rtspsrc->latency = g_value_get_uint (value); |
| break; |
| case PROP_DROP_ON_LATENCY: |
| rtspsrc->drop_on_latency = g_value_get_boolean (value); |
| break; |
| case PROP_CONNECTION_SPEED: |
| rtspsrc->connection_speed = g_value_get_uint64 (value); |
| break; |
| case PROP_NAT_METHOD: |
| rtspsrc->nat_method = g_value_get_enum (value); |
| break; |
| case PROP_DO_RTCP: |
| rtspsrc->do_rtcp = g_value_get_boolean (value); |
| break; |
| case PROP_DO_RTSP_KEEP_ALIVE: |
| rtspsrc->do_rtsp_keep_alive = g_value_get_boolean (value); |
| break; |
| case PROP_PROXY: |
| gst_rtspsrc_set_proxy (rtspsrc, g_value_get_string (value)); |
| break; |
| case PROP_PROXY_ID: |
| g_free (rtspsrc->prop_proxy_id); |
| rtspsrc->prop_proxy_id = g_value_dup_string (value); |
| break; |
| case PROP_PROXY_PW: |
| g_free (rtspsrc->prop_proxy_pw); |
| rtspsrc->prop_proxy_pw = g_value_dup_string (value); |
| break; |
| case PROP_RTP_BLOCKSIZE: |
| rtspsrc->rtp_blocksize = g_value_get_uint (value); |
| break; |
| case PROP_USER_ID: |
| g_free (rtspsrc->user_id); |
| rtspsrc->user_id = g_value_dup_string (value); |
| break; |
| case PROP_USER_PW: |
| g_free (rtspsrc->user_pw); |
| rtspsrc->user_pw = g_value_dup_string (value); |
| break; |
| case PROP_BUFFER_MODE: |
| rtspsrc->buffer_mode = g_value_get_enum (value); |
| break; |
| case PROP_PORT_RANGE: |
| { |
| const gchar *str; |
| |
| str = g_value_get_string (value); |
| if (sscanf (str, "%u-%u", &rtspsrc->client_port_range.min, |
| &rtspsrc->client_port_range.max) != 2) { |
| rtspsrc->client_port_range.min = 0; |
| rtspsrc->client_port_range.max = 0; |
| } |
| break; |
| } |
| case PROP_UDP_BUFFER_SIZE: |
| rtspsrc->udp_buffer_size = g_value_get_int (value); |
| break; |
| case PROP_SHORT_HEADER: |
| rtspsrc->short_header = g_value_get_boolean (value); |
| break; |
| case PROP_PROBATION: |
| rtspsrc->probation = g_value_get_uint (value); |
| break; |
| case PROP_UDP_RECONNECT: |
| rtspsrc->udp_reconnect = g_value_get_boolean (value); |
| break; |
| case PROP_MULTICAST_IFACE: |
| g_free (rtspsrc->multi_iface); |
| |
| if (g_value_get_string (value) == NULL) |
| rtspsrc->multi_iface = g_strdup (DEFAULT_MULTICAST_IFACE); |
| else |
| rtspsrc->multi_iface = g_value_dup_string (value); |
| break; |
| case PROP_NTP_SYNC: |
| rtspsrc->ntp_sync = g_value_get_boolean (value); |
| /* The default value of max_ts_offset depends on ntp_sync. If user |
| * hasn't set it then change default value */ |
| if (!rtspsrc->max_ts_offset_is_set) { |
| if (rtspsrc->ntp_sync) { |
| rtspsrc->max_ts_offset = 0; |
| } else { |
| rtspsrc->max_ts_offset = DEFAULT_MAX_TS_OFFSET; |
| } |
| } |
| break; |
| case PROP_USE_PIPELINE_CLOCK: |
| rtspsrc->use_pipeline_clock = g_value_get_boolean (value); |
| break; |
| case PROP_SDES: |
| rtspsrc->sdes = g_value_dup_boxed (value); |
| break; |
| case PROP_TLS_VALIDATION_FLAGS: |
| rtspsrc->tls_validation_flags = g_value_get_flags (value); |
| break; |
| case PROP_TLS_DATABASE: |
| g_clear_object (&rtspsrc->tls_database); |
| rtspsrc->tls_database = g_value_dup_object (value); |
| break; |
| case PROP_TLS_INTERACTION: |
| g_clear_object (&rtspsrc->tls_interaction); |
| rtspsrc->tls_interaction = g_value_dup_object (value); |
| break; |
| case PROP_DO_RETRANSMISSION: |
| rtspsrc->do_retransmission = g_value_get_boolean (value); |
| break; |
| case PROP_NTP_TIME_SOURCE: |
| rtspsrc->ntp_time_source = g_value_get_enum (value); |
| break; |
| case PROP_USER_AGENT: |
| g_free (rtspsrc->user_agent); |
| rtspsrc->user_agent = g_value_dup_string (value); |
| break; |
| case PROP_MAX_RTCP_RTP_TIME_DIFF: |
| rtspsrc->max_rtcp_rtp_time_diff = g_value_get_int (value); |
| break; |
| case PROP_RFC7273_SYNC: |
| rtspsrc->rfc7273_sync = g_value_get_boolean (value); |
| break; |
| case PROP_MAX_TS_OFFSET_ADJUSTMENT: |
| rtspsrc->max_ts_offset_adjustment = g_value_get_uint64 (value); |
| break; |
| case PROP_MAX_TS_OFFSET: |
| rtspsrc->max_ts_offset = g_value_get_int64 (value); |
| rtspsrc->max_ts_offset_is_set = TRUE; |
| break; |
| case PROP_DEFAULT_VERSION: |
| rtspsrc->default_version = g_value_get_enum (value); |
| break; |
| case PROP_BACKCHANNEL: |
| rtspsrc->backchannel = g_value_get_enum (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value, |
| GParamSpec * pspec) |
| { |
| GstRTSPSrc *rtspsrc; |
| |
| rtspsrc = GST_RTSPSRC (object); |
| |
| switch (prop_id) { |
| case PROP_LOCATION: |
| g_value_set_string (value, rtspsrc->conninfo.location); |
| break; |
| case PROP_PROTOCOLS: |
| g_value_set_flags (value, rtspsrc->protocols); |
| break; |
| case PROP_DEBUG: |
| g_value_set_boolean (value, rtspsrc->debug); |
| break; |
| case PROP_RETRY: |
| g_value_set_uint (value, rtspsrc->retry); |
| break; |
| case PROP_TIMEOUT: |
| g_value_set_uint64 (value, rtspsrc->udp_timeout); |
| break; |
| case PROP_TCP_TIMEOUT: |
| { |
| guint64 timeout; |
| |
| timeout = ((guint64) rtspsrc->tcp_timeout.tv_sec) * G_USEC_PER_SEC + |
| rtspsrc->tcp_timeout.tv_usec; |
| g_value_set_uint64 (value, timeout); |
| break; |
| } |
| case PROP_LATENCY: |
| g_value_set_uint (value, rtspsrc->latency); |
| break; |
| case PROP_DROP_ON_LATENCY: |
| g_value_set_boolean (value, rtspsrc->drop_on_latency); |
| break; |
| case PROP_CONNECTION_SPEED: |
| g_value_set_uint64 (value, rtspsrc->connection_speed); |
| break; |
| case PROP_NAT_METHOD: |
| g_value_set_enum (value, rtspsrc->nat_method); |
| break; |
| case PROP_DO_RTCP: |
| g_value_set_boolean (value, rtspsrc->do_rtcp); |
| break; |
| case PROP_DO_RTSP_KEEP_ALIVE: |
| g_value_set_boolean (value, rtspsrc->do_rtsp_keep_alive); |
| break; |
| case PROP_PROXY: |
| { |
| gchar *str; |
| |
| if (rtspsrc->proxy_host) { |
| str = |
| g_strdup_printf ("%s:%d", rtspsrc->proxy_host, rtspsrc->proxy_port); |
| } else { |
| str = NULL; |
| } |
| g_value_take_string (value, str); |
| break; |
| } |
| case PROP_PROXY_ID: |
| g_value_set_string (value, rtspsrc->prop_proxy_id); |
| break; |
| case PROP_PROXY_PW: |
| g_value_set_string (value, rtspsrc->prop_proxy_pw); |
| break; |
| case PROP_RTP_BLOCKSIZE: |
| g_value_set_uint (value, rtspsrc->rtp_blocksize); |
| break; |
| case PROP_USER_ID: |
| g_value_set_string (value, rtspsrc->user_id); |
| break; |
| case PROP_USER_PW: |
| g_value_set_string (value, rtspsrc->user_pw); |
| break; |
| case PROP_BUFFER_MODE: |
| g_value_set_enum (value, rtspsrc->buffer_mode); |
| break; |
| case PROP_PORT_RANGE: |
| { |
| gchar *str; |
| |
| if (rtspsrc->client_port_range.min != 0) { |
| str = g_strdup_printf ("%u-%u", rtspsrc->client_port_range.min, |
| rtspsrc->client_port_range.max); |
| } else { |
| str = NULL; |
| } |
| g_value_take_string (value, str); |
| break; |
| } |
| case PROP_UDP_BUFFER_SIZE: |
| g_value_set_int (value, rtspsrc->udp_buffer_size); |
| break; |
| case PROP_SHORT_HEADER: |
| g_value_set_boolean (value, rtspsrc->short_header); |
| break; |
| case PROP_PROBATION: |
| g_value_set_uint (value, rtspsrc->probation); |
| break; |
| case PROP_UDP_RECONNECT: |
| g_value_set_boolean (value, rtspsrc->udp_reconnect); |
| break; |
| case PROP_MULTICAST_IFACE: |
| g_value_set_string (value, rtspsrc->multi_iface); |
| break; |
| case PROP_NTP_SYNC: |
| g_value_set_boolean (value, rtspsrc->ntp_sync); |
| break; |
| case PROP_USE_PIPELINE_CLOCK: |
| g_value_set_boolean (value, rtspsrc->use_pipeline_clock); |
| break; |
| case PROP_SDES: |
| g_value_set_boxed (value, rtspsrc->sdes); |
| break; |
| case PROP_TLS_VALIDATION_FLAGS: |
| g_value_set_flags (value, rtspsrc->tls_validation_flags); |
| break; |
| case PROP_TLS_DATABASE: |
| g_value_set_object (value, rtspsrc->tls_database); |
| break; |
| case PROP_TLS_INTERACTION: |
| g_value_set_object (value, rtspsrc->tls_interaction); |
| break; |
| case PROP_DO_RETRANSMISSION: |
| g_value_set_boolean (value, rtspsrc->do_retransmission); |
| break; |
| case PROP_NTP_TIME_SOURCE: |
| g_value_set_enum (value, rtspsrc->ntp_time_source); |
| break; |
| case PROP_USER_AGENT: |
| g_value_set_string (value, rtspsrc->user_agent); |
| break; |
| case PROP_MAX_RTCP_RTP_TIME_DIFF: |
| g_value_set_int (value, rtspsrc->max_rtcp_rtp_time_diff); |
| break; |
| case PROP_RFC7273_SYNC: |
| g_value_set_boolean (value, rtspsrc->rfc7273_sync); |
| break; |
| case PROP_MAX_TS_OFFSET_ADJUSTMENT: |
| g_value_set_uint64 (value, rtspsrc->max_ts_offset_adjustment); |
| break; |
| case PROP_MAX_TS_OFFSET: |
| g_value_set_int64 (value, rtspsrc->max_ts_offset); |
| break; |
| case PROP_DEFAULT_VERSION: |
| g_value_set_enum (value, rtspsrc->default_version); |
| break; |
| case PROP_BACKCHANNEL: |
| g_value_set_enum (value, rtspsrc->backchannel); |
| 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) |
| { |
| /* ignore unconfigured channels here (e.g., those that |
| * were explicitly skipped during SETUP) */ |
| if ((stream->channelpad[0] != NULL) && |
| (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); |
| } |
| } |
| |
| static gchar * |
| make_stream_id (GstRTSPStream * stream, const GstSDPMedia * media) |
| { |
| gchar *stream_id = |
| g_strdup_printf ("%s:%d:%d:%s:%d", media->media, media->port, |
| media->num_ports, media->proto, stream->default_pt); |
| |
| g_strcanon (stream_id, G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS, ':'); |
| |
| return stream_id; |
| } |
| |
| /* 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; |
| GstCaps *global_caps; |
| |
| /* get proto */ |
| proto = gst_sdp_media_get_proto (media); |
| if (proto == NULL) |
| goto no_proto; |
| |
| if (g_str_equal (proto, "RTP/AVP")) |
| stream->profile = GST_RTSP_PROFILE_AVP; |
| else if (g_str_equal (proto, "RTP/SAVP")) |
| stream->profile = GST_RTSP_PROFILE_SAVP; |
| else if (g_str_equal (proto, "RTP/AVPF")) |
| stream->profile = GST_RTSP_PROFILE_AVPF; |
| else if (g_str_equal (proto, "RTP/SAVPF")) |
| stream->profile = GST_RTSP_PROFILE_SAVPF; |
| else |
| goto unknown_proto; |
| |
| if (gst_sdp_media_get_attribute_val (media, "sendonly") != NULL && |
| /* We want to setup caps for streams configured as backchannel */ |
| !stream->is_backchannel && src->backchannel != BACKCHANNEL_NONE) |
| goto sendonly_media; |
| |
| /* Parse global SDP attributes once */ |
| global_caps = gst_caps_new_empty_simple ("application/x-unknown"); |
| GST_DEBUG ("mapping sdp session level attributes to caps"); |
| gst_sdp_message_attributes_to_caps (sdp, global_caps); |
| GST_DEBUG ("mapping sdp media level attributes to caps"); |
| gst_sdp_media_attributes_to_caps (media, global_caps); |
| |
| /* Keep a copy of the SDP key management */ |
| gst_sdp_media_parse_keymgmt (media, &stream->mikey); |
| if (stream->mikey == NULL) |
| gst_sdp_message_parse_keymgmt (sdp, &stream->mikey); |
| |
| len = gst_sdp_media_formats_len (media); |
| for (i = 0; i < len; i++) { |
| gint pt; |
| GstCaps *caps, *outcaps; |
| GstStructure *s; |
| const gchar *enc; |
| PtMapItem item; |
| |
| pt = atoi (gst_sdp_media_get_format (media, i)); |
| |
| GST_DEBUG_OBJECT (src, " looking at %d pt: %d", i, pt); |
| |
| /* convert caps */ |
| caps = gst_sdp_media_get_caps_from_media (media, pt); |
| if (caps == NULL) { |
| GST_WARNING_OBJECT (src, " skipping pt %d without caps", pt); |
| continue; |
| } |
| |
| /* do some tweaks */ |
| s = gst_caps_get_structure (caps, 0); |
| if ((enc = gst_structure_get_string (s, "encoding-name"))) { |
| stream->is_real = (strstr (enc, "-REAL") != NULL); |
| if (strcmp (enc, "X-ASF-PF") == 0) |
| stream->container = TRUE; |
| } |
| |
| /* Merge in global caps */ |
| /* Intersect will merge in missing fields to the current caps */ |
| outcaps = gst_caps_intersect (caps, global_caps); |
| gst_caps_unref (caps); |
| |
| /* the first pt will be the default */ |
| if (stream->ptmap->len == 0) |
| stream->default_pt = pt; |
| |
| item.pt = pt; |
| item.caps = outcaps; |
| |
| g_array_append_val (stream->ptmap, item); |
| } |
| |
| stream->stream_id = make_stream_id (stream, media); |
| |
| gst_caps_unref (global_caps); |
| return; |
| |
| no_proto: |
| { |
| GST_ERROR_OBJECT (src, "can't find proto in media"); |
| return; |
| } |
| unknown_proto: |
| { |
| GST_ERROR_OBJECT (src, "unknown proto in media: '%s'", proto); |
| return; |
| } |
| sendonly_media: |
| { |
| GST_DEBUG_OBJECT (src, "sendonly media ignored, no backchannel"); |
| 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, |
| gint n_streams) |
| { |
| GstRTSPStream *stream; |
| const gchar *control_url; |
| const GstSDPMedia *media; |
| |
| /* get media, should not return NULL */ |
| media = gst_sdp_message_get_media (sdp, idx); |
| if (media == NULL) |
| return NULL; |
| |
| stream = g_new0 (GstRTSPStream, 1); |
| stream->parent = src; |
| /* we mark the pad as not linked, we will mark it as OK when we add the pad to |
| * the element. */ |
| stream->last_ret = GST_FLOW_NOT_LINKED; |
| stream->added = FALSE; |
| stream->setup = FALSE; |
| stream->skipped = FALSE; |
| stream->id = idx; |
| stream->eos = FALSE; |
| stream->discont = TRUE; |
| stream->seqbase = -1; |
| stream->timebase = -1; |
| stream->send_ssrc = g_random_int (); |
| stream->profile = GST_RTSP_PROFILE_AVP; |
| stream->ptmap = g_array_new (FALSE, FALSE, sizeof (PtMapItem)); |
| stream->mikey = NULL; |
| stream->stream_id = NULL; |
| stream->is_backchannel = FALSE; |
| g_mutex_init (&stream->conninfo.send_lock); |
| g_mutex_init (&stream->conninfo.recv_lock); |
| g_array_set_clear_func (stream->ptmap, (GDestroyNotify) clear_ptmap_item); |
| |
| /* stream is sendonly and onvif backchannel is requested */ |
| if (gst_sdp_media_get_attribute_val (media, "sendonly") != NULL && |
| src->backchannel != BACKCHANNEL_NONE) |
| stream->is_backchannel = TRUE; |
| |
| /* 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)); |
| |
| /* RFC 2326, C.3: missing control_url permitted in case of a single stream */ |
| if (control_url == NULL && n_streams == 1) { |
| 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); |
| g_free (stream->stream_id); |
| |
| 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->rtpsrc) { |
| gst_element_set_state (stream->rtpsrc, GST_STATE_NULL); |
| gst_bin_remove (GST_BIN_CAST (src), stream->rtpsrc); |
| gst_object_unref (stream->rtpsrc); |
| } |
| if (stream->srcpad) { |
| gst_pad_set_active (stream->srcpad, FALSE); |
| if (stream->added) |
| gst_element_remove_pad (GST_ELEMENT_CAST (src), stream->srcpad); |
| } |
| if (stream->srtpenc) |
| gst_object_unref (stream->srtpenc); |
| if (stream->srtpdec) |
| gst_object_unref (stream->srtpdec); |
| if (stream->srtcpparams) |
| gst_caps_unref (stream->srtcpparams); |
| if (stream->mikey) |
| gst_mikey_message_unref (stream->mikey); |
| if (stream->rtcppad) |
| gst_object_unref (stream->rtcppad); |
| if (stream->session) |
| g_object_unref (stream->session); |
| if (stream->rtx_pt_map) |
| gst_structure_free (stream->rtx_pt_map); |
| |
| g_mutex_clear (&stream->conninfo.send_lock); |
| g_mutex_clear (&stream->conninfo.recv_lock); |
| |
| g_free (stream); |
| } |
| |
| static void |
| gst_rtspsrc_cleanup (GstRTSPSrc * src) |
| { |
| GList *walk; |
| |
| GST_DEBUG_OBJECT (src, "cleanup"); |
| |
| for (walk = src->streams; walk; walk = g_list_next (walk)) { |
| GstRTSPStream *stream = (GstRTSPStream *) walk->data; |
| |
| gst_rtspsrc_stream_free (src, stream); |
| } |
| g_list_free (src->streams); |
| src->streams = NULL; |
| if (src->manager) { |
| if (src->manager_sig_id) { |
| g_signal_handler_disconnect (src->manager, src->manager_sig_id); |
| src->manager_sig_id = 0; |
| } |
| gst_element_set_state (src->manager, GST_STATE_NULL); |
| gst_bin_remove (GST_BIN_CAST (src), src->manager); |
| src->manager = NULL; |
| } |
| if (src->props) |
| gst_structure_free (src->props); |
| src->props = NULL; |
| |
| g_free (src->content_base); |
| src->content_base = NULL; |
| |
| g_free (src->control); |
| src->control = NULL; |
| |
| if (src->range) |
| gst_rtsp_range_free (src->range); |
| src->range = NULL; |
| |
| /* don't clear the SDP when it was used in the url */ |
| if (src->sdp && !src->from_sdp) { |
| gst_sdp_message_free (src->sdp); |
| src->sdp = NULL; |
| } |
| |
| src->need_segment = FALSE; |
| |
| if (src->provided_clock) { |
| gst_object_unref (src->provided_clock); |
| src->provided_clock = NULL; |
| } |
| } |
| |
| static gboolean |
| gst_rtspsrc_alloc_udp_ports (GstRTSPStream * stream, |
| gint * rtpport, gint * rtcpport) |
| { |
| GstRTSPSrc *src; |
| GstStateChangeReturn ret; |
| GstElement *udpsrc0, *udpsrc1; |
| gint tmp_rtp, tmp_rtcp; |
| guint count; |
| const gchar *host; |
| |
| src = stream->parent; |
| |
| udpsrc0 = NULL; |
| udpsrc1 = NULL; |
| count = 0; |
| |
| /* Start at next port */ |
| tmp_rtp = src->next_port_num; |
| |
| if (stream->is_ipv6) |
| host = "udp://[::0]"; |
| else |
| host = "udp://0.0.0.0"; |
| |
| /* try to allocate 2 UDP ports, the RTP port should be an even |
| * number and the RTCP port should be the next (uneven) port */ |
| again: |
| |
| if (tmp_rtp != 0 && src->client_port_range.max > 0 && |
| tmp_rtp >= src->client_port_range.max) |
| goto no_ports; |
| |
| udpsrc0 = gst_element_make_from_uri (GST_URI_SRC, host, NULL, NULL); |
| if (udpsrc0 == NULL) |
| goto no_udp_protocol; |
| g_object_set (G_OBJECT (udpsrc0), "port", tmp_rtp, "reuse", FALSE, NULL); |
| |
| if (src->udp_buffer_size != 0) |
| g_object_set (G_OBJECT (udpsrc0), "buffer-size", src->udp_buffer_size, |
| NULL); |
| |
| ret = gst_element_set_state (udpsrc0, GST_STATE_READY); |
| if (ret == GST_STATE_CHANGE_FAILURE) { |
| if (tmp_rtp != 0) { |
| GST_DEBUG_OBJECT (src, "Unable to make udpsrc from RTP port %d", tmp_rtp); |
| |
| tmp_rtp += 2; |
| if (++count > src->retry) |
| goto no_ports; |
| |
| GST_DEBUG_OBJECT (src, "free RTP udpsrc"); |
| gst_element_set_state (udpsrc0, GST_STATE_NULL); |
| gst_object_unref (udpsrc0); |
| udpsrc0 = NULL; |
| |
| GST_DEBUG_OBJECT (src, "retry %d", count); |
| goto again; |
| } |
| goto no_udp_protocol; |
| } |
| |
| g_object_get (G_OBJECT (udpsrc0), "port", &tmp_rtp, NULL); |
| GST_DEBUG_OBJECT (src, "got RTP port %d", tmp_rtp); |
| |
| /* check if port is even */ |
| if ((tmp_rtp & 0x01) != 0) { |
| /* port not even, close and allocate another */ |
| if (++count > src->retry) |
| goto no_ports; |
| |
| GST_DEBUG_OBJECT (src, "RTP port not even"); |
| |
| GST_DEBUG_OBJECT (src, "free RTP udpsrc"); |
| gst_element_set_state (udpsrc0, GST_STATE_NULL); |
| gst_object_unref (udpsrc0); |
| udpsrc0 = NULL; |
| |
| GST_DEBUG_OBJECT (src, "retry %d", count); |
| tmp_rtp++; |
| goto again; |
| } |
| |
| /* allocate port+1 for RTCP now */ |
| udpsrc1 = gst_element_make_from_uri (GST_URI_SRC, host, NULL, NULL); |
| if (udpsrc1 == NULL) |
| goto no_udp_rtcp_protocol; |
| |
| /* set port */ |
| tmp_rtcp = tmp_rtp + 1; |
| if (src->client_port_range.max > 0 && tmp_rtcp > src->client_port_range.max) |
| goto no_ports; |
| |
| g_object_set (G_OBJECT (udpsrc1), "port", tmp_rtcp, "reuse", FALSE, NULL); |
| |
| GST_DEBUG_OBJECT (src, "starting RTCP on port %d", tmp_rtcp); |
| ret = gst_element_set_state (udpsrc1, GST_STATE_READY); |
| /* tmp_rtcp port is busy already : retry to make rtp/rtcp pair */ |
| if (ret == GST_STATE_CHANGE_FAILURE) { |
| GST_DEBUG_OBJECT (src, "Unable to make udpsrc from RTCP port %d", tmp_rtcp); |
| |
| if (++count > src->retry) |
| goto no_ports; |
| |
| GST_DEBUG_OBJECT (src, "free RTP udpsrc"); |
| gst_element_set_state (udpsrc0, GST_STATE_NULL); |
| gst_object_unref (udpsrc0); |
| udpsrc0 = NULL; |
| |
| GST_DEBUG_OBJECT (src, "free RTCP udpsrc"); |
| gst_element_set_state (udpsrc1, GST_STATE_NULL); |
| gst_object_unref (udpsrc1); |
| udpsrc1 = NULL; |
| |
| tmp_rtp += 2; |
| GST_DEBUG_OBJECT (src, "retry %d", count); |
| goto again; |
| } |
| |
| /* all fine, do port check */ |
| g_object_get (G_OBJECT (udpsrc0), "port", rtpport, NULL); |
| g_object_get (G_OBJECT (udpsrc1), "port", rtcpport, NULL); |
| |
| /* this should not happen... */ |
| if (*rtpport != tmp_rtp || *rtcpport != tmp_rtcp) |
| goto port_error; |
| |
| /* we keep these elements, we configure all in configure_transport when the |
| * server told us to really use the UDP ports. */ |
| stream->udpsrc[0] = gst_object_ref_sink (udpsrc0); |
| stream->udpsrc[1] = gst_object_ref_sink (udpsrc1); |
| gst_element_set_locked_state (stream->udpsrc[0], TRUE); |
| gst_element_set_locked_state (stream->udpsrc[1], TRUE); |
| |
| /* keep track of next available port number when we have a range |
| * configured */ |
| if (src->next_port_num != 0) |
| src->next_port_num = tmp_rtcp + 1; |
| |
| return TRUE; |
| |
| /* ERRORS */ |
| no_udp_protocol: |
| { |
| GST_DEBUG_OBJECT (src, "could not get UDP source"); |
| goto cleanup; |
| } |
| no_ports: |
| { |
| GST_DEBUG_OBJECT (src, "could not allocate UDP port pair after %d retries", |
| count); |
| goto cleanup; |
| } |
| no_udp_rtcp_protocol: |
| { |
| GST_DEBUG_OBJECT (src, "could not get UDP source for RTCP"); |
| goto cleanup; |
| } |
| port_error: |
| { |
| GST_DEBUG_OBJECT (src, "ports don't match rtp: %d<->%d, rtcp: %d<->%d", |
| tmp_rtp, *rtpport, tmp_rtcp, *rtcpport); |
| goto cleanup; |
| } |
| cleanup: |
| { |
| if (udpsrc0) { |
| gst_element_set_state (udpsrc0, GST_STATE_NULL); |
| gst_object_unref (udpsrc0); |
| } |
| if (udpsrc1) { |
| gst_element_set_state (udpsrc1, GST_STATE_NULL); |
| gst_object_unref (udpsrc1); |
| } |
| return FALSE; |
| } |
| } |
| |
| static void |
| gst_rtspsrc_set_state (GstRTSPSrc * src, GstState state) |
| { |
| GList *walk; |
| |
| if (src->manager) |
| gst_element_set_state (GST_ELEMENT_CAST (src->manager), state); |
| |
| for (walk = src->streams; walk; walk = g_list_next (walk)) { |
| GstRTSPStream *stream = (GstRTSPStream *) walk->data; |
| gint i; |
| |
| for (i = 0; i < 2; i++) { |
| if (stream->udpsrc[i]) |
| gst_element_set_state (stream->udpsrc[i], state); |
| } |
| } |
| } |
| |
| static void |
| gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing) |
| { |
| GstEvent *event; |
| gint cmd; |
| GstState state; |
| |
| if (flush) { |
| event = gst_event_new_flush_start (); |
| GST_DEBUG_OBJECT (src, "start flush"); |
| cmd = CMD_WAIT; |
| state = GST_STATE_PAUSED; |
| } else { |
| event = gst_event_new_flush_stop (FALSE); |
| GST_DEBUG_OBJECT (src, "stop flush; playing %d", playing); |
| cmd = CMD_LOOP; |
| if (playing) |
| state = GST_STATE_PLAYING; |
| else |
| state = GST_STATE_PAUSED; |
| } |
| gst_rtspsrc_push_event (src, event); |
| gst_rtspsrc_loop_send_cmd (src, cmd, CMD_LOOP); |
| gst_rtspsrc_set_state (src, state); |
| } |
| |
| static GstRTSPResult |
| gst_rtspsrc_connection_send (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, |
| GstRTSPMessage * message, GTimeVal * timeout) |
| { |
| GstRTSPResult ret; |
| |
| if (conninfo->connection) { |
| g_mutex_lock (&conninfo->send_lock); |
| ret = gst_rtsp_connection_send (conninfo->connection, message, timeout); |
| g_mutex_unlock (&conninfo->send_lock); |
| } else { |
| ret = GST_RTSP_ERROR; |
| } |
| |
| return ret; |
| } |
| |
| static GstRTSPResult |
| gst_rtspsrc_connection_receive (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, |
| GstRTSPMessage * message, GTimeVal * timeout) |
| { |
| GstRTSPResult ret; |
| |
| if (conninfo->connection) { |
| g_mutex_lock (&conninfo->recv_lock); |
| ret = gst_rtsp_connection_receive (conninfo->connection, message, timeout); |
| g_mutex_unlock (&conninfo->recv_lock); |
| } 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; |
| const gchar *seek_style = NULL; |
| |
| if (event) { |
| GST_DEBUG_OBJECT (src, "doing seek with event %" GST_PTR_FORMAT, 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; |
| |
| /* Check if we are not at all seekable */ |
| if (src->seekable == -1.0) |
| goto not_seekable; |
| |
| /* Additional seeking-to-beginning-only check */ |
| if (src->seekable == 0.0 && cur != 0) |
| goto not_seekable; |
| } else { |
| GST_DEBUG_OBJECT (src, "doing seek without event"); |
| flags = 0; |
| cur_type = GST_SEEK_TYPE_SET; |
| stop_type = GST_SEEK_TYPE_SET; |
| } |
| |
| /* get flush flag */ |
| flush = flags & GST_SEEK_FLAG_FLUSH; |
| skip = flags & GST_SEEK_FLAG_SKIP; |
| |
| /* now we need to make sure the streaming thread is stopped. We do this by |
| * either sending a FLUSH_START event downstream which will cause the |
| * streaming thread to stop with a WRONG_STATE. |
| * For a non-flushing seek we simply pause the task, which will happen as soon |
| * as it completes one iteration (and thus might block when the sink is |
| * blocking in preroll). */ |
| if (flush) { |
| GST_DEBUG_OBJECT (src, "starting flush"); |
| gst_rtspsrc_flush (src, TRUE, FALSE); |
| } else { |
| if (src->task) { |
| gst_task_pause (src->task); |
| } |
| } |
| |
| /* we should now be able to grab the streaming thread because we stopped it |
| * with the above flush/pause code */ |
| GST_RTSP_STREAM_LOCK (src); |
| |
| GST_DEBUG_OBJECT (src, "stopped streaming"); |
| |
| /* stop flushing the rtsp connection so we can send PAUSE/PLAY below */ |
| gst_rtspsrc_connection_flush (src, FALSE); |
| |
| /* copy segment, we need this because we still need the old |
| * segment when we close the current segment. */ |
| memcpy (&seeksegment, &src->segment, sizeof (GstSegment)); |
| |
| /* configure the seek parameters in the seeksegment. We will then have the |
| * right values in the segment to perform the seek */ |
| if (event) { |
| GST_DEBUG_OBJECT (src, "configuring seek"); |
| gst_segment_do_seek (&seeksegment, rate, format, flags, |
| cur_type, cur, stop_type, stop, &update); |
| } |
| |
| /* figure out the last position we need to play. If it's configured (stop != |
| * -1), use that, else we play until the total duration of the file */ |
| if ((stop = seeksegment.stop) == -1) |
| stop = seeksegment.duration; |
| |
| /* if we were playing, pause first */ |
| playing = (src->state == GST_RTSP_STATE_PLAYING); |
| if (playing) { |
| /* obtain current position in case seek fails */ |
| gst_rtspsrc_get_position (src); |
| gst_rtspsrc_pause (src, FALSE); |
| } |
| src->skip = skip; |
| |
| src->state = GST_RTSP_STATE_SEEKING; |
| |
| /* PLAY will add the range header now. */ |
| src->need_range = TRUE; |
| |
| /* prepare for streaming again */ |
| if (flush) { |
| /* if we started flush, we stop now */ |
| GST_DEBUG_OBJECT (src, "stopping flush"); |
| gst_rtspsrc_flush (src, FALSE, playing); |
| } |
| |
| /* now we did the seek and can activate the new segment values */ |
| memcpy (&src->segment, &seeksegment, sizeof (GstSegment)); |
| |
| /* if we're doing a segment seek, post a SEGMENT_START message */ |
| if (src->segment.flags & GST_SEEK_FLAG_SEGMENT) { |
| gst_element_post_message (GST_ELEMENT_CAST (src), |
| gst_message_new_segment_start (GST_OBJECT_CAST (src), |
| src->segment.format, src->segment.position)); |
| } |
| |
| /* now create the newsegment */ |
| GST_DEBUG_OBJECT (src, "Creating newsegment from %" G_GINT64_FORMAT |
| " to %" G_GINT64_FORMAT, src->segment.position, stop); |
| |
| /* mark discont */ |
| GST_DEBUG_OBJECT (src, "mark DISCONT, we did a seek to another position"); |
| for (walk = src->streams; walk; walk = g_list_next (walk)) { |
| GstRTSPStream *stream = (GstRTSPStream *) walk->data; |
| stream->discont = TRUE; |
| } |
| |
| /* and continue playing if needed */ |
| GST_OBJECT_LOCK (src); |
| playing = (GST_STATE_PENDING (src) == GST_STATE_VOID_PENDING |
| && GST_STATE (src) == GST_STATE_PLAYING) |
| || (GST_STATE_PENDING (src) == GST_STATE_PLAYING); |
| GST_OBJECT_UNLOCK (src); |
| |
| if (src->version >= GST_RTSP_VERSION_2_0) { |
| if (flags & GST_SEEK_FLAG_ACCURATE) |
| seek_style = "RAP"; |
| else if (flags & GST_SEEK_FLAG_KEY_UNIT) |
| seek_style = "CoRAP"; |
| else if (flags & GST_SEEK_FLAG_KEY_UNIT |
| && flags & GST_SEEK_FLAG_SNAP_BEFORE) |
| seek_style = "First-Prior"; |
| else if (flags & GST_SEEK_FLAG_KEY_UNIT && flags & GST_SEEK_FLAG_SNAP_AFTER) |
| seek_style = "Next"; |
| } |
| |
| if (playing) |
| gst_rtspsrc_play (src, &seeksegment, FALSE, seek_style); |
| |
| 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; |
| } |
| not_seekable: |
| { |
| GST_DEBUG_OBJECT (src, "stream is not seekable"); |
| 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; |
| } |
| |
| static gboolean |
| gst_rtspsrc_handle_src_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event) |
| { |
| GstRTSPStream *stream; |
| |
| stream = gst_pad_get_element_private (pad); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_STREAM_START:{ |
| const gchar *upstream_id; |
| gchar *stream_id; |
| |
| gst_event_parse_stream_start (event, &upstream_id); |
| stream_id = g_strdup_printf ("%s/%s", upstream_id, stream->stream_id); |
| |
| gst_event_unref (event); |
| event = gst_event_new_stream_start (stream_id); |
| g_free (stream_id); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return gst_pad_push_event (stream->srcpad, event); |
| } |
| |
| /* 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; |
| GstClockTime start = 0, duration = src->segment.duration; |
| |
| /* seeking without duration is unlikely */ |
| seekable = seekable && src->seekable >= 0.0 && src->segment.duration && |
| GST_CLOCK_TIME_IS_VALID (src->segment.duration); |
| |
| if (seekable) { |
| if (src->seekable > 0.0) { |
| start = src->last_pos - src->seekable * GST_SECOND; |
| } else { |
| /* src->seekable == 0 means that we can only seek to 0 */ |
| start = 0; |
| duration = 0; |
| } |
| } |
| |
| GST_LOG_OBJECT (src, "seekable : %d", seekable); |
| |
| gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, start, |
| 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 }; |
| GstRTSPConnInfo *conninfo; |
| |
| 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) |
| conninfo = &stream->conninfo; |
| else |
| conninfo = &src->conninfo; |
| |
| GST_DEBUG_OBJECT (src, "sending %u bytes RTCP", size); |
| ret = gst_rtspsrc_connection_send (src, conninfo, &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 GstFlowReturn |
| gst_rtspsrc_push_backchannel_buffer (GstRTSPSrc * src, guint id, |
| GstSample * sample) |
| { |
| GstFlowReturn res = GST_FLOW_OK; |
| GstRTSPStream *stream; |
| |
| if (!src->conninfo.connected || src->state != GST_RTSP_STATE_PLAYING) |
| goto out; |
| |
| stream = find_stream (src, &id, (gpointer) find_stream_by_id); |
| if (stream == NULL) { |
| GST_ERROR_OBJECT (src, "no stream with id %u", id); |
| goto out; |
| } |
| |
| if (src->interleaved) { |
| GstBuffer *buffer; |
| GstMapInfo map; |
| guint8 *data; |
| guint size; |
| GstRTSPResult ret; |
| GstRTSPMessage message = { 0 }; |
| GstRTSPConnInfo *conninfo; |
| |
| buffer = gst_sample_get_buffer (sample); |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| size = map.size; |
| data = map.data; |
| |
| gst_rtsp_message_init_data (&message, stream->channel[0]); |
| |
| /* lend the body data to the message */ |
| gst_rtsp_message_take_body (&message, data, size); |
| |
| if (stream->conninfo.connection) |
| conninfo = &stream->conninfo; |
| else |
| conninfo = &src->conninfo; |
| |
| GST_DEBUG_OBJECT (src, "sending %u bytes backchannel RTP", size); |
| ret = gst_rtspsrc_connection_send (src, conninfo, &message, NULL); |
| GST_DEBUG_OBJECT (src, "sent backchannel RTP, %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); |
| |
| res = GST_FLOW_OK; |
| } else { |
| g_signal_emit_by_name (stream->rtpsrc, "push-sample", sample, &res); |
| GST_DEBUG_OBJECT (src, "sent backchannel RTP sample %p: %s", sample, |
| gst_flow_get_name (res)); |
| } |
| |
| out: |
| gst_sample_unref (sample); |
| |
| 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; |
| } |
| |
| static gboolean |
| add_backchannel_fakesink (GstRTSPSrc * src, GstRTSPStream * stream, |
| GstPad * srcpad) |
| { |
| GstPad *sinkpad; |
| GstElement *fakesink; |
| |
| fakesink = gst_element_factory_make ("fakesink", NULL); |
| if (fakesink == NULL) { |
| GST_ERROR_OBJECT (src, "no fakesink"); |
| return FALSE; |
| } |
| |
| sinkpad = gst_element_get_static_pad (fakesink, "sink"); |
| |
| GST_DEBUG_OBJECT (src, "backchannel stream %p, hooking fakesink", stream); |
| |
| gst_bin_add (GST_BIN_CAST (src), fakesink); |
| if (gst_pad_link (srcpad, sinkpad) != GST_PAD_LINK_OK) { |
| GST_WARNING_OBJECT (src, "could not link to fakesink"); |
| return FALSE; |
| } |
| |
| gst_object_unref (sinkpad); |
| |
| gst_element_sync_state_with_parent (fakesink); |
| 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; |
| GstPad *internal_src; |
| |
| 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); |
| |
| /* We intercept and modify the stream start event */ |
| internal_src = |
| GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (stream->srcpad))); |
| gst_pad_set_element_private (internal_src, stream); |
| gst_pad_set_event_function (internal_src, gst_rtspsrc_handle_src_sink_event); |
| gst_object_unref (internal_src); |
| |
| 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); |
| |
| /* don't add the srcpad if this is a sendonly stream */ |
| if (stream->is_backchannel) |
| add_backchannel_fakesink (src, stream, stream->srcpad); |
| else |
| 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 st
|