X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Frtsp%2Fgstrtspsrc.c;h=cc97bf75f2c18201dc9315c4e7e9ba4fdf913a63;hb=9ee320f66e2676564040240b2e71c2f30be7cd6f;hp=9b54dbc0d140c854bc61315a605807e6e8e8a775;hpb=c36930535d5a20476aaa403477c51c76640a45a1;p=platform%2Fupstream%2Fgst-plugins-good.git diff --git a/gst/rtsp/gstrtspsrc.c b/gst/rtsp/gstrtspsrc.c index 9b54dbc..cc97bf7 100644 --- a/gst/rtsp/gstrtspsrc.c +++ b/gst/rtsp/gstrtspsrc.c @@ -42,6 +42,7 @@ */ /** * SECTION:element-rtspsrc + * @title: rtspsrc * * Makes a connection to an RTSP server and read the data. * rtspsrc strictly follows RFC 2326 and therefore does not (yet) support @@ -66,13 +67,24 @@ * rtspsrc acts like a live source and will therefore only generate data in the * PLAYING state. * - * - * Example launch line + * If a RTP session times out then the rtspsrc will generate an element message + * named "GstRTSPSrcTimeout". Currently this is only supported for timeouts + * triggered by RTCP. + * + * The message's structure contains three fields: + * + * #GstRTSPSrcTimeoutCause `cause`: the cause of the timeout. + * + * #gint `stream-number`: an internal identifier of the stream that timed out. + * + * #guint `ssrc`: the SSRC of the stream that timed out. + * + * ## Example launch line * |[ * gst-launch-1.0 rtspsrc location=rtsp://some.server/url ! fakesink * ]| Establish a connection to an RTSP server and send the raw RTP packets to a * fakesink. - * + * */ #ifdef HAVE_CONFIG_H @@ -124,6 +136,12 @@ enum SIGNAL_SELECT_STREAM, SIGNAL_NEW_MANAGER, SIGNAL_REQUEST_RTCP_KEY, + SIGNAL_ACCEPT_CERTIFICATE, + SIGNAL_BEFORE_SEND, + SIGNAL_PUSH_BACKCHANNEL_BUFFER, + SIGNAL_GET_PARAMETER, + SIGNAL_GET_PARAMETERS, + SIGNAL_SET_PARAMETER, LAST_SIGNAL }; @@ -172,6 +190,9 @@ enum _GstRtspSrcNtpTimeSource 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) @@ -195,6 +216,32 @@ gst_rtsp_src_ntp_time_source_get_type (void) 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 @@ -228,6 +275,11 @@ gst_rtsp_src_ntp_time_source_get_type (void) #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 +#define DEFAULT_TEARDOWN_TIMEOUT (100 * GST_MSECOND) enum { @@ -267,7 +319,12 @@ enum PROP_NTP_TIME_SOURCE, PROP_USER_AGENT, PROP_MAX_RTCP_RTP_TIME_DIFF, - PROP_RFC7273_SYNC + PROP_RFC7273_SYNC, + PROP_MAX_TS_OFFSET_ADJUSTMENT, + PROP_MAX_TS_OFFSET, + PROP_DEFAULT_VERSION, + PROP_BACKCHANNEL, + PROP_TEARDOWN_TIMEOUT, }; #define GST_TYPE_RTSP_NAT_METHOD (gst_rtsp_nat_method_get_type()) @@ -288,6 +345,22 @@ gst_rtsp_nat_method_get_type (void) 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) + +typedef struct _ParameterRequest +{ + gint cmd; + gchar *content_type; + GString *body; + GstPromise *promise; +} ParameterRequest; + static void gst_rtspsrc_finalize (GObject * object); static void gst_rtspsrc_set_property (GObject * object, guint prop_id, @@ -318,7 +391,7 @@ static GstRTSPResult gst_rtspsrc_send_cb (GstRTSPExtension * ext, static GstRTSPResult gst_rtspsrc_open (GstRTSPSrc * src, gboolean async); static GstRTSPResult gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, - gboolean async); + 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); @@ -335,6 +408,28 @@ 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 GstRTSPResult +gst_rtspsrc_get_parameter (GstRTSPSrc * src, ParameterRequest * req); + +static GstRTSPResult +gst_rtspsrc_set_parameter (GstRTSPSrc * src, ParameterRequest * req); + +static gboolean get_parameter (GstRTSPSrc * src, const gchar * parameter, + const gchar * content_type, GstPromise * promise); + +static gboolean get_parameters (GstRTSPSrc * src, gchar ** parameters, + const gchar * content_type, GstPromise * promise); + +static gboolean set_parameter (GstRTSPSrc * src, const gchar * name, + const gchar * value, const gchar * content_type, GstPromise * promise); + +static GstFlowReturn gst_rtspsrc_push_backchannel_buffer (GstRTSPSrc * src, + guint id, GstSample * sample); typedef struct { @@ -343,16 +438,18 @@ typedef struct } 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) +#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) +#define CMD_GET_PARAMETER (1 << 7) +#define CMD_SET_PARAMETER (1 << 8) /* mask for all commands */ -#define CMD_ALL ((CMD_LOOP << 1) - 1) +#define CMD_ALL ((CMD_SET_PARAMETER << 1) - 1) #define GST_ELEMENT_PROGRESS(el, type, code, text) \ G_STMT_START { \ @@ -388,6 +485,10 @@ cmd_to_string (guint cmd) return "RECONNECT"; case CMD_LOOP: return "LOOP"; + case CMD_GET_PARAMETER: + return "GET_PARAMETER"; + case CMD_SET_PARAMETER: + return "SET_PARAMETER"; } return "unknown"; @@ -415,6 +516,26 @@ select_stream_accum (GSignalInvocationHint * ihint, 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) { @@ -445,8 +566,10 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) g_object_class_install_property (gobject_class, PROP_DEBUG, g_param_spec_boolean ("debug", "Debug", - "Dump request and response messages to stdout", - DEFAULT_DEBUG, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + "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", @@ -582,7 +705,7 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) /** * GstRTSPSrc:port-range: * - * Configure the client port numbers that can be used to recieve RTP and + * Configure the client port numbers that can be used to receive RTP and * RTCP. */ g_object_class_install_property (gobject_class, PROP_PORT_RANGE, @@ -741,6 +864,79 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) 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)); + + /** + * GstRTSPSrc: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)); + + /** + * GstRTSPSrc: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:teardown-timeout + * + * When transitioning PAUSED-READY, allow up to timeout (in nanoseconds) + * delay in order to send teardown (0 = disabled) + * + * Since: 1.14 + */ + g_object_class_install_property (gobject_class, PROP_TEARDOWN_TIMEOUT, + g_param_spec_uint64 ("teardown-timeout", "Teardown Timeout", + "When transitioning PAUSED-READY, allow up to timeout (in nanoseconds) " + "delay in order to send teardown (0 = disabled)", + 0, G_MAXUINT64, DEFAULT_TEARDOWN_TIMEOUT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** * GstRTSPSrc::handle-request: * @rtspsrc: a #GstRTSPSrc * @request: a #GstRTSPMessage @@ -766,7 +962,7 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) * @rtspsrc: a #GstRTSPSrc * @sdp: a #GstSDPMessage * - * Emited when the client has retrieved the SDP and before it configures the + * Emitted 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 @@ -788,7 +984,7 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) * @num: the stream number * @caps: the stream caps * - * Emited before the client decides to configure the stream @num with + * Emitted before the client decides to configure the stream @num with * @caps. * * Returns: %TRUE when the stream should be selected, %FALSE when the stream @@ -807,7 +1003,7 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) * @rtspsrc: a #GstRTSPSrc * @manager: a #GstElement * - * Emited after a new manager (like rtpbin) was created and the default + * Emitted after a new manager (like rtpbin) was created and the default * properties were configured. * * Since: 1.4 @@ -822,7 +1018,7 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) * @rtspsrc: a #GstRTSPSrc * @num: the stream number * - * Signal emited to get the crypto parameters relevant to the RTCP + * Signal emitted 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. * @@ -832,6 +1028,119 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) 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); + + /** + * GstRTSPSrc::get-parameter: + * @rtspsrc: a #GstRTSPSrc + * @parameter: the parameter name + * @parameter: the content type + * @parameter: a pointer to #GstPromise + * + * Handle the GET_PARAMETER signal. + * + * Returns: %TRUE when the command could be issued, %FALSE otherwise + * + */ + gst_rtspsrc_signals[SIGNAL_GET_PARAMETER] = + g_signal_new ("get-parameter", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRTSPSrcClass, + get_parameter), NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, 3, G_TYPE_STRING, G_TYPE_STRING, GST_TYPE_PROMISE); + + /** + * GstRTSPSrc::get-parameters: + * @rtspsrc: a #GstRTSPSrc + * @parameter: a NULL-terminated array of parameters + * @parameter: the content type + * @parameter: a pointer to #GstPromise + * + * Handle the GET_PARAMETERS signal. + * + * Returns: %TRUE when the command could be issued, %FALSE otherwise + * + */ + gst_rtspsrc_signals[SIGNAL_GET_PARAMETERS] = + g_signal_new ("get-parameters", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRTSPSrcClass, + get_parameters), NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, 3, G_TYPE_STRV, G_TYPE_STRING, GST_TYPE_PROMISE); + + /** + * GstRTSPSrc::set-parameter: + * @rtspsrc: a #GstRTSPSrc + * @parameter: the parameter name + * @parameter: the parameter value + * @parameter: the content type + * @parameter: a pointer to #GstPromise + * + * Handle the SET_PARAMETER signal. + * + * Returns: %TRUE when the command could be issued, %FALSE otherwise + * + */ + gst_rtspsrc_signals[SIGNAL_SET_PARAMETER] = + g_signal_new ("set-parameter", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstRTSPSrcClass, + set_parameter), NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_BOOLEAN, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, + GST_TYPE_PROMISE); + gstelement_class->send_event = gst_rtspsrc_send_event; gstelement_class->provide_clock = gst_rtspsrc_provide_clock; gstelement_class->change_state = gst_rtspsrc_change_state; @@ -847,9 +1156,142 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) gstbin_class->handle_message = gst_rtspsrc_handle_message; + klass->push_backchannel_buffer = gst_rtspsrc_push_backchannel_buffer; + klass->get_parameter = GST_DEBUG_FUNCPTR (get_parameter); + klass->get_parameters = GST_DEBUG_FUNCPTR (get_parameters); + klass->set_parameter = GST_DEBUG_FUNCPTR (set_parameter); + gst_rtsp_ext_list_init (); } +static gboolean +validate_set_get_parameter_name (const gchar * parameter_name) +{ + gchar *ptr = (gchar *) parameter_name; + + while (*ptr) { + /* Don't allow '\r', '\n', \'t', ' ' etc in the parameter name */ + if (g_ascii_isspace (*ptr) || g_ascii_iscntrl (*ptr)) { + GST_DEBUG ("invalid parameter name '%s'", parameter_name); + return FALSE; + } + ptr++; + } + return TRUE; +} + +static gboolean +validate_set_get_parameters (gchar ** parameter_names) +{ + while (*parameter_names) { + if (!validate_set_get_parameter_name (*parameter_names)) { + return FALSE; + } + parameter_names++; + } + return TRUE; +} + +static gboolean +get_parameter (GstRTSPSrc * src, const gchar * parameter, + const gchar * content_type, GstPromise * promise) +{ + gchar *parameters[] = { (gchar *) parameter, NULL }; + + GST_LOG_OBJECT (src, "get_parameter: %s", GST_STR_NULL (parameter)); + + if (parameter == NULL || parameter[0] == '\0' || promise == NULL) { + GST_DEBUG ("invalid input"); + return FALSE; + } + + return get_parameters (src, parameters, content_type, promise); +} + +static gboolean +get_parameters (GstRTSPSrc * src, gchar ** parameters, + const gchar * content_type, GstPromise * promise) +{ + ParameterRequest *req; + + GST_LOG_OBJECT (src, "get_parameters: %d", g_strv_length (parameters)); + + if (parameters == NULL || promise == NULL) { + GST_DEBUG ("invalid input"); + return FALSE; + } + + if (src->state == GST_RTSP_STATE_INVALID) { + GST_DEBUG ("invalid state"); + return FALSE; + } + + if (!validate_set_get_parameters (parameters)) { + return FALSE; + } + + req = g_new0 (ParameterRequest, 1); + req->promise = gst_promise_ref (promise); + req->cmd = CMD_GET_PARAMETER; + /* Set the request body according to RFC 2326 or RFC 7826 */ + req->body = g_string_new (NULL); + while (*parameters) { + g_string_append_printf (req->body, "%s:\r\n", *parameters); + parameters++; + } + if (content_type) + req->content_type = g_strdup (content_type); + + GST_OBJECT_LOCK (src); + g_queue_push_tail (&src->set_get_param_q, req); + GST_OBJECT_UNLOCK (src); + + gst_rtspsrc_loop_send_cmd (src, CMD_GET_PARAMETER, CMD_LOOP); + + return TRUE; +} + +static gboolean +set_parameter (GstRTSPSrc * src, const gchar * name, const gchar * value, + const gchar * content_type, GstPromise * promise) +{ + ParameterRequest *req; + + GST_LOG_OBJECT (src, "set_parameter: %s: %s", GST_STR_NULL (name), + GST_STR_NULL (value)); + + if (name == NULL || name[0] == '\0' || value == NULL || promise == NULL) { + GST_DEBUG ("invalid input"); + return FALSE; + } + + if (src->state == GST_RTSP_STATE_INVALID) { + GST_DEBUG ("invalid state"); + return FALSE; + } + + if (!validate_set_get_parameter_name (name)) { + return FALSE; + } + + req = g_new0 (ParameterRequest, 1); + req->cmd = CMD_SET_PARAMETER; + req->promise = gst_promise_ref (promise); + req->body = g_string_new (NULL); + /* Set the request body according to RFC 2326 or RFC 7826 */ + g_string_append_printf (req->body, "%s: %s\r\n", name, value); + if (content_type) + req->content_type = g_strdup (content_type); + + GST_OBJECT_LOCK (src); + g_queue_push_tail (&src->set_get_param_q, req); + GST_OBJECT_UNLOCK (src); + + gst_rtspsrc_loop_send_cmd (src, CMD_SET_PARAMETER, CMD_LOOP); + + return TRUE; +} + static void gst_rtspsrc_init (GstRTSPSrc * src) { @@ -888,6 +1330,12 @@ gst_rtspsrc_init (GstRTSPSrc * src) 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; + src->teardown_timeout = DEFAULT_TEARDOWN_TIMEOUT; /* get a list of all extensions */ src->extensions = gst_rtsp_ext_list_get (); @@ -903,9 +1351,36 @@ gst_rtspsrc_init (GstRTSPSrc * src) /* protects our state changes from multiple invocations */ g_rec_mutex_init (&src->state_rec_lock); + g_queue_init (&src->set_get_param_q); + src->state = GST_RTSP_STATE_INVALID; + g_mutex_init (&src->conninfo.send_lock); + g_mutex_init (&src->conninfo.recv_lock); + g_cond_init (&src->cmd_cond); + 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 +free_param_data (ParameterRequest * req) +{ + gst_promise_unref (req->promise); + if (req->body) + g_string_free (req->body, TRUE); + g_free (req->content_type); + g_free (req); +} + +static void +free_param_queue (gpointer data) +{ + ParameterRequest *req = data; + + gst_promise_expire (req->promise); + free_param_data (req); } static void @@ -944,6 +1419,10 @@ gst_rtspsrc_finalize (GObject * object) 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_cond_clear (&rtspsrc->cmd_cond); + G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -954,9 +1433,9 @@ gst_rtspsrc_provide_clock (GstElement * element) GstClock *clock; if ((clock = src->provided_clock) != NULL) - gst_object_ref (clock); + return gst_object_ref (clock); - return clock; + return GST_ELEMENT_CLASS (parent_class)->provide_clock (element); } /* a proxy string of the format [user:passwd@]host[:port] */ @@ -1107,7 +1586,7 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, const gchar *str; str = g_value_get_string (value); - if (sscanf (str, "%u-%u", &rtspsrc->client_port_range.min, + if (str == NULL || 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; @@ -1136,6 +1615,15 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * 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); @@ -1170,6 +1658,22 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, 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; + case PROP_TEARDOWN_TIMEOUT: + rtspsrc->teardown_timeout = g_value_get_uint64 (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1319,6 +1823,21 @@ gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value, 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; + case PROP_TEARDOWN_TIMEOUT: + g_value_set_uint64 (value, rtspsrc->teardown_timeout); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1337,7 +1856,10 @@ find_stream_by_id (GstRTSPStream * stream, gint * id) static gint find_stream_by_channel (GstRTSPStream * stream, gint * channel) { - if (stream->channel[0] == *channel || stream->channel[1] == *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; @@ -1490,6 +2012,18 @@ gst_rtspsrc_collect_connections (GstRTSPSrc * src, const GstSDPMessage * sdp, } } +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= RTP/AVP */ static void @@ -1516,6 +2050,11 @@ gst_rtspsrc_collect_payloads (GstRTSPSrc * src, const GstSDPMessage * sdp, 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"); @@ -1570,6 +2109,8 @@ gst_rtspsrc_collect_payloads (GstRTSPSrc * src, const GstSDPMessage * sdp, g_array_append_val (stream->ptmap, item); } + stream->stream_id = make_stream_id (stream, media); + gst_caps_unref (global_caps); return; @@ -1580,7 +2121,12 @@ no_proto: } unknown_proto: { - GST_ERROR_OBJECT (src, "unknown proto in media %s", proto); + GST_ERROR_OBJECT (src, "unknown proto in media: '%s'", proto); + return; + } +sendonly_media: + { + GST_DEBUG_OBJECT (src, "sendonly media ignored, no backchannel"); return; } } @@ -1610,7 +2156,8 @@ clear_ptmap_item (PtMapItem * item) } static GstRTSPStream * -gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) +gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx, + gint n_streams) { GstRTSPStream *stream; const gchar *control_url; @@ -1638,8 +2185,17 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) 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); @@ -1665,6 +2221,11 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, GstSDPMessage * sdp, gint idx) 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 @@ -1715,11 +2276,14 @@ gst_rtspsrc_stream_free (GstRTSPSrc * src, GstRTSPStream * stream) 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]); + if (gst_object_has_as_parent (GST_OBJECT (stream->udpsrc[i]), + GST_OBJECT (src))) + gst_bin_remove (GST_BIN_CAST (src), stream->udpsrc[i]); gst_object_unref (stream->udpsrc[i]); } if (stream->channelpad[i]) @@ -1727,14 +2291,16 @@ gst_rtspsrc_stream_free (GstRTSPSrc * src, GstRTSPStream * stream) if (stream->udpsink[i]) { gst_element_set_state (stream->udpsink[i], GST_STATE_NULL); - gst_bin_remove (GST_BIN_CAST (src), stream->udpsink[i]); + if (gst_object_has_as_parent (GST_OBJECT (stream->udpsink[i]), + GST_OBJECT (src))) + gst_bin_remove (GST_BIN_CAST (src), stream->udpsink[i]); gst_object_unref (stream->udpsink[i]); } } - if (stream->fakesrc) { - gst_element_set_state (stream->fakesrc, GST_STATE_NULL); - gst_bin_remove (GST_BIN_CAST (src), stream->fakesrc); - gst_object_unref (stream->fakesrc); + if (stream->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); @@ -1755,6 +2321,10 @@ gst_rtspsrc_stream_free (GstRTSPSrc * src, GstRTSPStream * stream) 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); } @@ -1807,6 +2377,11 @@ gst_rtspsrc_cleanup (GstRTSPSrc * src) gst_object_unref (src->provided_clock); src->provided_clock = NULL; } + + /* free parameter requests queue */ + if (!g_queue_is_empty (&src->set_get_param_q)) + g_queue_free_full (&src->set_get_param_q, free_param_queue); + } static gboolean @@ -2007,7 +2582,8 @@ gst_rtspsrc_set_state (GstRTSPSrc * src, GstState state) } static void -gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing) +gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing, + guint32 seqnum) { GstEvent *event; gint cmd; @@ -2015,11 +2591,13 @@ gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing) if (flush) { event = gst_event_new_flush_start (); + gst_event_set_seqnum (event, seqnum); GST_DEBUG_OBJECT (src, "start flush"); cmd = CMD_WAIT; state = GST_STATE_PAUSED; } else { event = gst_event_new_flush_stop (FALSE); + gst_event_set_seqnum (event, seqnum); GST_DEBUG_OBJECT (src, "stop flush; playing %d", playing); cmd = CMD_LOOP; if (playing) @@ -2033,29 +2611,35 @@ gst_rtspsrc_flush (GstRTSPSrc * src, gboolean flush, gboolean playing) } static GstRTSPResult -gst_rtspsrc_connection_send (GstRTSPSrc * src, GstRTSPConnection * conn, +gst_rtspsrc_connection_send (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, GstRTSPMessage * message, GTimeVal * timeout) { GstRTSPResult ret; - if (conn) - ret = gst_rtsp_connection_send (conn, message, timeout); - else + 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, GstRTSPConnection * conn, +gst_rtspsrc_connection_receive (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, GstRTSPMessage * message, GTimeVal * timeout) { GstRTSPResult ret; - if (conn) - ret = gst_rtsp_connection_receive (conn, message, timeout); - else + 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; } @@ -2104,26 +2688,31 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) gboolean playing; GstSegment seeksegment = { 0, }; GList *walk; + const gchar *seek_style = NULL; - if (event) { - GST_DEBUG_OBJECT (src, "doing seek with 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); + 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; + /* no negative rates yet */ + if (rate < 0.0) + goto negative_rate; - /* we need TIME format */ - if (format != src->segment.format) - goto no_format; - } else { - GST_DEBUG_OBJECT (src, "doing seek without event"); - flags = 0; - cur_type = GST_SEEK_TYPE_SET; - stop_type = GST_SEEK_TYPE_SET; - } + /* 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; + + if (flags & GST_SEEK_FLAG_SEGMENT) + goto invalid_segment_flag; /* get flush flag */ flush = flags & GST_SEEK_FLAG_FLUSH; @@ -2137,7 +2726,7 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) * blocking in preroll). */ if (flush) { GST_DEBUG_OBJECT (src, "starting flush"); - gst_rtspsrc_flush (src, TRUE, FALSE); + gst_rtspsrc_flush (src, TRUE, FALSE, gst_event_get_seqnum (event)); } else { if (src->task) { gst_task_pause (src->task); @@ -2159,20 +2748,17 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) /* 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); - } + GST_DEBUG_OBJECT (src, "configuring seek"); + gst_segment_do_seek (&seeksegment, rate, format, flags, + cur_type, cur, stop_type, stop, &update); /* figure out the last position we need to play. If it's configured (stop != * -1), use that, else we play until the total duration of the file */ if ((stop = seeksegment.stop) == -1) stop = seeksegment.duration; - playing = (src->state == GST_RTSP_STATE_PLAYING); - /* if we were playing, pause first */ + playing = (src->state == GST_RTSP_STATE_PLAYING); if (playing) { /* obtain current position in case seek fails */ gst_rtspsrc_get_position (src); @@ -2185,15 +2771,11 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) /* PLAY will add the range header now. */ src->need_range = TRUE; - /* and continue playing */ - if (playing) - gst_rtspsrc_play (src, &seeksegment, FALSE); - /* prepare for streaming again */ if (flush) { /* if we started flush, we stop now */ GST_DEBUG_OBJECT (src, "stopping flush"); - gst_rtspsrc_flush (src, FALSE, playing); + gst_rtspsrc_flush (src, FALSE, playing, gst_event_get_seqnum (event)); } /* now we did the seek and can activate the new segment values */ @@ -2217,6 +2799,28 @@ gst_rtspsrc_perform_seek (GstRTSPSrc * src, GstEvent * event) 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; @@ -2232,7 +2836,17 @@ 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; + } +invalid_segment_flag: + { + GST_WARNING_OBJECT (src, "Segment seeks not supported"); + return FALSE; + } +} static gboolean gst_rtspsrc_handle_src_event (GstPad * pad, GstObject * parent, @@ -2275,6 +2889,34 @@ gst_rtspsrc_handle_src_event (GstPad * pad, GstObject * parent, 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 @@ -2386,13 +3028,26 @@ gst_rtspsrc_handle_src_query (GstPad * pad, GstObject * parent, 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 && src->segment.duration && + seekable = seekable && src->seekable >= 0.0 && src->segment.duration && GST_CLOCK_TIME_IS_VALID (src->segment.duration); - gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, - 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; @@ -2438,7 +3093,7 @@ gst_rtspsrc_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) guint size; GstRTSPResult ret; GstRTSPMessage message = { 0 }; - GstRTSPConnection *conn; + GstRTSPConnInfo *conninfo; stream = (GstRTSPStream *) gst_pad_get_element_private (pad); src = stream->parent; @@ -2453,12 +3108,12 @@ gst_rtspsrc_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) gst_rtsp_message_take_body (&message, data, size); if (stream->conninfo.connection) - conn = stream->conninfo.connection; + conninfo = &stream->conninfo; else - conn = src->conninfo.connection; + conninfo = &src->conninfo; GST_DEBUG_OBJECT (src, "sending %u bytes RTCP", size); - ret = gst_rtspsrc_connection_send (src, conn, &message, NULL); + 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 @@ -2472,6 +3127,71 @@ gst_rtspsrc_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * 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) { @@ -2499,6 +3219,24 @@ was_ok: } } +static GstPadProbeReturn +udpsrc_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + guint32 *segment_seqnum = user_data; + + switch (GST_EVENT_TYPE (info->data)) { + case GST_EVENT_SEGMENT: + if (!gst_event_is_writable (info->data)) + info->data = gst_event_make_writable (info->data); + + *segment_seqnum = gst_event_get_seqnum (info->data); + default: + break; + } + + return GST_PAD_PROBE_OK; +} + static gboolean copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data) { @@ -2510,6 +3248,35 @@ copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data) 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 @@ -2521,6 +3288,7 @@ new_manager_pad (GstElement * manager, GstPad * pad, GstRTSPSrc * src) GList *ostreams; GstRTSPStream *stream; gboolean all_added; + GstPad *internal_src; GST_DEBUG_OBJECT (src, "got new manager pad %" GST_PTR_FORMAT, pad); @@ -2565,11 +3333,23 @@ new_manager_pad (GstElement * manager, GstPad * pad, GstRTSPSrc * src) 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); - gst_element_add_pad (GST_ELEMENT_CAST (src), 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"); @@ -2667,7 +3447,7 @@ on_bye_ssrc (GObject * session, GObject * source, GstRTSPStream * stream) } static void -on_timeout (GObject * session, GObject * source, GstRTSPStream * stream) +on_timeout_common (GObject * session, GObject * source, GstRTSPStream * stream) { GstRTSPSrc *src = stream->parent; guint ssrc; @@ -2682,6 +3462,22 @@ on_timeout (GObject * session, GObject * source, GstRTSPStream * stream) } static void +on_timeout (GObject * session, GObject * source, GstRTSPStream * stream) +{ + GstRTSPSrc *src = stream->parent; + + /* timeout, post element message */ + gst_element_post_message (GST_ELEMENT_CAST (src), + gst_message_new_element (GST_OBJECT_CAST (src), + gst_structure_new ("GstRTSPSrcTimeout", + "cause", G_TYPE_ENUM, GST_RTSP_SRC_TIMEOUT_CAUSE_RTCP, + "stream-number", G_TYPE_INT, stream->id, "ssrc", G_TYPE_UINT, + stream->ssrc, NULL))); + + on_timeout_common (session, source, stream); +} + +static void on_npt_stop (GstElement * rtpbin, guint session, guint ssrc, GstRTSPSrc * src) { GstRTSPStream *stream; @@ -2796,6 +3592,11 @@ request_rtp_decoder (GstElement * rtpbin, guint session, GstRTSPStream * stream) stream->srtpdec = gst_element_factory_make ("srtpdec", name); g_free (name); + if (stream->srtpdec == NULL) { + GST_ELEMENT_ERROR (stream->parent, CORE, MISSING_PLUGIN, (NULL), + ("no srtpdec element present!")); + return NULL; + } g_signal_connect (stream->srtpdec, "request-key", (GCallback) request_key, stream); } @@ -2824,6 +3625,12 @@ request_rtcp_encoder (GstElement * rtpbin, guint session, stream->srtpenc = gst_element_factory_make ("srtpenc", name); g_free (name); + if (stream->srtpenc == NULL) { + GST_ELEMENT_ERROR (stream->parent, CORE, MISSING_PLUGIN, (NULL), + ("no srtpenc element present!")); + return NULL; + } + /* get RTCP crypto parameters from caps */ s = gst_caps_get_structure (stream->srtcpparams, 0); if (s) { @@ -2843,6 +3650,10 @@ request_rtcp_encoder (GstElement * rtpbin, guint session, gst_value_deserialize (&rtcp_auth, str); gst_structure_get (s, "srtp-key", GST_TYPE_BUFFER, &buf, NULL); + g_object_set_property (G_OBJECT (stream->srtpenc), "rtp-cipher", + &rtcp_cipher); + g_object_set_property (G_OBJECT (stream->srtpenc), "rtp-auth", + &rtcp_auth); g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-cipher", &rtcp_cipher); g_object_set_property (G_OBJECT (stream->srtpenc), "rtcp-auth", @@ -3059,6 +3870,23 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, src->max_rtcp_rtp_time_diff, NULL); } + if (g_object_class_find_property (klass, "max-ts-offset-adjustment")) { + g_object_set (src->manager, "max-ts-offset-adjustment", + src->max_ts_offset_adjustment, NULL); + } + + if (g_object_class_find_property (klass, "max-ts-offset")) { + gint64 max_ts_offset; + + /* setting max-ts-offset in the manager has side effects so only do it + * if the value differs */ + g_object_get (src->manager, "max-ts-offset", &max_ts_offset, NULL); + if (max_ts_offset != src->max_ts_offset) { + g_object_set (src->manager, "max-ts-offset", src->max_ts_offset, + NULL); + } + } + /* buffer mode pauses are handled by adding offsets to buffer times, * but some depayloaders may have a hard time syncing output times * with such input times, e.g. container ones, most notably ASF */ @@ -3166,8 +3994,8 @@ gst_rtspsrc_stream_configure_manager (GstRTSPSrc * src, GstRTSPStream * stream, g_signal_connect (rtpsession, "on-bye-ssrc", (GCallback) on_bye_ssrc, stream); - g_signal_connect (rtpsession, "on-bye-timeout", (GCallback) on_timeout, - stream); + g_signal_connect (rtpsession, "on-bye-timeout", + (GCallback) on_timeout_common, stream); g_signal_connect (rtpsession, "on-timeout", (GCallback) on_timeout, stream); g_signal_connect (rtpsession, "on-ssrc-active", @@ -3431,7 +4259,7 @@ gst_rtspsrc_stream_configure_mcast (GstRTSPSrc * src, GstRTSPStream * stream, gst_object_ref_sink (stream->udpsrc[1]); if (src->multi_iface != NULL) - g_object_set (G_OBJECT (stream->udpsrc[0]), "multicast-iface", + g_object_set (G_OBJECT (stream->udpsrc[1]), "multicast-iface", src->multi_iface, NULL); gst_element_set_state (stream->udpsrc[1], GST_STATE_READY); @@ -3494,6 +4322,10 @@ gst_rtspsrc_stream_configure_udp (GstRTSPSrc * src, GstRTSPStream * stream, GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BUFFER_LIST, pad_blocked, src, NULL); + gst_pad_add_probe (stream->blockedpad, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, udpsrc_probe_cb, + &(stream->segment_seqnum[0]), NULL); + if (stream->channelpad[0]) { GST_DEBUG_OBJECT (src, "connecting UDP source 0 to manager"); /* configure for UDP delivery, we need to connect the UDP pads to @@ -3529,6 +4361,9 @@ gst_rtspsrc_stream_configure_udp (GstRTSPSrc * src, GstRTSPStream * stream, GST_DEBUG_OBJECT (src, "connecting UDP source 1 to manager"); pad = gst_element_get_static_pad (stream->udpsrc[1], "src"); + gst_pad_add_probe (pad, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, udpsrc_probe_cb, + &(stream->segment_seqnum[1]), NULL); gst_pad_link_full (pad, stream->channelpad[1], GST_PAD_LINK_CHECK_NOTHING); gst_object_unref (pad); @@ -3567,7 +4402,7 @@ gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src, goto no_destination; /* try to construct the fakesrc to the RTP port of the server to open up any - * NAT firewalls */ + * NAT firewalls or, if backchannel, construct an appsrc */ if (do_rtp) { GST_DEBUG_OBJECT (src, "configure RTP UDP sink for %s:%d", destination, rtp_port); @@ -3601,28 +4436,36 @@ gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src, g_object_unref (socket); } - /* the source for the dummy packets to open up NAT */ - stream->fakesrc = gst_element_factory_make ("fakesrc", NULL); - if (stream->fakesrc == NULL) - goto no_fakesrc_element; - - /* random data in 5 buffers, a size of 200 bytes should be fine */ - g_object_set (G_OBJECT (stream->fakesrc), "filltype", 3, "num-buffers", 5, - "sizetype", 2, "sizemax", 200, "silent", TRUE, NULL); + if (stream->is_backchannel) { + /* appsrc is for the app to shovel data using push-backchannel-buffer */ + stream->rtpsrc = gst_element_factory_make ("appsrc", NULL); + if (stream->rtpsrc == NULL) + goto no_appsrc_element; - /* we don't want to consider this a sink */ - GST_OBJECT_FLAG_UNSET (stream->udpsink[0], GST_ELEMENT_FLAG_SINK); + /* interal use only, don't emit signals */ + g_object_set (G_OBJECT (stream->rtpsrc), "emit-signals", TRUE, + "is-live", TRUE, NULL); + } else { + /* the source for the dummy packets to open up NAT */ + stream->rtpsrc = gst_element_factory_make ("fakesrc", NULL); + if (stream->rtpsrc == NULL) + goto no_fakesrc_element; + + /* random data in 5 buffers, a size of 200 bytes should be fine */ + g_object_set (G_OBJECT (stream->rtpsrc), "filltype", 3, "num-buffers", 5, + "sizetype", 2, "sizemax", 200, "silent", TRUE, NULL); + } /* keep everything locked */ gst_element_set_locked_state (stream->udpsink[0], TRUE); - gst_element_set_locked_state (stream->fakesrc, TRUE); + gst_element_set_locked_state (stream->rtpsrc, TRUE); gst_object_ref (stream->udpsink[0]); gst_bin_add (GST_BIN_CAST (src), stream->udpsink[0]); - gst_object_ref (stream->fakesrc); - gst_bin_add (GST_BIN_CAST (src), stream->fakesrc); + gst_object_ref (stream->rtpsrc); + gst_bin_add (GST_BIN_CAST (src), stream->rtpsrc); - gst_element_link_pads_full (stream->fakesrc, "src", stream->udpsink[0], + gst_element_link_pads_full (stream->rtpsrc, "src", stream->udpsink[0], "sink", GST_PAD_LINK_CHECK_NOTHING); } if (do_rtcp) { @@ -3659,9 +4502,6 @@ gst_rtspsrc_stream_configure_udp_sinks (GstRTSPSrc * src, g_object_unref (socket); } - /* we don't want to consider this a sink */ - GST_OBJECT_FLAG_UNSET (stream->udpsink[1], GST_ELEMENT_FLAG_SINK); - /* we keep this playing always */ gst_element_set_locked_state (stream->udpsink[1], TRUE); gst_element_set_state (stream->udpsink[1], GST_STATE_PLAYING); @@ -3696,6 +4536,11 @@ no_sink_element: GST_ERROR_OBJECT (src, "no UDP sink element found"); return FALSE; } +no_appsrc_element: + { + GST_ERROR_OBJECT (src, "no appsrc element found"); + return FALSE; + } no_fakesrc_element: { GST_ERROR_OBJECT (src, "no fakesrc element found"); @@ -3769,8 +4614,8 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, case GST_RTSP_LOWER_TRANS_UDP: if (!gst_rtspsrc_stream_configure_udp (src, stream, transport, &outpad)) goto transport_failed; - /* configure udpsinks back to the server for RTCP messages and for the - * dummy RTP messages to open NAT. */ + /* configure udpsinks back to the server for RTCP messages, for the + * dummy RTP messages to open NAT, and for the backchannel */ if (!gst_rtspsrc_stream_configure_udp_sinks (src, stream, transport)) goto transport_failed; break; @@ -3778,8 +4623,12 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, goto unknown_transport; } - if (outpad) { - GST_DEBUG_OBJECT (src, "creating ghostpad"); + /* using backchannel and no manager, hence no srcpad for this stream */ + if (outpad && stream->is_backchannel) { + add_backchannel_fakesink (src, stream, outpad); + gst_object_unref (outpad); + } else if (outpad) { + GST_DEBUG_OBJECT (src, "creating ghostpad for stream %p", stream); gst_pad_use_fixed_caps (outpad); @@ -3803,17 +4652,17 @@ gst_rtspsrc_stream_configure_transport (GstRTSPStream * stream, /* ERRORS */ transport_failed: { - GST_DEBUG_OBJECT (src, "failed to configure transport"); + GST_WARNING_OBJECT (src, "failed to configure transport"); return FALSE; } unknown_transport: { - GST_DEBUG_OBJECT (src, "unknown transport"); + GST_WARNING_OBJECT (src, "unknown transport"); return FALSE; } no_manager: { - GST_DEBUG_OBJECT (src, "cannot get a session manager"); + GST_WARNING_OBJECT (src, "cannot get a session manager"); return FALSE; } } @@ -3832,13 +4681,18 @@ gst_rtspsrc_send_dummy_packets (GstRTSPSrc * src) for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; - if (stream->fakesrc && stream->udpsink[0]) { + if (!stream->rtpsrc || !stream->udpsink[0]) + continue; + + if (stream->is_backchannel) + GST_DEBUG_OBJECT (src, "starting backchannel stream %p", stream); + else GST_DEBUG_OBJECT (src, "sending dummy packet to stream %p", stream); - gst_element_set_state (stream->udpsink[0], GST_STATE_NULL); - gst_element_set_state (stream->fakesrc, GST_STATE_NULL); - gst_element_set_state (stream->udpsink[0], GST_STATE_PLAYING); - gst_element_set_state (stream->fakesrc, GST_STATE_PLAYING); - } + + gst_element_set_state (stream->udpsink[0], GST_STATE_NULL); + gst_element_set_state (stream->rtpsrc, GST_STATE_NULL); + gst_element_set_state (stream->udpsink[0], GST_STATE_PLAYING); + gst_element_set_state (stream->rtpsrc, GST_STATE_PLAYING); } return TRUE; } @@ -3880,7 +4734,10 @@ gst_rtspsrc_activate_streams (GstRTSPSrc * src) /* add the pad */ if (!stream->added) { GST_DEBUG_OBJECT (src, "adding stream pad %p", stream); - gst_element_add_pad (GST_ELEMENT_CAST (src), stream->srcpad); + if (stream->is_backchannel) + add_backchannel_fakesink (src, stream, stream->srcpad); + else + gst_element_add_pad (GST_ELEMENT_CAST (src), stream->srcpad); stream->added = TRUE; } } @@ -3948,8 +4805,10 @@ gst_rtspsrc_configure_caps (GstRTSPSrc * src, GstSegment * segment, GST_DEBUG_OBJECT (src, "stream %p, pt %d, caps %" GST_PTR_FORMAT, stream, item->pt, caps); - if (item->pt == stream->default_pt && stream->udpsrc[0]) { - g_object_set (stream->udpsrc[0], "caps", caps, NULL); + if (item->pt == stream->default_pt) { + if (stream->udpsrc[0]) + g_object_set (stream->udpsrc[0], "caps", caps, NULL); + stream->need_caps = TRUE; } } } @@ -4004,8 +4863,16 @@ gst_rtspsrc_stream_push_event (GstRTSPSrc * src, GstRTSPStream * stream, goto done; if (stream->udpsrc[0]) { - gst_event_ref (event); - res = gst_element_send_event (stream->udpsrc[0], event); + GstEvent *sent_event; + + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + sent_event = gst_event_new_eos (); + gst_event_set_seqnum (sent_event, stream->segment_seqnum[0]); + } else { + sent_event = gst_event_ref (event); + } + + res = gst_element_send_event (stream->udpsrc[0], sent_event); } else if (stream->channelpad[0]) { gst_event_ref (event); if (GST_PAD_IS_SRC (stream->channelpad[0])) @@ -4015,8 +4882,18 @@ gst_rtspsrc_stream_push_event (GstRTSPSrc * src, GstRTSPStream * stream, } if (stream->udpsrc[1]) { - gst_event_ref (event); - res &= gst_element_send_event (stream->udpsrc[1], event); + GstEvent *sent_event; + + if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + sent_event = gst_event_new_eos (); + if (stream->segment_seqnum[1] != GST_SEQNUM_INVALID) { + gst_event_set_seqnum (sent_event, stream->segment_seqnum[1]); + } + } else { + sent_event = gst_event_ref (event); + } + + res &= gst_element_send_event (stream->udpsrc[1], sent_event); } else if (stream->channelpad[1]) { gst_event_ref (event); if (GST_PAD_IS_SRC (stream->channelpad[1])) @@ -4048,6 +4925,19 @@ gst_rtspsrc_push_event (GstRTSPSrc * src, GstEvent * event) return res; } +static gboolean +accept_certificate_cb (GTlsConnection * conn, GTlsCertificate * peer_cert, + GTlsCertificateFlags errors, gpointer user_data) +{ + GstRTSPSrc *src = user_data; + gboolean accept = FALSE; + + g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_ACCEPT_CERTIFICATE], 0, conn, + peer_cert, errors, &accept); + + return accept; +} + static GstRTSPResult gst_rtsp_conninfo_connect (GstRTSPSrc * src, GstRTSPConnInfo * info, gboolean async) @@ -4090,6 +4980,8 @@ gst_rtsp_conninfo_connect (GstRTSPSrc * src, GstRTSPConnInfo * info, if (src->tls_interaction) gst_rtsp_connection_set_tls_interaction (info->connection, src->tls_interaction); + gst_rtsp_connection_set_accept_certificate_func (info->connection, + accept_certificate_cb, src, NULL); } if (info->url->transports & GST_RTSP_LOWER_TRANS_HTTP) @@ -4129,6 +5021,7 @@ gst_rtsp_conninfo_connect (GstRTSPSrc * src, GstRTSPConnInfo * info, goto could_not_connect; } } while (!info->connected && retry); + gst_rtsp_message_unset (&response); return GST_RTSP_OK; @@ -4172,6 +5065,7 @@ gst_rtsp_conninfo_close (GstRTSPSrc * src, GstRTSPConnInfo * info, GST_DEBUG_OBJECT (src, "freeing connection..."); gst_rtsp_connection_free (info->connection); info->connection = NULL; + info->flushing = FALSE; } GST_RTSP_STATE_UNLOCK (src); return GST_RTSP_OK; @@ -4232,7 +5126,7 @@ gst_rtspsrc_init_request (GstRTSPSrc * src, GstRTSPMessage * msg, /* FIXME, handle server request, reply with OK, for now */ static GstRTSPResult -gst_rtspsrc_handle_request (GstRTSPSrc * src, GstRTSPConnection * conn, +gst_rtspsrc_handle_request (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, GstRTSPMessage * request) { GstRTSPMessage response = { 0 }; @@ -4240,8 +5134,7 @@ gst_rtspsrc_handle_request (GstRTSPSrc * src, GstRTSPConnection * conn, GST_DEBUG_OBJECT (src, "got server request message"); - if (src->debug) - gst_rtsp_message_dump (request); + DEBUG_RTSP (src, request); res = gst_rtsp_ext_list_receive_request (src->extensions, request); @@ -4258,10 +5151,9 @@ gst_rtspsrc_handle_request (GstRTSPSrc * src, GstRTSPConnection * conn, g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_HANDLE_REQUEST], 0, request, &response); - if (src->debug) - gst_rtsp_message_dump (&response); + DEBUG_RTSP (src, &response); - res = gst_rtspsrc_connection_send (src, conn, &response, NULL); + res = gst_rtspsrc_connection_send (src, conninfo, &response, NULL); if (res < 0) goto send_error; @@ -4310,12 +5202,9 @@ gst_rtspsrc_send_keep_alive (GstRTSPSrc * src) if (res < 0) goto send_error; - if (src->debug) - gst_rtsp_message_dump (&request); + request.type_data.request.version = src->version; - res = - gst_rtspsrc_connection_send (src, src->conninfo.connection, &request, - NULL); + res = gst_rtspsrc_connection_send (src, &src->conninfo, &request, NULL); if (res < 0) goto send_error; @@ -4441,6 +5330,7 @@ gst_rtspsrc_handle_data (GstRTSPSrc * src, GstRTSPMessage * message) gst_pad_send_event (ostream->channelpad[0], gst_event_new_caps (caps)); } + ostream->need_caps = FALSE; if (ostream->profile == GST_RTSP_PROFILE_SAVP || ostream->profile == GST_RTSP_PROFILE_SAVPF) @@ -4504,6 +5394,28 @@ gst_rtspsrc_handle_data (GstRTSPSrc * src, GstRTSPMessage * message) gst_rtspsrc_push_event (src, gst_event_new_segment (&segment)); } + if (stream->need_caps) { + GstCaps *caps; + + if ((caps = stream_get_caps_for_pt (stream, stream->default_pt))) { + /* only streams that have a connection to the outside world */ + if (stream->setup) { + /* Only need to update the TCP caps here, UDP is already handled */ + if (stream->channelpad[0]) { + if (GST_PAD_IS_SRC (stream->channelpad[0])) + gst_pad_push_event (stream->channelpad[0], + gst_event_new_caps (caps)); + else + gst_pad_send_event (stream->channelpad[0], + gst_event_new_caps (caps)); + } + stream->need_caps = FALSE; + } + } + + stream->need_caps = FALSE; + } + if (stream->discont && !is_rtcp) { /* mark first RTP buffer as discont */ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); @@ -4573,7 +5485,7 @@ gst_rtspsrc_loop_interleaved (GstRTSPSrc * src) /* protect the connection with the connection lock so that we can see when * we are finished doing server communication */ res = - gst_rtspsrc_connection_receive (src, src->conninfo.connection, + gst_rtspsrc_connection_receive (src, &src->conninfo, &message, src->ptcp_timeout); switch (res) { @@ -4599,9 +5511,7 @@ gst_rtspsrc_loop_interleaved (GstRTSPSrc * src) switch (message.type) { case GST_RTSP_MESSAGE_REQUEST: /* server sends us a request message, handle it */ - res = - gst_rtspsrc_handle_request (src, src->conninfo.connection, - &message); + res = gst_rtspsrc_handle_request (src, &src->conninfo, &message); if (res == GST_RTSP_EEOF) goto server_eof; else if (res < 0) @@ -4610,8 +5520,7 @@ gst_rtspsrc_loop_interleaved (GstRTSPSrc * src) case GST_RTSP_MESSAGE_RESPONSE: /* we ignore response messages */ GST_DEBUG_OBJECT (src, "ignoring response message"); - if (src->debug) - gst_rtsp_message_dump (&message); + DEBUG_RTSP (src, &message); break; case GST_RTSP_MESSAGE_DATA: GST_DEBUG_OBJECT (src, "got data message"); @@ -4692,7 +5601,7 @@ gst_rtspsrc_loop_udp (GstRTSPSrc * src) /* we should continue reading the TCP socket because the server might * send us requests. When the session timeout expires, we need to send a * keep-alive request to keep the session open. */ - res = gst_rtspsrc_connection_receive (src, src->conninfo.connection, + res = gst_rtspsrc_connection_receive (src, &src->conninfo, &message, &tv_timeout); switch (res) { @@ -4732,9 +5641,7 @@ gst_rtspsrc_loop_udp (GstRTSPSrc * src) switch (message.type) { case GST_RTSP_MESSAGE_REQUEST: /* server sends us a request message, handle it */ - res = - gst_rtspsrc_handle_request (src, src->conninfo.connection, - &message); + res = gst_rtspsrc_handle_request (src, &src->conninfo, &message); if (res == GST_RTSP_EEOF) goto server_eof; else if (res < 0) @@ -4743,8 +5650,7 @@ gst_rtspsrc_loop_udp (GstRTSPSrc * src) case GST_RTSP_MESSAGE_RESPONSE: /* we ignore response and data messages */ GST_DEBUG_OBJECT (src, "ignoring response message"); - if (src->debug) - gst_rtsp_message_dump (&message); + DEBUG_RTSP (src, &message); if (message.type_data.response.code == GST_RTSP_STS_UNAUTHORIZED) { GST_DEBUG_OBJECT (src, "but is Unauthorized response ..."); if (gst_rtspsrc_setup_auth (src, &message) && !(retry++)) { @@ -4861,15 +5767,15 @@ gst_rtspsrc_reconnect (GstRTSPSrc * src, gboolean async) * that nothing happened. It's most likely a firewall thing. */ GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), ("Could not receive any UDP packets for %.4f seconds, maybe your " - "firewall is blocking it. Retrying using a TCP connection.", - gst_guint64_to_gdouble (src->udp_timeout / 1000000.0))); + "firewall is blocking it. Retrying using a tcp connection.", + gst_guint64_to_gdouble (src->udp_timeout) / 1000000.0)); /* open new connection using tcp */ if (gst_rtspsrc_open (src, async) < 0) goto open_failed; /* start playback */ - if (gst_rtspsrc_play (src, &src->segment, async) < 0) + if (gst_rtspsrc_play (src, &src->segment, async, NULL) < 0) goto play_failed; done: @@ -4883,7 +5789,7 @@ no_protocols: GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), ("Could not receive any UDP packets for %.4f seconds, maybe your " "firewall is blocking it. No other protocols to try.", - gst_guint64_to_gdouble (src->udp_timeout / 1000000.0))); + gst_guint64_to_gdouble (src->udp_timeout) / 1000000.0)); return GST_RTSP_ERROR; } open_failed: @@ -4911,6 +5817,14 @@ gst_rtspsrc_loop_start_cmd (GstRTSPSrc * src, gint cmd) case CMD_PAUSE: GST_ELEMENT_PROGRESS (src, START, "request", ("Sending PAUSE request")); break; + case CMD_GET_PARAMETER: + GST_ELEMENT_PROGRESS (src, START, "request", + ("Sending GET_PARAMETER request")); + break; + case CMD_SET_PARAMETER: + GST_ELEMENT_PROGRESS (src, START, "request", + ("Sending SET_PARAMETER request")); + break; case CMD_CLOSE: GST_ELEMENT_PROGRESS (src, START, "close", ("Closing Stream")); break; @@ -4932,6 +5846,14 @@ gst_rtspsrc_loop_complete_cmd (GstRTSPSrc * src, gint cmd) case CMD_PAUSE: GST_ELEMENT_PROGRESS (src, COMPLETE, "request", ("Sent PAUSE request")); break; + case CMD_GET_PARAMETER: + GST_ELEMENT_PROGRESS (src, COMPLETE, "request", + ("Sent GET_PARAMETER request")); + break; + case CMD_SET_PARAMETER: + GST_ELEMENT_PROGRESS (src, COMPLETE, "request", + ("Sent SET_PARAMETER request")); + break; case CMD_CLOSE: GST_ELEMENT_PROGRESS (src, COMPLETE, "close", ("Closed Stream")); break; @@ -4953,6 +5875,14 @@ gst_rtspsrc_loop_cancel_cmd (GstRTSPSrc * src, gint cmd) case CMD_PAUSE: GST_ELEMENT_PROGRESS (src, CANCELED, "request", ("PAUSE canceled")); break; + case CMD_GET_PARAMETER: + GST_ELEMENT_PROGRESS (src, CANCELED, "request", + ("GET_PARAMETER canceled")); + break; + case CMD_SET_PARAMETER: + GST_ELEMENT_PROGRESS (src, CANCELED, "request", + ("SET_PARAMETER canceled")); + break; case CMD_CLOSE: GST_ELEMENT_PROGRESS (src, CANCELED, "close", ("Close canceled")); break; @@ -4974,6 +5904,12 @@ gst_rtspsrc_loop_error_cmd (GstRTSPSrc * src, gint cmd) case CMD_PAUSE: GST_ELEMENT_PROGRESS (src, ERROR, "request", ("PAUSE failed")); break; + case CMD_GET_PARAMETER: + GST_ELEMENT_PROGRESS (src, ERROR, "request", ("GET_PARAMETER failed")); + break; + case CMD_SET_PARAMETER: + GST_ELEMENT_PROGRESS (src, ERROR, "request", ("SET_PARAMETER failed")); + break; case CMD_CLOSE: GST_ELEMENT_PROGRESS (src, ERROR, "close", ("Close failed")); break; @@ -5006,11 +5942,24 @@ gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd, gint mask) GST_OBJECT_LOCK (src); old = src->pending_cmd; + if (old == CMD_RECONNECT) { GST_DEBUG_OBJECT (src, "ignore, we were reconnecting"); cmd = CMD_RECONNECT; - } - if (old != CMD_WAIT) { + } else if (old == CMD_CLOSE) { + /* our CMD_CLOSE might have interrutped CMD_LOOP. gst_rtspsrc_loop + * will send a CMD_WAIT which would cancel our pending CMD_CLOSE (if + * still pending). We just avoid it here by making sure CMD_CLOSE is + * still the pending command. */ + GST_DEBUG_OBJECT (src, "ignore, we were closing"); + cmd = CMD_CLOSE; + } else if (old == CMD_SET_PARAMETER) { + GST_DEBUG_OBJECT (src, "ignore, we have a pending %s", cmd_to_string (old)); + cmd = CMD_SET_PARAMETER; + } else if (old == CMD_GET_PARAMETER) { + GST_DEBUG_OBJECT (src, "ignore, we have a pending %s", cmd_to_string (old)); + cmd = CMD_GET_PARAMETER; + } else if (old != CMD_WAIT) { src->pending_cmd = CMD_WAIT; GST_OBJECT_UNLOCK (src); /* cancel previous request */ @@ -5037,6 +5986,28 @@ gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd, gint mask) } static gboolean +gst_rtspsrc_loop_send_cmd_and_wait (GstRTSPSrc * src, gint cmd, gint mask, + GstClockTime timeout) +{ + gboolean flushed = gst_rtspsrc_loop_send_cmd (src, cmd, mask); + + if (timeout > 0) { + gint64 end_time = g_get_monotonic_time () + (timeout / 1000); + GST_OBJECT_LOCK (src); + while (src->pending_cmd == cmd || src->busy_cmd == cmd) { + if (!g_cond_wait_until (&src->cmd_cond, GST_OBJECT_GET_LOCK (src), + end_time)) { + GST_WARNING_OBJECT (src, + "Timed out waiting for TEARDOWN to be processed."); + break; /* timeout passed */ + } + } + GST_OBJECT_UNLOCK (src); + } + return flushed; +} + +static gboolean gst_rtspsrc_loop (GstRTSPSrc * src) { GstFlowReturn ret; @@ -5082,9 +6053,7 @@ pause: } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) { /* for fatal errors we post an error message, post the error before the * EOS so the app knows about the error first. */ - GST_ELEMENT_ERROR (src, STREAM, FAILED, - ("Internal data flow error."), - ("streaming task paused, reason %s (%d)", reason, ret)); + GST_ELEMENT_FLOW_ERROR (src, ret); gst_rtspsrc_push_event (src, gst_event_new_eos ()); } gst_rtspsrc_loop_send_cmd (src, CMD_WAIT, CMD_LOOP); @@ -5115,131 +6084,6 @@ gst_rtsp_auth_method_to_string (GstRTSPAuthMethod method) } #endif -static const gchar * -gst_rtspsrc_skip_lws (const gchar * s) -{ - while (g_ascii_isspace (*s)) - s++; - return s; -} - -static const gchar * -gst_rtspsrc_unskip_lws (const gchar * s, const gchar * start) -{ - while (s > start && g_ascii_isspace (*(s - 1))) - s--; - return s; -} - -static const gchar * -gst_rtspsrc_skip_commas (const gchar * s) -{ - /* The grammar allows for multiple commas */ - while (g_ascii_isspace (*s) || *s == ',') - s++; - return s; -} - -static const gchar * -gst_rtspsrc_skip_item (const gchar * s) -{ - gboolean quoted = FALSE; - const gchar *start = s; - - /* A list item ends at the last non-whitespace character - * before a comma which is not inside a quoted-string. Or at - * the end of the string. - */ - while (*s) { - if (*s == '"') - quoted = !quoted; - else if (quoted) { - if (*s == '\\' && *(s + 1)) - s++; - } else { - if (*s == ',') - break; - } - s++; - } - - return gst_rtspsrc_unskip_lws (s, start); -} - -static void -gst_rtsp_decode_quoted_string (gchar * quoted_string) -{ - gchar *src, *dst; - - src = quoted_string + 1; - dst = quoted_string; - while (*src && *src != '"') { - if (*src == '\\' && *(src + 1)) - src++; - *dst++ = *src++; - } - *dst = '\0'; -} - -/* Extract the authentication tokens that the server provided for each method - * into an array of structures and give those to the connection object. - */ -static void -gst_rtspsrc_parse_digest_challenge (GstRTSPConnection * conn, - const gchar * header, gboolean * stale) -{ - GSList *list = NULL, *iter; - const gchar *end; - gchar *item, *eq, *name_end, *value; - - g_return_if_fail (stale != NULL); - - gst_rtsp_connection_clear_auth_params (conn); - *stale = FALSE; - - /* Parse a header whose content is described by RFC2616 as - * "#something", where "something" does not itself contain commas, - * except as part of quoted-strings, into a list of allocated strings. - */ - header = gst_rtspsrc_skip_commas (header); - while (*header) { - end = gst_rtspsrc_skip_item (header); - list = g_slist_prepend (list, g_strndup (header, end - header)); - header = gst_rtspsrc_skip_commas (end); - } - if (!list) - return; - - list = g_slist_reverse (list); - for (iter = list; iter; iter = iter->next) { - item = iter->data; - - eq = strchr (item, '='); - if (eq) { - name_end = (gchar *) gst_rtspsrc_unskip_lws (eq, item); - if (name_end == item) { - /* That's no good... */ - g_free (item); - continue; - } - - *name_end = '\0'; - - value = (gchar *) gst_rtspsrc_skip_lws (eq + 1); - if (*value == '"') - gst_rtsp_decode_quoted_string (value); - } else - value = NULL; - - if (value && strcmp (item, "stale") == 0 && strcmp (value, "TRUE") == 0) - *stale = TRUE; - gst_rtsp_connection_set_auth_param (conn, item, value); - g_free (item); - } - - g_slist_free (list); -} - /* Parse a WWW-Authenticate Response header and determine the * available authentication methods * @@ -5249,24 +6093,47 @@ gst_rtspsrc_parse_digest_challenge (GstRTSPConnection * conn, * At the moment, for Basic auth, we just do a minimal check and don't * even parse out the realm */ static void -gst_rtspsrc_parse_auth_hdr (gchar * hdr, GstRTSPAuthMethod * methods, - GstRTSPConnection * conn, gboolean * stale) +gst_rtspsrc_parse_auth_hdr (GstRTSPMessage * response, + GstRTSPAuthMethod * methods, GstRTSPConnection * conn, gboolean * stale) { - gchar *start; + GstRTSPAuthCredential **credentials, **credential; - g_return_if_fail (hdr != NULL); + g_return_if_fail (response != NULL); g_return_if_fail (methods != NULL); g_return_if_fail (stale != NULL); - /* Skip whitespace at the start of the string */ - for (start = hdr; start[0] != '\0' && g_ascii_isspace (start[0]); start++); + credentials = + gst_rtsp_message_parse_auth_credentials (response, + GST_RTSP_HDR_WWW_AUTHENTICATE); + if (!credentials) + return; + + credential = credentials; + while (*credential) { + if ((*credential)->scheme == GST_RTSP_AUTH_BASIC) { + *methods |= GST_RTSP_AUTH_BASIC; + } else if ((*credential)->scheme == GST_RTSP_AUTH_DIGEST) { + GstRTSPAuthParam **param = (*credential)->params; + + *methods |= GST_RTSP_AUTH_DIGEST; + + gst_rtsp_connection_clear_auth_params (conn); + *stale = FALSE; + + while (*param) { + if (strcmp ((*param)->name, "stale") == 0 + && g_ascii_strcasecmp ((*param)->value, "TRUE") == 0) + *stale = TRUE; + gst_rtsp_connection_set_auth_param (conn, (*param)->name, + (*param)->value); + param++; + } + } - if (g_ascii_strncasecmp (start, "basic", 5) == 0) - *methods |= GST_RTSP_AUTH_BASIC; - else if (g_ascii_strncasecmp (start, "digest ", 7) == 0) { - *methods |= GST_RTSP_AUTH_DIGEST; - gst_rtspsrc_parse_digest_challenge (conn, &start[7], stale); + credential++; } + + gst_rtsp_auth_credentials_free (credentials); } /** @@ -5293,16 +6160,12 @@ gst_rtspsrc_setup_auth (GstRTSPSrc * src, GstRTSPMessage * response) GstRTSPResult auth_result; GstRTSPUrl *url; GstRTSPConnection *conn; - gchar *hdr; gboolean stale = FALSE; conn = src->conninfo.connection; /* Identify the available auth methods and see if any are supported */ - if (gst_rtsp_message_get_header (response, GST_RTSP_HDR_WWW_AUTHENTICATE, - &hdr, 0) == GST_RTSP_OK) { - gst_rtspsrc_parse_auth_hdr (hdr, &avail_methods, conn, &stale); - } + gst_rtspsrc_parse_auth_hdr (response, &avail_methods, conn, &stale); if (avail_methods == GST_RTSP_AUTH_NONE) goto no_auth_available; @@ -5378,46 +6241,29 @@ no_user_pass: } static GstRTSPResult -gst_rtspsrc_try_send (GstRTSPSrc * src, GstRTSPConnection * conn, - GstRTSPMessage * request, GstRTSPMessage * response, - GstRTSPStatusCode * code) +gst_rtsp_src_receive_response (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, + GstRTSPMessage * response, GstRTSPStatusCode * code) { - GstRTSPResult res; GstRTSPStatusCode thecode; gchar *content_base = NULL; - gint try = 0; + GstRTSPResult res = gst_rtspsrc_connection_receive (src, conninfo, + response, src->ptcp_timeout); -again: - if (!src->short_header) - gst_rtsp_ext_list_before_send (src->extensions, request); - - GST_DEBUG_OBJECT (src, "sending message"); - - if (src->debug) - gst_rtsp_message_dump (request); - - res = gst_rtspsrc_connection_send (src, conn, request, src->ptcp_timeout); - if (res < 0) - goto send_error; - - gst_rtsp_connection_reset_timeout (conn); - -next: - res = gst_rtspsrc_connection_receive (src, conn, response, src->ptcp_timeout); if (res < 0) goto receive_error; - if (src->debug) - gst_rtsp_message_dump (response); + DEBUG_RTSP (src, response); switch (response->type) { case GST_RTSP_MESSAGE_REQUEST: - res = gst_rtspsrc_handle_request (src, conn, response); + res = gst_rtspsrc_handle_request (src, conninfo, response); if (res == GST_RTSP_EEOF) goto server_eof; else if (res < 0) goto handle_request_failed; - goto next; + + /* Not a response, receive next message */ + return gst_rtsp_src_receive_response (src, conninfo, response, code); case GST_RTSP_MESSAGE_RESPONSE: /* ok, a response is good */ GST_DEBUG_OBJECT (src, "received response message"); @@ -5426,11 +6272,15 @@ next: /* get next response */ GST_DEBUG_OBJECT (src, "handle data response message"); gst_rtspsrc_handle_data (src, response); - goto next; + + /* Not a response, receive next message */ + return gst_rtsp_src_receive_response (src, conninfo, response, code); default: GST_WARNING_OBJECT (src, "ignoring unknown message type %d", response->type); - goto next; + + /* Not a response, receive next message */ + return gst_rtsp_src_receive_response (src, conninfo, response, code); } thecode = response->type_data.response.code; @@ -5452,38 +6302,15 @@ next: g_free (src->content_base); src->content_base = g_strdup (content_base); } - gst_rtsp_ext_list_after_send (src->extensions, request, response); return GST_RTSP_OK; /* ERRORS */ -send_error: - { - gchar *str = gst_rtsp_strresult (res); - - if (res != GST_RTSP_EINTR) { - GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), - ("Could not send message. (%s)", str)); - } else { - GST_WARNING_OBJECT (src, "send interrupted"); - } - g_free (str); - return res; - } receive_error: { switch (res) { case GST_RTSP_EEOF: - GST_WARNING_OBJECT (src, "server closed connection"); - if ((try == 0) && !src->interleaved && src->udp_reconnect) { - try++; - /* if reconnect succeeds, try again */ - if ((res = - gst_rtsp_conninfo_reconnect (src, &src->conninfo, - FALSE)) == 0) - goto again; - } - /* only try once after reconnect, then fallthrough and error out */ + return GST_RTSP_EEOF; default: { gchar *str = gst_rtsp_strresult (res); @@ -5516,13 +6343,78 @@ server_eof: } } + +static GstRTSPResult +gst_rtspsrc_try_send (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, + GstRTSPMessage * request, GstRTSPMessage * response, + GstRTSPStatusCode * code) +{ + GstRTSPResult res; + gint try = 0; + gboolean allow_send = TRUE; + +again: + if (!src->short_header) + gst_rtsp_ext_list_before_send (src->extensions, request); + + g_signal_emit (src, gst_rtspsrc_signals[SIGNAL_BEFORE_SEND], 0, + request, &allow_send); + if (!allow_send) { + GST_DEBUG_OBJECT (src, "skipping message, disabled by signal"); + return GST_RTSP_OK; + } + + GST_DEBUG_OBJECT (src, "sending message"); + + DEBUG_RTSP (src, request); + + res = gst_rtspsrc_connection_send (src, conninfo, request, src->ptcp_timeout); + if (res < 0) + goto send_error; + + gst_rtsp_connection_reset_timeout (conninfo->connection); + if (!response) + return res; + + res = gst_rtsp_src_receive_response (src, conninfo, response, code); + if (res == GST_RTSP_EEOF) { + GST_WARNING_OBJECT (src, "server closed connection"); + /* only try once after reconnect, then fallthrough and error out */ + if ((try == 0) && !src->interleaved && src->udp_reconnect) { + try++; + /* if reconnect succeeds, try again */ + if ((res = gst_rtsp_conninfo_reconnect (src, &src->conninfo, FALSE)) == 0) + goto again; + } + } + gst_rtsp_ext_list_after_send (src->extensions, request, response); + + return res; + +send_error: + { + gchar *str = gst_rtsp_strresult (res); + + if (res != GST_RTSP_EINTR) { + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message. (%s)", str)); + } else { + GST_WARNING_OBJECT (src, "send interrupted"); + } + g_free (str); + return res; + } +} + /** * gst_rtspsrc_send: * @src: the rtsp source - * @conn: the connection to send on + * @conninfo: the connection information to send on * @request: must point to a valid request * @response: must point to an empty #GstRTSPMessage * @code: an optional code result + * @versions: List of versions to try, setting it back onto the @request message + * if not set, `src->version` will be used as RTSP version. * * send @request and retrieve the response in @response. optionally @code can be * non-NULL in which case it will contain the status code of the response. @@ -5540,15 +6432,16 @@ server_eof: * Returns: #GST_RTSP_OK if the processing was successful. */ static GstRTSPResult -gst_rtspsrc_send (GstRTSPSrc * src, GstRTSPConnection * conn, +gst_rtspsrc_send (GstRTSPSrc * src, GstRTSPConnInfo * conninfo, GstRTSPMessage * request, GstRTSPMessage * response, - GstRTSPStatusCode * code) + GstRTSPStatusCode * code, GstRTSPVersion * versions) { GstRTSPStatusCode int_code = GST_RTSP_STS_OK; GstRTSPResult res = GST_RTSP_ERROR; gint count; gboolean retry; GstRTSPMethod method = GST_RTSP_INVALID; + gint version_retry = 0; count = 0; do { @@ -5561,8 +6454,12 @@ gst_rtspsrc_send (GstRTSPSrc * src, GstRTSPConnection * conn, /* save method so we can disable it when the server complains */ method = request->type_data.request.method; + if (!versions) + request->type_data.request.version = src->version; + if ((res = - gst_rtspsrc_try_send (src, conn, request, response, &int_code)) < 0) + gst_rtspsrc_try_send (src, conninfo, request, response, + &int_code)) < 0) goto error; switch (int_code) { @@ -5574,6 +6471,20 @@ gst_rtspsrc_send (GstRTSPSrc * src, GstRTSPConnection * conn, retry = TRUE; } break; + case GST_RTSP_STS_RTSP_VERSION_NOT_SUPPORTED: + GST_INFO_OBJECT (src, "Version %s not supported by the server", + versions ? gst_rtsp_version_as_text (versions[version_retry]) : + "unknown"); + if (versions && versions[version_retry] != GST_RTSP_VERSION_INVALID) { + GST_INFO_OBJECT (src, "Unsupported version %s => trying %s", + gst_rtsp_version_as_text (request->type_data.request.version), + gst_rtsp_version_as_text (versions[version_retry])); + request->type_data.request.version = versions[version_retry]; + retry = TRUE; + version_retry++; + break; + } + /* falltrough */ default: break; } @@ -5600,12 +6511,12 @@ error_response: switch (response->type_data.response.code) { case GST_RTSP_STS_NOT_FOUND: - GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), ("%s", - response->type_data.response.reason)); + RTSP_SRC_RESPONSE_ERROR (src, response, RESOURCE, NOT_FOUND, + "Not found"); break; case GST_RTSP_STS_UNAUTHORIZED: - GST_ELEMENT_ERROR (src, RESOURCE, NOT_AUTHORIZED, (NULL), ("%s", - response->type_data.response.reason)); + RTSP_SRC_RESPONSE_ERROR (src, response, RESOURCE, NOT_AUTHORIZED, + "Unauthorized"); break; case GST_RTSP_STS_MOVED_PERMANENTLY: case GST_RTSP_STS_MOVE_TEMPORARILY: @@ -5637,7 +6548,6 @@ error_response: src->conninfo.url->transports = transports; src->need_redirect = TRUE; - src->state = GST_RTSP_STATE_INIT; res = GST_RTSP_OK; break; } @@ -5650,9 +6560,8 @@ error_response: res = GST_RTSP_OK; break; default: - GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), - ("Got error response: %d (%s).", response->type_data.response.code, - response->type_data.response.reason)); + RTSP_SRC_RESPONSE_ERROR (src, response, RESOURCE, READ, + "Unhandled error"); break; } /* if we return ERROR we should unset the response ourselves */ @@ -5667,8 +6576,7 @@ static GstRTSPResult gst_rtspsrc_send_cb (GstRTSPExtension * ext, GstRTSPMessage * request, GstRTSPMessage * response, GstRTSPSrc * src) { - return gst_rtspsrc_send (src, src->conninfo.connection, request, response, - NULL); + return gst_rtspsrc_send (src, &src->conninfo, request, response, NULL, NULL); } @@ -5691,11 +6599,19 @@ gst_rtspsrc_parse_methods (GstRTSPSrc * src, GstRTSPMessage * response) while (TRUE) { respoptions = NULL; gst_rtsp_message_get_header (response, field, &respoptions, indx); - if (indx == 0 && !respoptions) { - /* if no Allow header was found then try the Public header... */ - field = GST_RTSP_HDR_PUBLIC; - gst_rtsp_message_get_header (response, field, &respoptions, indx); - } + if (!respoptions) + break; + + src->methods |= gst_rtsp_options_from_text (respoptions); + + indx++; + } + + indx = 0; + field = GST_RTSP_HDR_PUBLIC; + while (TRUE) { + respoptions = NULL; + gst_rtsp_message_get_header (response, field, &respoptions, indx); if (!respoptions) break; @@ -5715,7 +6631,7 @@ gst_rtspsrc_parse_methods (GstRTSPSrc * src, GstRTSPMessage * response) * this */ src->methods |= GST_RTSP_PLAY; /* also assume it will support Range */ - src->seekable = TRUE; + src->seekable = G_MAXFLOAT; /* we need describe and setup */ if (!(src->methods & GST_RTSP_DESCRIBE)) @@ -5894,10 +6810,14 @@ gst_rtspsrc_prepare_transports (GstRTSPStream * stream, gchar ** transports, g_string_append_printf (str, "%d", src->free_channel); else if (next[3] == '2') g_string_append_printf (str, "%d", src->free_channel + 1); + } p = next + 4; } + if (src->version >= GST_RTSP_VERSION_2_0) + src->free_channel += 2; + /* append final part */ g_string_append (str, p); @@ -5946,8 +6866,10 @@ default_srtcp_params (void) buf = gst_buffer_new_wrapped (key_data, KEY_SIZE); - caps = gst_caps_new_simple ("application/x-srtp", + caps = gst_caps_new_simple ("application/x-srtcp", "srtp-key", GST_TYPE_BUFFER, buf, + "srtp-cipher", G_TYPE_STRING, "aes-128-icm", + "srtp-auth", G_TYPE_STRING, "hmac-sha1-80", "srtcp-cipher", G_TYPE_STRING, "aes-128-icm", "srtcp-auth", G_TYPE_STRING, "hmac-sha1-80", NULL); @@ -5983,6 +6905,172 @@ gst_rtspsrc_stream_make_keymgmt (GstRTSPSrc * src, GstRTSPStream * stream) return result; } +static GstRTSPResult +gst_rtsp_src_setup_stream_from_response (GstRTSPSrc * src, + GstRTSPStream * stream, GstRTSPMessage * response, + GstRTSPLowerTrans * protocols, gint retry, gint * rtpport, gint * rtcpport) +{ + gchar *resptrans = NULL; + GstRTSPTransport transport = { 0 }; + + gst_rtsp_message_get_header (response, GST_RTSP_HDR_TRANSPORT, &resptrans, 0); + if (!resptrans) { + gst_rtspsrc_stream_free_udp (stream); + goto no_transport; + } + + /* parse transport, go to next stream on parse error */ + if (gst_rtsp_transport_parse (resptrans, &transport) != GST_RTSP_OK) { + GST_WARNING_OBJECT (src, "failed to parse transport %s", resptrans); + return GST_RTSP_ELAST; + } + + /* update allowed transports for other streams. once the transport of + * one stream has been determined, we make sure that all other streams + * are configured in the same way */ + switch (transport.lower_transport) { + case GST_RTSP_LOWER_TRANS_TCP: + GST_DEBUG_OBJECT (src, "stream %p as TCP interleaved", stream); + if (protocols) + *protocols = GST_RTSP_LOWER_TRANS_TCP; + src->interleaved = TRUE; + if (src->version < GST_RTSP_VERSION_2_0) { + /* update free channels */ + src->free_channel = MAX (transport.interleaved.min, src->free_channel); + src->free_channel = MAX (transport.interleaved.max, src->free_channel); + src->free_channel++; + } + break; + case GST_RTSP_LOWER_TRANS_UDP_MCAST: + /* only allow multicast for other streams */ + GST_DEBUG_OBJECT (src, "stream %p as UDP multicast", stream); + if (protocols) + *protocols = GST_RTSP_LOWER_TRANS_UDP_MCAST; + /* if the server selected our ports, increment our counters so that + * we select a new port later */ + if (src->next_port_num == transport.port.min && + src->next_port_num + 1 == transport.port.max) { + src->next_port_num += 2; + } + break; + case GST_RTSP_LOWER_TRANS_UDP: + /* only allow unicast for other streams */ + GST_DEBUG_OBJECT (src, "stream %p as UDP unicast", stream); + if (protocols) + *protocols = GST_RTSP_LOWER_TRANS_UDP; + break; + default: + GST_DEBUG_OBJECT (src, "stream %p unknown transport %d", stream, + transport.lower_transport); + break; + } + + if (!src->interleaved || !retry) { + /* now configure the stream with the selected transport */ + if (!gst_rtspsrc_stream_configure_transport (stream, &transport)) { + GST_DEBUG_OBJECT (src, + "could not configure stream %p transport, skipping stream", stream); + goto done; + } else if (stream->udpsrc[0] && stream->udpsrc[1] && rtpport && rtcpport) { + /* retain the first allocated UDP port pair */ + g_object_get (G_OBJECT (stream->udpsrc[0]), "port", rtpport, NULL); + g_object_get (G_OBJECT (stream->udpsrc[1]), "port", rtcpport, NULL); + } + } + /* we need to activate at least one stream when we detect activity */ + src->need_activate = TRUE; + + /* stream is setup now */ + stream->setup = TRUE; + stream->waiting_setup_response = FALSE; + + if (src->version >= GST_RTSP_VERSION_2_0) { + gchar *prop, *media_properties; + gchar **props; + gint i; + + if (gst_rtsp_message_get_header (response, GST_RTSP_HDR_MEDIA_PROPERTIES, + &media_properties, 0) != GST_RTSP_OK) { + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Error: No MEDIA_PROPERTY header in a SETUP request in RTSP 2.0" + " - this header is mandatory.")); + + gst_rtsp_message_unset (response); + return GST_RTSP_ERROR; + } + + props = g_strsplit (media_properties, ",", -2); + for (i = 0; props[i]; i++) { + prop = props[i]; + + while (*prop == ' ') + prop++; + + if (strstr (prop, "Random-Access")) { + gchar **random_seekable_val = g_strsplit (prop, "=", 2); + + if (!random_seekable_val[1]) + src->seekable = G_MAXFLOAT; + else + src->seekable = g_ascii_strtod (random_seekable_val[1], NULL); + + g_strfreev (random_seekable_val); + } else if (!g_strcmp0 (prop, "No-Seeking")) { + src->seekable = -1.0; + } else if (!g_strcmp0 (prop, "Beginning-Only")) { + src->seekable = 0.0; + } + } + + g_strfreev (props); + } + +done: + /* clean up our transport struct */ + gst_rtsp_transport_init (&transport); + /* clean up used RTSP messages */ + gst_rtsp_message_unset (response); + + return GST_RTSP_OK; + +no_transport: + { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), + ("Server did not select transport.")); + + gst_rtsp_message_unset (response); + return GST_RTSP_ERROR; + } +} + +static GstRTSPResult +gst_rtspsrc_setup_streams_end (GstRTSPSrc * src, gboolean async) +{ + GList *tmp; + GstRTSPConnInfo *conninfo; + + g_assert (src->version >= GST_RTSP_VERSION_2_0); + + conninfo = &src->conninfo; + for (tmp = src->streams; tmp; tmp = tmp->next) { + GstRTSPStream *stream = (GstRTSPStream *) tmp->data; + GstRTSPMessage response = { 0, }; + + if (!stream->waiting_setup_response) + continue; + + if (!src->conninfo.connection) + conninfo = &((GstRTSPStream *) tmp->data)->conninfo; + + gst_rtsp_src_receive_response (src, conninfo, &response, NULL); + + gst_rtsp_src_setup_stream_from_response (src, stream, + &response, NULL, 0, NULL, NULL); + } + + return GST_RTSP_OK; +} + /* Perform the SETUP request for all the streams. * * We ask the server for a specific transport, which initially includes all the @@ -5992,11 +7080,14 @@ gst_rtspsrc_stream_make_keymgmt (GstRTSPSrc * src, GstRTSPStream * stream) * Once the server replied with a transport, we configure the other streams * with the same transport. * - * This function will also configure the stream for the selected transport, - * which basically means creating the pipeline. + * In case setup request are not pipelined, this function will also configure the + * stream for the selected transport, * which basically means creating the pipeline. + * Otherwise, the first stream is setup right away from the reply and a + * CMD_FINALIZE_SETUP command is set for the stream pipelines to happen on the + * remaining streams from the RTSP thread. */ static GstRTSPResult -gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) +gst_rtspsrc_setup_streams_start (GstRTSPSrc * src, gboolean async) { GList *walk; GstRTSPResult res = GST_RTSP_ERROR; @@ -6009,6 +7100,7 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) gint rtpport, rtcpport; GstRTSPUrl *url; gchar *hval; + gchar *pipelined_request_id = NULL; if (src->conninfo.connection) { url = gst_rtsp_connection_get_url (src->conninfo.connection); @@ -6035,7 +7127,7 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) goto no_streams; for (walk = src->streams; walk; walk = g_list_next (walk)) { - GstRTSPConnection *conn; + GstRTSPConnInfo *conninfo; gchar *transports; gint retry = 0; guint mask = 0; @@ -6046,7 +7138,7 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) caps = stream_get_caps_for_pt (stream, stream->default_pt); if (caps == NULL) { - GST_DEBUG_OBJECT (src, "skipping stream %p, no caps", stream); + GST_WARNING_OBJECT (src, "skipping stream %p, no caps", stream); continue; } @@ -6091,18 +7183,19 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) /* skip setup if we have no URL for it */ if (stream->conninfo.location == NULL) { - GST_DEBUG_OBJECT (src, "skipping stream %p, no setup", stream); + GST_WARNING_OBJECT (src, "skipping stream %p, no setup", stream); continue; } if (src->conninfo.connection == NULL) { if (!gst_rtsp_conninfo_connect (src, &stream->conninfo, async)) { - GST_DEBUG_OBJECT (src, "skipping stream %p, failed to connect", stream); + GST_WARNING_OBJECT (src, "skipping stream %p, failed to connect", + stream); continue; } - conn = stream->conninfo.connection; + conninfo = &stream->conninfo; } else { - conn = src->conninfo.connection; + conninfo = &src->conninfo; } GST_DEBUG_OBJECT (src, "doing setup of stream %p with %s", stream, stream->conninfo.location); @@ -6147,7 +7240,6 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) } GST_DEBUG_OBJECT (src, "transport is now %s", GST_STR_NULL (transports)); - /* create SETUP request */ res = gst_rtspsrc_init_request (src, &request, GST_RTSP_SETUP, @@ -6157,9 +7249,24 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) goto create_request_failed; } + if (src->version >= GST_RTSP_VERSION_2_0) { + if (!pipelined_request_id) + pipelined_request_id = g_strdup_printf ("%d", + g_random_int_range (0, G_MAXINT32)); + + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_PIPELINED_REQUESTS, + pipelined_request_id); + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_ACCEPT_RANGES, + "npt, clock, smpte, clock"); + } + /* select transport */ gst_rtsp_message_take_header (&request, GST_RTSP_HDR_TRANSPORT, transports); + if (stream->is_backchannel && src->backchannel == BACKCHANNEL_ONVIF) + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, + BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL); + /* set up keys */ if (stream->profile == GST_RTSP_PROFILE_SAVP || stream->profile == GST_RTSP_PROFILE_SAVPF) { @@ -6179,7 +7286,9 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) stream->id)); /* handle the code ourselves */ - res = gst_rtspsrc_send (src, conn, &request, &response, &code); + res = + gst_rtspsrc_send (src, conninfo, &request, + pipelined_request_id ? NULL : &response, &code, NULL); if (res < 0) goto send_error; @@ -6219,106 +7328,50 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) goto response_error; } - /* parse response transport */ - { - gchar *resptrans = NULL; - GstRTSPTransport transport = { 0 }; - - gst_rtsp_message_get_header (&response, GST_RTSP_HDR_TRANSPORT, - &resptrans, 0); - if (!resptrans) { - gst_rtspsrc_stream_free_udp (stream); - goto no_transport; - } - /* parse transport, go to next stream on parse error */ - if (gst_rtsp_transport_parse (resptrans, &transport) != GST_RTSP_OK) { - GST_WARNING_OBJECT (src, "failed to parse transport %s", resptrans); - goto next; - } - - /* update allowed transports for other streams. once the transport of - * one stream has been determined, we make sure that all other streams - * are configured in the same way */ - switch (transport.lower_transport) { - case GST_RTSP_LOWER_TRANS_TCP: - GST_DEBUG_OBJECT (src, "stream %p as TCP interleaved", stream); - protocols = GST_RTSP_LOWER_TRANS_TCP; - src->interleaved = TRUE; - /* update free channels */ - src->free_channel = - MAX (transport.interleaved.min, src->free_channel); - src->free_channel = - MAX (transport.interleaved.max, src->free_channel); - src->free_channel++; - break; - case GST_RTSP_LOWER_TRANS_UDP_MCAST: - /* only allow multicast for other streams */ - GST_DEBUG_OBJECT (src, "stream %p as UDP multicast", stream); - protocols = GST_RTSP_LOWER_TRANS_UDP_MCAST; - /* if the server selected our ports, increment our counters so that - * we select a new port later */ - if (src->next_port_num == transport.port.min && - src->next_port_num + 1 == transport.port.max) { - src->next_port_num += 2; - } - break; - case GST_RTSP_LOWER_TRANS_UDP: - /* only allow unicast for other streams */ - GST_DEBUG_OBJECT (src, "stream %p as UDP unicast", stream); - protocols = GST_RTSP_LOWER_TRANS_UDP; - break; + if (!pipelined_request_id) { + /* parse response transport */ + res = gst_rtsp_src_setup_stream_from_response (src, stream, + &response, &protocols, retry, &rtpport, &rtcpport); + switch (res) { + case GST_RTSP_ERROR: + goto cleanup_error; + case GST_RTSP_ELAST: + goto retry; default: - GST_DEBUG_OBJECT (src, "stream %p unknown transport %d", stream, - transport.lower_transport); break; } - - if (!src->interleaved || !retry) { - /* now configure the stream with the selected transport */ - if (!gst_rtspsrc_stream_configure_transport (stream, &transport)) { - GST_DEBUG_OBJECT (src, - "could not configure stream %p transport, skipping stream", - stream); - goto next; - } else if (stream->udpsrc[0] && stream->udpsrc[1]) { - /* retain the first allocated UDP port pair */ - g_object_get (G_OBJECT (stream->udpsrc[0]), "port", &rtpport, NULL); - g_object_get (G_OBJECT (stream->udpsrc[1]), "port", &rtcpport, NULL); - } - } - /* we need to activate at least one streams when we detect activity */ + } else { + stream->waiting_setup_response = TRUE; + /* we need to activate at least one stream when we detect activity */ src->need_activate = TRUE; + } - /* stream is setup now */ - stream->setup = TRUE; - { - GList *skip = walk; + { + GList *skip = walk; - while (TRUE) { - GstRTSPStream *sskip; + while (TRUE) { + GstRTSPStream *sskip; - skip = g_list_next (skip); - if (skip == NULL) - break; + skip = g_list_next (skip); + if (skip == NULL) + break; - sskip = (GstRTSPStream *) skip->data; + sskip = (GstRTSPStream *) skip->data; - /* skip all streams with the same control url */ - if (g_str_equal (stream->conninfo.location, sskip->conninfo.location)) { - GST_DEBUG_OBJECT (src, "found stream %p with same control %s", - sskip, sskip->conninfo.location); - sskip->skipped = TRUE; - } + /* skip all streams with the same control url */ + if (g_str_equal (stream->conninfo.location, sskip->conninfo.location)) { + GST_DEBUG_OBJECT (src, "found stream %p with same control %s", + sskip, sskip->conninfo.location); + sskip->skipped = TRUE; } } - next: - /* clean up our transport struct */ - gst_rtsp_transport_init (&transport); - /* clean up used RTSP messages */ - gst_rtsp_message_unset (&request); - gst_rtsp_message_unset (&response); } + gst_rtsp_message_unset (&request); + } + + if (pipelined_request_id) { + gst_rtspsrc_setup_streams_end (src, TRUE); } /* store the transport protocol that was configured */ @@ -6326,6 +7379,9 @@ gst_rtspsrc_setup_streams (GstRTSPSrc * src, gboolean async) gst_rtsp_ext_list_stream_select (src->extensions, url); + if (pipelined_request_id) + g_free (pipelined_request_id); + /* if there is nothing to activate, error out */ if (!src->need_activate) goto nothing_to_activate; @@ -6384,13 +7440,6 @@ send_error: g_free (str); goto cleanup_error; } -no_transport: - { - GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), - ("Server did not select transport.")); - res = GST_RTSP_ERROR; - goto cleanup_error; - } nothing_to_activate: { /* none of the available error codes is really right .. */ @@ -6409,6 +7458,8 @@ nothing_to_activate: } cleanup_error: { + if (pipelined_request_id) + g_free (pipelined_request_id); gst_rtsp_message_unset (&request); gst_rtsp_message_unset (&response); return res; @@ -6561,8 +7612,7 @@ gst_rtspsrc_open_from_sdp (GstRTSPSrc * src, GstSDPMessage * sdp, else src->props = gst_structure_new_empty ("RTSPProperties"); - if (src->debug) - gst_sdp_message_dump (sdp); + DEBUG_SDP (src, sdp); gst_rtsp_ext_list_parse_sdp (src->extensions, sdp, src->props); @@ -6634,13 +7684,13 @@ gst_rtspsrc_open_from_sdp (GstRTSPSrc * src, GstSDPMessage * sdp, /* create streams */ n_streams = gst_sdp_message_medias_len (sdp); for (i = 0; i < n_streams; i++) { - gst_rtspsrc_create_stream (src, sdp, i); + gst_rtspsrc_create_stream (src, sdp, i, n_streams); } src->state = GST_RTSP_STATE_INIT; /* setup streams */ - if ((res = gst_rtspsrc_setup_streams (src, async)) < 0) + if ((res = gst_rtspsrc_setup_streams_start (src, async)) < 0) goto setup_failed; /* reset our state */ @@ -6670,6 +7720,13 @@ gst_rtspsrc_retrieve_sdp (GstRTSPSrc * src, GstSDPMessage ** sdp, guint8 *data; guint size; gchar *respcont = NULL; + GstRTSPVersion versions[] = + { GST_RTSP_VERSION_2_0, GST_RTSP_VERSION_INVALID }; + + src->version = src->default_version; + if (src->default_version == GST_RTSP_VERSION_2_0) { + versions[0] = GST_RTSP_VERSION_1_0; + } restart: src->need_redirect = FALSE; @@ -6685,7 +7742,7 @@ restart: goto connect_failed; /* create OPTIONS */ - GST_DEBUG_OBJECT (src, "create options..."); + GST_DEBUG_OBJECT (src, "create options... (%s)", async ? "async" : "sync"); res = gst_rtspsrc_init_request (src, &request, GST_RTSP_OPTIONS, src->conninfo.url_str); @@ -6693,15 +7750,21 @@ restart: goto create_request_failed; /* send OPTIONS */ + request.type_data.request.version = src->version; GST_DEBUG_OBJECT (src, "send options..."); if (async) GST_ELEMENT_PROGRESS (src, CONTINUE, "open", ("Retrieving server options")); if ((res = - gst_rtspsrc_send (src, src->conninfo.connection, &request, &response, - NULL)) < 0) + gst_rtspsrc_send (src, &src->conninfo, &request, &response, + NULL, versions)) < 0) { goto send_error; + } + + src->version = request.type_data.request.version; + GST_INFO_OBJECT (src, "Now using version: %s", + gst_rtsp_version_as_text (src->version)); /* parse OPTIONS */ if (!gst_rtspsrc_parse_methods (src, &response)) @@ -6719,6 +7782,11 @@ restart: gst_rtsp_message_add_header (&request, GST_RTSP_HDR_ACCEPT, "application/sdp"); + if (src->backchannel == BACKCHANNEL_ONVIF) + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, + BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL); + /* TODO: Handle the case when backchannel is unsupported and goto restart */ + /* send DESCRIBE */ GST_DEBUG_OBJECT (src, "send describe..."); @@ -6726,11 +7794,11 @@ restart: GST_ELEMENT_PROGRESS (src, CONTINUE, "open", ("Retrieving media info")); if ((res = - gst_rtspsrc_send (src, src->conninfo.connection, &request, &response, - NULL)) < 0) + gst_rtspsrc_send (src, &src->conninfo, &request, &response, + NULL, NULL)) < 0) goto send_error; - /* we only perform redirect for the describe, currently */ + /* we only perform redirect for describe and play, currently */ if (src->need_redirect) { /* close connection, we don't have to send a TEARDOWN yet, ignore the * result. */ @@ -6753,8 +7821,24 @@ restart: /* could not be set but since the request returned OK, we assume it * was SDP, else check it. */ if (respcont) { - if (g_ascii_strcasecmp (respcont, "application/sdp") != 0) + const gchar *props = strchr (respcont, ';'); + + if (props) { + gchar *mimetype = g_strndup (respcont, props - respcont); + + mimetype = g_strstrip (mimetype); + if (g_ascii_strcasecmp (mimetype, "application/sdp") != 0) { + g_free (mimetype); + goto wrong_content_type; + } + + /* TODO: Check for charset property and do conversions of all messages if + * needed. Some servers actually send that property */ + + g_free (mimetype); + } else if (g_ascii_strcasecmp (respcont, "application/sdp") != 0) { goto wrong_content_type; + } } /* get message body and parse as SDP */ @@ -6927,15 +8011,19 @@ gst_rtspsrc_close (GstRTSPSrc * src, gboolean async, gboolean only_close) /* do TEARDOWN */ res = gst_rtspsrc_init_request (src, &request, GST_RTSP_TEARDOWN, setup_url); + GST_LOG_OBJECT (src, "Teardown on %s", setup_url); if (res < 0) goto create_request_failed; + if (stream->is_backchannel && src->backchannel == BACKCHANNEL_ONVIF) + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, + BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL); + if (async) GST_ELEMENT_PROGRESS (src, CONTINUE, "close", ("Closing stream")); if ((res = - gst_rtspsrc_send (src, info->connection, &request, &response, - NULL)) < 0) + gst_rtspsrc_send (src, info, &request, &response, NULL, NULL)) < 0) goto send_error; /* FIXME, parse result? */ @@ -7157,7 +8245,10 @@ clear_rtp_base (GstRTSPSrc * src, GstRTSPStream * stream) item->caps = gst_caps_make_writable (item->caps); s = gst_caps_get_structure (item->caps, 0); gst_structure_remove_fields (s, "clock-base", "seqnum-base", NULL); + if (item->pt == stream->default_pt && stream->udpsrc[0]) + g_object_set (stream->udpsrc[0], "caps", item->caps, NULL); } + stream->need_caps = TRUE; } static GstRTSPResult @@ -7185,7 +8276,8 @@ done: } static GstRTSPResult -gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) +gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async, + const gchar * seek_style) { GstRTSPMessage request = { 0 }; GstRTSPMessage response = { 0 }; @@ -7197,6 +8289,7 @@ gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) GST_DEBUG_OBJECT (src, "PLAY..."); +restart: if ((res = gst_rtspsrc_ensure_open (src, async)) < 0) goto open_failed; @@ -7223,7 +8316,7 @@ gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; const gchar *setup_url; - GstRTSPConnection *conn; + GstRTSPConnInfo *conninfo; /* try aggregate control first but do non-aggregate control otherwise */ if (control) @@ -7232,9 +8325,9 @@ gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) continue; if (src->conninfo.connection) { - conn = src->conninfo.connection; + conninfo = &src->conninfo; } else if (stream->conninfo.connection) { - conn = stream->conninfo.connection; + conninfo = &stream->conninfo; } else { continue; } @@ -7244,7 +8337,7 @@ gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) if (res < 0) goto create_request_failed; - if (src->need_range) { + if (src->need_range && src->seekable >= 0.0) { hval = gen_range_header (src, segment); gst_rtsp_message_take_header (&request, GST_RTSP_HDR_RANGE, hval); @@ -7263,18 +8356,48 @@ gst_rtspsrc_play (GstRTSPSrc * src, GstSegment * segment, gboolean async) gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SPEED, hval); } + if (seek_style) + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SEEK_STYLE, + seek_style); + + /* when we have an ONVIF audio backchannel, the PLAY request must have the + * Require: header when doing either aggregate or non-aggregate control */ + if (src->backchannel == BACKCHANNEL_ONVIF && + (control || stream->is_backchannel)) + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, + BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL); + if (async) GST_ELEMENT_PROGRESS (src, CONTINUE, "request", ("Sending PLAY request")); - if ((res = gst_rtspsrc_send (src, conn, &request, &response, NULL)) < 0) + if ((res = + gst_rtspsrc_send (src, conninfo, &request, &response, NULL, NULL)) + < 0) goto send_error; + if (src->need_redirect) { + GST_DEBUG_OBJECT (src, + "redirect: tearing down and restarting with new url"); + /* teardown and restart with new url */ + gst_rtspsrc_close (src, TRUE, FALSE); + /* reset protocols to force re-negotiation with redirected url */ + src->cur_protocols = src->protocols; + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + goto restart; + } + /* seek may have silently failed as it is not supported */ if (!(src->methods & GST_RTSP_PLAY)) { GST_DEBUG_OBJECT (src, "PLAY Range not supported; re-enable PLAY"); + + if (src->version >= GST_RTSP_VERSION_2_0 && src->seekable >= 0.0) { + GST_WARNING_OBJECT (src, "Server declared stream as seekable but" + " playing with range failed... Ignoring information."); + } /* obviously it is supported as we made it here */ src->methods |= GST_RTSP_PLAY; - src->seekable = FALSE; + src->seekable = -1.0; /* but there is nothing to parse in the response, * so convey we have no idea and not to expect anything particular */ clear_rtp_base (src, stream); @@ -7364,17 +8487,17 @@ done: /* ERRORS */ open_failed: { - GST_DEBUG_OBJECT (src, "failed to open stream"); + GST_WARNING_OBJECT (src, "failed to open stream"); goto done; } not_supported: { - GST_DEBUG_OBJECT (src, "PLAY is not supported"); + GST_WARNING_OBJECT (src, "PLAY is not supported"); goto done; } was_playing: { - GST_DEBUG_OBJECT (src, "we were already PLAYING"); + GST_WARNING_OBJECT (src, "we were already PLAYING"); goto done; } create_request_failed: @@ -7432,7 +8555,7 @@ gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async) * aggregate control */ for (walk = src->streams; walk; walk = g_list_next (walk)) { GstRTSPStream *stream = (GstRTSPStream *) walk->data; - GstRTSPConnection *conn; + GstRTSPConnInfo *conninfo; const gchar *setup_url; /* try aggregate control first but do non-aggregate control otherwise */ @@ -7442,9 +8565,9 @@ gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async) continue; if (src->conninfo.connection) { - conn = src->conninfo.connection; + conninfo = &src->conninfo; } else if (stream->conninfo.connection) { - conn = stream->conninfo.connection; + conninfo = &stream->conninfo; } else { continue; } @@ -7458,7 +8581,16 @@ gst_rtspsrc_pause (GstRTSPSrc * src, gboolean async) setup_url)) < 0) goto create_request_failed; - if ((res = gst_rtspsrc_send (src, conn, &request, &response, NULL)) < 0) + /* when we have an ONVIF audio backchannel, the PAUSE request must have the + * Require: header when doing either aggregate or non-aggregate control */ + if (src->backchannel == BACKCHANNEL_ONVIF && + (control || stream->is_backchannel)) + gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, + BACKCHANNEL_ONVIF_HDR_REQUIRE_VAL); + + if ((res = + gst_rtspsrc_send (src, conninfo, &request, &response, NULL, + NULL)) < 0) goto send_error; gst_rtsp_message_unset (&request); @@ -7607,13 +8739,22 @@ static void gst_rtspsrc_thread (GstRTSPSrc * src) { gint cmd; + ParameterRequest *req = NULL; GST_OBJECT_LOCK (src); cmd = src->pending_cmd; if (cmd == CMD_RECONNECT || cmd == CMD_PLAY || cmd == CMD_PAUSE - || cmd == CMD_LOOP || cmd == CMD_OPEN) - src->pending_cmd = CMD_LOOP; - else + || cmd == CMD_LOOP || cmd == CMD_OPEN || cmd == CMD_GET_PARAMETER + || cmd == CMD_SET_PARAMETER) { + if (g_queue_is_empty (&src->set_get_param_q)) { + src->pending_cmd = CMD_LOOP; + } else { + ParameterRequest *next_req; + req = g_queue_pop_head (&src->set_get_param_q); + next_req = g_queue_peek_head (&src->set_get_param_q); + src->pending_cmd = next_req ? next_req->cmd : CMD_LOOP; + } + } else src->pending_cmd = CMD_WAIT; GST_DEBUG_OBJECT (src, "got command %s", cmd_to_string (cmd)); @@ -7628,7 +8769,7 @@ gst_rtspsrc_thread (GstRTSPSrc * src) gst_rtspsrc_open (src, TRUE); break; case CMD_PLAY: - gst_rtspsrc_play (src, &src->segment, TRUE); + gst_rtspsrc_play (src, &src->segment, TRUE, NULL); break; case CMD_PAUSE: gst_rtspsrc_pause (src, TRUE); @@ -7636,6 +8777,12 @@ gst_rtspsrc_thread (GstRTSPSrc * src) case CMD_CLOSE: gst_rtspsrc_close (src, TRUE, FALSE); break; + case CMD_GET_PARAMETER: + gst_rtspsrc_get_parameter (src, req); + break; + case CMD_SET_PARAMETER: + gst_rtspsrc_set_parameter (src, req); + break; case CMD_LOOP: gst_rtspsrc_loop (src); break; @@ -7647,6 +8794,8 @@ gst_rtspsrc_thread (GstRTSPSrc * src) } GST_OBJECT_LOCK (src); + /* No more cmds, wake any waiters */ + g_cond_broadcast (&src->cmd_cond); /* and go back to sleep */ if (src->pending_cmd == CMD_WAIT) { if (src->task) @@ -7782,7 +8931,8 @@ gst_rtspsrc_change_state (GstElement * element, GstStateChange transition) ret = GST_STATE_CHANGE_NO_PREROLL; break; case GST_STATE_CHANGE_PAUSED_TO_READY: - gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_CLOSE, CMD_PAUSE); + gst_rtspsrc_loop_send_cmd_and_wait (rtspsrc, CMD_CLOSE, CMD_ALL, + rtspsrc->teardown_timeout); ret = GST_STATE_CHANGE_SUCCESS; break; case GST_STATE_CHANGE_READY_TO_NULL: @@ -7790,6 +8940,16 @@ gst_rtspsrc_change_state (GstElement * element, GstStateChange transition) ret = GST_STATE_CHANGE_SUCCESS; break; default: + /* Otherwise it's success, we don't want to return spurious + * NO_PREROLL or ASYNC from internal elements as we care for + * state changes ourselves here + * + * This is to catch PAUSED->PAUSED and PLAYING->PLAYING transitions. + */ + if (GST_STATE_TRANSITION_NEXT (transition) == GST_STATE_PAUSED) + ret = GST_STATE_CHANGE_NO_PREROLL; + else + ret = GST_STATE_CHANGE_SUCCESS; break; } @@ -7947,3 +9107,526 @@ gst_rtspsrc_uri_handler_init (gpointer g_iface, gpointer iface_data) iface->get_uri = gst_rtspsrc_uri_get_uri; iface->set_uri = gst_rtspsrc_uri_set_uri; } + + +/* send GET_PARAMETER */ +static GstRTSPResult +gst_rtspsrc_get_parameter (GstRTSPSrc * src, ParameterRequest * req) +{ + GstRTSPMessage request = { 0 }; + GstRTSPMessage response = { 0 }; + GstRTSPResult res; + GstRTSPStatusCode code = GST_RTSP_STS_OK; + const gchar *control; + gchar *recv_body = NULL; + guint recv_body_len; + + GST_DEBUG_OBJECT (src, "creating server get_parameter"); + + if ((res = gst_rtspsrc_ensure_open (src, FALSE)) < 0) + goto open_failed; + + control = get_aggregate_control (src); + if (control == NULL) + goto no_control; + + if (!(src->methods & GST_RTSP_GET_PARAMETER)) + goto not_supported; + + gst_rtspsrc_connection_flush (src, FALSE); + + res = gst_rtsp_message_init_request (&request, GST_RTSP_GET_PARAMETER, + control); + if (res < 0) + goto create_request_failed; + + res = gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CONTENT_TYPE, + req->content_type == NULL ? "text/parameters" : req->content_type); + if (res < 0) + goto add_content_hdr_failed; + + if (req->body && req->body->len) { + res = + gst_rtsp_message_set_body (&request, (guint8 *) req->body->str, + req->body->len); + if (res < 0) + goto set_body_failed; + } + + if ((res = gst_rtspsrc_send (src, &src->conninfo, + &request, &response, &code, NULL)) < 0) + goto send_error; + + res = gst_rtsp_message_get_body (&response, (guint8 **) & recv_body, + &recv_body_len); + if (res < 0) + goto get_body_failed; + +done: + { + gst_promise_reply (req->promise, + gst_structure_new ("get-parameter-reply", + "rtsp-result", G_TYPE_INT, res, + "rtsp-code", G_TYPE_INT, code, + "rtsp-reason", G_TYPE_STRING, gst_rtsp_status_as_text (code), + "body", G_TYPE_STRING, GST_STR_NULL (recv_body), NULL)); + free_param_data (req); + + + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + + return res; + } + + /* ERRORS */ +open_failed: + { + GST_DEBUG_OBJECT (src, "failed to open stream"); + goto done; + } +no_control: + { + GST_DEBUG_OBJECT (src, "no control url to send GET_PARAMETER"); + res = GST_RTSP_ERROR; + goto done; + } +not_supported: + { + GST_DEBUG_OBJECT (src, "GET_PARAMETER is not supported"); + res = GST_RTSP_ERROR; + goto done; + } +create_request_failed: + { + GST_DEBUG_OBJECT (src, "could not create GET_PARAMETER request"); + goto done; + } +add_content_hdr_failed: + { + GST_DEBUG_OBJECT (src, "could not add content header"); + goto done; + } +set_body_failed: + { + GST_DEBUG_OBJECT (src, "could not set body"); + goto done; + } +send_error: + { + gchar *str = gst_rtsp_strresult (res); + + GST_ELEMENT_WARNING (src, RESOURCE, WRITE, (NULL), + ("Could not send get-parameter. (%s)", str)); + g_free (str); + goto done; + } +get_body_failed: + { + GST_DEBUG_OBJECT (src, "could not get body"); + goto done; + } +} + +/* send SET_PARAMETER */ +static GstRTSPResult +gst_rtspsrc_set_parameter (GstRTSPSrc * src, ParameterRequest * req) +{ + GstRTSPMessage request = { 0 }; + GstRTSPMessage response = { 0 }; + GstRTSPResult res = GST_RTSP_OK; + GstRTSPStatusCode code = GST_RTSP_STS_OK; + const gchar *control; + + GST_DEBUG_OBJECT (src, "creating server set_parameter"); + + if ((res = gst_rtspsrc_ensure_open (src, FALSE)) < 0) + goto open_failed; + + control = get_aggregate_control (src); + if (control == NULL) + goto no_control; + + if (!(src->methods & GST_RTSP_SET_PARAMETER)) + goto not_supported; + + gst_rtspsrc_connection_flush (src, FALSE); + + res = + gst_rtsp_message_init_request (&request, GST_RTSP_SET_PARAMETER, control); + if (res < 0) + goto send_error; + + res = gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CONTENT_TYPE, + req->content_type == NULL ? "text/parameters" : req->content_type); + if (res < 0) + goto add_content_hdr_failed; + + if (req->body && req->body->len) { + res = + gst_rtsp_message_set_body (&request, (guint8 *) req->body->str, + req->body->len); + + if (res < 0) + goto set_body_failed; + } + + if ((res = gst_rtspsrc_send (src, &src->conninfo, + &request, &response, &code, NULL)) < 0) + goto send_error; + +done: + { + gst_promise_reply (req->promise, gst_structure_new ("set-parameter-reply", + "rtsp-result", G_TYPE_INT, res, + "rtsp-code", G_TYPE_INT, code, + "rtsp-reason", G_TYPE_STRING, gst_rtsp_status_as_text (code), + NULL)); + free_param_data (req); + + gst_rtsp_message_unset (&request); + gst_rtsp_message_unset (&response); + + return res; + } + + /* ERRORS */ +open_failed: + { + GST_DEBUG_OBJECT (src, "failed to open stream"); + goto done; + } +no_control: + { + GST_DEBUG_OBJECT (src, "no control url to send SET_PARAMETER"); + res = GST_RTSP_ERROR; + goto done; + } +not_supported: + { + GST_DEBUG_OBJECT (src, "SET_PARAMETER is not supported"); + res = GST_RTSP_ERROR; + goto done; + } +add_content_hdr_failed: + { + GST_DEBUG_OBJECT (src, "could not add content header"); + goto done; + } +set_body_failed: + { + GST_DEBUG_OBJECT (src, "could not set body"); + goto done; + } +send_error: + { + gchar *str = gst_rtsp_strresult (res); + + GST_ELEMENT_WARNING (src, RESOURCE, WRITE, (NULL), + ("Could not send set-parameter. (%s)", str)); + g_free (str); + goto done; + } +} + +typedef struct _RTSPKeyValue +{ + GstRTSPHeaderField field; + gchar *value; + gchar *custom_key; /* custom header string (field is INVALID then) */ +} RTSPKeyValue; + +static void +key_value_foreach (GArray * array, GFunc func, gpointer user_data) +{ + guint i; + + g_return_if_fail (array != NULL); + + for (i = 0; i < array->len; i++) { + (*func) (&g_array_index (array, RTSPKeyValue, i), user_data); + } +} + +static void +dump_key_value (gpointer data, gpointer user_data G_GNUC_UNUSED) +{ + RTSPKeyValue *key_value = (RTSPKeyValue *) data; + GstRTSPSrc *src = GST_RTSPSRC (user_data); + const gchar *key_string; + + if (key_value->custom_key != NULL) + key_string = key_value->custom_key; + else + key_string = gst_rtsp_header_as_text (key_value->field); + + GST_LOG_OBJECT (src, " key: '%s', value: '%s'", key_string, + key_value->value); +} + +static void +gst_rtspsrc_print_rtsp_message (GstRTSPSrc * src, const GstRTSPMessage * msg) +{ + guint8 *data; + guint size; + GString *body_string = NULL; + + g_return_if_fail (src != NULL); + g_return_if_fail (msg != NULL); + + if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) < GST_LEVEL_LOG) + return; + + GST_LOG_OBJECT (src, "--------------------------------------------"); + switch (msg->type) { + case GST_RTSP_MESSAGE_REQUEST: + GST_LOG_OBJECT (src, "RTSP request message %p", msg); + GST_LOG_OBJECT (src, " request line:"); + GST_LOG_OBJECT (src, " method: '%s'", + gst_rtsp_method_as_text (msg->type_data.request.method)); + GST_LOG_OBJECT (src, " uri: '%s'", msg->type_data.request.uri); + GST_LOG_OBJECT (src, " version: '%s'", + gst_rtsp_version_as_text (msg->type_data.request.version)); + GST_LOG_OBJECT (src, " headers:"); + key_value_foreach (msg->hdr_fields, dump_key_value, src); + GST_LOG_OBJECT (src, " body:"); + gst_rtsp_message_get_body (msg, &data, &size); + if (size > 0) { + body_string = g_string_new_len ((const gchar *) data, size); + GST_LOG_OBJECT (src, " %s(%d)", body_string->str, size); + g_string_free (body_string, TRUE); + body_string = NULL; + } + break; + case GST_RTSP_MESSAGE_RESPONSE: + GST_LOG_OBJECT (src, "RTSP response message %p", msg); + GST_LOG_OBJECT (src, " status line:"); + GST_LOG_OBJECT (src, " code: '%d'", msg->type_data.response.code); + GST_LOG_OBJECT (src, " reason: '%s'", msg->type_data.response.reason); + GST_LOG_OBJECT (src, " version: '%s", + gst_rtsp_version_as_text (msg->type_data.response.version)); + GST_LOG_OBJECT (src, " headers:"); + key_value_foreach (msg->hdr_fields, dump_key_value, src); + gst_rtsp_message_get_body (msg, &data, &size); + GST_LOG_OBJECT (src, " body: length %d", size); + if (size > 0) { + body_string = g_string_new_len ((const gchar *) data, size); + GST_LOG_OBJECT (src, " %s(%d)", body_string->str, size); + g_string_free (body_string, TRUE); + body_string = NULL; + } + break; + case GST_RTSP_MESSAGE_HTTP_REQUEST: + GST_LOG_OBJECT (src, "HTTP request message %p", msg); + GST_LOG_OBJECT (src, " request line:"); + GST_LOG_OBJECT (src, " method: '%s'", + gst_rtsp_method_as_text (msg->type_data.request.method)); + GST_LOG_OBJECT (src, " uri: '%s'", msg->type_data.request.uri); + GST_LOG_OBJECT (src, " version: '%s'", + gst_rtsp_version_as_text (msg->type_data.request.version)); + GST_LOG_OBJECT (src, " headers:"); + key_value_foreach (msg->hdr_fields, dump_key_value, src); + GST_LOG_OBJECT (src, " body:"); + gst_rtsp_message_get_body (msg, &data, &size); + if (size > 0) { + body_string = g_string_new_len ((const gchar *) data, size); + GST_LOG_OBJECT (src, " %s(%d)", body_string->str, size); + g_string_free (body_string, TRUE); + body_string = NULL; + } + break; + case GST_RTSP_MESSAGE_HTTP_RESPONSE: + GST_LOG_OBJECT (src, "HTTP response message %p", msg); + GST_LOG_OBJECT (src, " status line:"); + GST_LOG_OBJECT (src, " code: '%d'", msg->type_data.response.code); + GST_LOG_OBJECT (src, " reason: '%s'", msg->type_data.response.reason); + GST_LOG_OBJECT (src, " version: '%s'", + gst_rtsp_version_as_text (msg->type_data.response.version)); + GST_LOG_OBJECT (src, " headers:"); + key_value_foreach (msg->hdr_fields, dump_key_value, src); + gst_rtsp_message_get_body (msg, &data, &size); + GST_LOG_OBJECT (src, " body: length %d", size); + if (size > 0) { + body_string = g_string_new_len ((const gchar *) data, size); + GST_LOG_OBJECT (src, " %s(%d)", body_string->str, size); + g_string_free (body_string, TRUE); + body_string = NULL; + } + break; + case GST_RTSP_MESSAGE_DATA: + GST_LOG_OBJECT (src, "RTSP data message %p", msg); + GST_LOG_OBJECT (src, " channel: '%d'", msg->type_data.data.channel); + GST_LOG_OBJECT (src, " size: '%d'", msg->body_size); + gst_rtsp_message_get_body (msg, &data, &size); + if (size > 0) { + body_string = g_string_new_len ((const gchar *) data, size); + GST_LOG_OBJECT (src, " %s(%d)", body_string->str, size); + g_string_free (body_string, TRUE); + body_string = NULL; + } + break; + default: + GST_LOG_OBJECT (src, "unsupported message type %d", msg->type); + break; + } + GST_LOG_OBJECT (src, "--------------------------------------------"); +} + +static void +gst_rtspsrc_print_sdp_media (GstRTSPSrc * src, GstSDPMedia * media) +{ + GST_LOG_OBJECT (src, " media: '%s'", GST_STR_NULL (media->media)); + GST_LOG_OBJECT (src, " port: '%u'", media->port); + GST_LOG_OBJECT (src, " num_ports: '%u'", media->num_ports); + GST_LOG_OBJECT (src, " proto: '%s'", GST_STR_NULL (media->proto)); + if (media->fmts && media->fmts->len > 0) { + guint i; + + GST_LOG_OBJECT (src, " formats:"); + for (i = 0; i < media->fmts->len; i++) { + GST_LOG_OBJECT (src, " format '%s'", g_array_index (media->fmts, + gchar *, i)); + } + } + GST_LOG_OBJECT (src, " information: '%s'", + GST_STR_NULL (media->information)); + if (media->connections && media->connections->len > 0) { + guint i; + + GST_LOG_OBJECT (src, " connections:"); + for (i = 0; i < media->connections->len; i++) { + GstSDPConnection *conn = + &g_array_index (media->connections, GstSDPConnection, i); + + GST_LOG_OBJECT (src, " nettype: '%s'", + GST_STR_NULL (conn->nettype)); + GST_LOG_OBJECT (src, " addrtype: '%s'", + GST_STR_NULL (conn->addrtype)); + GST_LOG_OBJECT (src, " address: '%s'", + GST_STR_NULL (conn->address)); + GST_LOG_OBJECT (src, " ttl: '%u'", conn->ttl); + GST_LOG_OBJECT (src, " addr_number: '%u'", conn->addr_number); + } + } + if (media->bandwidths && media->bandwidths->len > 0) { + guint i; + + GST_LOG_OBJECT (src, " bandwidths:"); + for (i = 0; i < media->bandwidths->len; i++) { + GstSDPBandwidth *bw = + &g_array_index (media->bandwidths, GstSDPBandwidth, i); + + GST_LOG_OBJECT (src, " type: '%s'", GST_STR_NULL (bw->bwtype)); + GST_LOG_OBJECT (src, " bandwidth: '%u'", bw->bandwidth); + } + } + GST_LOG_OBJECT (src, " key:"); + GST_LOG_OBJECT (src, " type: '%s'", GST_STR_NULL (media->key.type)); + GST_LOG_OBJECT (src, " data: '%s'", GST_STR_NULL (media->key.data)); + if (media->attributes && media->attributes->len > 0) { + guint i; + + GST_LOG_OBJECT (src, " attributes:"); + for (i = 0; i < media->attributes->len; i++) { + GstSDPAttribute *attr = + &g_array_index (media->attributes, GstSDPAttribute, i); + + GST_LOG_OBJECT (src, " attribute '%s' : '%s'", attr->key, attr->value); + } + } +} + +void +gst_rtspsrc_print_sdp_message (GstRTSPSrc * src, const GstSDPMessage * msg) +{ + g_return_if_fail (src != NULL); + g_return_if_fail (msg != NULL); + + if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) < GST_LEVEL_LOG) + return; + + GST_LOG_OBJECT (src, "--------------------------------------------"); + GST_LOG_OBJECT (src, "sdp packet %p:", msg); + GST_LOG_OBJECT (src, " version: '%s'", GST_STR_NULL (msg->version)); + GST_LOG_OBJECT (src, " origin:"); + GST_LOG_OBJECT (src, " username: '%s'", + GST_STR_NULL (msg->origin.username)); + GST_LOG_OBJECT (src, " sess_id: '%s'", + GST_STR_NULL (msg->origin.sess_id)); + GST_LOG_OBJECT (src, " sess_version: '%s'", + GST_STR_NULL (msg->origin.sess_version)); + GST_LOG_OBJECT (src, " nettype: '%s'", + GST_STR_NULL (msg->origin.nettype)); + GST_LOG_OBJECT (src, " addrtype: '%s'", + GST_STR_NULL (msg->origin.addrtype)); + GST_LOG_OBJECT (src, " addr: '%s'", GST_STR_NULL (msg->origin.addr)); + GST_LOG_OBJECT (src, " session_name: '%s'", + GST_STR_NULL (msg->session_name)); + GST_LOG_OBJECT (src, " information: '%s'", GST_STR_NULL (msg->information)); + GST_LOG_OBJECT (src, " uri: '%s'", GST_STR_NULL (msg->uri)); + + if (msg->emails && msg->emails->len > 0) { + guint i; + + GST_LOG_OBJECT (src, " emails:"); + for (i = 0; i < msg->emails->len; i++) { + GST_LOG_OBJECT (src, " email '%s'", g_array_index (msg->emails, gchar *, + i)); + } + } + if (msg->phones && msg->phones->len > 0) { + guint i; + + GST_LOG_OBJECT (src, " phones:"); + for (i = 0; i < msg->phones->len; i++) { + GST_LOG_OBJECT (src, " phone '%s'", g_array_index (msg->phones, gchar *, + i)); + } + } + GST_LOG_OBJECT (src, " connection:"); + GST_LOG_OBJECT (src, " nettype: '%s'", + GST_STR_NULL (msg->connection.nettype)); + GST_LOG_OBJECT (src, " addrtype: '%s'", + GST_STR_NULL (msg->connection.addrtype)); + GST_LOG_OBJECT (src, " address: '%s'", + GST_STR_NULL (msg->connection.address)); + GST_LOG_OBJECT (src, " ttl: '%u'", msg->connection.ttl); + GST_LOG_OBJECT (src, " addr_number: '%u'", msg->connection.addr_number); + if (msg->bandwidths && msg->bandwidths->len > 0) { + guint i; + + GST_LOG_OBJECT (src, " bandwidths:"); + for (i = 0; i < msg->bandwidths->len; i++) { + GstSDPBandwidth *bw = + &g_array_index (msg->bandwidths, GstSDPBandwidth, i); + + GST_LOG_OBJECT (src, " type: '%s'", GST_STR_NULL (bw->bwtype)); + GST_LOG_OBJECT (src, " bandwidth: '%u'", bw->bandwidth); + } + } + GST_LOG_OBJECT (src, " key:"); + GST_LOG_OBJECT (src, " type: '%s'", GST_STR_NULL (msg->key.type)); + GST_LOG_OBJECT (src, " data: '%s'", GST_STR_NULL (msg->key.data)); + if (msg->attributes && msg->attributes->len > 0) { + guint i; + + GST_LOG_OBJECT (src, " attributes:"); + for (i = 0; i < msg->attributes->len; i++) { + GstSDPAttribute *attr = + &g_array_index (msg->attributes, GstSDPAttribute, i); + + GST_LOG_OBJECT (src, " attribute '%s' : '%s'", attr->key, attr->value); + } + } + if (msg->medias && msg->medias->len > 0) { + guint i; + + GST_LOG_OBJECT (src, " medias:"); + for (i = 0; i < msg->medias->len; i++) { + GST_LOG_OBJECT (src, " media %u:", i); + gst_rtspsrc_print_sdp_media (src, &g_array_index (msg->medias, + GstSDPMedia, i)); + } + } + GST_LOG_OBJECT (src, "--------------------------------------------"); +}